diff --git a/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx index 72894d2b5fe..0409ef4d326 100644 --- a/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineFieldRow.tsx @@ -24,6 +24,7 @@ const getStyles = (theme: GrafanaTheme) => { flex-direction: row; flex-wrap: wrap; align-content: flex-start; + row-gap: ${theme.spacing.xs}; `, }; }; diff --git a/packages/grafana-ui/src/components/Segment/Segment.story.tsx b/packages/grafana-ui/src/components/Segment/Segment.story.tsx index 826e192746b..ab629544ce8 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.story.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.story.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; -import { Segment, Icon } from '@grafana/ui'; +import { Segment, Icon, SegmentSection } from '@grafana/ui'; const AddButton = ( @@ -17,13 +17,10 @@ const groupedOptions = [ const SegmentFrame = ({ options, children }: any) => ( <> -
-
- Segment Name -
+ {children} action('New value added')(value)} options={options} /> -
+ ); diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx index fb3bcf10612..0c720b7350c 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.story.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { AsyncState } from 'react-use/lib/useAsync'; import { action } from '@storybook/addon-actions'; import { SelectableValue } from '@grafana/data'; -import { SegmentAsync, Icon } from '@grafana/ui'; +import { SegmentAsync, Icon, SegmentSection } from '@grafana/ui'; const AddButton = (
@@ -21,17 +21,14 @@ const loadOptionsErr = (): Promise>> => const SegmentFrame = ({ loadOptions, children }: any) => ( <> -
-
- Segment Name -
+ {children} action('New value added')(value)} loadOptions={() => loadOptions(options)} /> -
+ ); diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx index 7377a0f59ab..af21609e1df 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.story.tsx @@ -1,15 +1,10 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; -import { SegmentInput, Icon } from '@grafana/ui'; +import { SegmentInput, Icon, SegmentSection } from '@grafana/ui'; const SegmentFrame = ({ children }: any) => ( <> -
-
- Segment Name -
- {children} -
+ {children} ); diff --git a/packages/grafana-ui/src/components/Segment/SegmentSection.tsx b/packages/grafana-ui/src/components/Segment/SegmentSection.tsx new file mode 100644 index 00000000000..8cc5b7f751d --- /dev/null +++ b/packages/grafana-ui/src/components/Segment/SegmentSection.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '../../themes'; +import { InlineLabel } from '../Forms/InlineLabel'; +import { InlineFieldRow } from '../Forms/InlineFieldRow'; + +/** + * Horizontal section for editor components. + * + * @alpha + */ +export const SegmentSection = ({ + label, + children, + fill, +}: { + // Name of the section + label: string; + // List of components in the section + children: React.ReactNode; + // Fill the space at the end + fill?: boolean; +}) => { + const styles = useStyles2(getStyles); + return ( + <> + + + {label} + + {children} + {fill && ( +
+ {''} +
+ )} +
+ + ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + label: css` + color: ${theme.colors.primary.text}; + `, + fill: css` + flex-grow: 1; + margin-bottom: ${theme.spacing(0.5)}; + `, +}); diff --git a/packages/grafana-ui/src/components/Segment/index.ts b/packages/grafana-ui/src/components/Segment/index.ts index 040d96f3612..95c32736f69 100644 --- a/packages/grafana-ui/src/components/Segment/index.ts +++ b/packages/grafana-ui/src/components/Segment/index.ts @@ -2,5 +2,6 @@ export { Segment } from './Segment'; export { SegmentAsync } from './SegmentAsync'; export { SegmentSelect } from './SegmentSelect'; export { SegmentInput } from './SegmentInput'; +export { SegmentSection } from './SegmentSection'; export { SegmentProps } from './types'; export { useExpandableLabel } from './useExpandableLabel'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 7d61b2523f4..3c7458379a0 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -152,7 +152,7 @@ export { CertificationKey } from './DataSourceSettings/CertificationKey'; export { Spinner } from './Spinner/Spinner'; export { FadeTransition } from './transitions/FadeTransition'; export { SlideOutTransition } from './transitions/SlideOutTransition'; -export { Segment, SegmentAsync, SegmentInput, SegmentSelect } from './Segment/'; +export { Segment, SegmentAsync, SegmentInput, SegmentSelect, SegmentSection } from './Segment/'; export { Drawer } from './Drawer/Drawer'; export { Slider } from './Slider/Slider'; export { RangeSlider } from './Slider/RangeSlider'; diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 0b695b30911..1fb3645faad 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -12,12 +12,11 @@ import { DataSourceHttpSettings, GraphContextMenu, Icon, - Spinner, LegacyForms, SeriesColorPickerPopoverWithTheme, + Spinner, UnitPicker, } from '@grafana/ui'; -import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor'; import { LokiAnnotationsQueryEditor } from '../plugins/datasource/loki/components/AnnotationsQueryEditor'; import { HelpModal } from './components/help/HelpModal'; import { Footer } from './components/Footer/Footer'; @@ -25,11 +24,7 @@ import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { SearchField, SearchResults, SearchResultsFilter } from '../features/search'; import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings'; import QueryEditor from 'app/plugins/datasource/grafana-azure-monitor-datasource/components/QueryEditor/QueryEditor'; -import { GraphiteTextEditor } from '../plugins/datasource/graphite/components/GraphiteTextEditor'; -import { PlayButton } from '../plugins/datasource/graphite/components/PlayButton'; -import { AddGraphiteFunction } from '../plugins/datasource/graphite/components/AddGraphiteFunction'; -import { GraphiteFunctionEditor } from '../plugins/datasource/graphite/components/GraphiteFunctionEditor'; -import { SeriesSection } from '../plugins/datasource/graphite/components/SeriesSection'; +import { GraphiteQueryEditor } from '../plugins/datasource/graphite/components/GraphiteQueryEditor'; const { SecretFormField } = LegacyForms; @@ -207,10 +202,5 @@ export function registerAngularDirectives() { ]); // Temporal wrappers for Graphite migration - react2AngularDirective('functionEditor', FunctionEditor, ['func', 'onRemove', 'onMoveLeft', 'onMoveRight']); - react2AngularDirective('graphiteTextEditor', GraphiteTextEditor, ['rawQuery', 'dispatch']); - react2AngularDirective('playButton', PlayButton, ['dispatch']); - react2AngularDirective('addGraphiteFunction', AddGraphiteFunction, ['funcDefs', 'dispatch']); - react2AngularDirective('graphiteFunctionEditor', GraphiteFunctionEditor, ['func', 'dispatch']); - react2AngularDirective('seriesSection', SeriesSection, ['state', 'dispatch']); + react2AngularDirective('graphiteQueryEditor', GraphiteQueryEditor, ['state', 'dispatch']); } diff --git a/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx b/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx index 74160af4f69..c130bf0c5d9 100644 --- a/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx +++ b/public/app/plugins/datasource/graphite/components/AddGraphiteFunction.tsx @@ -5,14 +5,14 @@ import { actions } from '../state/actions'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { css, cx } from '@emotion/css'; import { mapFuncDefsToSelectables } from './helpers'; -import { Dispatch } from 'redux'; +import { useDispatch } from '../state/context'; type Props = { - dispatch: Dispatch; funcDefs: FuncDefs; }; -export function AddGraphiteFunction({ dispatch, funcDefs }: Props) { +export function AddGraphiteFunction({ funcDefs }: Props) { + const dispatch = useDispatch(); const [value, setValue] = useState | undefined>(undefined); const styles = useStyles2(getStyles); @@ -37,7 +37,7 @@ export function AddGraphiteFunction({ dispatch, funcDefs }: Props) { options={options} onChange={setValue} inputMinWidth={150} - > + /> ); } diff --git a/public/app/plugins/datasource/graphite/FunctionEditor.test.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx similarity index 96% rename from public/app/plugins/datasource/graphite/FunctionEditor.test.tsx rename to public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx index db5260fad8c..b8527aa1a4c 100644 --- a/public/app/plugins/datasource/graphite/FunctionEditor.test.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditor.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { FunctionEditor } from './FunctionEditor'; -import { FuncInstance } from './gfunc'; +import { FuncInstance } from '../gfunc'; function mockFunctionInstance(name: string, unknown?: boolean): FuncInstance { const def = { diff --git a/public/app/plugins/datasource/graphite/FunctionEditor.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx similarity index 98% rename from public/app/plugins/datasource/graphite/FunctionEditor.tsx rename to public/app/plugins/datasource/graphite/components/FunctionEditor.tsx index fbcab41ddca..38932d077c3 100644 --- a/public/app/plugins/datasource/graphite/FunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditor.tsx @@ -1,7 +1,7 @@ import React, { useRef } from 'react'; import { PopoverController, Popover, ClickOutsideWrapper, Icon, Tooltip, useStyles2 } from '@grafana/ui'; import { FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls'; -import { FuncInstance } from './gfunc'; +import { FuncInstance } from '../gfunc'; import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; diff --git a/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx b/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx similarity index 97% rename from public/app/plugins/datasource/graphite/FunctionEditorControls.tsx rename to public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx index 731cc96bb7e..f4d1fa52fa8 100644 --- a/public/app/plugins/datasource/graphite/FunctionEditorControls.tsx +++ b/public/app/plugins/datasource/graphite/components/FunctionEditorControls.tsx @@ -1,6 +1,6 @@ import React, { Suspense } from 'react'; import { Icon, Tooltip } from '@grafana/ui'; -import { FuncInstance } from './gfunc'; +import { FuncInstance } from '../gfunc'; export interface FunctionEditorControlsProps { onMoveLeft: (func: FuncInstance) => void; diff --git a/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx b/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx new file mode 100644 index 00000000000..81cce8c702a --- /dev/null +++ b/public/app/plugins/datasource/graphite/components/FunctionsSection.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { FuncDefs, FuncInstance } from '../gfunc'; +import { GraphiteFunctionEditor } from './GraphiteFunctionEditor'; +import { AddGraphiteFunction } from './AddGraphiteFunction'; +import { SegmentSection } from '@grafana/ui'; + +type Props = { + functions: FuncInstance[]; + funcDefs: FuncDefs; +}; + +export function FunctionsSection({ functions = [], funcDefs }: Props) { + return ( + + {functions.map((func: FuncInstance, index: number) => { + return !func.hidden && ; + })} + + + ); +} diff --git a/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx index 9be0952e31a..fcf1b93cef3 100644 --- a/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/GraphiteFunctionEditor.tsx @@ -5,18 +5,19 @@ import { css, cx } from '@emotion/css'; import { FuncInstance } from '../gfunc'; import { EditableParam, FunctionParamEditor } from './FunctionParamEditor'; import { actions } from '../state/actions'; -import { FunctionEditor } from '../FunctionEditor'; +import { FunctionEditor } from './FunctionEditor'; import { mapFuncInstanceToParams } from './helpers'; +import { useDispatch } from '../state/context'; export type FunctionEditorProps = { func: FuncInstance; - dispatch: (action: any) => void; }; /** * Allows editing function params and removing/moving a function (note: editing function name is not supported) */ -export function GraphiteFunctionEditor({ func, dispatch }: FunctionEditorProps) { +export function GraphiteFunctionEditor({ func }: FunctionEditorProps) { + const dispatch = useDispatch(); const styles = useStyles2(getStyles); // keep track of mouse over and isExpanded state to display buttons for adding optional/multiple params @@ -81,6 +82,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ borderRadius: theme.shape.borderRadius(), marginRight: theme.spacing(0.5), padding: `0 ${theme.spacing(1)}`, + height: `${theme.v1.spacing.formInputHeight}px`, }), error: css` border: 1px solid ${theme.colors.error.main}; diff --git a/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx new file mode 100644 index 00000000000..d5f861cb43a --- /dev/null +++ b/public/app/plugins/datasource/graphite/components/GraphiteQueryEditor.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Dispatch } from 'redux'; +import { GraphiteQueryEditorState } from '../state/store'; +import { GraphiteTextEditor } from './GraphiteTextEditor'; +import { SeriesSection } from './SeriesSection'; +import { GraphiteContext } from '../state/context'; +import { FunctionsSection } from './FunctionsSection'; + +type Props = { + state: GraphiteQueryEditorState; + dispatch: Dispatch; +}; + +export function GraphiteQueryEditor({ dispatch, state }: Props) { + return ( + + {state.target?.textEditor && } + {!state.target?.textEditor && ( + <> + + + + )} + + ); +} diff --git a/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx b/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx index 68a4d1bc591..5941a4e3953 100644 --- a/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/GraphiteTextEditor.tsx @@ -1,14 +1,15 @@ import React, { useCallback } from 'react'; import { QueryField } from '@grafana/ui'; import { actions } from '../state/actions'; -import { Dispatch } from 'redux'; +import { useDispatch } from '../state/context'; type Props = { rawQuery: string; - dispatch: Dispatch; }; -export function GraphiteTextEditor({ rawQuery, dispatch }: Props) { +export function GraphiteTextEditor({ rawQuery }: Props) { + const dispatch = useDispatch(); + const updateQuery = useCallback( (query: string) => { dispatch(actions.updateQuery({ query })); @@ -21,15 +22,13 @@ export function GraphiteTextEditor({ rawQuery, dispatch }: Props) { }, [dispatch]); return ( - <> - - + ); } diff --git a/public/app/plugins/datasource/graphite/components/MetricSegment.tsx b/public/app/plugins/datasource/graphite/components/MetricSegment.tsx index 4afcfe4a67c..ff4717fb7cb 100644 --- a/public/app/plugins/datasource/graphite/components/MetricSegment.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricSegment.tsx @@ -1,17 +1,16 @@ import React, { useCallback, useMemo } from 'react'; import { SegmentAsync } from '@grafana/ui'; import { actions } from '../state/actions'; -import { Dispatch } from 'redux'; import { GraphiteSegment } from '../types'; import { SelectableValue } from '@grafana/data'; import { getAltSegmentsSelectables } from '../state/providers'; import { debounce } from 'lodash'; import { GraphiteQueryEditorState } from '../state/store'; +import { useDispatch } from '../state/context'; type Props = { segment: GraphiteSegment; metricIndex: number; - dispatch: Dispatch; state: GraphiteQueryEditorState; }; @@ -25,7 +24,8 @@ type Props = { * getAltSegmentsSelectables() also returns list of tags for segment with index=0. Once a tag is selected the editor * enters tag-adding mode (see SeriesSection and GraphiteQueryModel.seriesByTagUsed). */ -export function MetricSegment({ dispatch, metricIndex, segment, state }: Props) { +export function MetricSegment({ metricIndex, segment, state }: Props) { + const dispatch = useDispatch(); const loadOptions = useCallback( (value: string | undefined) => { return getAltSegmentsSelectables(state, metricIndex, value || ''); diff --git a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx b/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx similarity index 98% rename from public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx rename to public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx index e39aaf04295..3a7ce13b615 100644 --- a/public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx @@ -1,9 +1,9 @@ import { css, cx } from '@emotion/css'; import React, { PureComponent } from 'react'; import { MetadataInspectorProps, rangeUtil } from '@grafana/data'; -import { GraphiteDatasource } from './datasource'; -import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from './types'; -import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from './meta'; +import { GraphiteDatasource } from '../datasource'; +import { GraphiteQuery, GraphiteOptions, MetricTankSeriesMeta } from '../types'; +import { parseSchemaRetentions, getRollupNotice, getRuntimeConsolidationNotice } from '../meta'; import { stylesFactory } from '@grafana/ui'; import { config } from 'app/core/config'; diff --git a/public/app/plugins/datasource/graphite/components/MetricsSection.tsx b/public/app/plugins/datasource/graphite/components/MetricsSection.tsx index 6dd3e5fea62..34fb24160d4 100644 --- a/public/app/plugins/datasource/graphite/components/MetricsSection.tsx +++ b/public/app/plugins/datasource/graphite/components/MetricsSection.tsx @@ -1,34 +1,19 @@ import React from 'react'; -import { Dispatch } from 'redux'; import { GraphiteSegment } from '../types'; import { GraphiteQueryEditorState } from '../state/store'; import { MetricSegment } from './MetricSegment'; -import { css } from '@emotion/css'; -import { useStyles2 } from '@grafana/ui'; type Props = { segments: GraphiteSegment[]; - dispatch: Dispatch; state: GraphiteQueryEditorState; }; -export function MetricsSection({ dispatch, segments = [], state }: Props) { - const styles = useStyles2(getStyles); - +export function MetricsSection({ segments = [], state }: Props) { return ( -
+ <> {segments.map((segment, index) => { - return ; + return ; })} -
+ ); } - -function getStyles() { - return { - container: css` - display: flex; - flex-direction: row; - `, - }; -} diff --git a/public/app/plugins/datasource/graphite/components/PlayButton.tsx b/public/app/plugins/datasource/graphite/components/PlayButton.tsx index c9f7035c162..cc1084cc316 100644 --- a/public/app/plugins/datasource/graphite/components/PlayButton.tsx +++ b/public/app/plugins/datasource/graphite/components/PlayButton.tsx @@ -1,14 +1,10 @@ import React, { useCallback } from 'react'; import { Button } from '@grafana/ui'; import { actions } from '../state/actions'; -import { Dispatch } from 'redux'; +import { useDispatch } from '../state/context'; -type Props = { - rawQuery: string; - dispatch: Dispatch; -}; - -export function PlayButton({ dispatch }: Props) { +export function PlayButton() { + const dispatch = useDispatch(); const onClick = useCallback(() => { dispatch(actions.unpause()); }, [dispatch]); diff --git a/public/app/plugins/datasource/graphite/components/SeriesSection.tsx b/public/app/plugins/datasource/graphite/components/SeriesSection.tsx index 647a4232fc9..7ec29a4a3d4 100644 --- a/public/app/plugins/datasource/graphite/components/SeriesSection.tsx +++ b/public/app/plugins/datasource/graphite/components/SeriesSection.tsx @@ -1,23 +1,23 @@ import React from 'react'; -import { Dispatch } from 'redux'; import { GraphiteQueryEditorState } from '../state/store'; import { TagsSection } from './TagsSection'; import { MetricsSection } from './MetricsSection'; +import { SegmentSection } from '@grafana/ui'; type Props = { - dispatch: Dispatch; state: GraphiteQueryEditorState; }; -export function SeriesSection({ dispatch, state }: Props) { - return state.queryModel?.seriesByTagUsed ? ( - +export function SeriesSection({ state }: Props) { + const sectionContent = state.queryModel?.seriesByTagUsed ? ( + ) : ( - + + ); + + return ( + + {sectionContent} + ); } diff --git a/public/app/plugins/datasource/graphite/components/TagEditor.tsx b/public/app/plugins/datasource/graphite/components/TagEditor.tsx index d22b8f6d7bd..4dfa8a1e5ad 100644 --- a/public/app/plugins/datasource/graphite/components/TagEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/TagEditor.tsx @@ -1,16 +1,15 @@ import React, { useCallback, useMemo } from 'react'; -import { Dispatch } from 'redux'; import { Segment, SegmentAsync } from '@grafana/ui'; import { actions } from '../state/actions'; import { GraphiteTag, GraphiteTagOperator } from '../types'; import { getTagOperatorsSelectables, getTagsSelectables, getTagValuesSelectables } from '../state/providers'; import { GraphiteQueryEditorState } from '../state/store'; import { debounce } from 'lodash'; +import { useDispatch } from '../state/context'; type Props = { tag: GraphiteTag; tagIndex: number; - dispatch: Dispatch; state: GraphiteQueryEditorState; }; @@ -22,7 +21,8 @@ type Props = { * Options for tag names and values are reloaded while user is typing with backend taking care of auto-complete * (auto-complete cannot be implemented in front-end because backend returns only limited number of entries) */ -export function TagEditor({ dispatch, tag, tagIndex, state }: Props) { +export function TagEditor({ tag, tagIndex, state }: Props) { + const dispatch = useDispatch(); const getTagsOptions = useCallback( (inputValue: string | undefined) => { return getTagsSelectables(state, tagIndex, inputValue || ''); diff --git a/public/app/plugins/datasource/graphite/components/TagsSection.tsx b/public/app/plugins/datasource/graphite/components/TagsSection.tsx index 2a59005268d..8fdf09313c2 100644 --- a/public/app/plugins/datasource/graphite/components/TagsSection.tsx +++ b/public/app/plugins/datasource/graphite/components/TagsSection.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useMemo } from 'react'; -import { Dispatch } from 'redux'; import { GraphiteSegment } from '../types'; import { GraphiteTag } from '../graphite_query'; import { GraphiteQueryEditorState } from '../state/store'; @@ -11,9 +10,10 @@ import { css } from '@emotion/css'; import { mapSegmentsToSelectables } from './helpers'; import { TagEditor } from './TagEditor'; import { debounce } from 'lodash'; +import { useDispatch } from '../state/context'; +import { PlayButton } from './PlayButton'; type Props = { - dispatch: Dispatch; tags: GraphiteTag[]; addTagSegments: GraphiteSegment[]; state: GraphiteQueryEditorState; @@ -25,7 +25,8 @@ type Props = { * Options for tag names are reloaded while user is typing with backend taking care of auto-complete * (auto-complete cannot be implemented in front-end because backend returns only limited number of entries) */ -export function TagsSection({ dispatch, tags, state, addTagSegments }: Props) { +export function TagsSection({ tags, state, addTagSegments }: Props) { + const dispatch = useDispatch(); const styles = useStyles2(getStyles); const newTagsOptions = mapSegmentsToSelectables(addTagSegments || []); @@ -43,9 +44,9 @@ export function TagsSection({ dispatch, tags, state, addTagSegments }: Props) { ]); return ( -
+ <> {tags.map((tag, index) => { - return ; + return ; })} {newTagsOptions.length && ( @@ -58,16 +59,13 @@ export function TagsSection({ dispatch, tags, state, addTagSegments }: Props) { Component={
+ {state.paused && } + ); } function getStyles(theme: GrafanaTheme2) { return { - container: css` - display: flex; - flex-direction: row; - `, button: css` margin-right: ${theme.spacing(0.5)}; `, diff --git a/public/app/plugins/datasource/graphite/module.ts b/public/app/plugins/datasource/graphite/module.ts index 174061621b0..4d4e0f243cb 100644 --- a/public/app/plugins/datasource/graphite/module.ts +++ b/public/app/plugins/datasource/graphite/module.ts @@ -2,7 +2,7 @@ import { GraphiteDatasource } from './datasource'; import { GraphiteQueryCtrl } from './query_ctrl'; import { DataSourcePlugin } from '@grafana/data'; import { ConfigEditor } from './configuration/ConfigEditor'; -import { MetricTankMetaInspector } from './MetricTankMetaInspector'; +import { MetricTankMetaInspector } from './components/MetricTankMetaInspector'; class AnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index 4e5406798d0..0d879461995 100644 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -1,42 +1,3 @@ - -
- -
- -
-
-
- -
- - - -
- -
- -
-
-
-
- -
-
- -
- -
- -
- - - -
-
-
-
-
+
diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts index f662f3405d6..f179d0387b1 100644 --- a/public/app/plugins/datasource/graphite/query_ctrl.ts +++ b/public/app/plugins/datasource/graphite/query_ctrl.ts @@ -4,13 +4,7 @@ import { auto } from 'angular'; import { TemplateSrv } from '@grafana/runtime'; import { actions } from './state/actions'; import { createStore, GraphiteQueryEditorState } from './state/store'; -import { - GraphiteActionDispatcher, - GraphiteQueryEditorAngularDependencies, - GraphiteSegment, - GraphiteTag, -} from './types'; -import { ChangeEvent } from 'react'; +import { GraphiteActionDispatcher, GraphiteQueryEditorAngularDependencies } from './types'; /** * @deprecated Moved to state/store @@ -81,174 +75,7 @@ export class GraphiteQueryCtrl extends QueryCtrl { this.dispatch(actions.init(deps as GraphiteQueryEditorAngularDependencies)); } - parseTarget() { - // WIP: moved to state/helpers (the same name) - } - async toggleEditorMode() { await this.dispatch(actions.toggleEditorMode()); } - - buildSegments(modifyLastSegment = true) { - // WIP: moved to state/helpers (the same name) - } - - addSelectMetricSegment() { - // WIP: moved to state/helpers (the same name) - } - - checkOtherSegments(fromIndex: number, modifyLastSegment = true) { - // WIP: moved to state/helpers (the same name) - } - - setSegmentFocus(segmentIndex: any) { - // WIP: removed - } - - /** - * Get list of options for an empty segment or a segment with metric when it's clicked/opened. - * - * This is used for new segments and segments with metrics selected. - */ - getAltSegments(index: number, text: string): void { - // WIP: moved to state/providers (the same name) - } - - addAltTagSegments(prefix: string, altSegments: any[]) { - // WIP: moved to state/providers (the same name) - } - - removeTaggedEntry(altSegments: any[]) { - // WIP: moved to state/providers (the same name) - } - - /** - * Apply changes to a given metric segment - */ - async segmentValueChanged(segment: GraphiteSegment, index: number) { - // WIP: moved to MetricsSegment - } - - spliceSegments(index: any) { - // WIP: moved to state/helpers (the same name) - } - - emptySegments() { - // WIP: moved to state/helpers (the same name) - } - - async targetTextChanged(event: ChangeEvent) { - // WIP: removed, handled by GraphiteTextEditor - } - - updateModelTarget() { - // WIP: moved to state/helpers as handleTargetChanged() - } - - async addFunction(name: string) { - // WIP: removed, called from AddGraphiteFunction - } - - removeFunction(func: any) { - // WIP: converted to "removeFunction" action and handled in state/store reducer - // It's now dispatched in func_editor - } - - moveFunction(func: any, offset: any) { - // WIP: converted to "moveFunction" action and handled in state/store reducer - // It's now dispatched in func_editor - } - - addSeriesByTagFunc(tag: string) { - // WIP: moved to state/helpers (the same name) - // It's now dispatched in func_editor - } - - smartlyHandleNewAliasByNode(func: { def: { name: string }; params: number[]; added: boolean }) { - // WIP: moved to state/helpers (the same name) - } - - getAllTags() { - // WIP: removed. It was not used. - } - - /** - * Get list of tags for editing exiting tag with - */ - getTags(index: number, query: string): void { - // WIP: removed, called from TagsSection - } - - /** - * Get tag list when adding a new tag with - */ - getTagsAsSegments(query: string): void { - // WIP: removed, called from TagsSection - } - - /** - * Get list of available tag operators - */ - getTagOperators(): void { - // WIP: removed, called from TagsSection - } - - getAllTagValues(tag: { key: any }) { - // WIP: removed. It was not used. - } - - /** - * Get list of available tag values - */ - getTagValues(tag: GraphiteTag, index: number, query: string): void { - // WIP: removed, called from TagsSection - } - - /** - * Apply changes when a tag is changed - */ - async tagChanged(tag: GraphiteTag, index: number) { - // WIP: removed, called from TagsSection - } - - async addNewTag(segment: GraphiteSegment) { - // WIP: removed, called from TagsSection - } - - removeTag(index: any) { - // WIP: removed. It was not used. - // Tags are removed by selecting the segment called "-- remove tag --" - } - - fixTagSegments() { - // WIP: moved to state/helpers (the same name) - } - - showDelimiter(index: number) { - // WIP: removed. It was not used because of broken syntax in the template. The logic has been moved directly to the template - } - - pause() { - // WIP: moved to state/helpers (the same name) - } - - async unpause() { - // WIP: removed, called from PlayButton - } - - getCollapsedText() { - // WIP: removed. It was not used. - } - - handleTagsAutoCompleteError(error: Error): void { - // WIP: moved to state/helpers (the same name) - } - - handleMetricsAutoCompleteError(error: Error): void { - // WIP: moved to state/helpers (the same name) - } } - -// WIP: moved to state/providers (the same names) -// function mapToDropdownOptions(results: any[]) {} -// function removeTagPrefix(value: string): string {} diff --git a/public/app/plugins/datasource/graphite/state/context.tsx b/public/app/plugins/datasource/graphite/state/context.tsx new file mode 100644 index 00000000000..78b06381366 --- /dev/null +++ b/public/app/plugins/datasource/graphite/state/context.tsx @@ -0,0 +1,16 @@ +import React, { createContext, Dispatch, PropsWithChildren, useContext } from 'react'; +import { AnyAction } from '@reduxjs/toolkit'; + +type Props = { + dispatch: Dispatch; +}; + +const DispatchContext = createContext>({} as Dispatch); + +export const useDispatch = () => { + return useContext(DispatchContext); +}; + +export const GraphiteContext = ({ children, dispatch }: PropsWithChildren) => { + return {children}; +}; diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx index 74bdc8b1847..d88f6e329b3 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.test.tsx @@ -50,12 +50,12 @@ describe('InfluxDB InfluxQL Visual Editor', () => { }; assertEditor( query, - 'from[default][select measurement]where[+]' + - 'select[field]([value])[mean]()[+]' + - 'group by[time]([$__interval])[fill]([null])[+]' + - 'timezone[(optional)]order by time[ASC]' + - 'limit[(optional)]slimit[(optional)]' + - 'format as[time_series]alias[Naming pattern]' + 'FROM[default][select measurement]WHERE[+]' + + 'SELECT[field]([value])[mean]()[+]' + + 'GROUP BY[time]([$__interval])[fill]([null])[+]' + + 'TIMEZONE[(optional)]ORDER BY TIME[ASC]' + + 'LIMIT[(optional)]SLIMIT[(optional)]' + + 'FORMAT AS[time_series]ALIAS[Naming pattern]' ); }); it('should have the alias-field hidden when format-as-table', () => { @@ -66,12 +66,12 @@ describe('InfluxDB InfluxQL Visual Editor', () => { }; assertEditor( query, - 'from[default][select measurement]where[+]' + - 'select[field]([value])[mean]()[+]' + - 'group by[time]([$__interval])[fill]([null])[+]' + - 'timezone[(optional)]order by time[ASC]' + - 'limit[(optional)]slimit[(optional)]' + - 'format as[table]' + 'FROM[default][select measurement]WHERE[+]' + + 'SELECT[field]([value])[mean]()[+]' + + 'GROUP BY[time]([$__interval])[fill]([null])[+]' + + 'TIMEZONE[(optional)]ORDER BY TIME[ASC]' + + 'LIMIT[(optional)]SLIMIT[(optional)]' + + 'FORMAT AS[table]' ); }); it('should handle complex query', () => { @@ -145,13 +145,13 @@ describe('InfluxDB InfluxQL Visual Editor', () => { }; assertEditor( query, - 'from[default][cpu]where[cpu][=][cpu1][AND][cpu][<][cpu3][+]' + - 'select[field]([usage_idle])[mean]()[+]' + + 'FROM[default][cpu]WHERE[cpu][=][cpu1][AND][cpu][<][cpu3][+]' + + 'SELECT[field]([usage_idle])[mean]()[+]' + '[field]([usage_guest])[median]()[holt_winters_with_fit]([10],[2])[+]' + - 'group by[time]([$__interval])[tag]([cpu])[tag]([host])[fill]([null])[+]' + - 'timezone[UTC]order by time[DESC]' + - 'limit[4]slimit[5]' + - 'format as[logs]alias[all i as]' + 'GROUP BY[time]([$__interval])[tag]([cpu])[tag]([host])[fill]([null])[+]' + + 'TIMEZONE[UTC]ORDER BY TIME[DESC]' + + 'LIMIT[4]SLIMIT[5]' + + 'FORMAT AS[logs]ALIAS[all i as]' ); }); }); diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx index 00b199b333f..5c87bcc5a5b 100644 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx +++ b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/Editor.tsx @@ -24,10 +24,11 @@ import { changeGroupByPart, } from '../../queryUtils'; import { FormatAsSection } from './FormatAsSection'; -import { SectionLabel } from './SectionLabel'; -import { SectionFill } from './SectionFill'; import { DEFAULT_RESULT_FORMAT } from '../constants'; import { getNewSelectPartOptions, getNewGroupByPartOptions, makePartList } from './partListUtils'; +import { InlineLabel, SegmentSection, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { css } from '@emotion/css'; type Props = { query: InfluxQuery; @@ -51,15 +52,8 @@ function withTemplateVariableOptions(optionsPromise: Promise): Promise return optionsPromise.then((options) => [...getTemplateVariableOptions(), ...options]); } -const SectionWrap = ({ initialName, children }: { initialName: string; children: React.ReactNode }) => ( -
- - {children} - -
-); - export const Editor = (props: Props): JSX.Element => { + const styles = useStyles2(getStyles); const query = normalizeQuery(props.query); const { datasource } = props; const { measurement, policy } = query; @@ -112,7 +106,7 @@ export const Editor = (props: Props): JSX.Element => { return (
- + { } onChange={handleFromSectionChange} /> - + + WHERE + { withTemplateVariableOptions(getTagValues(key, measurement, policy, query.tags ?? [], datasource)) } /> - + {selectLists.map((sel, index) => ( - + Promise.resolve(getNewSelectPartOptions())} @@ -150,9 +146,9 @@ export const Editor = (props: Props): JSX.Element => { onAppliedChange(removeSelectPart(query, partIndex, index)); }} /> - + ))} - + getNewGroupByPartOptions(query, getTagKeys)} @@ -167,8 +163,8 @@ export const Editor = (props: Props): JSX.Element => { onAppliedChange(removeGroupByPart(query, partIndex)); }} /> - - + + { onAppliedChange({ ...query, tz }); }} /> - + + ORDER BY TIME + { onAppliedChange({ ...query, orderByTime: v }); }} /> - + {/* query.fill is ignored in the query-editor, and it is deleted whenever query-editor changes. the influx_query_model still handles it, but the new approach seem to be to handle "fill" inside query.groupBy. so, if you - have a panel where in the json you have query.fill, it will be appled, + have a panel where in the json you have query.fill, it will be applied, as long as you do not edit that query. */} - + { onAppliedChange({ ...query, limit }); }} /> - + + SLIMIT + { onAppliedChange({ ...query, slimit }); }} /> - - + + { @@ -215,7 +215,9 @@ export const Editor = (props: Props): JSX.Element => { /> {query.resultFormat !== 'table' && ( <> - + + ALIAS + { /> )} - +
); }; + +function getStyles(theme: GrafanaTheme2) { + return { + inlineLabel: css` + color: ${theme.colors.primary.text}; + `, + }; +} diff --git a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/SectionLabel.tsx b/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/SectionLabel.tsx deleted file mode 100644 index 93df9933cb1..00000000000 --- a/public/app/plugins/datasource/influxdb/components/VisualInfluxQLEditor/SectionLabel.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { cx, css } from '@emotion/css'; - -type Props = { - name: string; - isInitial?: boolean; -}; - -const uppercaseClass = css({ - textTransform: 'uppercase', -}); - -export const SectionLabel = ({ name, isInitial }: Props) => ( - -);