Skip to content

Commit 933bcd5

Browse files
authored
feat(4300/3427): LSP integration in all flux monaco editors (#4566)
Our flux LSP integration with the monaco-editor was previously using the monaco-clientlanguage feature provider API, which required each feature to be wired together in code. Instead, we are now using a direct json-rpc message channel. Our implementation includes the LSP server hosted on a worker, and a fallback worker using a thin abstract layer to mimic the worker interface. We also need to provide the variable definitions (e.g. `v.windowPeriod`) to the LSP, within a separate prelude document. A few more technical notes: * the LSP currently assess documents in order. Therefore the prelude document must be created before the main editor document (monotonically increasing). * variables are stored in state, in different locations based upon the application. Therefore the variables must be provided to the editor from the parent. * during hmr, the webpack worker plugin does not recall the namespace of the previously generated workers. Therefore, it may overwrite the csv worker with the LSP worker (e.g. both will be 0.worker.js). Hence the updates to webpack. * the lsp server triggered requests, requires the onMessage method to be overwritten prior to the server run.
1 parent 6ffee9b commit 933bcd5

File tree

29 files changed

+567
-708
lines changed

29 files changed

+567
-708
lines changed

cypress/e2e/shared/editor.test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {Organization} from '../../../src/types'
2+
3+
// to see list of monaco-editor widgets to check:
4+
// document.querySelectorAll('[widgetid]')
5+
6+
describe('Editor+LSP communication', () => {
7+
const runTest = editorSelector => {
8+
it('receives LSP-triggered server events', () => {
9+
cy.getByTestID(editorSelector).then(() => {
10+
cy.getByTestID('flux-editor', {timeout: 30000})
11+
.should('be.visible')
12+
.monacoType('foo |> bar')
13+
.within(() => {
14+
cy.get('.squiggly-error', {timeout: 30000}).should('be.visible')
15+
})
16+
cy.getByTestID('flux-editor').monacoType('{selectall}{del}')
17+
})
18+
})
19+
20+
it('has a roundtrip request-response, from the editor to the LSP server', () => {
21+
cy.getByTestID(editorSelector).then(() => {
22+
cy.getByTestID('flux-editor', {timeout: 30000})
23+
.monacoType('{selectall} {backspace}')
24+
.monacoType('from(')
25+
.within(() => {
26+
cy.get('[widgetid="editor.widget.suggestWidget"]', {
27+
timeout: 30000,
28+
}).should('be.visible')
29+
})
30+
.monacoType(`{selectall}{del}`)
31+
})
32+
})
33+
}
34+
35+
describe('in Flows:', () => {
36+
before(() => {
37+
cy.flush()
38+
cy.signin()
39+
cy.get('@org').then(({id}: Organization) =>
40+
cy.fixture('routes').then(({orgs}) => {
41+
cy.visit(`${orgs}/${id}`)
42+
})
43+
)
44+
cy.getByTestID('version-info')
45+
cy.getByTestID('nav-item-flows').should('be.visible')
46+
cy.getByTestID('nav-item-flows').click()
47+
48+
cy.getByTestID('preset-new')
49+
.first()
50+
.click()
51+
cy.getByTestID('time-machine-submit-button').should('be.visible')
52+
53+
cy.get('.flow-divider--button')
54+
.first()
55+
.click()
56+
cy.get('.insert-cell-menu.always-on').contains('Add Another Panel')
57+
cy.getByTestID('add-flow-btn--rawFluxEditor')
58+
.last()
59+
.click()
60+
cy.getByTestID('flux-editor').should('be.visible')
61+
})
62+
63+
runTest('flux-editor')
64+
})
65+
66+
describe('in DataExplorer', () => {
67+
before(() => {
68+
cy.flush()
69+
cy.signin()
70+
cy.get('@org').then(({id}: Organization) => {
71+
cy.createMapVariable(id)
72+
cy.fixture('routes').then(({orgs, explorer}) => {
73+
cy.visit(`${orgs}/${id}${explorer}`)
74+
cy.getByTestID('tree-nav').should('be.visible')
75+
})
76+
})
77+
cy.getByTestID('switch-to-script-editor')
78+
.should('be.visible')
79+
.click()
80+
})
81+
82+
runTest('time-machine--bottom')
83+
})
84+
})

cypress/e2e/shared/explorer.test.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -430,29 +430,16 @@ describe('DataExplorer', () => {
430430
.click()
431431
})
432432

433-
it('shows the proper errors and query button state', () => {
433+
it('shows the proper query button state', () => {
434434
cy.getByTestID('time-machine-submit-button').should('be.disabled')
435435

436436
cy.getByTestID('time-machine--bottom').then(() => {
437437
cy.getByTestID('flux-editor', {timeout: 30000})
438438
.should('be.visible')
439-
.monacoType('foo |> bar')
440-
.within(() => {
441-
cy.get('.squiggly-error', {timeout: 30000}).should('be.visible')
442-
})
443-
.monacoType('{selectall} {backspace}')
444-
.monacoType('from(')
445-
.within(() => {
446-
cy.get('[widgetid="editor.widget.suggestWidget"]', {
447-
timeout: 30000,
448-
}).should('be.visible')
449-
})
450-
.monacoType(`{selectall}{del}from(bucket: )`)
439+
.monacoType(`{selectall}{del}from(bucket: "my-bucket")`)
451440
})
452441

453442
cy.getByTestID('time-machine-submit-button').should('not.be.disabled')
454-
455-
cy.getByTestID('flux-editor').monacoType('yo')
456443
cy.getByTestID('flux-editor').monacoType('{selectall}{del}')
457444
})
458445

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
"webpack-merge": "^4.2.2"
163163
},
164164
"dependencies": {
165+
"@codingame/monaco-jsonrpc": "^0.3.1",
165166
"@docsearch/react": "^3.0.0-alpha.37",
166167
"@influxdata/clockface": "^4.0.1",
167168
"@influxdata/flux-lsp-browser": "0.8.8",
@@ -220,6 +221,7 @@
220221
"rudder-sdk-js": "^1.0.16",
221222
"s2-geometry": "^1.2.10",
222223
"use-local-storage-state": "^9.0.2",
224+
"vscode-languageserver-protocol": "^3.16.0",
223225
"worker-plugin": "^5.0.1",
224226
"y-websocket": "^1.4.3",
225227
"yjs": "^13.5.36"

src/dataExplorer/components/ResultsPane.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import {downloadTextFile} from 'src/shared/utils/download'
2323
import {event} from 'src/cloud/utils/reporting'
2424
import {QueryContext} from 'src/shared/contexts/query'
2525
import {notify} from 'src/shared/actions/notifications'
26+
import {TIME_RANGE_START, TIME_RANGE_STOP} from 'src/variables/constants'
27+
import {getRangeVariable} from 'src/variables/utils/getTimeRangeVars'
28+
import {getWindowPeriodVariableFromVariables} from 'src/variables/utils/getWindowVars'
2629

2730
import {DEFAULT_TIME_RANGE} from 'src/shared/constants/timeRanges'
2831

@@ -98,6 +101,15 @@ const ResultsPane: FC = () => {
98101
})
99102
}
100103

104+
const timeVars = [
105+
getRangeVariable(TIME_RANGE_START, timeRange),
106+
getRangeVariable(TIME_RANGE_STOP, timeRange),
107+
]
108+
109+
const variables = timeVars.concat(
110+
getWindowPeriodVariableFromVariables(text, timeVars) || []
111+
)
112+
101113
return (
102114
<DraggableResizer
103115
handleOrientation={Orientation.Horizontal}
@@ -120,7 +132,11 @@ const ResultsPane: FC = () => {
120132
/>
121133
}
122134
>
123-
<FluxMonacoEditor script={text} onChangeScript={setText} />
135+
<FluxMonacoEditor
136+
variables={variables}
137+
script={text}
138+
onChangeScript={setText}
139+
/>
124140
</Suspense>
125141
</div>
126142
<div style={{width: '100%'}}>

src/flows/components/FlowPipe.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {FC, createElement, useMemo} from 'react'
33
import Pipe from 'src/flows/components/Pipe'
44
import FlowPanel from 'src/flows/components/panel/FlowPanel'
55
import {PipeProvider} from 'src/flows/context/pipe'
6+
import {VariablesProvider} from 'src/flows/context/variables'
67

78
export interface FlowPipeProps {
89
id: string
@@ -12,15 +13,17 @@ const FlowPipe: FC<FlowPipeProps> = ({id}) => {
1213
return useMemo(
1314
() => (
1415
<PipeProvider id={id}>
15-
<Pipe
16-
Context={props => {
17-
const _props = {
18-
...props,
19-
id,
20-
}
21-
return createElement(FlowPanel, _props)
22-
}}
23-
/>
16+
<VariablesProvider>
17+
<Pipe
18+
Context={props => {
19+
const _props = {
20+
...props,
21+
id,
22+
}
23+
return createElement(FlowPanel, _props)
24+
}}
25+
/>
26+
</VariablesProvider>
2427
</PipeProvider>
2528
),
2629
[id]

src/flows/components/QueryTextPreview.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/flows/context/variables.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, {FC, useContext, useEffect, useState} from 'react'
2+
import {Variable} from 'src/types'
3+
import {FlowQueryContext} from 'src/flows/context/flow.query'
4+
import {FlowContext} from 'src/flows/context/flow.current'
5+
import {PipeContext} from 'src/flows/context/pipe'
6+
import {getRangeVariable} from 'src/variables/utils/getTimeRangeVars'
7+
import {TIME_RANGE_START, TIME_RANGE_STOP} from 'src/variables/constants'
8+
import {getWindowPeriodVariableFromVariables} from 'src/variables/utils/getWindowVars'
9+
10+
const EMPTY_STATE = [] as Variable[]
11+
12+
interface VariablesContextType {
13+
variables: Variable[]
14+
}
15+
16+
const DEFAULT_CONTEXT: VariablesContextType = {
17+
variables: [],
18+
}
19+
20+
export const VariablesContext = React.createContext<VariablesContextType>(
21+
DEFAULT_CONTEXT
22+
)
23+
24+
export const VariablesProvider: FC = ({children}) => {
25+
const {id, range: pipeRange} = useContext(PipeContext)
26+
const {flow} = useContext(FlowContext)
27+
const {getPanelQueries} = useContext(FlowQueryContext)
28+
const {source} = getPanelQueries(id)
29+
const [variables, setVariables] = useState(EMPTY_STATE)
30+
31+
useEffect(() => {
32+
if ((!pipeRange && !flow?.range) || !source) {
33+
return
34+
}
35+
const range = pipeRange || flow?.range
36+
const timeVars = [
37+
getRangeVariable(TIME_RANGE_START, range),
38+
getRangeVariable(TIME_RANGE_STOP, range),
39+
]
40+
const windowVar = getWindowPeriodVariableFromVariables(source, timeVars)
41+
setVariables(!!windowVar ? timeVars.concat(windowVar) : timeVars)
42+
}, [id, source, pipeRange, flow?.range])
43+
44+
return (
45+
<VariablesContext.Provider value={{variables}}>
46+
{children}
47+
</VariablesContext.Provider>
48+
)
49+
}

src/flows/pipes/RawFluxEditor/readOnly.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import {
66
TechnoSpinner,
77
} from '@influxdata/clockface'
88

9-
// Types
9+
// Types and Context
1010
import {PipeProp} from 'src/types/flows'
11+
import {VariablesContext} from 'src/flows/context/variables'
1112

1213
// Components
1314
import {PipeContext} from 'src/flows/context/pipe'
@@ -23,6 +24,7 @@ const Query: FC<PipeProp> = ({Context}) => {
2324
const {data} = useContext(PipeContext)
2425
const {queries, activeQuery} = data
2526
const query = queries[activeQuery]
27+
const {variables} = useContext(VariablesContext)
2628

2729
return (
2830
<Context>
@@ -36,6 +38,7 @@ const Query: FC<PipeProp> = ({Context}) => {
3638
>
3739
<FluxMonacoEditor
3840
script={query.text}
41+
variables={variables}
3942
onChangeScript={() => {}}
4043
onSubmitScript={() => {}}
4144
autogrow

src/flows/pipes/RawFluxEditor/view.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
EditorProvider,
2929
InjectionType,
3030
} from 'src/flows/context/editor'
31+
import {VariablesContext} from 'src/flows/context/variables'
3132

3233
// Components
3334
import SecretsList from 'src/flows/pipes/RawFluxEditor/SecretsList'
@@ -59,6 +60,7 @@ const Query: FC<PipeProp> = ({Context}) => {
5960
const {setEditor, inject, updateText} = editorContext
6061
const {queries, activeQuery} = data
6162
const query = queries[activeQuery]
63+
const {variables} = useContext(VariablesContext)
6264

6365
useEffect(() => {
6466
if (isFlagEnabled('fluxInjectSecrets')) {
@@ -155,6 +157,7 @@ const Query: FC<PipeProp> = ({Context}) => {
155157
>
156158
<FluxMonacoEditor
157159
script={query.text}
160+
variables={variables}
158161
onChangeScript={updateText}
159162
setEditorInstance={setEditor}
160163
wrapLines="on"
@@ -163,7 +166,13 @@ const Query: FC<PipeProp> = ({Context}) => {
163166
</Suspense>
164167
</Context>
165168
),
166-
[RemoteDataState.Loading, query.text, updateText, editorContext.editor]
169+
[
170+
RemoteDataState.Loading,
171+
query.text,
172+
updateText,
173+
editorContext.editor,
174+
variables,
175+
]
167176
)
168177
}
169178

0 commit comments

Comments
 (0)