From a91d41f12a65f89f19e078b1f6ea96451082ebc7 Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Fri, 23 Jul 2021 12:00:44 +0300 Subject: [PATCH] Vmui/query editor (#1472) * fix: move request button to server input * feat: add switch for query autocomplete * refactor: rename state for popover open * feat: add detect os by userAgent * fix: change hotkey to run query for mac * fix: change detect mac os * fix: change div to span inside Typography Co-authored-by: yury --- .../Home/Configurator/ExecutionControls.tsx | 20 ++--- .../Home/Configurator/QueryConfigurator.tsx | 89 +++++++++++++++---- .../Home/Configurator/QueryEditor.tsx | 38 +++++--- .../vmui/src/components/Home/HomeLayout.tsx | 4 +- .../src/components/LineChart/ChartTooltip.tsx | 2 +- app/vmui/packages/vmui/src/index.css | 6 ++ .../packages/vmui/src/state/common/reducer.ts | 13 ++- app/vmui/packages/vmui/src/utils/detect-os.ts | 13 +++ app/vmui/packages/vmui/src/utils/storage.ts | 7 +- 9 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 app/vmui/packages/vmui/src/utils/detect-os.ts diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx index 77e3877c1..f04c7ce43 100644 --- a/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx +++ b/app/vmui/packages/vmui/src/components/Home/Configurator/ExecutionControls.tsx @@ -1,7 +1,5 @@ import React, {FC, useEffect, useState} from "react"; import {Box, FormControlLabel, IconButton, Switch, Tooltip} from "@material-ui/core"; -import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"; - import EqualizerIcon from "@material-ui/icons/Equalizer"; import {useAppDispatch, useAppState} from "../../../state/common/StateContext"; import CircularProgressWithLabel from "../../common/CircularProgressWithLabel"; @@ -70,23 +68,19 @@ export const ExecutionControls: FC = () => { }; return - - - dispatch({type: "RUN_QUERY"})}> - - - - {} label="Auto-refresh" />} {autoRefresh && <> - {iterateDelays();}} /> - - {iterateDelays();}}> - + {iterateDelays();}} /> + + + {iterateDelays();}}> + + } ; }; \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx index 4200c11a8..d8789b93d 100644 --- a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx +++ b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryConfigurator.tsx @@ -1,4 +1,4 @@ -import React, {FC, useState} from "react"; +import React, {FC, useRef, useState} from "react"; import { Accordion, AccordionDetails, @@ -7,7 +7,10 @@ import { Grid, IconButton, TextField, - Typography + Typography, + FormControlLabel, + Tooltip, + Switch, } from "@material-ui/core"; import QueryEditor from "./QueryEditor"; import {TimeSelector} from "./TimeSelector"; @@ -15,14 +18,36 @@ import {useAppDispatch, useAppState} from "../../../state/common/StateContext"; import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import SecurityIcon from "@material-ui/icons/Security"; import {AuthDialog} from "./AuthDialog"; +import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline"; +import Portal from "@material-ui/core/Portal"; +import Popover from "@material-ui/core/Popover"; +import SettingsIcon from "@material-ui/icons/Settings"; +import {saveToStorage} from "../../../utils/storage"; const QueryConfigurator: FC = () => { const {serverUrl, query, time: {duration}} = useAppState(); const dispatch = useAppDispatch(); + const {queryControls: {autocomplete}} = useAppState(); + const onChangeAutocomplete = () => { + dispatch({type: "TOGGLE_AUTOCOMPLETE"}); + saveToStorage("AUTOCOMPLETE", !autocomplete); + }; + const [dialogOpen, setDialogOpen] = useState(false); const [expanded, setExpanded] = useState(true); + const [popoverOpen, setPopoverOpen] = useState(false); + const refSettings = useRef(null); + + const queryContainer = useRef(null); + + const onSetDuration = (dur: string) => dispatch({type: "SET_DURATION", payload: dur}); + const onRunQuery = () => dispatch({type: "RUN_QUERY"}); + const onSetQuery = (query: string) => dispatch({type: "SET_QUERY", payload: query}); + const onSetServer = ({target: {value}}: {target: {value: string}}) => { + dispatch({type: "SET_SERVER", payload: value}); + }; return ( <> @@ -35,31 +60,65 @@ const QueryConfigurator: FC = () => { Query Configuration - {!expanded && e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}> - dispatch({type: "SET_QUERY", payload: query})}/> - } + e.stopPropagation()} onFocusCapture={e => e.stopPropagation()}> + + + + - + dispatch({type: "SET_SERVER", payload: e.target.value})}/> - - setDialogOpen(true)}> - - + onChange={onSetServer}/> + + + + + + + + + + setDialogOpen(true)}> + + + - dispatch({type: "SET_QUERY", payload: query})}/> - + + + {/* for portal QueryEditor */} +
+ +
+ + setPopoverOpen(!popoverOpen)}> + + + + setPopoverOpen(false)} + anchorEl={refSettings.current}> + + {} + label="Autocomplete" + />} + + +
+ - + { height: "calc(100% - 18px)", marginTop: "16px" }}> - dispatch({type: "SET_DURATION", payload: dur})} duration={duration}/> + diff --git a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx index 88d01b6d1..e7cba128d 100644 --- a/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx +++ b/app/vmui/packages/vmui/src/components/Home/Configurator/QueryEditor.tsx @@ -4,15 +4,20 @@ import {defaultKeymap} from "@codemirror/next/commands"; import React, {FC, useEffect, useRef, useState} from "react"; import { PromQLExtension } from "codemirror-promql"; import { basicSetup } from "@codemirror/next/basic-setup"; +import {isMacOs} from "../../../utils/detect-os"; export interface QueryEditorProps { setQuery: (query: string) => void; + runQuery: () => void; query: string; server: string; oneLiner?: boolean; + autocomplete: boolean } -const QueryEditor: FC = ({query, setQuery, server, oneLiner = false}) => { +const QueryEditor: FC = ({ + query, setQuery, runQuery, server, oneLiner = false, autocomplete +}) => { const ref = useRef(null); @@ -33,28 +38,41 @@ const QueryEditor: FC = ({query, setQuery, server, oneLiner = // update state on change of autocomplete server useEffect(() => { - const promQL = new PromQLExtension().setComplete({url: server}); + const promQL = new PromQLExtension(); + promQL.activateCompletion(autocomplete); + promQL.setComplete({url: server}); const listenerExtension = EditorView.updateListener.of(editorUpdate => { if (editorUpdate.docChanged) { - setQuery( - editorUpdate.state.doc.toJSON().map(el => el.trim()).join("") - ); + setQuery(editorUpdate.state.doc.toJSON().map(el => el.trim()).join("")); } - }); editorView?.setState(EditorState.create({ doc: query, - extensions: [basicSetup, keymap(defaultKeymap), listenerExtension, promQL.asExtension()] + extensions: [ + basicSetup, + keymap(defaultKeymap), + listenerExtension, + promQL.asExtension(), + keymap([ + { + key: isMacOs() ? "Cmd-Enter" : "Ctrl-Enter", + run: (): boolean => { + runQuery(); + return true; + }, + }, + ]), + ] })); - }, [server, editorView]); + }, [server, editorView, autocomplete]); return ( <> - {/*Class one-line-scroll and other codemirror stylings are declared in index.css*/} -
+ {/*Class one-line-scroll and other codemirror styles are declared in index.css*/} +
); }; diff --git a/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx b/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx index 2df5fa271..309d764f3 100644 --- a/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx +++ b/app/vmui/packages/vmui/src/components/Home/HomeLayout.tsx @@ -21,7 +21,7 @@ const HomeLayout: FC = () => { <> - + VM UI @@ -43,7 +43,7 @@ const HomeLayout: FC = () => { Create an issue
- + diff --git a/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx b/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx index f28827195..62180d290 100644 --- a/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx +++ b/app/vmui/packages/vmui/src/components/LineChart/ChartTooltip.tsx @@ -37,7 +37,7 @@ export const ChartTooltip: React.FC = ({data, time}) => { {data.metrics.map(({key, value}) => - + {key}:  {value} )} diff --git a/app/vmui/packages/vmui/src/index.css b/app/vmui/packages/vmui/src/index.css index d68a476d0..95fed9dba 100644 --- a/app/vmui/packages/vmui/src/index.css +++ b/app/vmui/packages/vmui/src/index.css @@ -31,3 +31,9 @@ code { .one-line-scroll .cm-wrap { height: 24px; } +.cm-content, .cm-gutter { min-height: 51px; } + +.one-line-scroll .cm-content, +.one-line-scroll .cm-gutter { + min-height: auto; +} diff --git a/app/vmui/packages/vmui/src/state/common/reducer.ts b/app/vmui/packages/vmui/src/state/common/reducer.ts index d7941a4ff..af981a654 100644 --- a/app/vmui/packages/vmui/src/state/common/reducer.ts +++ b/app/vmui/packages/vmui/src/state/common/reducer.ts @@ -15,6 +15,7 @@ export interface AppState { time: TimeState; queryControls: { autoRefresh: boolean; + autocomplete: boolean } } @@ -28,6 +29,7 @@ export type Action = | { type: "RUN_QUERY"} | { type: "RUN_QUERY_TO_NOW"} | { type: "TOGGLE_AUTOREFRESH"} + | { type: "TOGGLE_AUTOCOMPLETE"} export const initialState: AppState = { serverUrl: getFromStorage("PREFERRED_URL") as string || "https://", // https://demo.promlabs.com or https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus", @@ -38,7 +40,8 @@ export const initialState: AppState = { period: getTimeperiodForDuration("1h") }, queryControls: { - autoRefresh: false + autoRefresh: false, + autocomplete: getFromStorage("AUTOCOMPLETE") as boolean || false } }; @@ -99,6 +102,14 @@ export function reducer(state: AppState, action: Action): AppState { autoRefresh: !state.queryControls.autoRefresh } }; + case "TOGGLE_AUTOCOMPLETE": + return { + ...state, + queryControls: { + ...state.queryControls, + autocomplete: !state.queryControls.autocomplete + } + }; case "RUN_QUERY": return { ...state, diff --git a/app/vmui/packages/vmui/src/utils/detect-os.ts b/app/vmui/packages/vmui/src/utils/detect-os.ts new file mode 100644 index 000000000..ee0ca40d2 --- /dev/null +++ b/app/vmui/packages/vmui/src/utils/detect-os.ts @@ -0,0 +1,13 @@ +const desktopOs = { + windows: "Windows", + mac: "Mac OS", + linux: "Linux" +}; + +export const getOs = () : string => { + return Object.values(desktopOs).find(os => navigator.userAgent.indexOf(os) >= 0) || "unknown"; +}; + +export const isMacOs = (): boolean => { + return getOs() === desktopOs.mac; +}; \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/utils/storage.ts b/app/vmui/packages/vmui/src/utils/storage.ts index fdba0e0a4..7e7c3d1aa 100644 --- a/app/vmui/packages/vmui/src/utils/storage.ts +++ b/app/vmui/packages/vmui/src/utils/storage.ts @@ -1,4 +1,9 @@ -export type StorageKeys = "PREFERRED_URL" | "LAST_QUERY" | "BASIC_AUTH_DATA" | "BEARER_AUTH_DATA" | "AUTH_TYPE"; +export type StorageKeys = "PREFERRED_URL" + | "LAST_QUERY" + | "BASIC_AUTH_DATA" + | "BEARER_AUTH_DATA" + | "AUTH_TYPE" + | "AUTOCOMPLETE"; export const saveToStorage = (key: StorageKeys, value: string | boolean | Record): void => { if (value) {