Skip to content

Commit

Permalink
Implementing ReferenceOrValueEmbeddable for visualize embeddable (#76088
Browse files Browse the repository at this point in the history
) (#77457)

* Adding dashboard as dependency to visualize

* Making visualize embeddable as ReferenceOrValueEmbeddable

* Adding attribute service to visualize embeddable

* Binding createFromSavedObject method

* Add/remove visualize embeddable from library

* Removing debugger statement

* Using custom save method on attribute service

* Reverting to not using attribute service

* Resetting flag value

* Fix i18n title and update title

* Using custom save method on attribute service

* Fixing eslint

* Using custom save method in visualize embeddable factory

* Making getInputAsValueType return Promise as it should

* Better error handling when saving a visualization fails

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Maja Grubic and elasticmachine committed Sep 15, 2020
1 parent 6d73ae6 commit b0a8f2b
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface AttributeServiceOptions<A extends { title: string }> {
type: string,
attributes: A,
savedObjectId?: string
) => Promise<{ id: string }>;
) => Promise<{ id?: string } | { error: Error }>;
customUnwrapMethod?: (savedObject: SimpleSavedObject<A>) => A;
}

Expand Down Expand Up @@ -124,7 +124,10 @@ export class AttributeService<
newAttributes,
savedObjectId
);
return { ...originalInput, savedObjectId: savedItem.id } as RefType;
if ('id' in savedItem) {
return { ...originalInput, savedObjectId: savedItem.id } as RefType;
}
return { ...originalInput } as RefType;
}

if (savedObjectId) {
Expand Down Expand Up @@ -208,7 +211,6 @@ export class AttributeService<
return { error };
}
};

