Skip to content

Commit 6f007a1

Browse files
yiannisnikolopouloscursoragentnreesekibanamachine
authored
[Synthetics] Embeddable schema registration (elastic#251445)
## Summary Closes elastic#250473 This PR adds proper "dashboards as code" schema registrations for the two Synthetics embeddables, enabling REST API validation, OpenAPI documentation generation, and full programmatic dashboard creation support. - [ ] SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE - [ ] SYNTHETICS_MONITORS_EMBEDDABLE ## 🔬 How to test Before starting to test: 1. Start Kibana: `yarn start --no-base-path` and generate a private location using `node x-pack/scripts/synthetics_private_location.js` 2. Create a browser monitor ## Testing OAS documentation - Add `server.oas.enabled: true` to config/kibana.dev.yml - Start kibana with `yarn start --no-base-path` - Copy paste URL http://localhost:5601/api/oas?pathStartsWith=/api/dashboard&access=internal&version=1 into your browser. - View `panels` schema and verify both `Synthetics stats overview embeddable schema` and `Synthetics monitors embeddable schema` appear in the list. ## Testing validation - In Kibana dev tools enter the command below. Verify command returns 400 due to the unexpected `foo` key. ``` POST kbn:/api/dashboards?apiVersion=1 { "data": { "title": "Synthetics embeddable stats overview schema validation demo", "panels": [ { "config": { "foo": "I am an unexpected key and should cause a validation error" }, "grid": { "x": 0, "y": 0 }, "type": "SYNTHETICS_STATS_OVERVIEW_EMBEDDABLE" } ] } } ``` - Remove the foo key from the command above and re-run the command. Verify dashboard is created. Go to the dashboard application and open the dashboard. - Repeat the same validation test for the monitors overview embeddable: ``` POST kbn:/api/dashboards?apiVersion=1 { "data": { "title": "Synthetics monitors overview embeddable schema validation demo", "panels": [ { "config": { "view": "cardView", "foo": "I am an unexpected key and should cause a validation error" }, "grid": { "x": 0, "y": 0 }, "type": "SYNTHETICS_MONITORS_EMBEDDABLE" } ] } } ``` - Remove the foo key and re-run. Verify dashboard is created and the monitors overview panel renders. <img width="862" height="432" alt="image" src="https://github.com/user-attachments/assets/c3c3f83d-8a60-4b85-8335-108eed49957a" /> ## Testing the UI ### Test 1: Stats Overview Embeddable 1. Navigate to Dashboards → Create dashboard 2. Click Add panel → Find "Monitors stats" under Observability 4. Add the panel to dashboard 5. Click the pencil icon on the panel → Edit 6. Configure filters: - Add a tag filter - Add a location filter 7. Click Save and return 8. Click Save on the dashboard (name it "Test Synthetics Schema") 9. Expected: Dashboard saves successfully ✅ 10. Refresh the page 11. Expected: Panel loads with filters intact ✅ ### Test 2: Stats Overview Embeddable with Drilldown 1. Navigate to Dashboards → Create dashboard 2. Click Add panel → Find "Monitors stats" under Observability 3. Add the panel to dashboard 4. Click the pencil icon on the panel → Edit 5. Configure filters: - Add a tag filter - Add a location filter 6. Click Save and return 7. Click on ... dots > Create Drilldown > Save panel 8. Click Save on the dashboard (name it "Test Synthetics Schema with Drilldown") 9. Expected: Dashboard saves successfully ✅ 10. Refresh the page 12. Expected: Panel loads with filters intact ✅ ### Test 3: Monitors Overview Embeddable 1. In the same dashboard, click Add panel 2. Find "Monitors overview" under Observability 3. Add the panel 4. Click the pencil icon on the panel → Edit 5. Configure: - Add a tag filter - Add a location filter 6. Change view mode (Card View ↔ Compact View) 7. Save the panel 8. Save the dashboard 9. Expected: Dashboard saves successfully ✅ 10. Refresh the page 13. Expected: Panel loads with correct view mode and filters ✅ > [!NOTE] > To ensure backward compatibility, confirm that previously stored(before the changes of this PR) synthetics embeddables continue to work as expected. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Nathan Reese <reese.nathan@gmail.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent d8bb887 commit 6f007a1

36 files changed

Lines changed: 915 additions & 96 deletions

src/platform/plugins/shared/dashboard/test/scout_oas_schema/api/fixtures/schema_snapshot.json

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,290 @@
1919
"additionalProperties": false,
2020
"required": ["content"]
2121
},
22+
{
23+
"type": "object",
24+
"description": "Synthetics stats overview embeddable schema",
25+
"properties": {
26+
"description": {
27+
"type": "string"
28+
},
29+
"hide_title": {
30+
"type": "boolean"
31+
},
32+
"title": {
33+
"type": "string"
34+
},
35+
"drilldowns": {
36+
"type": "array",
37+
"maxItems": 100,
38+
"items": {
39+
"type": "object",
40+
"properties": {
41+
"encode_url": {
42+
"type": "boolean",
43+
"description": "When true, URL is escaped using percent encoding",
44+
"default": true
45+
},
46+
"open_in_new_tab": {
47+
"type": "boolean",
48+
"default": true
49+
},
50+
"url": {
51+
"type": "string",
52+
"description": "Templated Url. Variables documented at https://www.elastic.co/docs/explore-analyze/dashboards/drilldowns#url-template-variable"
53+
},
54+
"label": {
55+
"type": "string"
56+
},
57+
"trigger": {
58+
"type": "string",
59+
"enum": ["on_open_panel_menu"]
60+
},
61+
"type": {
62+
"type": "string",
63+
"enum": ["url_drilldown"]
64+
}
65+
},
66+
"additionalProperties": false,
67+
"required": ["url", "label", "trigger", "type"]
68+
}
69+
},
70+
"filters": {
71+
"type": "object",
72+
"properties": {
73+
"projects": {
74+
"type": "array",
75+
"description": "Filter by project",
76+
"maxItems": 100,
77+
"items": {
78+
"type": "object",
79+
"properties": {
80+
"label": {
81+
"type": "string",
82+
"description": "Display label for the filter option"
83+
},
84+
"value": {
85+
"type": "string",
86+
"description": "Value for the filter option"
87+
}
88+
},
89+
"additionalProperties": false,
90+
"required": ["label", "value"]
91+
}
92+
},
93+
"tags": {
94+
"type": "array",
95+
"description": "Filter by tags",
96+
"maxItems": 100,
97+
"items": {
98+
"type": "object",
99+
"properties": {
100+
"label": {
101+
"type": "string",
102+
"description": "Display label for the filter option"
103+
},
104+
"value": {
105+
"type": "string",
106+
"description": "Value for the filter option"
107+
}
108+
},
109+
"additionalProperties": false,
110+
"required": ["label", "value"]
111+
}
112+
},
113+
"monitor_ids": {
114+
"type": "array",
115+
"description": "Filter by monitor IDs",
116+
"maxItems": 5000,
117+
"items": {
118+
"type": "object",
119+
"properties": {
120+
"label": {
121+
"type": "string",
122+
"description": "Display label for the filter option"
123+
},
124+
"value": {
125+
"type": "string",
126+
"description": "Value for the filter option"
127+
}
128+
},
129+
"additionalProperties": false,
130+
"required": ["label", "value"]
131+
}
132+
},
133+
"monitor_types": {
134+
"type": "array",
135+
"description": "Filter by monitor types",
136+
"maxItems": 10,
137+
"items": {
138+
"type": "object",
139+
"properties": {
140+
"label": {
141+
"type": "string",
142+
"description": "Display label for the filter option"
143+
},
144+
"value": {
145+
"type": "string",
146+
"description": "Value for the filter option"
147+
}
148+
},
149+
"additionalProperties": false,
150+
"required": ["label", "value"]
151+
}
152+
},
153+
"locations": {
154+
"type": "array",
155+
"description": "Filter by monitor locations",
156+
"maxItems": 100,
157+
"items": {
158+
"type": "object",
159+
"properties": {
160+
"label": {
161+
"type": "string",
162+
"description": "Display label for the filter option"
163+
},
164+
"value": {
165+
"type": "string",
166+
"description": "Value for the filter option"
167+
}
168+
},
169+
"additionalProperties": false,
170+
"required": ["label", "value"]
171+
}
172+
}
173+
},
174+
"additionalProperties": false
175+
}
176+
},
177+
"additionalProperties": false
178+
},
179+
{
180+
"type": "object",
181+
"description": "Synthetics monitors embeddable schema",
182+
"properties": {
183+
"filters": {
184+
"type": "object",
185+
"properties": {
186+
"projects": {
187+
"type": "array",
188+
"description": "Filter by project",
189+
"maxItems": 100,
190+
"items": {
191+
"type": "object",
192+
"properties": {
193+
"label": {
194+
"type": "string",
195+
"description": "Display label for the filter option"
196+
},
197+
"value": {
198+
"type": "string",
199+
"description": "Value for the filter option"
200+
}
201+
},
202+
"additionalProperties": false,
203+
"required": ["label", "value"]
204+
}
205+
},
206+
"tags": {
207+
"type": "array",
208+
"description": "Filter by tags",
209+
"maxItems": 100,
210+
"items": {
211+
"type": "object",
212+
"properties": {
213+
"label": {
214+
"type": "string",
215+
"description": "Display label for the filter option"
216+
},
217+
"value": {
218+
"type": "string",
219+
"description": "Value for the filter option"
220+
}
221+
},
222+
"additionalProperties": false,
223+
"required": ["label", "value"]
224+
}
225+
},
226+
"monitor_ids": {
227+
"type": "array",
228+
"description": "Filter by monitor IDs",
229+
"maxItems": 5000,
230+
"items": {
231+
"type": "object",
232+
"properties": {
233+
"label": {
234+
"type": "string",
235+
"description": "Display label for the filter option"
236+
},
237+
"value": {
238+
"type": "string",
239+
"description": "Value for the filter option"
240+
}
241+
},
242+
"additionalProperties": false,
243+
"required": ["label", "value"]
244+
}
245+
},
246+
"monitor_types": {
247+
"type": "array",
248+
"description": "Filter by monitor types",
249+
"maxItems": 10,
250+
"items": {
251+
"type": "object",
252+
"properties": {
253+
"label": {
254+
"type": "string",
255+
"description": "Display label for the filter option"
256+
},
257+
"value": {
258+
"type": "string",
259+
"description": "Value for the filter option"
260+
}
261+
},
262+
"additionalProperties": false,
263+
"required": ["label", "value"]
264+
}
265+
},
266+
"locations": {
267+
"type": "array",
268+
"description": "Filter by monitor locations",
269+
"maxItems": 100,
270+
"items": {
271+
"type": "object",
272+
"properties": {
273+
"label": {
274+
"type": "string",
275+
"description": "Display label for the filter option"
276+
},
277+
"value": {
278+
"type": "string",
279+
"description": "Value for the filter option"
280+
}
281+
},
282+
"additionalProperties": false,
283+
"required": ["label", "value"]
284+
}
285+
}
286+
},
287+
"additionalProperties": false
288+
},
289+
"view": {
290+
"description": "View mode for the monitors embeddable (defaults to cardView)",
291+
"type": "string",
292+
"enum": ["cardView", "compactView"]
293+
},
294+
"description": {
295+
"type": "string"
296+
},
297+
"hide_title": {
298+
"type": "boolean"
299+
},
300+
"title": {
301+
"type": "string"
302+
}
303+
},
304+
"additionalProperties": false
305+
},
22306
{
23307
"type": "object",
24308
"properties": {},

src/platform/plugins/shared/dashboard/test/scout_oas_schema/api/tests/schema.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import snapshot from '../fixtures/schema_snapshot.json';
2222
*
2323
* See README.md for usage instructions.
2424
*/
25-
apiTest.describe('dashboard REST schema', { tag: tags.deploymentAgnostic }, () => {
25+
apiTest.describe('dashboard REST schema', { tag: tags.stateful.all }, () => {
2626
let viewerCredentials: RoleApiCredentials;
2727

2828
apiTest.beforeAll(async ({ requestAuth }) => {
@@ -71,7 +71,14 @@ apiTest.describe('dashboard REST schema', { tag: tags.deploymentAgnostic }, () =
7171
expect(panelSchema).toBeDefined();
7272

7373
const configSchema = panelSchema.properties.config;
74-
expect(configSchema.anyOf).toHaveLength(2);
74+
// API integration tests do not support jest expect
75+
// so we had to roll our own toMatchSnapshot
76+
// To update snapshot:
77+
// 1) uncomment console.log
78+
// 2) run test
79+
// 3) replace snapshot file contents with copy of consoled output
80+
// console.log(JSON.stringify(configSchema.anyOf, null, ' '));
81+
expect(configSchema.anyOf).toHaveLength(4);
7582
expect(configSchema.anyOf).toStrictEqual(snapshot);
7683
});
7784
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { MonitorFilters, MonitorOption } from '../../types';
9+
10+
interface LegacyStoredFilters {
11+
monitorIds?: MonitorOption[];
12+
monitorTypes?: MonitorOption[];
13+
}
14+
15+
/**
16+
* Pre 9.4 the monitor_ids and monitor_types state was stored in a camelCased key called monitorIds and monitorTypes.
17+
* This transform out function ensures that this state is not dropped when loading from
18+
* a legacy stored state.
19+
*/
20+
export function transformFiltersOut<StateType extends { filters?: MonitorFilters }>(
21+
storedState: StateType
22+
) {
23+
if (!storedState.filters) {
24+
return storedState;
25+
}
26+
27+
const {
28+
monitorIds: legacyMonitorIds,
29+
monitorTypes: legacyMonitorTypes,
30+
...restOfFilters
31+
} = storedState.filters as MonitorFilters & LegacyStoredFilters;
32+
const monitorIds = storedState.filters.monitor_ids ?? legacyMonitorIds;
33+
const monitorTypes = storedState.filters.monitor_types ?? legacyMonitorTypes;
34+
return {
35+
...storedState,
36+
filters: {
37+
...restOfFilters,
38+
...(monitorIds ? { monitor_ids: monitorIds } : {}),
39+
...(monitorTypes ? { monitor_types: monitorTypes } : {}),
40+
},
41+
};
42+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export const SYNTHETICS_MONITORS_EMBEDDABLE = 'SYNTHETICS_MONITORS_EMBEDDABLE';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { Reference } from '@kbn/content-management-utils/src/types';
9+
import { transformTitlesOut } from '@kbn/presentation-publishing';
10+
import { flow } from 'lodash';
11+
import { transformFiltersOut } from '../bwc/transform_filters_out';
12+
import type { OverviewMonitorsEmbeddableState } from '../../types';
13+
14+
export function getTransformOut() {
15+
function transformOut(
16+
storedState: OverviewMonitorsEmbeddableState,
17+
_panelReferences?: Reference[],
18+
_containerReferences?: Reference[]
19+
): OverviewMonitorsEmbeddableState {
20+
const transformsFlow = flow(
21+
transformTitlesOut<OverviewMonitorsEmbeddableState>,
22+
transformFiltersOut<OverviewMonitorsEmbeddableState>
23+
);
24+
return transformsFlow(storedState);
25+
}
26+
return transformOut;
27+
}

0 commit comments

Comments
 (0)