Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Switch to embeddable factory interface with optional override (#61165) #62360

Merged
merged 1 commit into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import { i18n } from '@kbn/i18n';
import {
IContainer,
EmbeddableInput,
EmbeddableFactory,
EmbeddableFactoryDefinition,
} from '../../../../src/plugins/embeddable/public';
import { HelloWorldEmbeddable, HELLO_WORLD_EMBEDDABLE } from './hello_world_embeddable';

export class HelloWorldEmbeddableFactory extends EmbeddableFactory {
export class HelloWorldEmbeddableFactory implements EmbeddableFactoryDefinition {
public readonly type = HELLO_WORLD_EMBEDDABLE;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import { i18n } from '@kbn/i18n';
import {
EmbeddableFactory,
EmbeddableFactoryDefinition,
ContainerInput,
EmbeddableStart,
} from '../../../../src/plugins/embeddable/public';
Expand All @@ -29,22 +29,20 @@ interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
}

export class ListContainerFactory extends EmbeddableFactory {
export class ListContainerFactory implements EmbeddableFactoryDefinition {
public readonly type = LIST_CONTAINER;
public readonly isContainerType = true;

constructor(private getStartServices: () => Promise<StartServices>) {
super();
}
constructor(private getStartServices: () => Promise<StartServices>) {}

public async isEditable() {
return true;
}

public async create(initialInput: ContainerInput) {
public create = async (initialInput: ContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new ListContainer(initialInput, getEmbeddableFactory);
}
};

public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@
*/

import { i18n } from '@kbn/i18n';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { IContainer, EmbeddableFactoryDefinition } from '../../../../src/plugins/embeddable/public';
import {
MultiTaskTodoEmbeddable,
MULTI_TASK_TODO_EMBEDDABLE,
MultiTaskTodoInput,
MultiTaskTodoOutput,
} from './multi_task_todo_embeddable';

export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory<
MultiTaskTodoInput,
MultiTaskTodoOutput
> {
export class MultiTaskTodoEmbeddableFactory
implements EmbeddableFactoryDefinition<MultiTaskTodoInput, MultiTaskTodoOutput> {
public readonly type = MULTI_TASK_TODO_EMBEDDABLE;

public async isEditable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
*/

import { i18n } from '@kbn/i18n';
import { EmbeddableFactory, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import {
EmbeddableFactoryDefinition,
EmbeddableStart,
} from '../../../../src/plugins/embeddable/public';
import {
SEARCHABLE_LIST_CONTAINER,
SearchableListContainer,
Expand All @@ -29,22 +32,20 @@ interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
}

export class SearchableListContainerFactory extends EmbeddableFactory {
export class SearchableListContainerFactory implements EmbeddableFactoryDefinition {
public readonly type = SEARCHABLE_LIST_CONTAINER;
public readonly isContainerType = true;

constructor(private getStartServices: () => Promise<StartServices>) {
super();
}
constructor(private getStartServices: () => Promise<StartServices>) {}

public async isEditable() {
return true;
}

public async create(initialInput: SearchableContainerInput) {
public create = async (initialInput: SearchableContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new SearchableListContainer(initialInput, getEmbeddableFactory);
}
};

public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { OverlayStart } from 'kibana/public';
import { EuiFieldText } from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { IContainer, EmbeddableFactoryDefinition } from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput, TodoOutput } from './todo_embeddable';

function TaskInput({ onSave }: { onSave: (task: string) => void }) {
Expand All @@ -47,16 +47,11 @@ interface StartServices {
openModal: OverlayStart['openModal'];
}

export class TodoEmbeddableFactory extends EmbeddableFactory<
TodoInput,
TodoOutput,
TodoEmbeddable
> {
export class TodoEmbeddableFactory
implements EmbeddableFactoryDefinition<TodoInput, TodoOutput, TodoEmbeddable> {
public readonly type = TODO_EMBEDDABLE;

constructor(private getStartServices: () => Promise<StartServices>) {
super();
}
constructor(private getStartServices: () => Promise<StartServices>) {}

public async isEditable() {
return true;
Expand All @@ -72,7 +67,7 @@ export class TodoEmbeddableFactory extends EmbeddableFactory<
* used to collect specific embeddable input that the container will not provide, like
* in this case, the task string.
*/
public async getExplicitInput() {
public getExplicitInput = async () => {
const { openModal } = await this.getStartServices();
return new Promise<{ task: string }>(resolve => {
const onSave = (task: string) => resolve({ task });
Expand All @@ -87,7 +82,7 @@ export class TodoEmbeddableFactory extends EmbeddableFactory<
)
);
});
}
};

public getDisplayName() {
return i18n.translate('embeddableExamples.todo.displayName', {
Expand Down
15 changes: 11 additions & 4 deletions examples/embeddable_explorer/public/todo_embeddable_example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ import {
import {
TodoEmbeddable,
TODO_EMBEDDABLE,
TodoEmbeddableFactory,
TodoInput,
} from '../../../examples/embeddable_examples/public/todo';
import { EmbeddableStart, EmbeddableRoot } from '../../../src/plugins/embeddable/public';
import {
EmbeddableStart,
EmbeddableRoot,
EmbeddableOutput,
ErrorEmbeddable,
} from '../../../src/plugins/embeddable/public';

interface Props {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
Expand All @@ -53,7 +58,7 @@ interface State {
}

export class TodoEmbeddableExample extends React.Component<Props, State> {
private embeddable?: TodoEmbeddable;
private embeddable?: TodoEmbeddable | ErrorEmbeddable;

constructor(props: Props) {
super(props);
Expand All @@ -62,7 +67,9 @@ export class TodoEmbeddableExample extends React.Component<Props, State> {
}

public componentDidMount() {
const factory = this.props.getEmbeddableFactory(TODO_EMBEDDABLE) as TodoEmbeddableFactory;
const factory = this.props.getEmbeddableFactory<TodoInput, EmbeddableOutput, TodoEmbeddable>(
TODO_EMBEDDABLE
);

if (factory === undefined) {
throw new Error('Embeddable factory is undefined!');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
DashboardContainerFactory,
DashboardContainerInput,
DashboardPanelState,
} from '../../../../../../plugins/dashboard/public';
Expand All @@ -58,6 +57,7 @@ import {
isErrorEmbeddable,
openAddPanelFlyout,
ViewMode,
ContainerOutput,
} from '../../../../../../plugins/embeddable/public';
import { NavAction, SavedDashboardPanel } from './types';

Expand Down Expand Up @@ -307,83 +307,92 @@ export class DashboardAppController {
let outputSubscription: Subscription | undefined;

const dashboardDom = document.getElementById('dashboardViewport');
const dashboardFactory = embeddable.getEmbeddableFactory(
DASHBOARD_CONTAINER_TYPE
) as DashboardContainerFactory;
dashboardFactory
.create(getDashboardInput())
.then((container: DashboardContainer | ErrorEmbeddable) => {
if (!isErrorEmbeddable(container)) {
dashboardContainer = container;

dashboardContainer.renderEmpty = () => {
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
return isEmptyState ? (
<DashboardEmptyScreen
{...getEmptyScreenProps(shouldShowEditHelp, isEmptyInReadOnlyMode)}
/>
) : null;
};

updateIndexPatterns(dashboardContainer);

outputSubscription = dashboardContainer.getOutput$().subscribe(() => {
updateIndexPatterns(dashboardContainer);
});

inputSubscription = dashboardContainer.getInput$().subscribe(() => {
let dirty = false;
const dashboardFactory = embeddable.getEmbeddableFactory<
DashboardContainerInput,
ContainerOutput,
DashboardContainer
>(DASHBOARD_CONTAINER_TYPE);

if (dashboardFactory) {
dashboardFactory
.create(getDashboardInput())
.then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
if (container && !isErrorEmbeddable(container)) {
dashboardContainer = container;

dashboardContainer.renderEmpty = () => {
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
const isEmptyState =
shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
return isEmptyState ? (
<DashboardEmptyScreen
{...getEmptyScreenProps(shouldShowEditHelp, isEmptyInReadOnlyMode)}
/>
) : null;
};

// This has to be first because handleDashboardContainerChanges causes
// appState.save which will cause refreshDashboardContainer to be called.

if (
!esFilters.compareFilters(
container.getInput().filters,
queryFilter.getFilters(),
esFilters.COMPARE_ALL_OPTIONS
)
) {
// Add filters modifies the object passed to it, hence the clone deep.
queryFilter.addFilters(_.cloneDeep(container.getInput().filters));
updateIndexPatterns(dashboardContainer);

dashboardStateManager.applyFilters($scope.model.query, container.getInput().filters);
dirty = true;
}
outputSubscription = dashboardContainer.getOutput$().subscribe(() => {
updateIndexPatterns(dashboardContainer);
});

dashboardStateManager.handleDashboardContainerChanges(container);
$scope.$evalAsync(() => {
if (dirty) {
updateState();
inputSubscription = dashboardContainer.getInput$().subscribe(() => {
let dirty = false;

// This has to be first because handleDashboardContainerChanges causes
// appState.save which will cause refreshDashboardContainer to be called.

if (
!esFilters.compareFilters(
container.getInput().filters,
queryFilter.getFilters(),
esFilters.COMPARE_ALL_OPTIONS
)
) {
// Add filters modifies the object passed to it, hence the clone deep.
queryFilter.addFilters(_.cloneDeep(container.getInput().filters));

dashboardStateManager.applyFilters(
$scope.model.query,
container.getInput().filters
);
dirty = true;
}

dashboardStateManager.handleDashboardContainerChanges(container);
$scope.$evalAsync(() => {
if (dirty) {
updateState();
}
});
});
});

dashboardStateManager.registerChangeListener(() => {
// we aren't checking dirty state because there are changes the container needs to know about
// that won't make the dashboard "dirty" - like a view mode change.
refreshDashboardContainer();
});
dashboardStateManager.registerChangeListener(() => {
// we aren't checking dirty state because there are changes the container needs to know about
// that won't make the dashboard "dirty" - like a view mode change.
refreshDashboardContainer();
});

// This code needs to be replaced with a better mechanism for adding new embeddables of
// any type from the add panel. Likely this will happen via creating a visualization "inline",
// without navigating away from the UX.
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
container.addSavedObjectEmbeddable(type, id);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
// This code needs to be replaced with a better mechanism for adding new embeddables of
// any type from the add panel. Likely this will happen via creating a visualization "inline",
// without navigating away from the UX.
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
container.addSavedObjectEmbeddable(type, id);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
}
}
}

if (dashboardDom) {
container.render(dashboardDom);
}
});
if (dashboardDom && container) {
container.render(dashboardDom);
}
});
}

// Part of the exposed plugin API - do not remove without careful consideration.
this.appStatus = {
Expand Down
Loading