if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) {
this.showSaveModal(
<SavedObjectSaveModal
Expand Down
35 changes: 35 additions & 0 deletions src/plugins/dashboard/public/mocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { DashboardStart } from './plugin';

export type Start = jest.Mocked<DashboardStart>;

const createStartContract = (): DashboardStart => {
// @ts-ignore
const startContract: DashboardStart = {
getAttributeService: jest.fn(),
};

return startContract;
};

export const dashboardPluginMock = {
createStartContract,
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export const defaultEmbeddableFactoryProvider = <
getExplicitInput: def.getExplicitInput
? def.getExplicitInput.bind(def)
: () => Promise.resolve({}),
createFromSavedObject:
def.createFromSavedObject ??
((savedObjectId: string, input: Partial<I>, parent?: IContainer) => {
throw new Error(`Creation from saved object not supported by type ${def.type}`);
}),
createFromSavedObject: def.createFromSavedObject
? def.createFromSavedObject.bind(def)
: (savedObjectId: string, input: Partial<I>, parent?: IContainer) => {
throw new Error(`Creation from saved object not supported by type ${def.type}`);
},
create: def.create.bind(def),
type: def.type,
isEditable: def.isEditable.bind(def),
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/visualizations/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"version": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "usageCollection", "inspector"],
"requiredPlugins": ["data", "expressions", "uiActions", "embeddable", "usageCollection", "inspector", "dashboard"],
"requiredBundles": ["kibanaUtils", "discover", "savedObjects"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
*/

import { Vis } from '../types';
import { VisualizeInput, VisualizeEmbeddable } from './visualize_embeddable';
import {
VisualizeInput,
VisualizeEmbeddable,
VisualizeByValueInput,
VisualizeByReferenceInput,
VisualizeSavedObjectAttributes,
} from './visualize_embeddable';
import { IContainer, ErrorEmbeddable } from '../../../../plugins/embeddable/public';
import { DisabledLabEmbeddable } from './disabled_lab_embeddable';
import {
Expand All @@ -30,10 +36,18 @@ import {
} from '../services';
import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory';
import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants';
import { SavedVisualizationsLoader } from '../saved_visualizations';
import { AttributeService } from '../../../dashboard/public';

export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDeps) => async (
vis: Vis,
input: Partial<VisualizeInput> & { id: string },
savedVisualizationsLoader?: SavedVisualizationsLoader,
attributeService?: AttributeService<
VisualizeSavedObjectAttributes,
VisualizeByValueInput,
VisualizeByReferenceInput
>,
parent?: IContainer
): Promise<VisualizeEmbeddable | ErrorEmbeddable | DisabledLabEmbeddable> => {
const savedVisualizations = getSavedVisualizationsLoader();
Expand All @@ -55,6 +69,7 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe
const indexPattern = vis.data.indexPattern;
const indexPatterns = indexPattern ? [indexPattern] : [];
const editable = getCapabilities().visualize.save as boolean;

return new VisualizeEmbeddable(
getTimeFilter(),
{
Expand All @@ -66,6 +81,8 @@ export const createVisEmbeddableFromObject = (deps: VisualizeEmbeddableFactoryDe
deps,
},
input,
attributeService,
savedVisualizationsLoader,
parent
);
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import _, { get } from 'lodash';
import { Subscription } from 'rxjs';
import * as Rx from 'rxjs';
import { i18n } from '@kbn/i18n';
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
import {
IIndexPattern,
Expand All @@ -35,6 +36,8 @@ import {
Embeddable,
IContainer,
Adapters,
SavedObjectEmbeddableInput,
ReferenceOrValueEmbeddable,
} from '../../../../plugins/embeddable/public';
import {
IExpressionLoaderParams,
Expand All @@ -47,6 +50,10 @@ import { getExpressions, getUiActions } from '../services';
import { VIS_EVENT_TO_TRIGGER } from './events';
import { VisualizeEmbeddableFactoryDeps } from './visualize_embeddable_factory';
import { TriggerId } from '../../../ui_actions/public';
import { SavedObjectAttributes } from '../../../../core/types';
import { AttributeService } from '../../../dashboard/public';
import { SavedVisualizationsLoader } from '../saved_visualizations';
import { VisSavedObject } from '../types';

const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;

Expand Down Expand Up @@ -75,9 +82,19 @@ export interface VisualizeOutput extends EmbeddableOutput {
visTypeName: string;
}

export type VisualizeSavedObjectAttributes = SavedObjectAttributes & {
title: string;
vis?: Vis;
savedVis?: VisSavedObject;
};
export type VisualizeByValueInput = { attributes: VisualizeSavedObjectAttributes } & VisualizeInput;
export type VisualizeByReferenceInput = SavedObjectEmbeddableInput & VisualizeInput;

type ExpressionLoader = InstanceType<ExpressionsStart['ExpressionLoader']>;

export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOutput> {
export class VisualizeEmbeddable
extends Embeddable<VisualizeInput, VisualizeOutput>
implements ReferenceOrValueEmbeddable<VisualizeByValueInput, VisualizeByReferenceInput> {
private handler?: ExpressionLoader;
private timefilter: TimefilterContract;
private timeRange?: TimeRange;
Expand All @@ -93,11 +110,23 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
private abortController?: AbortController;
private readonly deps: VisualizeEmbeddableFactoryDeps;
private readonly inspectorAdapters?: Adapters;
private attributeService?: AttributeService<
VisualizeSavedObjectAttributes,
VisualizeByValueInput,
VisualizeByReferenceInput
>;
private savedVisualizationsLoader?: SavedVisualizationsLoader;

constructor(
timefilter: TimefilterContract,
{ vis, editPath, editUrl, indexPatterns, editable, deps }: VisualizeEmbeddableConfiguration,
initialInput: VisualizeInput,
attributeService?: AttributeService<
VisualizeSavedObjectAttributes,
VisualizeByValueInput,
VisualizeByReferenceInput
>,
savedVisualizationsLoader?: SavedVisualizationsLoader,
parent?: IContainer
) {
super(
Expand All @@ -118,6 +147,8 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
this.vis = vis;
this.vis.uiState.on('change', this.uiStateChangeHandler);
this.vis.uiState.on('reload', this.reload);
this.attributeService = attributeService;
this.savedVisualizationsLoader = savedVisualizationsLoader;

this.autoRefreshFetchSubscription = timefilter
.getAutoRefreshFetch$()
Expand Down Expand Up @@ -381,4 +412,52 @@ export class VisualizeEmbeddable extends Embeddable<VisualizeInput, VisualizeOut
public supportedTriggers(): TriggerId[] {
return this.vis.type.getSupportedTriggers?.() ?? [];
}

inputIsRefType = (input: VisualizeInput): input is VisualizeByReferenceInput => {
if (!this.attributeService) {
throw new Error('AttributeService must be defined for getInputAsRefType');
}
return this.attributeService.inputIsRefType(input as VisualizeByReferenceInput);
};

getInputAsValueType = async (): Promise<VisualizeByValueInput> => {
const input = {
savedVis: this.vis.serialize(),
};
if (this.getTitle()) {
input.savedVis.title = this.getTitle();
}
delete input.savedVis.id;
return new Promise<VisualizeByValueInput>((resolve) => {
resolve({ ...(input as VisualizeByValueInput) });
});
};

getInputAsRefType = async (): Promise<VisualizeByReferenceInput> => {
const savedVis = await this.savedVisualizationsLoader?.get({});
if (!savedVis) {
throw new Error('Error creating a saved vis object');
}
if (!this.attributeService) {
throw new Error('AttributeService must be defined for getInputAsRefType');
}
const saveModalTitle = this.getTitle()
? this.getTitle()
: i18n.translate('visualizations.embeddable.placeholderTitle', {
defaultMessage: 'Placeholder Title',
});
// @ts-ignore
const attributes: VisualizeSavedObjectAttributes = {
savedVis,
vis: this.vis,
title: this.vis.title,
};
return this.attributeService.getInputAsRefType(
{
id: this.id,
attributes,
},
{ showSaveModal: true, saveModalTitle }
);
};
}
Loading

0 comments on commit b0a8f2b

Please sign in to comment.