Skip to content

Commit 00ad46b

Browse files
[dashboards as code] links embeddable schema (elastic#239354)
## Summary Fixes elastic#238089 Registers links embeddable schema. ### 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 links embeddable schema is in oneOf list. <img width="1065" height="833" alt="Screenshot 2025-10-16 at 15 56 27" src="https://github.com/user-attachments/assets/ac648c8e-efcb-413f-9084-ad7991e9152f" /> ### Manual Testing 1. Save an existing dashboard with links panels (both by-value and by-reference) through the UI - should succeed 2. Clone a dashboard with links panels - should maintain panel configurations correctly 3. Export and import a dashboard with links panels - should preserve all link configurations ### Testing Schema Validation #### Test 1: By-Value Links Panel with Invalid Property (Should Return 400) Expected Result: Returns 400 Bad Request due to unexpected `invalidKey` property ``` POST kbn:/api/dashboards/dashboard?apiVersion=1 { "title": "Links by-value validation - invalid", "panels": [ { "config": { "links": [ { "label": "My Dashboard Link", "type":"dashboardLink","id":"c9c3d1fd-04f9-45ac-89a7-e26e71b785ce", "destination":"7adfa750-4c81-11e8-b3d7-01146121b73d", "order": 0, "options": { "openInNewTab": false, "useCurrentFilters": true, "useCurrentDateRange": true } } ], "layout": "vertical", "invalidKey": "This should cause a validation error" }, "grid": { "x": 0, "y": 0, "w": 12, "h": 8 }, "type": "links" } ] } ``` --- Test 2: By-Value Links Panel with Valid Data (Should Succeed) Expected Result: Dashboard is created successfully ``` POST kbn:/api/dashboards/dashboard?apiVersion=1 { "title": "Links by-value validation - Valid", "panels": [ { "config": { "links": [ { "label": "My Dashboard Link", "type":"dashboardLink","id":"c9c3d1fd-04f9-45ac-89a7-e26e71b785ce", "destination":"7adfa750-4c81-11e8-b3d7-01146121b73d", "order": 0, "options": { "openInNewTab": false, "useCurrentFilters": true, "useCurrentDateRange": true } } ], "layout": "vertical", }, "grid": { "x": 0, "y": 0, "w": 12, "h": 8 }, "type": "links" } ] } ``` --- Before proceeding, please create a links saved object and save it to the library. Then replace the `savedObjectId` property in the Post requests below with the newly created SO ID. Test 3: By-Reference Links Panel with Invalid Property (Should Return 400) Expected Result: Returns 400 Bad Request due to unexpected `invalidKey` property ``` POST kbn:/api/dashboards/dashboard?apiVersion=1 { "title": "Links by-reference validation - invalid", "panels": [ { "config": { "savedObjectId": "LINK_SAVED_OBJECT_ID", "invalidKey": "This should cause a validation error" }, "grid": { "x": 0, "y": 0, "w": 12, "h": 8 }, "type": "links" } ] } ``` --- Test 4: By-Reference Links Panel with Valid Data (Should Succeed) Expected Result: Dashboard is created successfully ``` POST kbn:/api/dashboards/dashboard?apiVersion=1 { "title": "Links by-reference validation - valid", "panels": [ { "config": { "savedObjectId": "LINK_SAVED_OBJECT_ID", "title": "My Saved Links", "description": "A collection of useful links" }, "grid": { "x": 0, "y": 0, "w": 12, "h": 8 }, "type": "links" } ] } ``` After creation, navigate to the Dashboard app and verify newly created dashboards render correctly. --------- Co-authored-by: kibanamachine <[email protected]>
1 parent 3adfd6c commit 00ad46b

File tree

6 files changed

+93
-22
lines changed

6 files changed

+93
-22
lines changed

src/platform/plugins/private/links/common/embeddable/types.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,8 @@
88
*/
99

1010
import type { SerializedTitles } from '@kbn/presentation-publishing';
11-
import type { LinksState, StoredLinksState } from '../../server';
11+
import type { StoredLinksState } from '../../server';
1212

13-
export interface LinksByReferenceState {
14-
savedObjectId: string;
15-
}
16-
17-
export type LinksByValueState = Pick<LinksState, 'layout' | 'links'>;
18-
19-
export type LinksEmbeddableState = SerializedTitles & (LinksByValueState | LinksByReferenceState);
13+
export type { LinksByReferenceState, LinksByValueState, LinksEmbeddableState } from '../../server';
2014

2115
export type StoredLinksEmbeddableState = SerializedTitles & StoredLinksState;

src/platform/plugins/private/links/server/content_management/schema/v1/cm_services.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,29 @@ export const externalLinkSchema = schema.object({
8585
),
8686
});
8787

