From 2fc08462f830f219c2b470d2d18a4c70d1bf974f Mon Sep 17 00:00:00 2001 From: asimonok Date: Tue, 21 Nov 2023 13:27:23 +0300 Subject: [PATCH 1/3] Add Data Source option for reset button --- src/components/FormPanel/FormPanel.test.tsx | 157 ++++++++++++++++++++ src/components/FormPanel/FormPanel.tsx | 73 ++++++++- src/constants/request.ts | 2 + src/module.ts | 42 +++++- src/types/request.ts | 14 ++ 5 files changed, 285 insertions(+), 3 deletions(-) diff --git a/src/components/FormPanel/FormPanel.test.tsx b/src/components/FormPanel/FormPanel.test.tsx index c9bd93b0..1b232370 100644 --- a/src/components/FormPanel/FormPanel.test.tsx +++ b/src/components/FormPanel/FormPanel.test.tsx @@ -1461,6 +1461,163 @@ describe('Panel', () => { jest.mocked(getAppEvents).mockClear(); }); + + it('Should run reset datasource request', async () => { + /** + * Render + */ + jest.mocked(fetch).mockImplementationOnce( + () => + Promise.resolve({ + ok: true, + json: jest.fn(() => + Promise.resolve({ + test: '123', + number: 123, + }) + ), + }) as any + ); + + const datasourceRequestMock = jest.fn(() => + Promise.resolve({ + message: 'hello', + ok: true, + }) + ) as any; + jest.mocked(useDatasourceRequest).mockImplementation(() => datasourceRequestMock); + + const { rerender } = await act(async () => + render( + getComponent({ + options: { + elements: [ + { ...FormElementDefault, id: 'test', value: '123' }, + { type: FormElementType.NUMBER, id: 'number', value: 123 }, + ], + }, + }) + ) + ); + /** + * Trigger element updates + */ + await act(async () => + rerender( + getComponent({ + options: { + elements: [ + { ...FormElementDefault, id: 'test', value: '123' }, + { type: FormElementType.NUMBER, id: 'number', value: 111 }, + { type: FormElementType.DISABLED, id: 'disabled', value: '222' }, + ], + resetAction: { + datasource: 'abc', + mode: ResetActionMode.DATASOURCE, + payloadMode: PayloadMode.CUSTOM, + getPayload: `return { key1: 'value' }`, + }, + }, + }) + ) + ); + + /** + * Check if Reset can be run + */ + expect(selectors.buttonReset()).toBeInTheDocument(); + expect(selectors.buttonReset()).not.toBeDisabled(); + + /** + * Run reset request + */ + await act(async () => { + fireEvent.click(selectors.buttonReset()); + }); + + expect(datasourceRequestMock).toHaveBeenCalledWith({ + datasource: 'abc', + query: { + key1: 'value', + }, + replaceVariables: expect.any(Function), + }); + }); + + it('Should show reset datasource request error', async () => { + /** + * Render + */ + jest.mocked(fetch).mockImplementationOnce( + () => + Promise.resolve({ + ok: true, + json: jest.fn(() => + Promise.resolve({ + test: '123', + number: 123, + }) + ), + }) as any + ); + + const datasourceRequestMock = jest.fn(() => + Promise.reject({ + message: 'hello', + }) + ); + jest.mocked(useDatasourceRequest).mockImplementation(() => datasourceRequestMock); + + const { rerender } = await act(async () => + render( + getComponent({ + options: { + elements: [ + { ...FormElementDefault, id: 'test', value: '123' }, + { type: FormElementType.NUMBER, id: 'number', value: 123 }, + ], + }, + }) + ) + ); + /** + * Trigger element updates + */ + await act(async () => + rerender( + getComponent({ + options: { + elements: [ + { ...FormElementDefault, id: 'test', value: '123' }, + { type: FormElementType.NUMBER, id: 'number', value: 111 }, + { type: FormElementType.DISABLED, id: 'disabled', value: '222' }, + ], + resetAction: { + datasource: 'abc', + mode: ResetActionMode.DATASOURCE, + payloadMode: PayloadMode.CUSTOM, + getPayload: `return { key1: 'value' }`, + }, + }, + }) + ) + ); + + /** + * Check if Reset can be run + */ + expect(selectors.buttonReset()).toBeInTheDocument(); + expect(selectors.buttonReset()).not.toBeDisabled(); + + /** + * Run reset request + */ + await act(async () => { + fireEvent.click(selectors.buttonReset()); + }); + + await waitFor(() => expect(selectors.errorMessage()).toBeInTheDocument()); + }); }); describe('Confirm changes', () => { diff --git a/src/components/FormPanel/FormPanel.tsx b/src/components/FormPanel/FormPanel.tsx index d80f8a07..7f1fc60d 100644 --- a/src/components/FormPanel/FormPanel.tsx +++ b/src/components/FormPanel/FormPanel.tsx @@ -467,15 +467,86 @@ export const FormPanel: React.FC = ({ return; } + if (options.resetAction.mode === ResetActionMode.CUSTOM) { + /** + * Loading + */ + setLoading(LoadingMode.RESET); + + /** + * Execute Custom Code and reset Loading + */ + await executeCustomCode({ code: options.resetAction.code, initial }); + setLoading(LoadingMode.NONE); + } + /** * Loading */ setLoading(LoadingMode.RESET); + if (!options.resetAction.datasource) { + /** + * Show No Data Source Error and Reset Loading + */ + setError('Please select Data Source for Reset Request.'); + setLoading(LoadingMode.NONE); + + return; + } + + /** + * Set payload + */ + const payload = GetPayloadForRequest({ + request: { + datasource: options.resetAction.datasource, + payloadMode: PayloadMode.CUSTOM, + method: RequestMethod.NONE, + url: '', + header: [], + getPayload: options.resetAction.getPayload, + contentType: ContentType.PLAIN, + code: options.resetAction.code, + highlight: false, + highlightColor: '', + confirm: false, + updatedOnly: false, + }, + elements, + initial, + }); + + /** + * Datasource query + */ + const response: FetchResponse | null = await datasourceRequest({ + query: payload, + datasource: options.resetAction.datasource, + replaceVariables, + }).catch((error: DataQueryError) => { + setError(JSON.stringify(error)); + return null; + }); + + let currentElements = elements; + if (response && response.ok) { + /** + * Change Elements With Data Source Values + */ + const queryResponse = toDataQueryResponse(response as FetchResponse); + currentElements = getElementsWithFieldValues(queryResponse.data, RequestMethod.DATASOURCE); + + /** + * Update Elements + */ + onChangeElements(currentElements); + } + /** * Execute Custom Code and reset Loading */ - await executeCustomCode({ code: options.resetAction.code, initial }); + await executeCustomCode({ code: options.resetAction.code, initial, response, currentElements }); setLoading(LoadingMode.NONE); }; diff --git a/src/constants/request.ts b/src/constants/request.ts index 6c609fde..cfa9b6e2 100644 --- a/src/constants/request.ts +++ b/src/constants/request.ts @@ -110,6 +110,7 @@ export const PayloadModeOptions = [ export const enum ResetActionMode { INITIAL = 'initial', CUSTOM = 'custom', + DATASOURCE = 'datasource', } /** @@ -118,4 +119,5 @@ export const enum ResetActionMode { export const ResetActionOptions = [ { label: 'Custom Code', value: ResetActionMode.CUSTOM }, { label: 'Initial request', value: ResetActionMode.INITIAL }, + { label: 'Data Source', value: ResetActionMode.DATASOURCE }, ]; diff --git a/src/module.ts b/src/module.ts index 98aed14d..f41dfb31 100644 --- a/src/module.ts +++ b/src/module.ts @@ -593,7 +593,7 @@ export const plugin = new PanelPlugin(FormPanel).setNoPadding().se .addRadio({ path: 'resetAction.mode', name: 'Reset Action', - category: ['Reset request'], + category: ['Reset Request'], description: 'What action should be called by clicking on reset button.', settings: { options: ResetActionOptions, @@ -607,15 +607,53 @@ export const plugin = new PanelPlugin(FormPanel).setNoPadding().se name: 'Custom Code', description: 'Custom code to execute reset request.', editor: CustomCodeEditor, - category: ['Reset request'], + category: ['Reset Request'], settings: { language: CodeLanguage.JAVASCRIPT, }, defaultValue: CodeResetDefault, showIf: (config) => config.reset.variant !== ButtonVariant.HIDDEN && config.resetAction.mode === ResetActionMode.CUSTOM, + }) + .addCustomEditor({ + id: 'resetAction.datasource', + path: 'resetAction.datasource', + name: 'Data Source', + category: ['Reset Request'], + editor: DatasourceEditor, + showIf: (config) => config.resetAction.mode === ResetActionMode.DATASOURCE, + }) + .addCustomEditor({ + id: 'resetAction.code', + path: 'resetAction.code', + name: 'Custom Code', + description: 'Custom code to execute after reset request.', + editor: CustomCodeEditor, + category: ['Reset Request'], + settings: { + language: CodeLanguage.JAVASCRIPT, + }, + defaultValue: CodeUpdateDefault, + showIf: (config) => config.resetAction.mode === ResetActionMode.DATASOURCE, }); + /** + * Reset Request Payload + */ + builder.addCustomEditor({ + id: 'resetAction.getPayload', + path: 'resetAction.getPayload', + name: 'Create Payload', + description: 'Custom code to create payload for the reset data source request.', + editor: CustomCodeEditor, + category: ['Reset Request Payload'], + settings: { + language: CodeLanguage.JAVASCRIPT, + }, + defaultValue: PayloadInitialDefault, + showIf: (config) => config.resetAction.mode === ResetActionMode.DATASOURCE && !!config.resetAction.datasource, + }); + /** * Save Defaults Button */ diff --git a/src/types/request.ts b/src/types/request.ts index da5328b1..79c69bed 100644 --- a/src/types/request.ts +++ b/src/types/request.ts @@ -107,4 +107,18 @@ export interface ResetAction { * @type {string} */ code: string; + + /** + * Data Source + * + * @type {string} + */ + datasource: string; + + /** + * Get Payload + * + * @type {string} + */ + getPayload: string; } From ea3012f7587fed38c0e6c16c39ef504121d3b978 Mon Sep 17 00:00:00 2001 From: asimonok Date: Tue, 21 Nov 2023 13:36:38 +0300 Subject: [PATCH 2/3] Fix tests --- src/module.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/module.test.ts b/src/module.test.ts index 79e3551a..36b682fa 100644 --- a/src/module.test.ts +++ b/src/module.test.ts @@ -266,5 +266,22 @@ describe('plugin', () => { expect(shownOptionsPaths).toEqual(expect.arrayContaining(['resetAction.code'])); }); + + it('Should show reset payload if reset action is data source', () => { + const shownOptionsPaths: string[] = []; + + builder.addCustomEditor.mockImplementation( + addInputImplementation( + { + reset: { variant: ButtonVariant.CUSTOM } as any, + resetAction: { mode: ResetActionMode.DATASOURCE, datasource: '123' } as any, + }, + shownOptionsPaths + ) + ); + plugin['optionsSupplier'](builder); + + expect(shownOptionsPaths).toEqual(expect.arrayContaining(['resetAction.getPayload'])); + }); }); }); From a8bc4e9a4ee3131b24f73671a8ba66218f472c32 Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Tue, 21 Nov 2023 14:28:53 -0500 Subject: [PATCH 3/3] Add clear error for Reset request --- CHANGELOG.md | 1 + src/components/FormPanel/FormPanel.tsx | 5 +++++ src/constants/request.ts | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47217016..5dfd6f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Update to Grafana 10.2.1 (#292) - Update ESLint configuration (#294) - Add Autosize Code Editor (#295) +- Add Data Source option for Reset button (#296) ### Bugfixes diff --git a/src/components/FormPanel/FormPanel.tsx b/src/components/FormPanel/FormPanel.tsx index 7f1fc60d..8673ee0c 100644 --- a/src/components/FormPanel/FormPanel.tsx +++ b/src/components/FormPanel/FormPanel.tsx @@ -459,6 +459,11 @@ export const FormPanel: React.FC = ({ * Reset Request */ const resetRequest = async () => { + /** + * Clear Error + */ + setError(''); + if (options.resetAction.mode === ResetActionMode.INITIAL) { /** * Use Initial Request diff --git a/src/constants/request.ts b/src/constants/request.ts index cfa9b6e2..4b0c0cc9 100644 --- a/src/constants/request.ts +++ b/src/constants/request.ts @@ -118,6 +118,6 @@ export const enum ResetActionMode { */ export const ResetActionOptions = [ { label: 'Custom Code', value: ResetActionMode.CUSTOM }, - { label: 'Initial request', value: ResetActionMode.INITIAL }, + { label: 'Initial Request', value: ResetActionMode.INITIAL }, { label: 'Data Source', value: ResetActionMode.DATASOURCE }, ];