diff --git a/ui/src/buckets/actions/thunks.ts b/ui/src/buckets/actions/thunks.ts index ef966de447..bea99dcdd2 100644 --- a/ui/src/buckets/actions/thunks.ts +++ b/ui/src/buckets/actions/thunks.ts @@ -54,6 +54,23 @@ import {LIMIT} from 'src/resources/constants' type Action = BucketAction | NotifyAction +export const fetchAllBuckets = async (orgID: string) => { + const resp = await api.getBuckets({ + query: {orgID, limit: LIMIT}, + }) + + if (resp.status !== 200) { + throw new Error(resp.data.message) + } + + const demoDataBuckets = await fetchDemoDataBuckets() + + return normalize( + [...resp.data.buckets, ...demoDataBuckets], + arrayOfBuckets + ) +} + export const getBuckets = () => async ( dispatch: Dispatch, getState: GetState @@ -65,20 +82,7 @@ export const getBuckets = () => async ( } const org = getOrg(state) - const resp = await api.getBuckets({ - query: {orgID: org.id, limit: LIMIT}, - }) - - if (resp.status !== 200) { - throw new Error(resp.data.message) - } - - const demoDataBuckets = await fetchDemoDataBuckets() - - const buckets = normalize( - [...resp.data.buckets, ...demoDataBuckets], - arrayOfBuckets - ) + const buckets = await fetchAllBuckets(org.id) dispatch(setBuckets(RemoteDataState.Done, buckets)) } catch (error) { diff --git a/ui/src/external/monaco.flux.server.ts b/ui/src/external/monaco.flux.server.ts index bb9c08a948..f4f078e058 100644 --- a/ui/src/external/monaco.flux.server.ts +++ b/ui/src/external/monaco.flux.server.ts @@ -22,6 +22,8 @@ import {getAllVariables, asAssignment} from 'src/variables/selectors' import {buildVarsOption} from 'src/variables/utils/buildVarsOption' import {runQuery} from 'src/shared/apis/query' import {parseResponse as parse} from 'src/shared/parsing/flux/response' +import {getOrg} from 'src/organizations/selectors' +import {fetchAllBuckets} from 'src/buckets/actions/thunks' import {store} from 'src/index' @@ -109,8 +111,6 @@ const queryTagValues = async (orgID, bucket, tag) => { export class LSPServer { private server: WASMServer private messageID: number = 0 - private buckets: string[] = [] - private orgID: string = '' private documentVersions: {[key: string]: number} = {} public store: Store @@ -125,7 +125,8 @@ export class LSPServer { getTagKeys = async bucket => { try { - const response = await queryTagKeys(this.orgID, bucket) + const org = getOrg(this.store.getState()) + const response = await queryTagKeys(org.id, bucket) return parseQueryResponse(response) } catch (e) { console.error(e) @@ -135,7 +136,8 @@ export class LSPServer { getTagValues = async (bucket, tag) => { try { - const response = await queryTagValues(this.orgID, bucket, tag) + const org = getOrg(this.store.getState()) + const response = await queryTagValues(org.id, bucket, tag) return parseQueryResponse(response) } catch (e) { console.error(e) @@ -143,13 +145,22 @@ export class LSPServer { } } - getBuckets = () => { - return Promise.resolve(this.buckets) + getBuckets = async () => { + try { + const org = getOrg(this.store.getState()) + const buckets = await fetchAllBuckets(org.id) + + return Object.values(buckets.entities.buckets).map(b => b.name) + } catch (e) { + console.error(e) + return [] + } } getMeasurements = async (bucket: string) => { try { - const response = await queryMeasurements(this.orgID, bucket) + const org = getOrg(this.store.getState()) + const response = await queryMeasurements(org.id, bucket) return parseQueryResponse(response) } catch (e) { console.error(e) @@ -157,14 +168,6 @@ export class LSPServer { } } - updateBuckets(buckets: string[]) { - this.buckets = buckets - } - - setOrg(orgID: string) { - this.orgID = orgID - } - initialize() { return this.send(initialize(this.currentMessageID)) } diff --git a/ui/src/notebooks/components/Header.tsx b/ui/src/notebooks/components/Header.tsx index 2258bec97f..cbd248dc03 100644 --- a/ui/src/notebooks/components/Header.tsx +++ b/ui/src/notebooks/components/Header.tsx @@ -1,4 +1,4 @@ -import React, {FC, useContext} from 'react' +import React, {FC, useContext, useCallback, useMemo} from 'react' import {Page} from '@influxdata/clockface' import {NotebookContext} from 'src/notebooks/context/notebook' @@ -14,25 +14,34 @@ import {SubmitQueryButton} from 'src/timeMachine/components/SubmitQueryButton' const FULL_WIDTH = true -const Header: FC = () => { - const {id} = useContext(NotebookContext) - const {timeContext, addTimeContext, updateTimeContext} = useContext( - TimeContext - ) +const ConnectedTimeZoneDropdown = React.memo(() => { const {timeZone, onSetTimeZone} = useContext(AppSettingContext) - if (!timeContext.hasOwnProperty(id)) { - addTimeContext(id) - return null + return +}) + +const ConnectedTimeRangeDropdown = ({context, update}) => { + const {range} = context + + const updateRange = range => { + update({ + range, + }) } - const {refresh, range} = timeContext[id] + return useMemo(() => { + return + }, [range]) +} - function updateRefresh(interval: number) { +const ConnectedAutoRefreshDropdown = ({context, update}) => { + const {refresh} = context + + const updateRefresh = (interval: number) => { const status = interval === 0 ? AutoRefreshStatus.Paused : AutoRefreshStatus.Active - updateTimeContext(id, { + update({ refresh: { status, interval, @@ -40,13 +49,46 @@ const Header: FC = () => { } as TimeBlock) } - function updateRange(range) { - updateTimeContext(id, { - ...timeContext[id], - range, - }) + return useMemo( + () => ( + + ), + [refresh] + ) +} + +const EnsureTimeContextExists: FC = () => { + const {id} = useContext(NotebookContext) + const {timeContext, addTimeContext, updateTimeContext} = useContext( + TimeContext + ) + + const update = useCallback( + data => { + updateTimeContext(id, data) + }, + [id] + ) + + if (!timeContext.hasOwnProperty(id)) { + addTimeContext(id) + return null } + return ( + <> + + + + + ) +} + +const Header: FC = () => { function submit() {} // eslint-disable-line @typescript-eslint/no-empty-function return ( @@ -60,16 +102,7 @@ const Header: FC = () => {
- - - + { ) } -export {Header} - export default () => ( @@ -91,3 +122,5 @@ export default () => ( ) + +export {Header} diff --git a/ui/src/notebooks/components/Notebook.tsx b/ui/src/notebooks/components/Notebook.tsx index 326bbf7310..d96897344f 100644 --- a/ui/src/notebooks/components/Notebook.tsx +++ b/ui/src/notebooks/components/Notebook.tsx @@ -4,7 +4,6 @@ import {Page} from '@influxdata/clockface' import {NotebookProvider} from 'src/notebooks/context/notebook' import Header from 'src/notebooks/components/Header' import PipeList from 'src/notebooks/components/PipeList' -import NotebookPanel from 'src/notebooks/components/panel/NotebookPanel' // NOTE: uncommon, but using this to scope the project // within the page and not bleed it's dependancies outside @@ -24,5 +23,4 @@ const NotebookPage: FC = () => { ) } -export {NotebookPanel} export default NotebookPage diff --git a/ui/src/notebooks/components/Pipe.tsx b/ui/src/notebooks/components/Pipe.tsx index c51906f136..d9a7f33ff2 100644 --- a/ui/src/notebooks/components/Pipe.tsx +++ b/ui/src/notebooks/components/Pipe.tsx @@ -1,4 +1,4 @@ -import {FC, createElement} from 'react' +import {FC, createElement, useMemo} from 'react' import {PIPE_DEFINITIONS, PipeProp} from 'src/notebooks' @@ -10,7 +10,10 @@ const Pipe: FC = props => { return null } - return createElement(PIPE_DEFINITIONS[data.type].component, props) + return useMemo( + () => createElement(PIPE_DEFINITIONS[data.type].component, props), + [props.data] + ) } export default Pipe diff --git a/ui/src/notebooks/components/PipeList.tsx b/ui/src/notebooks/components/PipeList.tsx index 9110bf4f92..b4c3130f14 100644 --- a/ui/src/notebooks/components/PipeList.tsx +++ b/ui/src/notebooks/components/PipeList.tsx @@ -1,30 +1,46 @@ -import React, {FC, useContext, createElement} from 'react' +import React, {FC, useContext, useCallback, createElement, useMemo} from 'react' import {PipeContextProps, PipeData} from 'src/notebooks' import Pipe from 'src/notebooks/components/Pipe' import {NotebookContext} from 'src/notebooks/context/notebook' import NotebookPanel from 'src/notebooks/components/panel/NotebookPanel' -const PipeList: FC = () => { - const {id, pipes, updatePipe} = useContext(NotebookContext) - const _pipes = pipes.map((pipe, index) => { - const panel: FC = props => { +interface NotebookPipeProps { + index: number + data: PipeData + onUpdate: (index: number, pipe: PipeData) => void +} + +const NotebookPipe: FC = ({index, data, onUpdate}) => { + const panel: FC = useMemo( + () => props => { const _props = { ...props, index, } return createElement(NotebookPanel, _props) - } - const onUpdate = (data: PipeData) => { - updatePipe(index, data) - } + }, + [index] + ) + const _onUpdate = (data: PipeData) => { + onUpdate(index, data) + } + + return +} + +const PipeList: FC = () => { + const {id, pipes, updatePipe} = useContext(NotebookContext) + const update = useCallback(updatePipe, [id]) + + const _pipes = pipes.map((_, index) => { return ( - ) }) diff --git a/ui/src/notebooks/components/panel/NotebookPanel.tsx b/ui/src/notebooks/components/panel/NotebookPanel.tsx index 7c24edd2a5..b15bd951b9 100644 --- a/ui/src/notebooks/components/panel/NotebookPanel.tsx +++ b/ui/src/notebooks/components/panel/NotebookPanel.tsx @@ -1,5 +1,5 @@ // Libraries -import React, {FC, useContext} from 'react' +import React, {FC, useContext, useCallback} from 'react' import classnames from 'classnames' // Components @@ -20,15 +20,57 @@ export interface Props extends PipeContextProps { index: number } -const NotebookPanel: FC = ({index, children}) => { - const {pipes, removePipe, movePipe, meta} = useContext(NotebookContext) +export interface HeaderProps { + index: number +} + +const NotebookPanelHeader: FC = ({index}) => { + const {pipes, removePipe, movePipe} = useContext(NotebookContext) const canBeMovedUp = index > 0 const canBeMovedDown = index < pipes.length - 1 const canBeRemoved = index !== 0 - const moveUp = canBeMovedUp ? () => movePipe(index, index - 1) : null - const moveDown = canBeMovedDown ? () => movePipe(index, index + 1) : null - const remove = canBeRemoved ? () => removePipe(index) : null + const moveUp = useCallback( + canBeMovedUp ? () => movePipe(index, index - 1) : null, + [index, pipes] + ) + const moveDown = useCallback( + canBeMovedDown ? () => movePipe(index, index + 1) : null, + [index, pipes] + ) + const remove = useCallback(canBeRemoved ? () => removePipe(index) : null, [ + index, + pipes, + ]) + + return ( +
+ + + + + + + + + +
+ ) +} + +const NotebookPanel: FC = props => { + const {index, children} = props + const {meta} = useContext(NotebookContext) const isVisible = meta[index].visible @@ -39,28 +81,8 @@ const NotebookPanel: FC = ({index, children}) => { return (
-
- - - - - - - - - -
-
{isVisible && children}
+ +
{children}
) } diff --git a/ui/src/notebooks/context/app.tsx b/ui/src/notebooks/context/app.tsx index 3cacbad00e..10d0479fe1 100644 --- a/ui/src/notebooks/context/app.tsx +++ b/ui/src/notebooks/context/app.tsx @@ -30,22 +30,20 @@ export const AppSettingContext = React.createContext( DEFAULT_CONTEXT ) -export const AppSettingProvider: FC = ({ - timeZone, - onSetTimeZone, - children, -}) => { - return ( - - {children} - - ) -} +export const AppSettingProvider: FC = React.memo( + ({timeZone, onSetTimeZone, children}) => { + return ( + + {children} + + ) + } +) const mstp = (state: AppState): StateProps => { return { diff --git a/ui/src/notebooks/context/notebook.tsx b/ui/src/notebooks/context/notebook.tsx index a94e8b3b4c..fab72356bc 100644 --- a/ui/src/notebooks/context/notebook.tsx +++ b/ui/src/notebooks/context/notebook.tsx @@ -1,4 +1,4 @@ -import React, {FC, useState} from 'react' +import React, {FC, useState, useCallback} from 'react' import {PipeData} from 'src/notebooks' export interface PipeMeta { @@ -49,68 +49,86 @@ export const NotebookProvider: FC = ({children}) => { const [pipes, setPipes] = useState(DEFAULT_CONTEXT.pipes) const [meta, setMeta] = useState(DEFAULT_CONTEXT.meta) - function addPipe(pipe: PipeData) { - const add = data => { - return pipes => { - pipes.push(data) + const _setPipes = useCallback(setPipes, [id]) + const _setMeta = useCallback(setMeta, [id]) + + const addPipe = useCallback( + (pipe: PipeData) => { + const add = data => { + return pipes => { + pipes.push(data) + return pipes.slice() + } + } + _setPipes(add(pipe)) + _setMeta( + add({ + title: `Notebook_${++GENERATOR_INDEX}`, + visible: true, + }) + ) + }, + [id] + ) + + const updatePipe = useCallback( + (idx: number, pipe: PipeData) => { + _setPipes(pipes => { + pipes[idx] = { + ...pipes[idx], + ...pipe, + } + return pipes.slice() + }) + }, + [id] + ) + + const updateMeta = useCallback( + (idx: number, pipe: PipeMeta) => { + _setMeta(pipes => { + pipes[idx] = { + ...pipes[idx], + ...pipe, + } + return pipes.slice() + }) + }, + [id] + ) + + const movePipe = useCallback( + (currentIdx: number, newIdx: number) => { + const move = list => { + const idx = ((newIdx % list.length) + list.length) % list.length + + if (idx === currentIdx) { + return list + } + + const pipe = list.splice(currentIdx, 1) + + list.splice(idx, 0, pipe[0]) + + return list.slice() + } + _setPipes(move) + _setMeta(move) + }, + [id] + ) + + const removePipe = useCallback( + (idx: number) => { + const remove = pipes => { + pipes.splice(idx, 1) return pipes.slice() } - } - setPipes(add(pipe)) - setMeta( - add({ - title: `Notebook_${++GENERATOR_INDEX}`, - visible: true, - }) - ) - } - - function updatePipe(idx: number, pipe: PipeData) { - setPipes(pipes => { - pipes[idx] = { - ...pipes[idx], - ...pipe, - } - return pipes.slice() - }) - } - - function updateMeta(idx: number, pipe: PipeMeta) { - setMeta(pipes => { - pipes[idx] = { - ...pipes[idx], - ...pipe, - } - return pipes.slice() - }) - } - - function movePipe(currentIdx: number, newIdx: number) { - const move = list => { - const idx = ((newIdx % list.length) + list.length) % list.length - - if (idx === currentIdx) { - return list - } - - const pipe = list.splice(currentIdx, 1) - - list.splice(idx, 0, pipe[0]) - - return list.slice() - } - setPipes(move) - setMeta(move) - } - - function removePipe(idx: number) { - const remove = pipes => { - pipes.splice(idx, 1) - return pipes.slice() - } - setPipes(remove) - setMeta(remove) - } + _setPipes(remove) + _setMeta(remove) + }, + [id] + ) return ( (DEFAULT_CONTEXT) export const TimeProvider: FC = ({children}) => { const [timeContext, setTimeContext] = useState({}) - function addTimeContext(id: string, block?: TimeBlock) { + const addTimeContext = useCallback((id: string, block?: TimeBlock) => { setTimeContext(ranges => { if (ranges.hasOwnProperty(id)) { throw new Error( @@ -50,9 +50,9 @@ export const TimeProvider: FC = ({children}) => { [id]: {...(block || DEFAULT_STATE)}, } }) - } + }, []) - function updateTimeContext(id: string, block: TimeBlock) { + const updateTimeContext = useCallback((id: string, block: TimeBlock) => { setTimeContext(ranges => { return { ...ranges, @@ -62,9 +62,9 @@ export const TimeProvider: FC = ({children}) => { }, } }) - } + }, []) - function removeTimeContext(id: string) { + const removeTimeContext = useCallback((id: string) => { setTimeContext(ranges => { if (!ranges.hasOwnProperty(id)) { throw new Error(`TimeContext[${id}] doesn't exist`) @@ -73,7 +73,7 @@ export const TimeProvider: FC = ({children}) => { delete ranges[id] return {...ranges} }) - } + }, []) return ( = ({data, onUpdate, Context}) => { + const {queries, activeQuery} = data + const query = queries[activeQuery] + + function updateText(text) { + const _queries = queries.slice() + _queries[activeQuery] = { + ...queries[activeQuery], + text, + } + + onUpdate({queries: _queries}) + } + + return useMemo( + () => ( + + {}} + /> + + ), + [query.text] + ) +} + +export default Query diff --git a/ui/src/notebooks/style.scss b/ui/src/notebooks/style.scss index 22c5481530..bc3e570088 100644 --- a/ui/src/notebooks/style.scss +++ b/ui/src/notebooks/style.scss @@ -143,6 +143,7 @@ $notebook-divider-height: ($cf-marg-a * 2) + $cf-border; padding-top: 0; // flex: 1 0 0; position: relative; + min-height: 200px; } // Special styling for query builder inside notebook panel diff --git a/ui/src/shared/components/FluxBucketProvider.tsx b/ui/src/shared/components/FluxBucketProvider.tsx deleted file mode 100644 index 0593ad057c..0000000000 --- a/ui/src/shared/components/FluxBucketProvider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// Libraries -import {FC} from 'react' -import {connect} from 'react-redux' - -import {AppState, Bucket, ResourceType} from 'src/types' -import {getAll} from 'src/resources/selectors' -import {getOrg} from 'src/organizations/selectors' - -import loadServer from 'src/external/monaco.flux.server' - -const FluxBucketProvider: FC<{}> = () => { - return null -} - -const mstp = (state: AppState): {} => { - const buckets = getAll(state, ResourceType.Buckets) - const org = getOrg(state) - - loadServer().then(server => { - server.updateBuckets(buckets.map(b => b.name)) - server.setOrg(org.id || '') - }) - - return {} -} - -export default connect<{}, {}>( - mstp, - null -)(FluxBucketProvider) diff --git a/ui/src/shared/components/FluxMonacoEditor.tsx b/ui/src/shared/components/FluxMonacoEditor.tsx index 9c556d0350..e010abe519 100644 --- a/ui/src/shared/components/FluxMonacoEditor.tsx +++ b/ui/src/shared/components/FluxMonacoEditor.tsx @@ -4,8 +4,6 @@ import {ProtocolToMonacoConverter} from 'monaco-languageclient/lib/monaco-conver // Components import MonacoEditor from 'react-monaco-editor' -import FluxBucketProvider from 'src/shared/components/FluxBucketProvider' -import GetResources from 'src/resources/components/GetResources' // Utils import FLUXLANGID from 'src/external/monaco.flux.syntax' @@ -16,7 +14,7 @@ import {isFlagEnabled} from 'src/shared/utils/featureFlag' // Types import {OnChangeScript} from 'src/types/flux' -import {EditorType, ResourceType} from 'src/types' +import {EditorType} from 'src/types' import './FluxMonacoEditor.scss' import {editor as monacoEditor} from 'monaco-editor' @@ -103,9 +101,6 @@ const FluxEditorMonaco: FC = ({ return (
- - - void) + onSubmitQueries: typeof saveAndExecuteQueries | (() => void) } type Props = StateProps & DispatchProps @@ -160,6 +160,8 @@ const TimeMachineFluxEditor: FC = ({ ) } +export {TimeMachineFluxEditor} + const mstp = (state: AppState) => { const activeQueryText = getActiveQuery(state).text const {activeTab} = getActiveTimeMachine(state)