Skip to content

Commit

Permalink
Embeddable API Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
stacey-gammon committed Apr 25, 2019
1 parent 71e61fa commit 3090ca9
Show file tree
Hide file tree
Showing 53 changed files with 3,261 additions and 318 deletions.
8 changes: 7 additions & 1 deletion src/dev/jest/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ export default {
'packages/kbn-ui-framework/src/services/**/*.js',
'!packages/kbn-ui-framework/src/services/index.js',
'!packages/kbn-ui-framework/src/services/**/*/index.js',
'src/legacy/core_plugins/metrics/**/*.js'
'src/legacy/core_plugins/metrics/**/*.js',
'src/legacy/core_plugins/embeddable_api/public/**/*',
'!src/legacy/core_plugins/embeddable_api/public/__test__/**/*',
'!src/legacy/core_plugins/embeddable_api/public/**/__snapshots__/**/*',
'src/legacy/core_plugins/dashboard_embeddable/public/**/*',
'!src/legacy/core_plugins/dashboard_embeddable/public/__test__/**/*',
'!src/legacy/core_plugins/dashboard_embeddable/public/**/__snapshots__/**/*',
],
moduleNameMapper: {
'^plugins/([^\/.]*)/(.*)': '<rootDir>/src/legacy/core_plugins/$1/public/$2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
* under the License.
*/

// @ts-ignore: implicit any for JS file
import { uiRegistry } from '../registry/_registry';
// @ts-ignore
import { resolve } from 'path';

/**
* Registry of functions (EmbeddableFactoryProviders) which return an EmbeddableFactory.
*/
export const EmbeddableFactoriesRegistryProvider = uiRegistry({
index: ['name'],
name: 'embeddableFactories',
});
// eslint-disable-next-line import/no-default-export
export default function(kibana: any) {
return new kibana.Plugin({
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
},
});
}
4 changes: 4 additions & 0 deletions src/legacy/core_plugins/embeddable_api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "embeddable_api",
"version": "kibana"
}
78 changes: 78 additions & 0 deletions src/legacy/core_plugins/embeddable_api/public/actions/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { EuiContextMenuItemIcon } from '@elastic/eui';

import { Container } from '../containers';
import { Embeddable } from '../embeddables';

export interface ExecuteActionContext<
E extends Embeddable = Embeddable,
C extends Container = Container,
AC extends {} = {}
> {
embeddable: E;
container?: C;
triggerContext?: AC;
}

export interface ActionContext<E extends Embeddable = Embeddable, C extends Container = Container> {
embeddable: E;
container?: C;
}

export abstract class Action<
E extends Embeddable = Embeddable,
C extends Container = Container,
T extends {} = {}
> {
/**
* Determined the order when there is more than one action matched to a trigger.
* Higher numbers are displayed first.
*/
public priority: number = 0;

constructor(public readonly id: string) {}

/**
* Optional icon that can be displayed along with the title.
*/
public getIcon(context: ActionContext): EuiContextMenuItemIcon | undefined {
return undefined;
}

/**
* Returns a title to be displayed to the user.
* @param context
*/
public abstract getTitle(context: ActionContext): string;

/**
* Returns a promise that resolves to true if this action is compatible given the context,
* otherwise resolves to false.
*/
public isCompatible(context: ActionContext): Promise<boolean> {
return Promise.resolve(true);
}

/**
* Executes the action.
*/
public abstract execute(context: ExecuteActionContext<E, C, T>): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { Action } from './action';
import { Embeddable } from '../embeddables';
import { Container } from '../containers';
import { triggerRegistry } from '../triggers';

class ActionRegistry {
private actions: { [key: string]: Action } = {};

public addAction(action: Action) {
if (this.getAction(action.id)) {
throw new Error('Action already exists');
}
this.actions[action.id] = action;
}

public getAction(id: string) {
return this.actions[id];
}

public removeAction(id: string) {
delete this.actions[id];
}

public getActions() {
return this.actions;
}

public reset() {
this.actions = {};
}

public async getActionsForTrigger(
triggerId: string,
context: { embeddable: Embeddable; container?: Container }
) {
const trigger = triggerRegistry.getTrigger(triggerId);

if (!trigger) {
throw new Error(`Trigger with id ${triggerId} does not exist`);
}

const actions: Action[] = [];
const promises = trigger.actionIds.map(async id => {
const action = actionRegistry.getAction(id);
if (!action) {
throw new Error(`Action ${id} does not exist`);
}
if (!context || (await action.isCompatible(context))) {
actions.push(action);
}
});

await Promise.all(promises);

return actions;
}
}

export const actionRegistry = new ActionRegistry();
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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 { Container, ContainerInput } from '../containers';
import { EmbeddableInput, Embeddable, EmbeddableOutput } from '../embeddables';
import { APPLY_FILTER_TRIGGER, triggerRegistry } from '../triggers';
import { Filter } from '../types';
import { Action } from './action';
import { actionRegistry } from './action_registry';

interface ApplyFilterContainerInput extends ContainerInput {
filters: Filter[];
}

const APPLY_FILTER_ACTION_ID = 'APPLY_FILTER_ACTION_ID';

function containerAcceptsFilterInput(
container: Embeddable | Container<{ id: string }, {}, ApplyFilterContainerInput>
): container is Container<{ id: string }, {}, ApplyFilterContainerInput> {
return (
(container as Container<{ id: string }, {}, ApplyFilterContainerInput>).getInput().filters !==
undefined
);
}

export class ApplyFilterAction extends Action<
Embeddable,
Container<EmbeddableInput, EmbeddableOutput, ApplyFilterContainerInput>,
{ filters: Filter[] }
> {
constructor() {
super(APPLY_FILTER_ACTION_ID);
}

public getTitle() {
return 'Apply filter to current view';
}

public isCompatible(context: { embeddable: Embeddable }) {
let root = context.embeddable;
while (root.parent) {
root = root.parent;
}

return Promise.resolve(containerAcceptsFilterInput(root));
}

public execute({
embeddable,
triggerContext,
}: {
embeddable: Embeddable;
triggerContext?: { filters: Filter[] };
}) {
if (!triggerContext) {
throw new Error('Applying a filter requires a filter as context');
}
let root = embeddable;
while (root.parent) {
root = root.parent;
}
(root as Container<{ id: string }, {}, ApplyFilterContainerInput>).updateInput({
filters: triggerContext.filters,
});
}
}
const applyFilterAction = new ApplyFilterAction();
if (!actionRegistry.getAction(applyFilterAction.id)) {
actionRegistry.addAction(new ApplyFilterAction());
}

triggerRegistry.attachAction({
triggerId: APPLY_FILTER_TRIGGER,
actionId: APPLY_FILTER_ACTION_ID,
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,5 @@
* under the License.
*/

export { EmbeddableFactory, OnEmbeddableStateChanged } from './embeddable_factory';
export * from './embeddable';
export * from './context_menu_actions';
export { EmbeddableFactoriesRegistryProvider } from './embeddable_factories_registry';
export { ContainerState, EmbeddableState, Query, Filters, TimeRange, RefreshConfig } from './types';
export { Action, ActionContext, ExecuteActionContext } from './action';
export { actionRegistry } from './action_registry';
Loading

0 comments on commit 3090ca9

Please sign in to comment.