mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Graphite: Migrate to React (part 4 & 5: group all components) (#37590)
* Add UMLs * Add rendered diagrams * Move QueryCtrl to flux * Remove redundant param in the reducer * Use named imports for lodash and fix typing for GraphiteTagOperator * Add missing async/await * Extract providers to a separate file * Clean up async await * Rename controller functions back to main * Simplify creating actions * Re-order controller functions * Separate helpers from actions * Rename vars * Simplify helpers * Move controller methods to state reducers * Remove docs (they are added in design doc) * Move actions.ts to state folder * Add docs * Add old methods stubs for easier review * Check how state dependencies will be mapped * Rename state to store * Rename state to store * Rewrite spec tests for Graphite Query Controller * Update docs * Update docs * Add GraphiteTextEditor * Add play button * Add AddGraphiteFunction * Use Segment to simplify AddGraphiteFunction * Memoize function defs * Fix useCallback deps * Update public/app/plugins/datasource/graphite/state/helpers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/helpers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/helpers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Update public/app/plugins/datasource/graphite/state/providers.ts Co-authored-by: Giordano Ricci <me@giordanoricci.com> * Add more type definitions * Remove submitOnClickAwayOption This behavior is actually needed to remove parameters in functions * Load function definitions before parsing the target on initial load * Add button padding * Fix loading function definitions * Change targetChanged to updateQuery to avoid mutating state directly It's also needed for extra refresh/runQuery execution as handleTargetChanged doesn't handle changing the raw query * Fix updating query after adding a function * Simplify updating function params * Migrate function editor to react * Simplify setting Segment Select min width * Remove unnecessary changes to SegmentInput * Extract view logic to a helper and update types definitions * Clean up types * Update FuncDef types and add tests * Show red border for unknown functions * Autofocus on new params * Extract params mapping to a helper * Split code between params and function editor * Focus on the first param when a function is added even if it's an optional argument * Add function editor tests * Remove todo marker * Fix adding new functions * Allow empty value in selects for removing function params * Add placeholders and fix styling * Add more docs * Create basic implementation for metrics and tags * Post merge fixes These files are not .ts * Remove mapping to Angular dropdowns * Simplify mapping tag names, values and operators * Simplify mapping metrics * Fix removing tags and autocomplete * Simplify debouncing providers * Ensure options are loaded twice and segment is opened * Remove focusing new segments logic (not supported by React's segment) * Clean up * Move debouncing to components * Simplify mapping to selectable options * Add docs * Group all components * Remove unused controller methods * Create Dispatch context * Group Series and Tags Sections * Create Functions section * Create Section component * use getStyles * remove redundant async/await * Remove * remove redundant async/await * Remove console.log and silent test console output * Do not display the name of the selected dropdown option * Move Section to grafana-ui * Update storybook * Simplify SectionLabel * Fix Influx tests * Fix API Extractor warnings * Fix API Extractor warnings * Do not show hidden functions * Use block docs for better doc generation * Handle undefined values provided for autocomplete * Section -> SegmentSection * Simplify section styling * Remove redundant div * Simplify SegmentSection component * Use theme.spacing * Use empty label instead of a single space label Co-authored-by: Giordano Ricci <me@giordanoricci.com>
This commit is contained in:
parent
f8d7726187
commit
d93d989a5a
@ -24,6 +24,7 @@ const getStyles = (theme: GrafanaTheme) => {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
row-gap: ${theme.spacing.xs};
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -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 = (
|
||||
<a className="gf-form-label query-part">
|
||||
@ -17,13 +17,10 @@ const groupedOptions = [
|
||||
|
||||
const SegmentFrame = ({ options, children }: any) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentSection label="Segment Name">
|
||||
{children}
|
||||
<Segment Component={AddButton} onChange={({ value }) => action('New value added')(value)} options={options} />
|
||||
</div>
|
||||
</SegmentSection>
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -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 = (
|
||||
<a className="gf-form-label query-part">
|
||||
@ -21,17 +21,14 @@ const loadOptionsErr = (): Promise<Array<SelectableValue<string>>> =>
|
||||
|
||||
const SegmentFrame = ({ loadOptions, children }: any) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
<SegmentSection label="Segment Name">
|
||||
{children}
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={(value) => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
/>
|
||||
</div>
|
||||
</SegmentSection>
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -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) => (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-8 query-keyword">Segment Name</span>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
<SegmentSection label="Segment Name">{children}</SegmentSection>
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -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 (
|
||||
<>
|
||||
<InlineFieldRow>
|
||||
<InlineLabel width={12} className={styles.label}>
|
||||
{label}
|
||||
</InlineLabel>
|
||||
{children}
|
||||
{fill && (
|
||||
<div className={styles.fill}>
|
||||
<InlineLabel>{''}</InlineLabel>
|
||||
</div>
|
||||
)}
|
||||
</InlineFieldRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
label: css`
|
||||
color: ${theme.colors.primary.text};
|
||||
`,
|
||||
fill: css`
|
||||
flex-grow: 1;
|
||||
margin-bottom: ${theme.spacing(0.5)};
|
||||
`,
|
||||
});
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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']);
|
||||
}
|
||||
|
@ -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<SelectableValue<string> | undefined>(undefined);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@ -37,7 +37,7 @@ export function AddGraphiteFunction({ dispatch, funcDefs }: Props) {
|
||||
options={options}
|
||||
onChange={setValue}
|
||||
inputMinWidth={150}
|
||||
></Segment>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
@ -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';
|
||||
|
@ -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;
|
@ -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 (
|
||||
<SegmentSection label="Functions" fill={true}>
|
||||
{functions.map((func: FuncInstance, index: number) => {
|
||||
return !func.hidden && <GraphiteFunctionEditor key={index} func={func} />;
|
||||
})}
|
||||
<AddGraphiteFunction funcDefs={funcDefs} />
|
||||
</SegmentSection>
|
||||
);
|
||||
}
|
@ -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};
|
||||
|
@ -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 (
|
||||
<GraphiteContext dispatch={dispatch}>
|
||||
{state.target?.textEditor && <GraphiteTextEditor rawQuery={state.target.target} />}
|
||||
{!state.target?.textEditor && (
|
||||
<>
|
||||
<SeriesSection state={state} />
|
||||
<FunctionsSection functions={state.queryModel?.functions} funcDefs={state.funcDefs!} />
|
||||
</>
|
||||
)}
|
||||
</GraphiteContext>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<>
|
||||
<QueryField
|
||||
query={rawQuery}
|
||||
onChange={updateQuery}
|
||||
onBlur={runQuery}
|
||||
onRunQuery={runQuery}
|
||||
placeholder={'Enter a Graphite query (run with Shift+Enter)'}
|
||||
portalOrigin="graphite"
|
||||
/>
|
||||
</>
|
||||
<QueryField
|
||||
query={rawQuery}
|
||||
onChange={updateQuery}
|
||||
onBlur={runQuery}
|
||||
onRunQuery={runQuery}
|
||||
placeholder={'Enter a Graphite query (run with Shift+Enter)'}
|
||||
portalOrigin="graphite"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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 || '');
|
||||
|
@ -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';
|
||||
|
@ -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 (
|
||||
<div className={styles.container}>
|
||||
<>
|
||||
{segments.map((segment, index) => {
|
||||
return <MetricSegment segment={segment} metricIndex={index} key={index} dispatch={dispatch} state={state} />;
|
||||
return <MetricSegment segment={segment} metricIndex={index} key={index} state={state} />;
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles() {
|
||||
return {
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -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 ? (
|
||||
<TagsSection
|
||||
dispatch={dispatch}
|
||||
tags={state.queryModel?.tags}
|
||||
addTagSegments={state.addTagSegments}
|
||||
state={state}
|
||||
/>
|
||||
export function SeriesSection({ state }: Props) {
|
||||
const sectionContent = state.queryModel?.seriesByTagUsed ? (
|
||||
<TagsSection tags={state.queryModel?.tags} addTagSegments={state.addTagSegments} state={state} />
|
||||
) : (
|
||||
<MetricsSection dispatch={dispatch} segments={state.segments} state={state} />
|
||||
<MetricsSection segments={state.segments} state={state} />
|
||||
);
|
||||
|
||||
return (
|
||||
<SegmentSection label="Series" fill={true}>
|
||||
{sectionContent}
|
||||
</SegmentSection>
|
||||
);
|
||||
}
|
||||
|
@ -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 || '');
|
||||
|
@ -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 (
|
||||
<div className={styles.container}>
|
||||
<>
|
||||
{tags.map((tag, index) => {
|
||||
return <TagEditor key={index} tagIndex={index} tag={tag} dispatch={dispatch} state={state} />;
|
||||
return <TagEditor key={index} tagIndex={index} tag={tag} state={state} />;
|
||||
})}
|
||||
{newTagsOptions.length && (
|
||||
<SegmentAsync<GraphiteSegment>
|
||||
@ -58,16 +59,13 @@ export function TagsSection({ dispatch, tags, state, addTagSegments }: Props) {
|
||||
Component={<Button icon="plus" variant="secondary" className={styles.button} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{state.paused && <PlayButton />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`,
|
||||
button: css`
|
||||
margin-right: ${theme.spacing(0.5)};
|
||||
`,
|
||||
|
@ -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';
|
||||
|
@ -1,42 +1,3 @@
|
||||
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.state.target.textEditor">
|
||||
<graphite-text-editor style="width: 100%" rawQuery="ctrl.state.target.target" dispatch="ctrl.dispatch"></graphite-text-editor>
|
||||
</div>
|
||||
|
||||
<div ng-hide="ctrl.target.textEditor">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6 query-keyword">Series</label>
|
||||
</div>
|
||||
|
||||
<series-section dispatch="ctrl.dispatch" tags="ctrl.state.queryModel.tags" addTagSegments="ctrl.state.addTagSegments" state="ctrl.state" segments="ctrl.state.segments" rawQuery="ctrl.state.target.target"></series-section>
|
||||
|
||||
<div ng-if="ctrl.state.paused" class="gf-form">
|
||||
<play-button dispatch="ctrl.dispatch" />
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6 query-keyword">Functions</label>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="func in ctrl.state.queryModel.functions" class="gf-form">
|
||||
<graphite-function-editor func="func" dispatch="ctrl.dispatch" ng-hide="func.hidden"></graphite-function-editor>
|
||||
</div>
|
||||
|
||||
<div class="gf-form dropdown">
|
||||
<add-graphite-function funcDefs="ctrl.state.funcDefs" dispatch="ctrl.dispatch" />
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<graphite-query-editor state="ctrl.state" dispatch="ctrl.dispatch"></graphite-query-editor>
|
||||
</query-editor-row>
|
||||
|
@ -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<HTMLInputElement>) {
|
||||
// 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 <gf-form-dropdown>
|
||||
*/
|
||||
getTags(index: number, query: string): void {
|
||||
// WIP: removed, called from TagsSection
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag list when adding a new tag with <metric-segment>
|
||||
*/
|
||||
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 {}
|
||||
|
16
public/app/plugins/datasource/graphite/state/context.tsx
Normal file
16
public/app/plugins/datasource/graphite/state/context.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { createContext, Dispatch, PropsWithChildren, useContext } from 'react';
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
|
||||
type Props = {
|
||||
dispatch: Dispatch<AnyAction>;
|
||||
};
|
||||
|
||||
const DispatchContext = createContext<Dispatch<AnyAction>>({} as Dispatch<AnyAction>);
|
||||
|
||||
export const useDispatch = () => {
|
||||
return useContext(DispatchContext);
|
||||
};
|
||||
|
||||
export const GraphiteContext = ({ children, dispatch }: PropsWithChildren<Props>) => {
|
||||
return <DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider>;
|
||||
};
|
@ -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]'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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<string[]>): Promise
|
||||
return optionsPromise.then((options) => [...getTemplateVariableOptions(), ...options]);
|
||||
}
|
||||
|
||||
const SectionWrap = ({ initialName, children }: { initialName: string; children: React.ReactNode }) => (
|
||||
<div className="gf-form-inline">
|
||||
<SectionLabel name={initialName} isInitial={true} />
|
||||
{children}
|
||||
<SectionFill />
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<SectionWrap initialName="from">
|
||||
<SegmentSection label="FROM" fill={true}>
|
||||
<FromSection
|
||||
policy={policy}
|
||||
measurement={measurement}
|
||||
@ -124,7 +118,9 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
}
|
||||
onChange={handleFromSectionChange}
|
||||
/>
|
||||
<SectionLabel name="where" />
|
||||
<InlineLabel width="auto" className={styles.inlineLabel}>
|
||||
WHERE
|
||||
</InlineLabel>
|
||||
<TagsSection
|
||||
tags={query.tags ?? []}
|
||||
onChange={handleTagsSectionChange}
|
||||
@ -133,9 +129,9 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
withTemplateVariableOptions(getTagValues(key, measurement, policy, query.tags ?? [], datasource))
|
||||
}
|
||||
/>
|
||||
</SectionWrap>
|
||||
</SegmentSection>
|
||||
{selectLists.map((sel, index) => (
|
||||
<SectionWrap key={index} initialName={index === 0 ? 'select' : ''}>
|
||||
<SegmentSection key={index} label={index === 0 ? 'SELECT' : ''} fill={true}>
|
||||
<PartListSection
|
||||
parts={sel}
|
||||
getNewPartOptions={() => Promise.resolve(getNewSelectPartOptions())}
|
||||
@ -150,9 +146,9 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
onAppliedChange(removeSelectPart(query, partIndex, index));
|
||||
}}
|
||||
/>
|
||||
</SectionWrap>
|
||||
</SegmentSection>
|
||||
))}
|
||||
<SectionWrap initialName="group by">
|
||||
<SegmentSection label="GROUP BY" fill={true}>
|
||||
<PartListSection
|
||||
parts={groupByList}
|
||||
getNewPartOptions={() => getNewGroupByPartOptions(query, getTagKeys)}
|
||||
@ -167,8 +163,8 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
onAppliedChange(removeGroupByPart(query, partIndex));
|
||||
}}
|
||||
/>
|
||||
</SectionWrap>
|
||||
<SectionWrap initialName="timezone">
|
||||
</SegmentSection>
|
||||
<SegmentSection label="TIMEZONE" fill={true}>
|
||||
<InputSection
|
||||
placeholder="(optional)"
|
||||
value={query.tz}
|
||||
@ -176,20 +172,22 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
onAppliedChange({ ...query, tz });
|
||||
}}
|
||||
/>
|
||||
<SectionLabel name="order by time" />
|
||||
<InlineLabel width="auto" className={styles.inlineLabel}>
|
||||
ORDER BY TIME
|
||||
</InlineLabel>
|
||||
<OrderByTimeSection
|
||||
value={query.orderByTime === 'DESC' ? 'DESC' : 'ASC' /* FIXME: make this shared with influx_query_model */}
|
||||
onChange={(v) => {
|
||||
onAppliedChange({ ...query, orderByTime: v });
|
||||
}}
|
||||
/>
|
||||
</SectionWrap>
|
||||
</SegmentSection>
|
||||
{/* 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. */}
|
||||
<SectionWrap initialName="limit">
|
||||
<SegmentSection label="LIMIT" fill={true}>
|
||||
<InputSection
|
||||
placeholder="(optional)"
|
||||
value={query.limit?.toString()}
|
||||
@ -197,7 +195,9 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
onAppliedChange({ ...query, limit });
|
||||
}}
|
||||
/>
|
||||
<SectionLabel name="slimit" />
|
||||
<InlineLabel width="auto" className={styles.inlineLabel}>
|
||||
SLIMIT
|
||||
</InlineLabel>
|
||||
<InputSection
|
||||
placeholder="(optional)"
|
||||
value={query.slimit?.toString()}
|
||||
@ -205,8 +205,8 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
onAppliedChange({ ...query, slimit });
|
||||
}}
|
||||
/>
|
||||
</SectionWrap>
|
||||
<SectionWrap initialName="format as">
|
||||
</SegmentSection>
|
||||
<SegmentSection label="FORMAT AS" fill={true}>
|
||||
<FormatAsSection
|
||||
format={query.resultFormat ?? DEFAULT_RESULT_FORMAT}
|
||||
onChange={(format) => {
|
||||
@ -215,7 +215,9 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
/>
|
||||
{query.resultFormat !== 'table' && (
|
||||
<>
|
||||
<SectionLabel name="alias" />
|
||||
<InlineLabel width="auto" className={styles.inlineLabel}>
|
||||
ALIAS
|
||||
</InlineLabel>
|
||||
<InputSection
|
||||
isWide
|
||||
placeholder="Naming pattern"
|
||||
@ -226,7 +228,15 @@ export const Editor = (props: Props): JSX.Element => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</SectionWrap>
|
||||
</SegmentSection>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
inlineLabel: css`
|
||||
color: ${theme.colors.primary.text};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -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) => (
|
||||
<label className={cx('gf-form-label query-keyword', { 'width-7': isInitial ?? false }, uppercaseClass)}>{name}</label>
|
||||
);
|
Loading…
Reference in New Issue
Block a user