Skip to content

Commit f123b50

Browse files
authored
[Dashboard] Composable content management transforms (#213831)
1 parent d46bd47 commit f123b50

17 files changed

+745
-182
lines changed

src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts

Lines changed: 20 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -7,145 +7,31 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { v4 as uuidv4 } from 'uuid';
1110
import { pick } from 'lodash';
1211

13-
import type { Query } from '@kbn/es-query';
14-
import {
15-
type ControlGroupChainingSystem,
16-
type ControlLabelPosition,
17-
type ControlPanelsState,
18-
type SerializedControlState,
19-
DEFAULT_AUTO_APPLY_SELECTIONS,
20-
DEFAULT_CONTROL_CHAINING,
21-
DEFAULT_CONTROL_GROW,
22-
DEFAULT_CONTROL_LABEL_POSITION,
23-
DEFAULT_CONTROL_WIDTH,
24-
DEFAULT_IGNORE_PARENT_SETTINGS,
25-
} from '@kbn/controls-plugin/common';
26-
import { SerializedSearchSourceFields, parseSearchSourceJSON } from '@kbn/data-plugin/common';
27-
2812
import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server';
2913
import type {
30-
ControlGroupAttributes,
3114
DashboardAttributes,
3215
DashboardGetOut,
3316
DashboardItem,
34-
DashboardOptions,
3517
ItemAttrsToSavedObjectAttrsReturn,
3618
PartialDashboardItem,
3719
SavedObjectToItemReturn,
3820
} from './types';
39-
import type {
40-
DashboardSavedObjectAttributes,
41-
SavedDashboardPanel,
42-
} from '../../dashboard_saved_object';
21+
import type { DashboardSavedObjectAttributes } from '../../dashboard_saved_object';
4322
import type {
4423
ControlGroupAttributes as ControlGroupAttributesV2,
4524
DashboardCrudTypes as DashboardCrudTypesV2,
4625
} from '../../../common/content_management/v2';
47-
import { DEFAULT_DASHBOARD_OPTIONS } from '../../../common/content_management';
48-
49-
function controlGroupInputOut(
50-
controlGroupInput?: DashboardSavedObjectAttributes['controlGroupInput']
51-
): ControlGroupAttributes | undefined {
52-
if (!controlGroupInput) {
53-
return;
54-
}
55-
const {
56-
panelsJSON,
57-
ignoreParentSettingsJSON,
58-
controlStyle = DEFAULT_CONTROL_LABEL_POSITION,
59-
chainingSystem = DEFAULT_CONTROL_CHAINING,
60-
showApplySelections = !DEFAULT_AUTO_APPLY_SELECTIONS,
61-
} = controlGroupInput;
62-
const controls = panelsJSON
63-
? Object.entries(JSON.parse(panelsJSON) as ControlPanelsState<SerializedControlState>).map(
64-
([
65-
id,
66-
{
67-
explicitInput,
68-
type,
69-
grow = DEFAULT_CONTROL_GROW,
70-
width = DEFAULT_CONTROL_WIDTH,
71-
order,
72-
},
73-
]) => ({
74-
controlConfig: explicitInput,
75-
id,
76-
grow,
77-
order,
78-
type,
79-
width,
80-
})
81-
)
82-
: [];
83-
84-
const {
85-
ignoreFilters = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreFilters,
86-
ignoreQuery = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreQuery,
87-
ignoreTimerange = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreTimerange,
88-
ignoreValidations = DEFAULT_IGNORE_PARENT_SETTINGS.ignoreValidations,
89-
} = ignoreParentSettingsJSON ? JSON.parse(ignoreParentSettingsJSON) : {};
90-
91-
// try to maintain a consistent (alphabetical) order of keys
92-
return {
93-
autoApplySelections: !showApplySelections,
94-
chainingSystem: chainingSystem as ControlGroupChainingSystem,
95-
controls,
96-
labelPosition: controlStyle as ControlLabelPosition,
97-
ignoreParentSettings: { ignoreFilters, ignoreQuery, ignoreTimerange, ignoreValidations },
98-
};
99-
}
100-
101-
function kibanaSavedObjectMetaOut(
102-
kibanaSavedObjectMeta: DashboardSavedObjectAttributes['kibanaSavedObjectMeta']
103-
): DashboardAttributes['kibanaSavedObjectMeta'] {
104-
const { searchSourceJSON } = kibanaSavedObjectMeta;
105-
if (!searchSourceJSON) {
106-
return {};
107-
}
108-
// Dashboards do not yet support ES|QL (AggregateQuery) in the search source
109-
return {
110-
searchSource: parseSearchSourceJSON(searchSourceJSON) as Omit<
111-
SerializedSearchSourceFields,
112-
'query'
113-
> & { query?: Query },
114-
};
115-
}
116-
117-
function optionsOut(optionsJSON: string): DashboardAttributes['options'] {
118-
const {
119-
hidePanelTitles = DEFAULT_DASHBOARD_OPTIONS.hidePanelTitles,
120-
useMargins = DEFAULT_DASHBOARD_OPTIONS.useMargins,
121-
syncColors = DEFAULT_DASHBOARD_OPTIONS.syncColors,
122-
syncCursor = DEFAULT_DASHBOARD_OPTIONS.syncCursor,
123-
syncTooltips = DEFAULT_DASHBOARD_OPTIONS.syncTooltips,
124-
} = JSON.parse(optionsJSON) as DashboardOptions;
125-
return {
126-
hidePanelTitles,
127-
useMargins,
128-
syncColors,
129-
syncCursor,
130-
syncTooltips,
131-
};
132-
}
133-
134-
function panelsOut(panelsJSON: string): DashboardAttributes['panels'] {
135-
const panels = JSON.parse(panelsJSON) as SavedDashboardPanel[];
136-
return panels.map(
137-
({ embeddableConfig, gridData, id, panelIndex, panelRefName, title, type, version }) => ({
138-
gridData,
139-
id,
140-
panelConfig: embeddableConfig,
141-
panelIndex,
142-
panelRefName,
143-
title,
144-
type,
145-
version,
146-
})
147-
);
148-
}
26+
import {
27+
transformControlGroupIn,
28+
transformControlGroupOut,
29+
transformOptionsOut,
30+
transformPanelsIn,
31+
transformPanelsOut,
32+
transformSearchSourceIn,
33+
transformSearchSourceOut,
34+
} from './transforms';
14935

15036
export function dashboardAttributesOut(
15137
attributes: DashboardSavedObjectAttributes | Partial<DashboardSavedObjectAttributes>
@@ -165,13 +51,13 @@ export function dashboardAttributesOut(
16551
} = attributes;
16652
// try to maintain a consistent (alphabetical) order of keys
16753
return {
168-
...(controlGroupInput && { controlGroupInput: controlGroupInputOut(controlGroupInput) }),
54+
...(controlGroupInput && { controlGroupInput: transformControlGroupOut(controlGroupInput) }),
16955
...(description && { description }),
17056
...(kibanaSavedObjectMeta && {
171-
kibanaSavedObjectMeta: kibanaSavedObjectMetaOut(kibanaSavedObjectMeta),
57+
kibanaSavedObjectMeta: transformSearchSourceOut(kibanaSavedObjectMeta),
17258
}),
173-
...(optionsJSON && { options: optionsOut(optionsJSON) }),
174-
...(panelsJSON && { panels: panelsOut(panelsJSON) }),
59+
...(optionsJSON && { options: transformOptionsOut(optionsJSON) }),
60+
...(panelsJSON && { panels: transformPanelsOut(panelsJSON) }),
17561
...(refreshInterval && {
17662
refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value },
17763
}),
@@ -183,54 +69,6 @@ export function dashboardAttributesOut(
18369
};
18470
}
18571

186-
function controlGroupInputIn(
187-
controlGroupInput?: ControlGroupAttributes
188-
): DashboardSavedObjectAttributes['controlGroupInput'] | undefined {
189-
if (!controlGroupInput) {
190-
return;
191-
}
192-
const { controls, ignoreParentSettings, labelPosition, chainingSystem, autoApplySelections } =
193-
controlGroupInput;
194-
const updatedControls = Object.fromEntries(
195-
controls.map(({ controlConfig, id = uuidv4(), ...restOfControl }) => {
196-
return [id, { ...restOfControl, explicitInput: controlConfig }];
197-
})
198-
);
199-
return {
200-
chainingSystem,
201-
controlStyle: labelPosition,
202-
ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings),
203-
panelsJSON: JSON.stringify(updatedControls),
204-
showApplySelections: !autoApplySelections,
205-
};
206-
}
207-
208-
function panelsIn(
209-
panels: DashboardAttributes['panels']
210-
): DashboardSavedObjectAttributes['panelsJSON'] {
211-
const updatedPanels = panels.map(({ panelIndex, gridData, panelConfig, ...restPanel }) => {
212-
const idx = panelIndex ?? uuidv4();
213-
return {
214-
...restPanel,
215-
embeddableConfig: panelConfig,
216-
panelIndex: idx,
217-
gridData: {
218-
...gridData,
219-
i: idx,
220-
},
221-
};
222-
});
223-
224-
return JSON.stringify(updatedPanels);
225-
}
226-
227-
function kibanaSavedObjectMetaIn(
228-
kibanaSavedObjectMeta: DashboardAttributes['kibanaSavedObjectMeta']
229-
) {
230-
const { searchSource } = kibanaSavedObjectMeta;
231-
return { searchSourceJSON: JSON.stringify(searchSource ?? {}) };
232-
}
233-
23472
export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['GetOut'] => {
23573
const { meta, item } = result;
23674
const { attributes, ...rest } = item;
@@ -250,14 +88,14 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['
25088

25189
const v2Attributes = {
25290
...(controlGroupInput && {
253-
controlGroupInput: controlGroupInputIn(controlGroupInput) as ControlGroupAttributesV2,
91+
controlGroupInput: transformControlGroupIn(controlGroupInput) as ControlGroupAttributesV2,
25492
}),
25593
description,
25694
...(kibanaSavedObjectMeta && {
257-
kibanaSavedObjectMeta: kibanaSavedObjectMetaIn(kibanaSavedObjectMeta),
95+
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
25896
}),
25997
...(options && { optionsJSON: JSON.stringify(options) }),
260-
panelsJSON: panels ? panelsIn(panels) : '[]',
98+
panelsJSON: panels ? transformPanelsIn(panels) : '[]',
26199
refreshInterval,
262100
...(timeFrom && { timeFrom }),
263101
timeRestore,
@@ -282,16 +120,16 @@ export const itemAttrsToSavedObjectAttrs = (
282120
const soAttributes = {
283121
...rest,
284122
...(controlGroupInput && {
285-
controlGroupInput: controlGroupInputIn(controlGroupInput),
123+
controlGroupInput: transformControlGroupIn(controlGroupInput),
286124
}),
287125
...(options && {
288126
optionsJSON: JSON.stringify(options),
289127
}),
290128
...(panels && {
291-
panelsJSON: panelsIn(panels),
129+
panelsJSON: transformPanelsIn(panels),
292130
}),
293131
...(kibanaSavedObjectMeta && {
294-
kibanaSavedObjectMeta: kibanaSavedObjectMetaIn(kibanaSavedObjectMeta),
132+
kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta),
295133
}),
296134
};
297135
return { attributes: soAttributes, error: null };
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { transformControlGroupIn } from './control_group_in_transforms';
11+
import { ControlGroupAttributes } from '../../types';
12+
import { CONTROL_WIDTH_OPTIONS } from '@kbn/controls-plugin/common';
13+
14+
jest.mock('uuid', () => ({
15+
v4: jest.fn(() => 'mock-uuid'),
16+
}));
17+
18+
describe('transformControlGroupIn', () => {
19+
const mockControlGroupInput: ControlGroupAttributes = {
20+
chainingSystem: 'NONE',
21+
labelPosition: 'oneLine',
22+
autoApplySelections: true,
23+
ignoreParentSettings: {
24+
ignoreFilters: true,
25+
ignoreQuery: true,
26+
ignoreTimerange: true,
27+
ignoreValidations: true,
28+
},
29+
controls: [
30+
{
31+
id: 'control1',
32+
type: 'type1',
33+
width: CONTROL_WIDTH_OPTIONS.SMALL,
34+
controlConfig: { bizz: 'buzz' },
35+
order: 0,
36+
grow: false,
37+
},
38+
{
39+
type: 'type2',
40+
grow: true,
41+
width: CONTROL_WIDTH_OPTIONS.SMALL,
42+
controlConfig: { boo: 'bear' },
43+
order: 1,
44+
},
45+
],
46+
};
47+
48+
it('should return undefined if controlGroupInput is undefined', () => {
49+
const result = transformControlGroupIn(undefined);
50+
expect(result).toBeUndefined();
51+
});
52+
53+
it('should transform controlGroupInput correctly', () => {
54+
const result = transformControlGroupIn(mockControlGroupInput);
55+
56+
expect(result).toEqual({
57+
chainingSystem: 'NONE',
58+
controlStyle: 'oneLine',
59+
showApplySelections: false,
60+
ignoreParentSettingsJSON: JSON.stringify({
61+
ignoreFilters: true,
62+
ignoreQuery: true,
63+
ignoreTimerange: true,
64+
ignoreValidations: true,
65+
}),
66+
panelsJSON: JSON.stringify({
67+
control1: {
68+
type: 'type1',
69+
width: 'small',
70+
order: 0,
71+
grow: false,
72+
explicitInput: { bizz: 'buzz' },
73+
},
74+
'mock-uuid': {
75+
type: 'type2',
76+
grow: true,
77+
width: 'small',
78+
order: 1,
79+
explicitInput: { boo: 'bear' },
80+
},
81+
}),
82+
});
83+
});
84+
85+
it('should handle empty controls array', () => {
86+
const controlGroupInput: ControlGroupAttributes = {
87+
...mockControlGroupInput,
88+
controls: [],
89+
};
90+
91+
const result = transformControlGroupIn(controlGroupInput);
92+
93+
expect(result).toEqual({
94+
chainingSystem: 'NONE',
95+
controlStyle: 'oneLine',
96+
showApplySelections: false,
97+
ignoreParentSettingsJSON: JSON.stringify({
98+
ignoreFilters: true,
99+
ignoreQuery: true,
100+
ignoreTimerange: true,
101+
ignoreValidations: true,
102+
}),
103+
panelsJSON: JSON.stringify({}),
104+
});
105+
});
106+
});

0 commit comments

Comments
 (0)