Skip to content

Commit 32abbff

Browse files
authored
[Time to Visualize] Lens By Value With AttributeService (#77561)
Used the attribute service to make lens work properly with by value embeddables.
1 parent a00b3ee commit 32abbff

File tree

37 files changed

+1783
-1318
lines changed

37 files changed

+1783
-1318
lines changed

src/plugins/dashboard/public/application/actions/library_notification_action.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class LibraryNotificationAction implements ActionByType<typeof ACTION_LIB
7979

8080
public isCompatible = async ({ embeddable }: LibraryNotificationActionContext) => {
8181
return (
82+
embeddable.getRoot().isContainer &&
8283
embeddable.getInput()?.viewMode !== ViewMode.VIEW &&
8384
isReferenceOrValueEmbeddable(embeddable) &&
8485
embeddable.inputIsRefType(embeddable.getInput())

src/plugins/dashboard/public/application/dashboard_app_controller.tsx

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ import {
6666
ViewMode,
6767
ContainerOutput,
6868
EmbeddableInput,
69-
SavedObjectEmbeddableInput,
7069
} from '../../../embeddable/public';
7170
import { NavAction, SavedDashboardPanel } from '../types';
7271

@@ -178,7 +177,7 @@ export class DashboardAppController {
178177
chrome.docTitle.change(dash.title);
179178
}
180179

181-
const incomingEmbeddable = embeddable
180+
let incomingEmbeddable = embeddable
182181
.getStateTransfer(scopedHistory())
183182
.getIncomingEmbeddablePackage();
184183

@@ -344,6 +343,22 @@ export class DashboardAppController {
344343
dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => {
345344
embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
346345
});
346+
347+
// If the incoming embeddable state's id already exists in the embeddables map, replace the input, retaining the existing gridData for that panel.
348+
if (incomingEmbeddable?.embeddableId && embeddablesMap[incomingEmbeddable.embeddableId]) {
349+
const originalPanelState = embeddablesMap[incomingEmbeddable.embeddableId];
350+
embeddablesMap[incomingEmbeddable.embeddableId] = {
351+
gridData: originalPanelState.gridData,
352+
type: incomingEmbeddable.type,
353+
explicitInput: {
354+
...originalPanelState.explicitInput,
355+
...incomingEmbeddable.input,
356+
id: incomingEmbeddable.embeddableId,
357+
},
358+
};
359+
incomingEmbeddable = undefined;
360+
}
361+
347362
let expandedPanelId;
348363
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
349364
expandedPanelId = dashboardContainer.getInput().expandedPanelId;
@@ -482,32 +497,16 @@ export class DashboardAppController {
482497
refreshDashboardContainer();
483498
});
484499

485-
if (incomingEmbeddable) {
486-
if ('id' in incomingEmbeddable) {
487-
container.addOrUpdateEmbeddable<SavedObjectEmbeddableInput>(
488-
incomingEmbeddable.type,
489-
{
490-
savedObjectId: incomingEmbeddable.id,
491-
}
492-
);
493-
} else if ('input' in incomingEmbeddable) {
494-
const input = incomingEmbeddable.input;
495-
// @ts-expect-error
496-
delete input.id;
497-
const explicitInput = {
498-
savedVis: input,
499-
};
500-
const embeddableId =
501-
'embeddableId' in incomingEmbeddable
502-
? incomingEmbeddable.embeddableId
503-
: undefined;
504-
container.addOrUpdateEmbeddable<EmbeddableInput>(
505-
incomingEmbeddable.type,
506-
// This ugly solution is temporary - https://github.com/elastic/kibana/pull/70272 fixes this whole section
507-
(explicitInput as unknown) as EmbeddableInput,
508-
embeddableId
509-
);
510-
}
500+
// If the incomingEmbeddable does not yet exist in the panels listing, create a new panel using the container's addEmbeddable method.
501+
if (
502+
incomingEmbeddable &&
503+
(!incomingEmbeddable.embeddableId ||
504+
!container.getInput().panels[incomingEmbeddable.embeddableId])
505+
) {
506+
container.addNewEmbeddable<EmbeddableInput>(
507+
incomingEmbeddable.type,
508+
incomingEmbeddable.input
509+
);
511510
}
512511
}
513512

src/plugins/dashboard/public/attribute_service/attribute_service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import { ATTRIBUTE_SERVICE_KEY } from './attribute_service';
21-
import { mockAttributeService } from './attribute_service_mock';
21+
import { mockAttributeService } from './attribute_service.mock';
2222
import { coreMock } from '../../../../core/public/mocks';
2323

2424
interface TestAttributes {

src/plugins/dashboard/public/attribute_service/attribute_service.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,8 @@ export class AttributeService<
156156
};
157157

158158
public getExplicitInputFromEmbeddable(embeddable: IEmbeddable): ValType | RefType {
159-
return embeddable.getRoot() &&
160-
(embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput
161-
? ((embeddable.getRoot() as Container).getInput().panels[embeddable.id].explicitInput as
162-
| ValType
163-
| RefType)
164-
: (embeddable.getInput() as ValType | RefType);
159+
return ((embeddable.getRoot() as Container).getInput()?.panels?.[embeddable.id]
160+
?.explicitInput ?? embeddable.getInput()) as ValType | RefType;
165161
}
166162

167163
getInputAsValueType = async (input: ValType | RefType): Promise<ValType> => {
@@ -204,7 +200,14 @@ export class AttributeService<
204200
const newAttributes = { ...input[ATTRIBUTE_SERVICE_KEY] };
205201
newAttributes.title = props.newTitle;
206202
const wrappedInput = (await this.wrapAttributes(newAttributes, true)) as RefType;
207-
resolve(wrappedInput);
203+
204+
// Remove unneeded attributes from the original input.
205+
delete (input as { [ATTRIBUTE_SERVICE_KEY]?: SavedObjectAttributes })[
206+
ATTRIBUTE_SERVICE_KEY
207+
];
208+
209+
// Combine input and wrapped input to preserve any passed in explicit Input.
210+
resolve({ ...input, ...wrappedInput });
208211
return { id: wrappedInput.savedObjectId };
209212
} catch (error) {
210213
reject(error);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service';

src/plugins/dashboard/public/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export {
3131
} from './application';
3232
export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
3333

34-
export { DashboardStart, DashboardUrlGenerator } from './plugin';
34+
export { DashboardStart, DashboardUrlGenerator, DashboardFeatureFlagConfig } from './plugin';
3535
export {
3636
DASHBOARD_APP_URL_GENERATOR,
3737
createDashboardUrlGenerator,
@@ -40,7 +40,7 @@ export {
4040
export { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
4141
export { SavedObjectDashboard } from './saved_dashboards';
4242
export { SavedDashboardPanel } from './types';
43-
export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service/attribute_service';
43+
export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './attribute_service';
4444

4545
export function plugin(initializerContext: PluginInitializerContext) {
4646
return new DashboardPlugin(initializerContext);

src/plugins/dashboard/public/mocks.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { DashboardStart } from './plugin';
2121

2222
export type Start = jest.Mocked<DashboardStart>;
23+
export { mockAttributeService } from './attribute_service/attribute_service.mock';
2324

2425
const createStartContract = (): DashboardStart => {
2526
// @ts-ignore

src/plugins/dashboard/public/plugin.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ declare module '../../share/public' {
117117

118118
export type DashboardUrlGenerator = UrlGeneratorContract<typeof DASHBOARD_APP_URL_GENERATOR>;
119119

120-
interface DashboardFeatureFlagConfig {
120+
export interface DashboardFeatureFlagConfig {
121121
allowByValueEmbeddables: boolean;
122122
}
123123

src/plugins/embeddable/public/lib/actions/edit_panel_action.test.tsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import { EditPanelAction } from './edit_panel_action';
21-
import { Embeddable, EmbeddableInput } from '../embeddables';
21+
import { Embeddable, EmbeddableInput, SavedObjectEmbeddableInput } from '../embeddables';
2222
import { ViewMode } from '../types';
2323
import { ContactCardEmbeddable } from '../test_samples';
2424
import { embeddablePluginMock } from '../../mocks';
@@ -53,20 +53,50 @@ test('is compatible when edit url is available, in edit mode and editable', asyn
5353
).toBe(true);
5454
});
5555

56-
test('redirects to app using state transfer', async () => {
56+
test('redirects to app using state transfer with by value mode', async () => {
5757
applicationMock.currentAppId$ = of('superCoolCurrentApp');
5858
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
59-
const input = { id: '123', viewMode: ViewMode.EDIT };
60-
const embeddable = new EditableEmbeddable(input, true);
59+
const embeddable = new EditableEmbeddable(
60+
({
61+
id: '123',
62+
viewMode: ViewMode.EDIT,
63+
coolInput1: 1,
64+
coolInput2: 2,
65+
} as unknown) as EmbeddableInput,
66+
true
67+
);
68+
embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' }));
69+
await action.execute({ embeddable });
70+
expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', {
71+
path: '/123',
72+
state: {
73+
originatingApp: 'superCoolCurrentApp',
74+
embeddableId: '123',
75+
valueInput: {
76+
id: '123',
77+
viewMode: ViewMode.EDIT,
78+
coolInput1: 1,
79+
coolInput2: 2,
80+
},
81+
},
82+
});
83+
});
84+
85+
test('redirects to app using state transfer without by value mode', async () => {
86+
applicationMock.currentAppId$ = of('superCoolCurrentApp');
87+
const action = new EditPanelAction(getFactory, applicationMock, stateTransferMock);
88+
const embeddable = new EditableEmbeddable(
89+
{ id: '123', viewMode: ViewMode.EDIT, savedObjectId: '1234' } as SavedObjectEmbeddableInput,
90+
true
91+
);
6192
embeddable.getOutput = jest.fn(() => ({ editApp: 'ultraVisualize', editPath: '/123' }));
6293
await action.execute({ embeddable });
6394
expect(stateTransferMock.navigateToEditor).toHaveBeenCalledWith('ultraVisualize', {
6495
path: '/123',
6596
state: {
6697
originatingApp: 'superCoolCurrentApp',
67-
byValueMode: true,
6898
embeddableId: '123',
69-
valueInput: input,
99+
valueInput: undefined,
70100
},
71101
});
72102
});

0 commit comments

Comments
 (0)