Skip to content

Commit 5fece3e

Browse files
nreeseelasticmachinekibanamachine
authored
[links] fix Can not save Dashboard after switching a Dashboard Link to an External Link (#243134)
Closes #242969 ### Test instructions * install web logs sample data * create new dashboard * add new links panel * add new dashboard link * Change dashboard link option * before clicking "Add link", switch to external link * set url * click add link * click saved links panel * save dashboard. Dashboard should save without any errors. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
1 parent 9f5ed05 commit 5fece3e

6 files changed

Lines changed: 131 additions & 32 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 { DashboardLink, ExternalLink, LinkOptions } from '../../../server';
11+
import type { LinkType } from '../../content_management';
12+
import { DASHBOARD_LINK_TYPE } from '../../content_management';
13+
14+
export function getOptions(type: LinkType, options: LinkOptions) {
15+
if (!options) return undefined;
16+
17+
if (type === DASHBOARD_LINK_TYPE) {
18+
const dashboardOptions = options as Required<DashboardLink>['options'];
19+
return {
20+
...(typeof dashboardOptions.openInNewTab === 'boolean' && {
21+
openInNewTab: dashboardOptions.openInNewTab,
22+
}),
23+
...(typeof dashboardOptions.useCurrentFilters === 'boolean' && {
24+
useCurrentFilters: dashboardOptions.useCurrentFilters,
25+
}),
26+
...(typeof dashboardOptions.useCurrentDateRange === 'boolean' && {
27+
useCurrentDateRange: dashboardOptions.useCurrentDateRange,
28+
}),
29+
};
30+
}
31+
32+
const urlOptions = options as Required<ExternalLink>['options'];
33+
return {
34+
...(typeof urlOptions.openInNewTab === 'boolean' && { openInNewTab: urlOptions.openInNewTab }),
35+
...(typeof urlOptions.encodeUrl === 'boolean' && { encodeUrl: urlOptions.encodeUrl }),
36+
};
37+
}

src/platform/plugins/private/links/common/embeddable/transforms/transform_out.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,51 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import type { StoredLinksEmbeddableState } from '../types';
10+
import type { LinkOptions } from '../../../server';
11+
import type { LinksEmbeddableState, StoredLinksEmbeddableState } from '../types';
1112
import type { StoredLinksByValueState910 } from './bwc';
1213
import { transformOut } from './transform_out';
1314

1415
describe('transformOut', () => {
16+
test('should remove options for other link types in by-value state', () => {
17+
const byValueState = {
18+
title: 'Custom title',
19+
layout: 'vertical',
20+
links: [
21+
{
22+
type: 'externalLink',
23+
id: 'e2ab286f-0945-4e17-b256-f497b6c3102e',
24+
order: 0,
25+
destination: 'https://github.com/',
26+
options: {
27+
openInNewTab: true,
28+
// these are dashboard link options saved in external link
29+
// state because of editor UI bug
30+
useCurrentDateRange: true,
31+
useCurrentFilters: true,
32+
} as LinkOptions,
33+
},
34+
],
35+
} as LinksEmbeddableState;
36+
expect(transformOut(byValueState, [])).toMatchInlineSnapshot(`
37+
Object {
38+
"layout": "vertical",
39+
"links": Array [
40+
Object {
41+
"destination": "https://github.com/",
42+
"id": "e2ab286f-0945-4e17-b256-f497b6c3102e",
43+
"options": Object {
44+
"openInNewTab": true,
45+
},
46+
"order": 0,
47+
"type": "externalLink",
48+
},
49+
],
50+
"title": "Custom title",
51+
}
52+
`);
53+
});
54+
1555
test('should inject dashboard references for by-value state', () => {
1656
const byValueState = {
1757
title: 'Custom title',

src/platform/plugins/private/links/common/embeddable/transforms/transform_out.ts

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

1010
import type { Reference } from '@kbn/content-management-utils';
11-
import type { StoredLinksEmbeddableState } from '../types';
11+
import type { LinksEmbeddableState, StoredLinksEmbeddableState } from '../types';
1212
import { type StoredLinksByValueState910, isLegacyState, transformLegacyState } from './bwc';
1313
import { LINKS_SAVED_OBJECT_TYPE } from '../../constants';
1414
import { injectReferences } from './references';
15+
import { getOptions } from './get_options';
1516

1617
export function transformOut(
17-
storedState: StoredLinksEmbeddableState | StoredLinksByValueState910,
18+
storedState: LinksEmbeddableState | StoredLinksEmbeddableState | StoredLinksByValueState910,
1819
references?: Reference[]
1920
) {
2021
const state = isLegacyState(storedState)
@@ -35,6 +36,9 @@ export function transformOut(
3536
// inject dashboard references when by-value
3637
return {
3738
...state,
38-
links: injectReferences(state.links, references),
39+
links: injectReferences(state.links, references).map((link) => ({
40+
...link,
41+
...(link.options && { options: getOptions(link.type, link.options) }),
42+
})),
3943
};
4044
}

src/platform/plugins/private/links/public/components/editor/link_editor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { LinkOptionsComponent } from './link_options';
3636
import type { UnorderedLink } from '../../editor/open_link_editor_flyout';
3737
import { LinkDestination } from './link_destination';
3838
import type { LinkOptions } from '../../../server';
39+
import { getOptions } from '../../../common/embeddable/transforms/get_options';
3940

4041
export const LinkEditor = ({
4142
link,
@@ -170,7 +171,7 @@ export const LinkEditor = ({
170171
type: selectedLinkType,
171172
id: link?.id ?? uuidv4(),
172173
destination: linkDestination,
173-
options: linkOptions,
174+
options: getOptions(selectedLinkType, linkOptions),
174175
title: defaultLinkLabel ?? '',
175176
description: linkDescription,
176177
});

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

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,27 @@ export const dashboardLinkSchema = schema.object({
4141
options: schema.maybe(
4242
schema.object(
4343
{
44-
openInNewTab: schema.boolean({
45-
meta: {
46-
description: 'Whether to open this link in a new tab when clicked',
47-
},
48-
}),
49-
useCurrentFilters: schema.boolean({
50-
meta: {
51-
description: 'Whether to use the filters and query from the origin dashboard',
52-
},
53-
}),
54-
useCurrentDateRange: schema.boolean({
55-
meta: {
56-
description: 'Whether to use the date range from the origin dashboard',
57-
},
58-
}),
44+
openInNewTab: schema.maybe(
45+
schema.boolean({
46+
meta: {
47+
description: 'Whether to open this link in a new tab when clicked',
48+
},
49+
})
50+
),
51+
useCurrentFilters: schema.maybe(
52+
schema.boolean({
53+
meta: {
54+
description: 'Whether to use the filters and query from the origin dashboard',
55+
},
56+
})
57+
),
58+
useCurrentDateRange: schema.maybe(
59+
schema.boolean({
60+
meta: {
61+
description: 'Whether to use the date range from the origin dashboard',
62+
},
63+
})
64+
),
5965
},
6066
{ unknowns: 'forbid' }
6167
)
@@ -69,16 +75,20 @@ export const externalLinkSchema = schema.object({
6975
options: schema.maybe(
7076
schema.object(
7177
{
72-
openInNewTab: schema.boolean({
73-
meta: {
74-
description: 'Whether to open this link in a new tab when clicked',
75-
},
76-
}),
77-
encodeUrl: schema.boolean({
78-
meta: {
79-
description: 'Whether to escape the URL with percent encoding',
80-
},
81-
}),
78+
openInNewTab: schema.maybe(
79+
schema.boolean({
80+
meta: {
81+
description: 'Whether to open this link in a new tab when clicked',
82+
},
83+
})
84+
),
85+
encodeUrl: schema.maybe(
86+
schema.boolean({
87+
meta: {
88+
description: 'Whether to escape the URL with percent encoding',
89+
},
90+
})
91+
),
8292
},
8393
{ unknowns: 'forbid' }
8494
)

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server';
1111
import type { Reference } from '@kbn/content-management-utils/src/types';
1212
import type { LinksItem } from '../../../../common/content_management';
13-
import type { LinksState, StoredLinksState } from './types';
13+
import type { DashboardLink, ExternalLink, LinksState, StoredLinksState } from './types';
1414
import {
1515
extractReferences,
1616
injectReferences,
1717
} from '../../../../common/embeddable/transforms/references';
18+
import { getOptions } from '../../../../common/embeddable/transforms/get_options';
1819

1920
type PartialSavedObject<T> = Omit<SavedObject<Partial<T>>, 'references'> & {
2021
references: SavedObjectReference[] | undefined;
@@ -34,7 +35,13 @@ export function savedObjectToItem(
3435
...rest,
3536
attributes: {
3637
...attributes,
37-
links,
38+
links: links.map(
39+
(link) =>
40+
({
41+
...link,
42+
...(link.options && { options: getOptions(link.type, link.options) }),
43+
} as DashboardLink | ExternalLink)
44+
),
3845
},
3946
references: (references ?? []).filter(({ type }) => type === 'tag'),
4047
};

0 commit comments

Comments
 (0)