From cbaf700d646373de6ed10d7264cc1a573b95a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Tue, 16 Mar 2021 10:47:33 +0100 Subject: [PATCH] influxdb: switch the raw influxql editor from angular to react (#31860) * influxdb: switch the raw influxql editor from angular to react * influxdb: raw-influxql: better callback-naming * influxdb: raw-influxql: use custom hook * influxdb: flux: raw-editor: add unit tests --- .../components/RawInfluxQLEditor.test.tsx | 111 ++++++++++++++++++ .../influxdb/components/RawInfluxQLEditor.tsx | 79 +++++++++++++ .../components/useShadowedState.test.ts | 27 +++++ .../influxdb/components/useShadowedState.ts | 16 +++ .../influxdb/components/useUniqueId.test.ts | 23 ++++ .../influxdb/components/useUniqueId.ts | 17 +++ .../app/plugins/datasource/influxdb/module.ts | 3 + .../influxdb/partials/query.editor.html | 43 +------ .../plugins/datasource/influxdb/query_ctrl.ts | 7 ++ .../influxdb/registerRawInfluxQLEditor.ts | 9 ++ .../app/plugins/datasource/influxdb/types.ts | 5 +- 11 files changed, 301 insertions(+), 39 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx create mode 100644 public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx create mode 100644 public/app/plugins/datasource/influxdb/components/useShadowedState.test.ts create mode 100644 public/app/plugins/datasource/influxdb/components/useShadowedState.ts create mode 100644 public/app/plugins/datasource/influxdb/components/useUniqueId.test.ts create mode 100644 public/app/plugins/datasource/influxdb/components/useUniqueId.ts create mode 100644 public/app/plugins/datasource/influxdb/registerRawInfluxQLEditor.ts diff --git a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx new file mode 100644 index 00000000000..cec25e90dc6 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.test.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { select } from 'react-select-event'; +import { RawInfluxQLEditor } from './RawInfluxQLEditor'; +import { InfluxQuery } from '../types'; + +const query: InfluxQuery = { + refId: 'A', + query: 'test query 1', + resultFormat: 'table', + alias: 'alias42', +}; + +describe('RawInfluxQLEditor', () => { + it('should render', () => { + render( null} onChange={() => null} query={query} />); + const queryTextarea = screen.getByLabelText('query'); + const aliasInput = screen.getByLabelText('Alias by'); + const formatSelect = screen.getByLabelText('Format as'); + + expect(formatSelect).toBeInTheDocument(); + expect(queryTextarea).toBeInTheDocument(); + expect(aliasInput).toBeInTheDocument(); + + expect(queryTextarea).toHaveValue('test query 1'); + expect(aliasInput).toHaveValue('alias42'); + + // the only way to validate the text-displayed on the select-box + expect(screen.getByText('Table')).toBeInTheDocument(); + }); + + it('should handle no-alias, no-query, no-resultFormat', () => { + const emptyQuery = { refId: 'B' }; + render( null} onChange={() => null} query={emptyQuery} />); + + const queryTextarea = screen.getByLabelText('query'); + const aliasInput = screen.getByLabelText('Alias by'); + + const formatSelect = screen.getByLabelText('Format as'); + expect(formatSelect).toBeInTheDocument(); + + expect(queryTextarea).toBeInTheDocument(); + expect(aliasInput).toBeInTheDocument(); + + expect(queryTextarea).toHaveValue(''); + expect(aliasInput).toHaveValue(''); + + // the only way to validate the text-displayed on the select-box + expect(screen.getByText('Time series')).toBeInTheDocument(); + }); + + it('should call onChange immediately when resultFormat change', async () => { + const onChange = jest.fn(); + render( null} onChange={onChange} query={query} />); + + const formatSelect = screen.getByLabelText('Format as'); + expect(formatSelect).toBeInTheDocument(); + + await select(formatSelect, 'Time series'); + + expect(onChange).toHaveBeenCalledWith({ ...query, resultFormat: 'time_series' }); + }); + + it('should only call onChange on blur when query changes', async () => { + const onChange = jest.fn(); + render( null} onChange={onChange} query={query} />); + + const queryTextarea = screen.getByLabelText('query'); + expect(queryTextarea).toBeInTheDocument(); + const aliasInput = screen.getByLabelText('Alias by'); + expect(aliasInput).toBeInTheDocument(); + + // value before + expect(queryTextarea).toHaveValue('test query 1'); + + userEvent.type(queryTextarea, 'new changes'); + + // the field should have a new value, but no onChange yet. + expect(queryTextarea).toHaveValue('test query 1new changes'); + expect(onChange).toHaveBeenCalledTimes(0); + + aliasInput.focus(); // this should trigger blur on queryTextarea + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith({ ...query, query: 'test query 1new changes' }); + }); + it('should only call onChange on blur when alias changes', async () => { + const onChange = jest.fn(); + render( null} onChange={onChange} query={query} />); + + const queryTextarea = screen.getByLabelText('query'); + expect(queryTextarea).toBeInTheDocument(); + const aliasInput = screen.getByLabelText('Alias by'); + expect(aliasInput).toBeInTheDocument(); + + // value before + expect(aliasInput).toHaveValue('alias42'); + + userEvent.type(aliasInput, 'new changes'); + + // the field should have a new value, but no onChange yet. + expect(aliasInput).toHaveValue('alias42new changes'); + expect(onChange).toHaveBeenCalledTimes(0); + + queryTextarea.focus(); // this should trigger blur on queryTextarea + + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith({ ...query, alias: 'alias42new changes' }); + }); +}); diff --git a/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx new file mode 100644 index 00000000000..17f86391954 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/components/RawInfluxQLEditor.tsx @@ -0,0 +1,79 @@ +import React, { FC } from 'react'; +import { TextArea, InlineFormLabel, Input, Select, HorizontalGroup } from '@grafana/ui'; +import { SelectableValue } from '@grafana/data'; +import { ResultFormat, InfluxQuery } from '../types'; +import { useShadowedState } from './useShadowedState'; +import { useUniqueId } from './useUniqueId'; + +const RESULT_FORMATS: Array> = [ + { label: 'Time series', value: 'time_series' }, + { label: 'Table', value: 'table' }, + { label: 'Logs', value: 'logs' }, +]; + +const DEFAULT_RESULT_FORMAT: ResultFormat = 'time_series'; + +type Props = { + query: InfluxQuery; + onChange: (query: InfluxQuery) => void; + onRunQuery: () => void; +}; + +// we handle 3 fields: "query", "alias", "resultFormat" +// "resultFormat" changes are applied immediately +// "query" and "alias" changes only happen on onblur +export const RawInfluxQLEditor: FC = ({ query, onChange, onRunQuery }) => { + const [currentQuery, setCurrentQuery] = useShadowedState(query.query); + const [currentAlias, setCurrentAlias] = useShadowedState(query.alias); + const aliasElementId = useUniqueId(); + const selectElementId = useUniqueId(); + + const applyDelayedChangesAndRunQuery = () => { + onChange({ + ...query, + query: currentQuery, + alias: currentAlias, + }); + onRunQuery(); + }; + + return ( +
+ -
-
-
- -
- -
-
-
- - -
-
-
-
-
+
diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 3981ea9a7ba..5666b914ed4 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -83,6 +83,13 @@ export class InfluxQueryCtrl extends QueryCtrl { this.target.query = target.query; }; + // only called from raw-mode influxql-editor + onRawInfluxQLChange = (target: InfluxQuery) => { + this.target.query = target.query; + this.target.resultFormat = target.resultFormat; + this.target.alias = target.alias; + }; + onRunQuery = () => { this.panelCtrl.refresh(); }; diff --git a/public/app/plugins/datasource/influxdb/registerRawInfluxQLEditor.ts b/public/app/plugins/datasource/influxdb/registerRawInfluxQLEditor.ts new file mode 100644 index 00000000000..be5159034d3 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/registerRawInfluxQLEditor.ts @@ -0,0 +1,9 @@ +import coreModule from 'app/core/core_module'; +import { RawInfluxQLEditor } from './components/RawInfluxQLEditor'; + +coreModule.directive('rawInfluxEditor', [ + 'reactDirective', + (reactDirective: any) => { + return reactDirective(RawInfluxQLEditor, ['query', 'onChange', 'onRunQuery']); + }, +]); diff --git a/public/app/plugins/datasource/influxdb/types.ts b/public/app/plugins/datasource/influxdb/types.ts index 929f7326c23..01d2a62a7fa 100644 --- a/public/app/plugins/datasource/influxdb/types.ts +++ b/public/app/plugins/datasource/influxdb/types.ts @@ -38,10 +38,12 @@ export interface InfluxQueryTag { value: string; } +export type ResultFormat = 'time_series' | 'table' | 'logs'; + export interface InfluxQuery extends DataQuery { policy?: string; measurement?: string; - resultFormat?: 'time_series' | 'table'; + resultFormat?: ResultFormat; orderByTime?: string; tags?: InfluxQueryTag[]; groupBy?: InfluxQueryPart[]; @@ -52,4 +54,5 @@ export interface InfluxQuery extends DataQuery { fill?: string; rawQuery?: boolean; query?: string; + alias?: string; }