diff --git a/packages/grafana-data/src/panel/PanelPlugin.test.tsx b/packages/grafana-data/src/panel/PanelPlugin.test.tsx
index 2e238e6af57..9b705d01fae 100644
--- a/packages/grafana-data/src/panel/PanelPlugin.test.tsx
+++ b/packages/grafana-data/src/panel/PanelPlugin.test.tsx
@@ -182,6 +182,27 @@ describe('PanelPlugin', () => {
expect(panel.fieldConfigDefaults.defaults.custom).toEqual(expectedDefaults);
});
+ test('throw error with array fieldConfigs', () => {
+ const panel = new PanelPlugin(() => {
+ return
Panel
;
+ });
+
+ panel.useFieldConfig({
+ useCustomConfig: (builder) => {
+ builder.addCustomEditor({
+ id: 'somethingUnique',
+ path: 'numericOption[0]',
+ name: 'Option editor',
+ description: 'Option editor description',
+ defaultValue: 10,
+ } as any);
+ },
+ });
+ expect(() => panel.fieldConfigRegistry).toThrowErrorMatchingInlineSnapshot(
+ `"[undefined] Field config paths do not support arrays: custom.somethingUnique"`
+ );
+ });
+
test('default values for nested paths', () => {
const panel = new PanelPlugin(() => {
return Panel
;
diff --git a/packages/grafana-data/src/panel/registryFactories.ts b/packages/grafana-data/src/panel/registryFactories.ts
index 7583df57e82..df811a35e20 100644
--- a/packages/grafana-data/src/panel/registryFactories.ts
+++ b/packages/grafana-data/src/panel/registryFactories.ts
@@ -75,6 +75,13 @@ export function createFieldConfigRegistry(
}
}
+ // assert that field configs do not use array path syntax
+ for (const item of registry.list()) {
+ if (item.path.indexOf('[') > 0) {
+ throw new Error(`[${pluginName}] Field config paths do not support arrays: ${item.id}`);
+ }
+ }
+
return registry;
}
diff --git a/public/app/features/dashboard/components/PanelEditor/utils.test.ts b/public/app/features/dashboard/components/PanelEditor/utils.test.ts
index c495c4c84e3..6c3df98517c 100644
--- a/public/app/features/dashboard/components/PanelEditor/utils.test.ts
+++ b/public/app/features/dashboard/components/PanelEditor/utils.test.ts
@@ -89,13 +89,16 @@ describe('updateDefaultFieldConfigValue', () => {
describe('setOptionImmutably', () => {
it.each`
- source | path | value | expected
- ${{}} | ${'a'} | ${1} | ${{ a: 1 }}
- ${{}} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
- ${{ a: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
- ${{ b: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } }, b: {} }}
- ${{ a: { b: { c: 3 } } }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
- `('numeric-like text mapping, value:${value', ({ source, path, value, expected }) => {
+ source | path | value | expected
+ ${{}} | ${'a'} | ${1} | ${{ a: 1 }}
+ ${{}} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
+ ${{ a: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
+ ${{ b: {} }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } }, b: {} }}
+ ${{ a: { b: { c: 3 } } }} | ${'a.b.c'} | ${[1, 2]} | ${{ a: { b: { c: [1, 2] } } }}
+ ${{}} | ${'a.b[2]'} | ${'x'} | ${{ a: { b: [undefined, undefined, 'x'] } }}
+ ${{}} | ${'a[0]'} | ${1} | ${{ a: [1] }}
+ ${{}} | ${'a[0].b.c'} | ${1} | ${{ a: [{ b: { c: 1 } }] }}
+ `('property value:${value', ({ source, path, value, expected }) => {
expect(setOptionImmutably(source, path, value)).toEqual(expected);
});
diff --git a/public/app/features/dashboard/components/PanelEditor/utils.ts b/public/app/features/dashboard/components/PanelEditor/utils.ts
index 74574949800..f7febcd196f 100644
--- a/public/app/features/dashboard/components/PanelEditor/utils.ts
+++ b/public/app/features/dashboard/components/PanelEditor/utils.ts
@@ -68,6 +68,21 @@ export function setOptionImmutably(options: T, path: string |
const splat = !Array.isArray(path) ? path.split('.') : path;
const key = splat.shift()!;
+ if (key.endsWith(']')) {
+ const idx = key.lastIndexOf('[');
+ const index = +key.substring(idx + 1, key.length - 1);
+ const propKey = key.substr(0, idx);
+ let current = (options as Record)[propKey];
+ const arr = Array.isArray(current) ? [...current] : [];
+ if (splat.length) {
+ if (current == null || typeof current !== 'object') {
+ current = {};
+ }
+ value = setOptionImmutably(current, splat, value);
+ }
+ arr[index] = value;
+ return { ...options, [propKey]: arr };
+ }
if (!splat.length) {
return { ...options, [key]: value };