From bf182e0beeaeaa309aa45b0df5e290b571ad085a Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Wed, 2 Oct 2024 17:27:42 +0300 Subject: [PATCH 1/7] update query fields --- src/hooks/useQueryFields.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/hooks/useQueryFields.ts b/src/hooks/useQueryFields.ts index bd2ecbe5..b33510e9 100644 --- a/src/hooks/useQueryFields.ts +++ b/src/hooks/useQueryFields.ts @@ -1,4 +1,4 @@ -import { DataFrame } from '@grafana/data'; +import { DataFrame, getFieldDisplayName } from '@grafana/data'; import { useMemo } from 'react'; import { QueryField } from '../types'; @@ -9,13 +9,17 @@ import { QueryField } from '../types'; export const useQueryFields = ({ data, isEnabled }: { data?: DataFrame[]; isEnabled: boolean }) => { return useMemo(() => { if (isEnabled && data) { - return data.reduce((acc: QueryField[], { fields, refId }) => { + return data.reduce((acc: QueryField[], frame) => { + const { fields, refId } = frame; return acc.concat( - fields.map((field) => ({ - value: field.name, - refId, - label: `${refId}:${field.name}`, - })) + fields.map((field) => { + const fieldName = getFieldDisplayName(field, frame, data); + return { + value: field.name, + refId, + label: `${refId}:${fieldName}`, + }; + }) ); }, []); } From e2591978c296117780de694cfa7166fa5f5a9091 Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Wed, 2 Oct 2024 17:49:30 +0300 Subject: [PATCH 2/7] add test cases --- src/hooks/useQueryFields.test.ts | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/hooks/useQueryFields.test.ts diff --git a/src/hooks/useQueryFields.test.ts b/src/hooks/useQueryFields.test.ts new file mode 100644 index 00000000..f51e8269 --- /dev/null +++ b/src/hooks/useQueryFields.test.ts @@ -0,0 +1,55 @@ +import { renderHook } from '@testing-library/react'; + +import { useQueryFields } from './useQueryFields'; + +const createDataFrame = ({ fields, frameName, refId }: { fields: any; frameName: string; refId?: any }) => ({ + refId: refId, + fields: fields.map((name: string) => ({ name })), + name: frameName, +}); + +describe('useQueryFields', () => { + it('Should return an array', () => { + const data: any = [ + createDataFrame({ refId: 'A', fields: ['field1', 'field2'], frameName: 'FrameA' }), + createDataFrame({ refId: 'B', fields: ['field3'], frameName: 'FrameB' }), + ]; + const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); + + expect(result.current).toEqual([ + { value: 'field1', refId: 'A', label: 'A:FrameA field1' }, + { value: 'field2', refId: 'A', label: 'A:FrameA field2' }, + { value: 'field3', refId: 'B', label: 'B:FrameB field3' }, + ]); + }); + + it('Should return an array if refId the same value', () => { + const data: any = [ + createDataFrame({ refId: 'A', fields: ['field1', 'field2'], frameName: 'Frame A' }), + createDataFrame({ refId: 'A', fields: ['field1', 'field2'], frameName: 'Frame A-1' }), + ]; + const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); + + expect(result.current).toEqual([ + { value: 'field1', refId: 'A', label: 'A:Frame A field1' }, + { value: 'field2', refId: 'A', label: 'A:Frame A field2' }, + { value: 'field1', refId: 'A', label: 'A:Frame A-1 field1' }, + { value: 'field2', refId: 'A', label: 'A:Frame A-1 field2' }, + ]); + }); + + it('Should return an array if refId is not provided', () => { + const data: any = [ + createDataFrame({ fields: ['field1', 'field2'], frameName: 'Frame A' }), + createDataFrame({ fields: ['field1', 'field2'], frameName: 'Frame A-1' }), + ]; + const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); + + expect(result.current).toEqual([ + { value: 'field1', refId: undefined, label: 'undefined:Frame A field1' }, + { value: 'field2', refId: undefined, label: 'undefined:Frame A field2' }, + { value: 'field1', refId: undefined, label: 'undefined:Frame A-1 field1' }, + { value: 'field2', refId: undefined, label: 'undefined:Frame A-1 field2' }, + ]); + }); +}); From 9927a31ebe0dc8fb41b12777629df72ca202f962 Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Wed, 2 Oct 2024 17:53:26 +0300 Subject: [PATCH 3/7] provisioning updated --- provisioning/dashboards/datasource.json | 186 +++++++++++++++++++++--- 1 file changed, 169 insertions(+), 17 deletions(-) diff --git a/provisioning/dashboards/datasource.json b/provisioning/dashboards/datasource.json index 429128ff..563d4f4e 100644 --- a/provisioning/dashboards/datasource.json +++ b/provisioning/dashboards/datasource.json @@ -21,6 +21,152 @@ "links": [], "liveNow": false, "panels": [ + { + "datasource": { + "default": true, + "type": "marcusolsson-static-datasource", + "uid": "P1D2C73DC01F2359B" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "buttonGroup": { + "orientation": "center", + "size": "md" + }, + "confirmModal": { + "body": "Please confirm to update changed values", + "cancel": "Cancel", + "columns": { + "include": ["name", "oldValue", "newValue"], + "name": "Label", + "newValue": "New Value", + "oldValue": "Old Value" + }, + "confirm": "Confirm", + "elementDisplayMode": "modified", + "title": "Confirm update request" + }, + "elementValueChanged": "", + "elements": [ + { + "hidden": false, + "id": "String", + "labelWidth": 10, + "queryField": { + "label": "undefined:val-1 category", + "value": "category" + }, + "section": "", + "title": "String", + "tooltip": "", + "type": "string", + "uid": "0a15f927-e435-4678-8341-10f0e5caa3fb", + "unit": "", + "value": "" + } + ], + "initial": { + "code": "", + "contentType": "application/json", + "getPayload": "return {}", + "highlight": false, + "highlightColor": "red", + "method": "query", + "payload": {} + }, + "layout": { + "orientation": "horizontal", + "padding": 10, + "sectionVariant": "default", + "variant": "single" + }, + "reset": { + "backgroundColor": "purple", + "foregroundColor": "yellow", + "icon": "process", + "text": "Reset", + "variant": "hidden" + }, + "resetAction": { + "code": "if (context.panel.response) {\n context.grafana.notifySuccess(['Update', 'Values updated successfully.']);\n context.grafana.locationService.reload();\n} else {\n context.grafana.notifyError(['Update', 'An error occurred updating values.']);\n}", + "confirm": false, + "getPayload": "return {}", + "mode": "initial", + "payload": {} + }, + "saveDefault": { + "icon": "save", + "text": "Save Default", + "variant": "hidden" + }, + "submit": { + "backgroundColor": "purple", + "foregroundColor": "yellow", + "icon": "cloud-upload", + "text": "Submit", + "variant": "primary" + }, + "sync": true, + "update": { + "code": "", + "confirm": false, + "contentType": "application/json", + "getPayload": "const payload = {};\n\ncontext.panel.elements.forEach((element) => {\n if (!element.value) {\n return;\n }\n\n payload[element.id] = element.value;\n})\n\nreturn payload;", + "method": "-", + "payload": "const payload = {};\n\ncontext.panel.elements.forEach((element) => {\n if (!element.value) {\n return;\n }\n\n payload[element.id] = element.value;\n})\n\nreturn payload;", + "payloadMode": "all" + }, + "updateEnabled": "auto" + }, + "pluginVersion": "4.6.0", + "targets": [ + { + "datasource": { + "type": "marcusolsson-static-datasource", + "uid": "P1D2C73DC01F2359B" + }, + "frame": { + "fields": [ + { + "config": {}, + "name": "value", + "type": "string", + "values": ["val-1", "val-2", "val-3"] + }, + { + "config": {}, + "name": "category", + "type": "string", + "values": ["cat-1", "cat-2", "cat-3"] + } + ], + "meta": {}, + "name": "A" + }, + "refId": "A" + } + ], + "title": "Transform data - for Query Fields", + "transformations": [ + { + "id": "partitionByValues", + "options": { + "fields": ["value"], + "keepFields": true, + "naming": { + "asLabels": false + } + } + } + ], + "type": "volkovlabs-form-panel" + }, { "datasource": { "type": "marcusolsson-static-datasource", @@ -28,6 +174,12 @@ }, "fieldConfig": { "defaults": { + "custom": { + "thresholdsStyle": { + "mode": "color", + "thresholds": [] + } + }, "thresholds": { "mode": "absolute", "steps": [ @@ -48,7 +200,7 @@ "h": 35, "w": 4, "x": 0, - "y": 0 + "y": 8 }, "id": 1, "options": { @@ -58,7 +210,14 @@ "customValue": false, "displayMode": "table", "emptyValue": false, - "favorites": true, + "favorites": { + "addQuery": {}, + "datasource": "", + "deleteQuery": {}, + "enabled": true, + "getQuery": {}, + "storage": "browser" + }, "filter": true, "groupSelection": false, "header": true, @@ -73,9 +232,10 @@ "showTotal": false, "statusSort": false, "sticky": true, + "tabsInOrder": true, "variable": "device" }, - "pluginVersion": "3.1.0", + "pluginVersion": "3.4.0", "type": "volkovlabs-variable-panel" }, { @@ -87,7 +247,7 @@ "h": 9, "w": 20, "x": 4, - "y": 0 + "y": 8 }, "id": 2, "options": { @@ -99,11 +259,7 @@ "body": "Please confirm to update changed values", "cancel": "Cancel", "columns": { - "include": [ - "name", - "oldValue", - "newValue" - ], + "include": ["name", "oldValue", "newValue"], "name": "Label", "newValue": "New Value", "oldValue": "Old Value" @@ -339,7 +495,7 @@ }, "updateEnabled": "auto" }, - "pluginVersion": "4.1.0", + "pluginVersion": "4.6.0", "targets": [ { "datasource": { @@ -382,7 +538,7 @@ "h": 9, "w": 20, "x": 4, - "y": 9 + "y": 17 }, "id": 3, "options": { @@ -394,11 +550,7 @@ "body": "Please confirm to update changed values", "cancel": "Cancel", "columns": { - "include": [ - "name", - "oldValue", - "newValue" - ], + "include": ["name", "oldValue", "newValue"], "name": "Label", "newValue": "New Value", "oldValue": "Old Value" @@ -611,7 +763,7 @@ }, "updateEnabled": "auto" }, - "pluginVersion": "4.1.0", + "pluginVersion": "4.6.0", "targets": [ { "datasource": { From ed7f4cbe45e6425f890b463bd01eb520d825cf83 Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Thu, 3 Oct 2024 11:32:01 +0300 Subject: [PATCH 4/7] update useQueryFields and get field from queryField for frame --- src/components/FormPanel/FormPanel.test.tsx | 157 ++++++++++++++++++++ src/components/FormPanel/FormPanel.tsx | 12 +- src/hooks/useQueryFields.test.ts | 26 ++-- src/hooks/useQueryFields.ts | 5 +- 4 files changed, 184 insertions(+), 16 deletions(-) diff --git a/src/components/FormPanel/FormPanel.test.tsx b/src/components/FormPanel/FormPanel.test.tsx index a450e79f..f41f3d58 100644 --- a/src/components/FormPanel/FormPanel.test.tsx +++ b/src/components/FormPanel/FormPanel.test.tsx @@ -750,6 +750,163 @@ describe('Panel', () => { ); }); + it('Should update elements with query result for the same refId', async () => { + /** + * Render + */ + await act(async () => { + render( + getComponent({ + options: { + initial: { + method: RequestMethod.QUERY, + }, + elements: [ + { + ...FORM_ELEMENT_DEFAULT, + type: FormElementType.CHECKBOX_LIST, + id: 'mapped', + queryField: { + refId: 'A', + value: 'metrics A-2 metric', + label: 'A:metrics A-2 metric', + }, + }, + { + ...FORM_ELEMENT_DEFAULT, + id: 'unmapped', + queryField: undefined, + }, + ], + }, + props: { + data: { + state: LoadingState.Done, + series: [ + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricA1', 'metricA2'], + }, + ], + name: 'metrics A-1', + refId: 'A', + }), + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricA1', 'metricA2'], + }, + ], + name: 'metrics A-2', + refId: 'A', + }), + ], + }, + }, + }) + ); + + await waitFor(() => expect(selectors.loadingBar(true)).not.toBeInTheDocument()); + }); + + expect(FormElements).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + elements: expect.arrayContaining([ + expect.objectContaining({ + id: 'mapped', + value: ['metricA1', 'metricA2'], + }), + expect.objectContaining({ + id: 'unmapped', + value: '', + }), + ]), + }), + expect.anything() + ); + }); + + it('Should update elements with query result for the same refId if refId undefined', async () => { + /** + * Render + */ + await act(async () => { + render( + getComponent({ + options: { + initial: { + method: RequestMethod.QUERY, + }, + elements: [ + { + ...FORM_ELEMENT_DEFAULT, + type: FormElementType.CHECKBOX_LIST, + id: 'mapped', + queryField: { + value: 'metrics A-1 metric', + label: 'undefined:metrics A-2 metric', + }, + }, + { + ...FORM_ELEMENT_DEFAULT, + id: 'unmapped', + queryField: undefined, + }, + ], + }, + props: { + data: { + state: LoadingState.Done, + series: [ + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricA1', 'metricA2'], + }, + ], + name: 'metrics A-1', + }), + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricA1', 'metricA2'], + }, + ], + name: 'metrics A-2', + }), + ], + }, + }, + }) + ); + + await waitFor(() => expect(selectors.loadingBar(true)).not.toBeInTheDocument()); + }); + + expect(FormElements).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + elements: expect.arrayContaining([ + expect.objectContaining({ + id: 'mapped', + value: ['metricA1', 'metricA2'], + }), + expect.objectContaining({ + id: 'unmapped', + value: '', + }), + ]), + }), + expect.anything() + ); + }); + it('Should not update elements if no query result', async () => { /** * Render diff --git a/src/components/FormPanel/FormPanel.tsx b/src/components/FormPanel/FormPanel.tsx index f06848a1..3b3ed86c 100644 --- a/src/components/FormPanel/FormPanel.tsx +++ b/src/components/FormPanel/FormPanel.tsx @@ -7,6 +7,7 @@ import { DataQueryError, DataQueryResponse, Field, + getFieldDisplayName, LoadingState, PanelProps, } from '@grafana/data'; @@ -187,8 +188,15 @@ export const FormPanel: React.FC = ({ const fieldConfig = sourceType === RequestMethod.QUERY ? element.queryField : { value: element.fieldName }; const field = frames .filter((frame) => (fieldConfig?.refId ? frame.refId === fieldConfig.refId : true)) - .reduce((acc: Field | undefined, { fields }) => { - const field = fields?.find((field: Field) => field.name === fieldConfig?.value); + .reduce((acc: Field | undefined, frame) => { + const { fields } = frame; + const field = fields?.find((field: Field) => { + /** + * Get unique field name + */ + const fieldName = getFieldDisplayName(field, frame, frames); + return fieldName === fieldConfig?.value; + }); if (field) { return field; } diff --git a/src/hooks/useQueryFields.test.ts b/src/hooks/useQueryFields.test.ts index f51e8269..864d0b87 100644 --- a/src/hooks/useQueryFields.test.ts +++ b/src/hooks/useQueryFields.test.ts @@ -11,15 +11,15 @@ const createDataFrame = ({ fields, frameName, refId }: { fields: any; frameName: describe('useQueryFields', () => { it('Should return an array', () => { const data: any = [ - createDataFrame({ refId: 'A', fields: ['field1', 'field2'], frameName: 'FrameA' }), - createDataFrame({ refId: 'B', fields: ['field3'], frameName: 'FrameB' }), + createDataFrame({ refId: 'A', fields: ['field1', 'field2'], frameName: 'Frame A' }), + createDataFrame({ refId: 'B', fields: ['field3'], frameName: 'Frame B' }), ]; const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); expect(result.current).toEqual([ - { value: 'field1', refId: 'A', label: 'A:FrameA field1' }, - { value: 'field2', refId: 'A', label: 'A:FrameA field2' }, - { value: 'field3', refId: 'B', label: 'B:FrameB field3' }, + { value: 'Frame A field1', refId: 'A', label: 'A:Frame A field1' }, + { value: 'Frame A field2', refId: 'A', label: 'A:Frame A field2' }, + { value: 'Frame B field3', refId: 'B', label: 'B:Frame B field3' }, ]); }); @@ -31,10 +31,10 @@ describe('useQueryFields', () => { const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); expect(result.current).toEqual([ - { value: 'field1', refId: 'A', label: 'A:Frame A field1' }, - { value: 'field2', refId: 'A', label: 'A:Frame A field2' }, - { value: 'field1', refId: 'A', label: 'A:Frame A-1 field1' }, - { value: 'field2', refId: 'A', label: 'A:Frame A-1 field2' }, + { value: 'Frame A field1', refId: 'A', label: 'A:Frame A field1' }, + { value: 'Frame A field2', refId: 'A', label: 'A:Frame A field2' }, + { value: 'Frame A-1 field1', refId: 'A', label: 'A:Frame A-1 field1' }, + { value: 'Frame A-1 field2', refId: 'A', label: 'A:Frame A-1 field2' }, ]); }); @@ -46,10 +46,10 @@ describe('useQueryFields', () => { const { result } = renderHook(() => useQueryFields({ data, isEnabled: true })); expect(result.current).toEqual([ - { value: 'field1', refId: undefined, label: 'undefined:Frame A field1' }, - { value: 'field2', refId: undefined, label: 'undefined:Frame A field2' }, - { value: 'field1', refId: undefined, label: 'undefined:Frame A-1 field1' }, - { value: 'field2', refId: undefined, label: 'undefined:Frame A-1 field2' }, + { value: 'Frame A field1', refId: undefined, label: 'undefined:Frame A field1' }, + { value: 'Frame A field2', refId: undefined, label: 'undefined:Frame A field2' }, + { value: 'Frame A-1 field1', refId: undefined, label: 'undefined:Frame A-1 field1' }, + { value: 'Frame A-1 field2', refId: undefined, label: 'undefined:Frame A-1 field2' }, ]); }); }); diff --git a/src/hooks/useQueryFields.ts b/src/hooks/useQueryFields.ts index b33510e9..45b088c5 100644 --- a/src/hooks/useQueryFields.ts +++ b/src/hooks/useQueryFields.ts @@ -13,9 +13,12 @@ export const useQueryFields = ({ data, isEnabled }: { data?: DataFrame[]; isEnab const { fields, refId } = frame; return acc.concat( fields.map((field) => { + /** + * Get unique field name + */ const fieldName = getFieldDisplayName(field, frame, data); return { - value: field.name, + value: fieldName, refId, label: `${refId}:${fieldName}`, }; From 1156fdc85519c3aae95318dc96c5af6a12205bf8 Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Thu, 3 Oct 2024 16:02:32 +0300 Subject: [PATCH 5/7] update CI test --- src/components/FormPanel/FormPanel.test.tsx | 121 +++++++++++++++----- 1 file changed, 93 insertions(+), 28 deletions(-) diff --git a/src/components/FormPanel/FormPanel.test.tsx b/src/components/FormPanel/FormPanel.test.tsx index f41f3d58..7e171940 100644 --- a/src/components/FormPanel/FormPanel.test.tsx +++ b/src/components/FormPanel/FormPanel.test.tsx @@ -2,7 +2,7 @@ import { AppEvents, EventBusSrv, FieldType, LoadingState, toDataFrame } from '@g import { getAppEvents, RefreshEvent } from '@grafana/runtime'; import { PanelContextProvider } from '@grafana/ui'; import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; -import React from 'react'; +import React, { ReactElement } from 'react'; import { CONFIRM_MODAL_DEFAULT, @@ -754,8 +754,36 @@ describe('Panel', () => { /** * Render */ + const data = { + state: LoadingState.Done, + series: [ + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricA1', 'metricA2'], + }, + ], + name: 'metrics A-1', + refId: 'A', + }), + toDataFrame({ + fields: [ + { + name: 'metric', + values: ['metricB1', 'metricB2'], + }, + ], + name: 'metrics A-2', + refId: 'A', + }), + ], + }; + + let rerenderCurrent: (ui: ReactElement) => void; + await act(async () => { - render( + const { rerender } = render( getComponent({ options: { initial: { @@ -780,40 +808,77 @@ describe('Panel', () => { ], }, props: { - data: { - state: LoadingState.Done, - series: [ - toDataFrame({ - fields: [ - { - name: 'metric', - values: ['metricA1', 'metricA2'], - }, - ], - name: 'metrics A-1', - refId: 'A', - }), - toDataFrame({ - fields: [ - { - name: 'metric', - values: ['metricA1', 'metricA2'], - }, - ], - name: 'metrics A-2', - refId: 'A', - }), - ], - }, + data: data, }, }) ); await waitFor(() => expect(selectors.loadingBar(true)).not.toBeInTheDocument()); + + rerenderCurrent = rerender; + }); + + /** + * Rerender with other query field + */ + await act(async () => { + rerenderCurrent( + getComponent({ + options: { + initial: { + method: RequestMethod.QUERY, + }, + elements: [ + { + ...FORM_ELEMENT_DEFAULT, + type: FormElementType.CHECKBOX_LIST, + id: 'mapped', + queryField: { + refId: 'A', + value: 'metrics A-1 metric', + label: 'A:metrics A-1 metric', + }, + }, + { + ...FORM_ELEMENT_DEFAULT, + id: 'unmapped', + queryField: undefined, + }, + ], + }, + props: { + data: data, + }, + }) + ); }); + expect(FormElements).toHaveBeenCalledTimes(6); + /** + * Check values after render call - 2nd call + */ expect(FormElements).toHaveBeenNthCalledWith( 2, + expect.objectContaining({ + elements: expect.arrayContaining([ + expect.objectContaining({ + id: 'mapped', + value: ['metricB1', 'metricB2'], + }), + expect.objectContaining({ + id: 'unmapped', + value: '', + }), + ]), + }), + expect.anything() + ); + + /** + * Check values after rerender call - 5th call + */ + expect(FormElements).toHaveBeenNthCalledWith( + 5, expect.objectContaining({ elements: expect.arrayContaining([ expect.objectContaining({ @@ -875,7 +940,7 @@ describe('Panel', () => { fields: [ { name: 'metric', - values: ['metricA1', 'metricA2'], + values: ['metricB1', 'metricB2'], }, ], name: 'metrics A-2', From 04327ed982b1e9f723d9dcb3e6e623bed591c923 Mon Sep 17 00:00:00 2001 From: vitPinchuk Date: Thu, 3 Oct 2024 16:09:09 +0300 Subject: [PATCH 6/7] ci update --- src/components/FormPanel/FormPanel.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/FormPanel/FormPanel.test.tsx b/src/components/FormPanel/FormPanel.test.tsx index 7e171940..adc18dac 100644 --- a/src/components/FormPanel/FormPanel.test.tsx +++ b/src/components/FormPanel/FormPanel.test.tsx @@ -851,6 +851,8 @@ describe('Panel', () => { }, }) ); + + await waitFor(() => expect(selectors.loadingBar(true)).not.toBeInTheDocument()); }); expect(FormElements).toHaveBeenCalledTimes(6); From 2264a8f60ae888c6fb444e8366cb4f33839b8892 Mon Sep 17 00:00:00 2001 From: Mikhail Volkov Date: Thu, 3 Oct 2024 15:09:37 -0400 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 641ee60b..03708fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated Autosize Code Editor toolbar (#506) - Added custom input for Select, Multi select (#507) +- Added Multiple initial fields support (#508) ## 4.6.0 (2024-09-28)