Skip to content

Commit 4ffd332

Browse files
authored
feat(6313): SQL composition (#6452)
* feat(6313): make language agnostic parent connector to handle shared functionality * feat(6313): SQL composition, using preset dataRanges * feat(6313): handle different timeRange types in the SQL composition * chore: update unit tests for new prop on SelectableTimeRange type * fix(6313): code cleanup and debug edge cases with use of durations without negative time
1 parent 73dd635 commit 4ffd332

File tree

18 files changed

+684
-216
lines changed

18 files changed

+684
-216
lines changed

src/dataExplorer/components/FieldSelector.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {PersistanceContext} from 'src/dataExplorer/context/persistance'
2020

2121
// Types
2222
import {RemoteDataState} from 'src/types'
23-
import {LanguageType} from 'src/dataExplorer/components/resources'
2423

2524
// Syles
2625
import './Schema.scss'
@@ -40,7 +39,7 @@ conceptually similar to a non-indexed column and value.`
4039
const FieldSelector: FC = () => {
4140
const {fields, loading} = useContext(FieldsContext)
4241
const {selectField, searchTerm} = useContext(FluxQueryBuilderContext)
43-
const {selection, resource} = useContext(PersistanceContext)
42+
const {selection} = useContext(PersistanceContext)
4443
const [fieldsToShow, setFieldsToShow] = useState([])
4544

4645
const handleSelectField = (field: string) => {
@@ -84,18 +83,7 @@ const FieldSelector: FC = () => {
8483
</div>
8584
)
8685
} else if (loading === RemoteDataState.Done && fieldsToShow.length) {
87-
if (resource?.language === LanguageType.SQL) {
88-
// readOnly
89-
list = fieldsToShow.map(field => (
90-
<dd
91-
key={field}
92-
className="field-selector--list-item--readonly"
93-
data-testid="field-selector--list-item--readonly"
94-
>
95-
<code>{field}</code>
96-
</dd>
97-
))
98-
} else if (isFlagEnabled('schemaComposition')) {
86+
if (isFlagEnabled('schemaComposition')) {
9987
list = (
10088
<SelectorList
10189
items={fieldsToShow}

src/dataExplorer/components/ResultsPane.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {TimeRange} from 'src/types'
3838
import {LanguageType} from 'src/dataExplorer/components/resources'
3939

4040
// Utils
41-
import {getOrg, isOrgIOx} from 'src/organizations/selectors'
41+
import {getOrg} from 'src/organizations/selectors'
4242
import {getRangeVariable} from 'src/variables/utils/getTimeRangeVars'
4343
import {event} from 'src/cloud/utils/reporting'
4444
import {notify} from 'src/shared/actions/notifications'
@@ -116,7 +116,6 @@ const ResultsPane: FC = () => {
116116
resource,
117117
} = useContext(PersistanceContext)
118118
const orgID = useSelector(getOrg)?.id
119-
const isIoxOrg = useSelector(isOrgIOx)
120119
const language = resource?.language ?? LanguageType.FLUX
121120
const dispatch = useDispatch()
122121

@@ -276,9 +275,7 @@ const ResultsPane: FC = () => {
276275
disabled={submitButtonDisabled}
277276
download={downloadByServiceWorker}
278277
/>
279-
{isIoxOrg && resource?.language === LanguageType.SQL ? null : (
280-
<NewDatePicker />
281-
)}
278+
<NewDatePicker />
282279
<SubmitQueryButton
283280
className="submit-btn"
284281
text="Run"

src/dataExplorer/components/SchemaBrowserHeading.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ const SchemaBrowserHeading: FC = () => {
4646
return null
4747
}
4848

49-
if (resource?.language === LanguageType.SQL) {
50-
return null
51-
}
49+
const label =
50+
resource?.language === LanguageType.SQL ? 'SQL Sync' : 'Flux Sync'
5251

5352
return (
5453
<FlexBox
@@ -65,7 +64,7 @@ const SchemaBrowserHeading: FC = () => {
6564
/>
6665
<InputLabel className="flux-sync--label">
6766
<SelectorTitle
68-
label="Flux Sync"
67+
label={label}
6968
tooltipContents={tooltipContents}
7069
icon={IconFont.Sync}
7170
/>

src/dataExplorer/components/TagSelector.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import SelectorList from 'src/timeMachine/components/SelectorList'
99
// Contexts
1010
import {FluxQueryBuilderContext} from 'src/dataExplorer/context/fluxQueryBuilder'
1111
import {TagsContext} from 'src/dataExplorer/context/tags'
12-
import {PersistanceContext} from 'src/dataExplorer/context/persistance'
1312

1413
// Types
1514
import {RemoteDataState} from 'src/types'
16-
import {LanguageType} from 'src/dataExplorer/components/resources'
1715

1816
// Utils
1917
import {
@@ -41,7 +39,6 @@ const TagValues: FC<TagValuesProps> = ({loading, tagKey, tagValues}) => {
4139
selectTagValue,
4240
searchTerm,
4341
} = useContext(FluxQueryBuilderContext)
44-
const {resource} = useContext(PersistanceContext)
4542
const {getTagValues} = useContext(TagsContext)
4643
const [valuesToShow, setValuesToShow] = useState([])
4744

@@ -85,18 +82,7 @@ const TagValues: FC<TagValuesProps> = ({loading, tagKey, tagValues}) => {
8582
</div>
8683
)
8784
} else if (loading === RemoteDataState.Done && valuesToShow.length) {
88-
if (resource?.language === LanguageType.SQL) {
89-
// readOnly
90-
list = valuesToShow.map(value => (
91-
<dd
92-
key={value}
93-
className="tag-selector-value--list-item--readonly"
94-
data-testid="tag-selector-value--list-item--readonly"
95-
>
96-
<code>{value}</code>
97-
</dd>
98-
))
99-
} else if (isFlagEnabled('schemaComposition')) {
85+
if (isFlagEnabled('schemaComposition')) {
10086
list = (
10187
<SelectorList
10288
items={valuesToShow}

src/dataExplorer/context/persistance.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,6 @@ export const PersistanceProvider: FC = ({children}) => {
194194
...(selection.composition || {}),
195195
...(newSelection.composition || {}),
196196
}
197-
if (resource?.language === LanguageType.SQL) {
198-
// cannot sync for sql support
199-
composition.synced = false
200-
}
201197
const nextState: CompositionSelection = {
202198
...selection,
203199
...newSelection,
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import * as MonacoTypes from 'monaco-editor/esm/vs/editor/editor.api'
2+
import isEqual from 'lodash/isEqual'
3+
4+
import {
5+
DEFAULT_SELECTION,
6+
CompositionSelection,
7+
} from 'src/dataExplorer/context/persistance'
8+
9+
// Types
10+
import {EditorType, RecursivePartial} from 'src/types'
11+
import {LspRange} from 'src/languageSupport/languages/agnostic/types'
12+
13+
// Utils
14+
import {notify} from 'src/shared/actions/notifications'
15+
import {oldSession} from 'src/shared/copy/notifications'
16+
17+
export class ConnectionManager {
18+
protected _editor: EditorType
19+
protected _model: MonacoTypes.editor.IModel
20+
protected _compositionStyle: string[] = []
21+
protected _session: CompositionSelection = JSON.parse(
22+
JSON.stringify(DEFAULT_SELECTION)
23+
)
24+
protected _callbackSetSession: (
25+
schema: RecursivePartial<CompositionSelection>
26+
) => void = () => null
27+
protected _dispatcher = _ => {}
28+
protected _first_load = true
29+
protected _compositionRange: LspRange = null
30+
31+
subscribeToModel(editor: EditorType) {
32+
this._editor = editor
33+
this._model = editor.getModel()
34+
}
35+
36+
_setSessionSync(synced: boolean) {
37+
this._callbackSetSession({
38+
composition: {synced},
39+
})
40+
}
41+
42+
_compositionSyncStyle(startLine: number, endLine: number, synced: boolean) {
43+
const classNamePrefix = synced
44+
? 'composition-sync--on'
45+
: 'composition-sync--off'
46+
47+
// Customize the full width of Monaco editor margin using API `marginClassName`
48+
// https://github.com/microsoft/monaco-editor/blob/35eb0ef/website/typedoc/monaco.d.ts#L1533
49+
const startLineStyle = {
50+
range: new MonacoTypes.Range(startLine, 1, startLine, 1),
51+
options: {
52+
marginClassName: `${classNamePrefix}--first`,
53+
},
54+
}
55+
const middleLinesStyle = {
56+
range: new MonacoTypes.Range(startLine, 1, endLine, 1),
57+
options: {
58+
marginClassName: classNamePrefix,
59+
},
60+
}
61+
const endLineStyle = {
62+
range: new MonacoTypes.Range(endLine, 1, endLine, 1),
63+
options: {
64+
marginClassName: `${classNamePrefix}--last`,
65+
},
66+
}
67+
return [startLineStyle, middleLinesStyle, endLineStyle]
68+
}
69+
70+
_setEditorBlockStyle(range: LspRange | null, synced: boolean = false) {
71+
this._compositionRange = range
72+
const shouldRemoveAllStyles = range == null
73+
74+
this._compositionStyle = this._editor.deltaDecorations(
75+
this._compositionStyle,
76+
shouldRemoveAllStyles
77+
? []
78+
: this._compositionSyncStyle(range.start.line, range.end.line, synced)
79+
)
80+
}
81+
82+
_isNewScript(
83+
schema: CompositionSelection,
84+
previousState: CompositionSelection
85+
): boolean {
86+
return previousState.bucket != null && schema.bucket == null
87+
}
88+
89+
_diffSchemaChange(
90+
schema: CompositionSelection,
91+
previousState: CompositionSelection,
92+
defaultText: string
93+
) {
94+
const toAdd: Partial<CompositionSelection> = {}
95+
const toRemove: Partial<CompositionSelection> = {}
96+
let shouldRemoveDefaultMsg = false
97+
98+
if (this._isNewScript(schema, previousState)) {
99+
// no action to take.
100+
// `textDocument/didChange` --> will inform LSP to drop composition
101+
return {toAdd, toRemove, shouldRemoveDefaultMsg}
102+
}
103+
104+
if (schema.bucket && previousState.bucket != schema.bucket) {
105+
toAdd.bucket = schema.bucket
106+
if (this._model.getValue() == defaultText) {
107+
shouldRemoveDefaultMsg = true
108+
}
109+
}
110+
if (schema.measurement && previousState.measurement != schema.measurement) {
111+
toAdd.measurement = schema.measurement
112+
}
113+
if (!isEqual(schema.fields, previousState.fields)) {
114+
const fieldsToRemove = previousState.fields.filter(
115+
f => !schema.fields.includes(f)
116+
)
117+
if (fieldsToRemove.length) {
118+
toRemove.fields = fieldsToRemove
119+
}
120+
const fieldsToAdd = schema.fields.filter(
121+
f => !previousState.fields.includes(f)
122+
)
123+
if (fieldsToAdd.length) {
124+
toAdd.fields = fieldsToAdd
125+
}
126+
}
127+
if (!isEqual(schema.tagValues, previousState.tagValues)) {
128+
const tagValuesToRemove = previousState.tagValues.filter(
129+
({key, value}) =>
130+
!schema.tagValues.some(pair => pair.value == value && pair.key == key)
131+
)
132+
if (tagValuesToRemove.length) {
133+
toRemove.tagValues = tagValuesToRemove
134+
}
135+
const tagValuesToAdd = schema.tagValues.filter(
136+
({key, value}) =>
137+
!previousState.tagValues.some(
138+
pair => pair.value == value && pair.key == key
139+
)
140+
)
141+
if (tagValuesToAdd.length) {
142+
toAdd.tagValues = tagValuesToAdd
143+
}
144+
}
145+
146+
return {toAdd, toRemove, shouldRemoveDefaultMsg}
147+
}
148+
149+
_updateLocalState(schema: CompositionSelection, sessionCb, dispatch) {
150+
if (!schema.composition) {
151+
dispatch(notify(oldSession()))
152+
return {shouldContinue: false}
153+
}
154+
155+
this._dispatcher = dispatch
156+
this._callbackSetSession = sessionCb
157+
const previousState = {
158+
...this._session,
159+
composition: {...this._session?.composition},
160+
}
161+
162+
if (!schema.composition.synced) {
163+
this._session.composition.synced = false
164+
this._setEditorBlockStyle(this._compositionRange)
165+
return {shouldContinue: false}
166+
}
167+
const syncTurnedBackOn =
168+
schema.composition.synced && !previousState.composition.synced
169+
if (syncTurnedBackOn) {
170+
this._setEditorBlockStyle(this._compositionRange, true)
171+
}
172+
173+
this._session = {...schema, composition: {...schema.composition}}
174+
return {previousState, shouldContinue: true}
175+
}
176+
177+
dispose() {
178+
this._model.onDidChangeContent(null)
179+
}
180+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface LspRange {
2+
start: LspPosition
3+
end: LspPosition
4+
}
5+
6+
export interface LspPosition {
7+
column: number
8+
line: number
9+
}

0 commit comments

Comments
 (0)