Skip to content

Commit 8da8291

Browse files
authored
[Controls] Omit null values from controlConfig if they cause validation errors (elastic#253131)
## Summary Some pre-9.4 controls store values we've defined as `maybe` as `null` instead of `undefined`. This PR strips null values during migration if they're causing schema validation errors. This was causing some controls to fail to migrate, and prevented dashboards on serverless from being duplicated. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
1 parent 0fdbb86 commit 8da8291

3 files changed

Lines changed: 96 additions & 21 deletions

File tree

src/platform/packages/shared/controls/controls-schemas/src/legacy_types.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ export type LegacyStoredOptionsListExplicitInput = LegacyStoredDataControlState
4444
hideExists?: boolean;
4545
hideSort?: boolean;
4646
};
47-
exclude?: boolean;
48-
existsSelected?: boolean;
49-
runPastTimeout?: boolean;
50-
searchTechnique?: string;
51-
selectedOptions?: Array<string | number>;
52-
singleSelect?: boolean;
53-
sort?: { by: string; direction: string };
47+
exclude?: boolean | null;
48+
existsSelected?: boolean | null;
49+
runPastTimeout?: boolean | null;
50+
searchTechnique?: string | null;
51+
selectedOptions?: Array<string | number> | null;
52+
singleSelect?: boolean | null;
53+
sort?: { by: string; direction: string } | null;
5454
};
5555

5656
export type LegacyStoredRangeSliderExplicitInput = LegacyStoredDataControlState & {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 { createEmbeddableSetupMock } from '@kbn/embeddable-plugin/server/mocks';
11+
import { registerOptionsListControlTransforms } from './options_list_control_transforms';
12+
import type { DrilldownTransforms } from '@kbn/embeddable-plugin/common';
13+
14+
const REF_NAME = 'test-data-view';
15+
16+
const baseState = {
17+
dataViewRefName: REF_NAME,
18+
field_name: 'test',
19+
title: 'Test',
20+
};
21+
22+
const panelReferences = [{ name: REF_NAME, type: 'index-pattern', id: 'data-view-id' }];
23+
24+
const getTransformOut = () => {
25+
const embeddable = createEmbeddableSetupMock();
26+
registerOptionsListControlTransforms(embeddable);
27+
28+
const [, transformsSetup] = embeddable.registerTransforms.mock.calls[0];
29+
const { transformOut } = transformsSetup.getTransforms!({} as DrilldownTransforms);
30+
return transformOut!;
31+
};
32+
33+
describe('options list control transforms', () => {
34+
describe('transformOut', () => {
35+
const transformOut = getTransformOut();
36+
37+
it('omits null values while keeping non-null values', () => {
38+
const result = transformOut(
39+
{
40+
...baseState,
41+
exclude: true,
42+
sort: null,
43+
existsSelected: null,
44+
runPastTimeout: null,
45+
searchTechnique: 'prefix',
46+
selectedOptions: ['val'],
47+
singleSelect: null,
48+
},
49+
panelReferences,
50+
undefined,
51+
undefined
52+
);
53+
54+
expect(result).toMatchInlineSnapshot(`
55+
Object {
56+
"data_view_id": "data-view-id",
57+
"exclude": true,
58+
"field_name": "test",
59+
"search_technique": "prefix",
60+
"selected_options": Array [
61+
"val",
62+
],
63+
"title": "Test",
64+
}
65+
`);
66+
});
67+
});
68+
});

src/platform/plugins/shared/controls/server/transforms/options_list_control_transforms.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99

1010
import type { Reference } from '@kbn/content-management-utils';
1111
import { OPTIONS_LIST_CONTROL } from '@kbn/controls-constants';
12-
import type {
13-
LegacyStoredOptionsListExplicitInput,
14-
OptionsListDSLControlState,
12+
import {
13+
type LegacyStoredOptionsListExplicitInput,
14+
type OptionsListDSLControlState,
1515
} from '@kbn/controls-schemas';
1616
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
1717
import { convertCamelCasedKeysToSnakeCase } from '@kbn/presentation-publishing';
18+
import { omitBy } from 'lodash';
1819
import { transformDataControlIn, transformDataControlOut } from './data_control_transforms';
1920

2021
const OPTIONS_LIST_REF_NAME = 'optionsListDataView' as const;
@@ -69,17 +70,23 @@ export const registerOptionsListControlTransforms = (embeddable: EmbeddableSetup
6970
} = convertCamelCasedKeysToSnakeCase<LegacyStoredOptionsListExplicitInput>(
7071
state as LegacyStoredOptionsListExplicitInput
7172
);
72-
return {
73-
...dataControlState,
74-
exclude,
75-
...{ sort: sort as OptionsListDSLControlState['sort'] },
76-
exists_selected,
77-
display_settings,
78-
run_past_timeout,
79-
search_technique: search_technique as OptionsListDSLControlState['search_technique'],
80-
selected_options,
81-
single_select,
82-
};
73+
74+
// Optional legacy props may have been stored as `null` instead of `undefined`, so drop all
75+
// null or undefined keys
76+
return omitBy(
77+
{
78+
...dataControlState,
79+
exclude,
80+
sort,
81+
exists_selected,
82+
display_settings,
83+
run_past_timeout,
84+
search_technique,
85+
selected_options,
86+
single_select,
87+
},
88+
(v) => v === null || v === undefined
89+
) as OptionsListDSLControlState;
8390
},
8491
}),
8592
});

0 commit comments

Comments
 (0)