Skip to content

Commit

Permalink
[Canvas] By-Value Embeddables (#113827)
Browse files Browse the repository at this point in the history
* [Canvas] Generic embeddable function (#104499)

* Created generic embeddable function

    Fixed telemetry

    Updates expression on input change

    Fixed ts errors

Store embeddable input to expression

Added lib functions

Added comments

Fixed type errors

Fixed ts errors

Clean up

Removed extraneous import

Added context type to embeddable function def

Fix import

Update encode/decode fns

Moved embeddable data url lib file

Added embeddable test

Updated comment

* Fix reference extract/inject in embeddable fn

* Simplify embeddable toExpression

* Moved labsService to flyout.tsx

* Added comment

* [Canvas] Adds Save and Return Workflow (#111411)

* [Canvas] Adds editor menu to Canvas (#113194)

* Merge existing embeddable input with incoming embeddable input (#116026)

* [Canvas] Extract and inject references for by-value embeddables (#115124)

* Extract/inject references for by-value embeddables in embeddable function

Fixed server interpreter setup

Register external functions in canvas_plugin_src plugin def

* Fixed ref name in embeddable.inject

* Fixed ts errors

* Fix missing type error

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
cqliu1 and kibanamachine authored Oct 27, 2021
1 parent 01292ce commit bacd7f9
Show file tree
Hide file tree
Showing 60 changed files with 1,345 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => {
<SolutionToolbarPopover
ownFocus
label={i18n.translate('dashboard.solutionToolbar.editorMenuButtonLabel', {
defaultMessage: 'All types',
defaultMessage: 'Select type',
})}
iconType="arrowDown"
iconSide="right"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'labs:canvas:byValueEmbeddable': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'labs:canvas:useDataService': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export interface UsageStats {
'banners:textColor': string;
'banners:backgroundColor': string;
'labs:canvas:enable_ui': boolean;
'labs:canvas:byValueEmbeddable': boolean;
'labs:canvas:useDataService': boolean;
'labs:presentation:timeToPresent': boolean;
'labs:dashboard:enable_ui': boolean;
Expand Down
16 changes: 15 additions & 1 deletion src/plugins/presentation_util/common/labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { i18n } from '@kbn/i18n';

export const LABS_PROJECT_PREFIX = 'labs:';
export const DEFER_BELOW_FOLD = `${LABS_PROJECT_PREFIX}dashboard:deferBelowFold` as const;
export const BY_VALUE_EMBEDDABLE = `${LABS_PROJECT_PREFIX}canvas:byValueEmbeddable` as const;

export const projectIDs = [DEFER_BELOW_FOLD] as const;
export const projectIDs = [DEFER_BELOW_FOLD, BY_VALUE_EMBEDDABLE] as const;
export const environmentNames = ['kibana', 'browser', 'session'] as const;
export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const;

Expand All @@ -34,6 +35,19 @@ export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = {
}),
solutions: ['dashboard'],
},
[BY_VALUE_EMBEDDABLE]: {
id: BY_VALUE_EMBEDDABLE,
isActive: true,
isDisplayed: true,
environments: ['kibana', 'browser', 'session'],
name: i18n.translate('presentationUtil.labs.enableByValueEmbeddableName', {
defaultMessage: 'By-Value Embeddables',
}),
description: i18n.translate('presentationUtil.labs.enableByValueEmbeddableDescription', {
defaultMessage: 'Enables support for by-value embeddables in Canvas',
}),
solutions: ['canvas'],
},
};

export type ProjectID = typeof projectIDs[number];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
.quickButtonGroup {
.quickButtonGroup__button {
background-color: $euiColorEmptyShade;
@include kbnThemeStyle('v8') {
// sass-lint:disable-block no-important
border-width: $euiBorderWidthThin !important;
border-style: solid !important;
border-color: $euiBorderColor !important;
.euiButtonGroup__buttons {
border-radius: $euiBorderRadius;

.quickButtonGroup__button {
background-color: $euiColorEmptyShade;
@include kbnThemeStyle('v8') {
// sass-lint:disable-block no-important
border-width: $euiBorderWidthThin !important;
border-style: solid !important;
border-color: $euiBorderColor !important;
}
}

.quickButtonGroup__button:first-of-type {
@include kbnThemeStyle('v8') {
// sass-lint:disable-block no-important
border-top-left-radius: $euiBorderRadius !important;
border-bottom-left-radius: $euiBorderRadius !important;
}
}

.quickButtonGroup__button:last-of-type {
@include kbnThemeStyle('v8') {
// sass-lint:disable-block no-important
border-top-right-radius: $euiBorderRadius !important;
border-bottom-right-radius: $euiBorderRadius !important;
}
}
}
}
6 changes: 6 additions & 0 deletions src/plugins/telemetry/schema/oss_plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7671,6 +7671,12 @@
"description": "Non-default value of setting."
}
},
"labs:canvas:byValueEmbeddable": {
"type": "boolean",
"_meta": {
"description": "Non-default value of setting."
}
},
"labs:canvas:useDataService": {
"type": "boolean",
"_meta": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
*/

import { ExpressionTypeDefinition } from '../../../../../src/plugins/expressions';
import { EmbeddableInput } from '../../../../../src/plugins/embeddable/common/';
import { EmbeddableInput } from '../../types';
import { EmbeddableTypes } from './embeddable_types';

export const EmbeddableExpressionType = 'embeddable';
export { EmbeddableTypes, EmbeddableInput };

export interface EmbeddableExpression<Input extends EmbeddableInput> {
/**
* The type of the expression result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,11 @@
*/

import { functions as commonFunctions } from '../common';
import { functions as externalFunctions } from '../external';
import { location } from './location';
import { markdown } from './markdown';
import { urlparam } from './urlparam';
import { escount } from './escount';
import { esdocs } from './esdocs';
import { essql } from './essql';

export const functions = [
location,
markdown,
urlparam,
escount,
esdocs,
essql,
...commonFunctions,
...externalFunctions,
];
export const functions = [location, markdown, urlparam, escount, esdocs, essql, ...commonFunctions];
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { embeddableFunctionFactory } from './embeddable';
import { getQueryFilters } from '../../../common/lib/build_embeddable_filters';
import { ExpressionValueFilter } from '../../../types';
import { encode } from '../../../common/lib/embeddable_dataurl';
import { InitializeArguments } from '.';

const filterContext: ExpressionValueFilter = {
type: 'filter',
and: [
{
type: 'filter',
and: [],
value: 'filter-value',
column: 'filter-column',
filterType: 'exactly',
},
{
type: 'filter',
and: [],
column: 'time-column',
filterType: 'time',
from: '2019-06-04T04:00:00.000Z',
to: '2019-06-05T04:00:00.000Z',
},
],
};

describe('embeddable', () => {
const fn = embeddableFunctionFactory({} as InitializeArguments)().fn;
const config = {
id: 'some-id',
timerange: { from: '15m', to: 'now' },
title: 'test embeddable',
};

const args = {
config: encode(config),
type: 'visualization',
};

it('accepts null context', () => {
const expression = fn(null, args, {} as any);

expect(expression.input.filters).toEqual([]);
});

it('accepts filter context', () => {
const expression = fn(filterContext, args, {} as any);
const embeddableFilters = getQueryFilters(filterContext.and);

expect(expression.input.filters).toEqual(embeddableFilters);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { ExpressionValueFilter, EmbeddableInput } from '../../../types';
import { EmbeddableExpressionType, EmbeddableExpression } from '../../expression_types';
import { getFunctionHelp } from '../../../i18n';
import { SavedObjectReference } from '../../../../../../src/core/types';
import { getQueryFilters } from '../../../common/lib/build_embeddable_filters';
import { decode, encode } from '../../../common/lib/embeddable_dataurl';
import { InitializeArguments } from '.';

export interface Arguments {
config: string;
type: string;
}

const defaultTimeRange = {
from: 'now-15m',
to: 'now',
};

const baseEmbeddableInput = {
timeRange: defaultTimeRange,
disableTriggers: true,
renderMode: 'noInteractivity',
};

type Return = EmbeddableExpression<EmbeddableInput>;

type EmbeddableFunction = ExpressionFunctionDefinition<
'embeddable',
ExpressionValueFilter | null,
Arguments,
Return
>;

export function embeddableFunctionFactory({
embeddablePersistableStateService,
}: InitializeArguments): () => EmbeddableFunction {
return function embeddable(): EmbeddableFunction {
const { help, args: argHelp } = getFunctionHelp().embeddable;

return {
name: 'embeddable',
help,
args: {
config: {
aliases: ['_'],
types: ['string'],
required: true,
help: argHelp.config,
},
type: {
types: ['string'],
required: true,
help: argHelp.type,
},
},
context: {
types: ['filter'],
},
type: EmbeddableExpressionType,
fn: (input, args) => {
const filters = input ? input.and : [];

const embeddableInput = decode(args.config) as EmbeddableInput;

return {
type: EmbeddableExpressionType,
input: {
...baseEmbeddableInput,
...embeddableInput,
filters: getQueryFilters(filters),
},
generatedAt: Date.now(),
embeddableType: args.type,
};
},

extract(state) {
const input = decode(state.config[0] as string);

// extracts references for by-reference embeddables
if (input.savedObjectId) {
const refName = 'embeddable.savedObjectId';

const references: SavedObjectReference[] = [
{
name: refName,
type: state.type[0] as string,
id: input.savedObjectId as string,
},
];

return {
state,
references,
};
}

// extracts references for by-value embeddables
const { state: extractedState, references: extractedReferences } =
embeddablePersistableStateService.extract({
...input,
type: state.type[0],
});

const { type, ...extractedInput } = extractedState;

return {
state: { ...state, config: [encode(extractedInput)], type: [type] },
references: extractedReferences,
};
},

inject(state, references) {
const input = decode(state.config[0] as string);
const savedObjectReference = references.find(
(ref) => ref.name === 'embeddable.savedObjectId'
);

// injects saved object id for by-references embeddable
if (savedObjectReference) {
input.savedObjectId = savedObjectReference.id;
state.config[0] = encode(input);
state.type[0] = savedObjectReference.type;
} else {
// injects references for by-value embeddables
const { type, ...injectedInput } = embeddablePersistableStateService.inject(
{ ...input, type: state.type[0] },
references
);
state.config[0] = encode(injectedInput);
state.type[0] = type;
}
return state;
},
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,26 @@
* 2.0.
*/

import { EmbeddableStart } from 'src/plugins/embeddable/public';
import { embeddableFunctionFactory } from './embeddable';
import { savedLens } from './saved_lens';
import { savedMap } from './saved_map';
import { savedSearch } from './saved_search';
import { savedVisualization } from './saved_visualization';

export const functions = [savedLens, savedMap, savedVisualization, savedSearch];
export interface InitializeArguments {
embeddablePersistableStateService: {
extract: EmbeddableStart['extract'];
inject: EmbeddableStart['inject'];
};
}

export function initFunctions(initialize: InitializeArguments) {
return [
embeddableFunctionFactory(initialize),
savedLens,
savedMap,
savedSearch,
savedVisualization,
];
}
Loading

0 comments on commit bacd7f9

Please sign in to comment.