88+
// Shared schema for links array - used by both saved objects and embeddables
89+
export const linksArraySchema = schema.arrayOf(
90+
schema.oneOf([dashboardLinkSchema, externalLinkSchema]),
91+
{
92+
meta: { description: 'The list of links to display' },
93+
}
94+
);
95+
96+
// Shared schema for layout - used by both saved objects and embeddables
97+
export const layoutSchema = schema.maybe(
98+
schema.oneOf([schema.literal(LINKS_HORIZONTAL_LAYOUT), schema.literal(LINKS_VERTICAL_LAYOUT)], {
99+
meta: {
100+
description: 'Denote whether to display the links in a horizontal or vertical layout',
101+
},
102+
})
103+
);
104+
88105
export const linksSchema = schema.object(
89106
{
90107
title: schema.string({ meta: { description: 'A human-readable title' } }),
91108
description: schema.maybe(schema.string({ meta: { description: 'A short description.' } })),
92-
links: schema.arrayOf(schema.oneOf([dashboardLinkSchema, externalLinkSchema]), {
93-
meta: { description: 'The list of links to display' },
94-
}),
95-
layout: schema.maybe(
96-
schema.oneOf(
97-
[schema.literal(LINKS_HORIZONTAL_LAYOUT), schema.literal(LINKS_VERTICAL_LAYOUT)],
98-
{
99-
meta: {
100-
description: 'Denote whether to display the links in a horizontal or vertical layout',
101-
},
102-
}
103-
)
104-
),
109+
links: linksArraySchema,
110+
layout: layoutSchema,
105111
},
106112
{ unknowns: 'forbid' }
107113
);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 type { TypeOf } from '@kbn/config-schema';
11+
import { schema } from '@kbn/config-schema';
12+
import { serializedTitlesSchema } from '@kbn/presentation-publishing-schemas';
13+
import { linksArraySchema, layoutSchema } from './content_management/schema/v1/cm_services';
14+
15+
// Links by-value state schema (contains layout and links)
16+
const linksByValueStateSchema = schema.object({
17+
layout: layoutSchema,
18+
links: linksArraySchema,
19+
});
20+
21+
// Links by-reference state schema (contains savedObjectId)
22+
const linksByReferenceStateSchema = schema.object({
23+
savedObjectId: schema.string({
24+
meta: { description: 'The ID of the saved links object' },
25+
}),
26+
});
27+
28+
// Links by-value embeddable schema (by-value state + titles)
29+
const linksByValueEmbeddableSchema = schema.allOf(
30+
[linksByValueStateSchema, serializedTitlesSchema],
31+
{
32+
meta: {
33+
description: 'Links by-value embeddable schema',
34+
},
35+
}
36+
);
37+
38+
// Links by-reference embeddable schema (by-reference state + titles)
39+
const linksByReferenceEmbeddableSchema = schema.allOf(
40+
[linksByReferenceStateSchema, serializedTitlesSchema],
41+
{
42+
meta: {
43+
description: 'Links by-reference embeddable schema',
44+
},
45+
}
46+
);
47+
48+
// Complete links embeddable schema (union of by-value and by-reference embeddables)
49+
export const linksEmbeddableSchema = schema.oneOf(
50+
[linksByValueEmbeddableSchema, linksByReferenceEmbeddableSchema],
51+
{
52+
meta: {
53+
description: 'Links embeddable schema',
54+
},
55+
}
56+
);
57+
58+
export type LinksByValueState = TypeOf<typeof linksByValueStateSchema>;
59+
export type LinksByReferenceState = TypeOf<typeof linksByReferenceStateSchema>;
60+
export type LinksEmbeddableState = TypeOf<typeof linksEmbeddableSchema>;

src/platform/plugins/private/links/server/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ export type {
1919
StoredLinksState,
2020
} from './content_management';
2121

22+
export type {
23+
LinksByValueState,
24+
LinksByReferenceState,
25+
LinksEmbeddableState,
26+
} from './embeddable_schemas';
27+
2228
export const plugin = async (initContext: PluginInitializerContext) => {
2329
const { LinksServerPlugin } = await import('./plugin');
2430
return new LinksServerPlugin(initContext);

src/platform/plugins/private/links/server/plugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type { LinksState } from './content_management';
2121
import { LinksStorage } from './content_management';
2222
import { linksSavedObjectType } from './saved_objects';
2323
import { transforms } from '../common/embeddable/transforms/transforms';
24+
import { linksEmbeddableSchema } from './embeddable_schemas';
2425

2526
export class LinksServerPlugin implements Plugin<object, object> {
2627
private readonly logger: Logger;
@@ -49,7 +50,10 @@ export class LinksServerPlugin implements Plugin<object, object> {
4950

5051
core.savedObjects.registerType<LinksState>(linksSavedObjectType);
5152

52-
plugins.embeddable.registerTransforms(LINKS_EMBEDDABLE_TYPE, transforms);
53+
plugins.embeddable.registerTransforms(LINKS_EMBEDDABLE_TYPE, {
54+
...transforms,
55+
schema: linksEmbeddableSchema,
56+
});
5357

5458
return {};
5559
}

src/platform/plugins/private/links/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@kbn/visualizations-plugin",
3636
"@kbn/presentation-containers",
3737
"@kbn/presentation-publishing",
38+
"@kbn/presentation-publishing-schemas",
3839
"@kbn/react-kibana-context-render",
3940
"@kbn/share-plugin",
4041
"@kbn/es-query",

0 commit comments

Comments
 (0)