diff --git a/x-pack/plugins/canvas/.storybook/storyshots.test.js b/x-pack/plugins/canvas/.storybook/storyshots.test.js index a3412c3a14e79..7195b97712464 100644 --- a/x-pack/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/plugins/canvas/.storybook/storyshots.test.js @@ -84,6 +84,10 @@ import { RenderedElement } from '../shareable_runtime/components/rendered_elemen jest.mock('../shareable_runtime/components/rendered_element'); RenderedElement.mockImplementation(() => 'RenderedElement'); +import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer'; +jest.mock('@elastic/eui/test-env/components/observer/observer'); +EuiObserver.mockImplementation(() => 'EuiObserver'); + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots diff --git a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts index 271fc7a979057..4b1f31cb14687 100644 --- a/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts +++ b/x-pack/plugins/canvas/__tests__/fixtures/workpads.ts @@ -25,6 +25,7 @@ const BaseWorkpad: CanvasWorkpad = { pages: [], colors: [], isWriteable: true, + variables: [], }; const BasePage: CanvasPage = { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js index 7384986fa5c2b..618fe756ba0a4 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js @@ -107,7 +107,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index 8acda5da4f0d2..78083f26a38b1 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -545,7 +545,7 @@ export const ComponentStrings = { }), getTitle: () => i18n.translate('xpack.canvas.pageConfig.title', { - defaultMessage: 'Page styles', + defaultMessage: 'Page settings', }), getTransitionLabel: () => i18n.translate('xpack.canvas.pageConfig.transitionLabel', { @@ -899,6 +899,144 @@ export const ComponentStrings = { defaultMessage: 'Close tray', }), }, + VarConfig: { + getAddButtonLabel: () => + i18n.translate('xpack.canvas.varConfig.addButtonLabel', { + defaultMessage: 'Add a variable', + }), + getAddTooltipLabel: () => + i18n.translate('xpack.canvas.varConfig.addTooltipLabel', { + defaultMessage: 'Add a variable', + }), + getCopyActionButtonLabel: () => + i18n.translate('xpack.canvas.varConfig.copyActionButtonLabel', { + defaultMessage: 'Copy snippet', + }), + getCopyActionTooltipLabel: () => + i18n.translate('xpack.canvas.varConfig.copyActionTooltipLabel', { + defaultMessage: 'Copy variable syntax to clipboard', + }), + getCopyNotificationDescription: () => + i18n.translate('xpack.canvas.varConfig.copyNotificationDescription', { + defaultMessage: 'Variable syntax copied to clipboard', + }), + getDeleteActionButtonLabel: () => + i18n.translate('xpack.canvas.varConfig.deleteActionButtonLabel', { + defaultMessage: 'Delete variable', + }), + getDeleteNotificationDescription: () => + i18n.translate('xpack.canvas.varConfig.deleteNotificationDescription', { + defaultMessage: 'Variable successfully deleted', + }), + getEditActionButtonLabel: () => + i18n.translate('xpack.canvas.varConfig.editActionButtonLabel', { + defaultMessage: 'Edit variable', + }), + getEmptyDescription: () => + i18n.translate('xpack.canvas.varConfig.emptyDescription', { + defaultMessage: + 'This workpad has no variables currently. You may add variables to store and edit common values. These variables can then be used in elements or within the expression editor.', + }), + getTableNameLabel: () => + i18n.translate('xpack.canvas.varConfig.tableNameLabel', { + defaultMessage: 'Name', + }), + getTableTypeLabel: () => + i18n.translate('xpack.canvas.varConfig.tableTypeLabel', { + defaultMessage: 'Type', + }), + getTableValueLabel: () => + i18n.translate('xpack.canvas.varConfig.tableValueLabel', { + defaultMessage: 'Value', + }), + getTitle: () => + i18n.translate('xpack.canvas.varConfig.titleLabel', { + defaultMessage: 'Variables', + }), + getTitleTooltip: () => + i18n.translate('xpack.canvas.varConfig.titleTooltip', { + defaultMessage: 'Add variables to store and edit common values', + }), + }, + VarConfigDeleteVar: { + getCancelButtonLabel: () => + i18n.translate('xpack.canvas.varConfigDeleteVar.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), + getDeleteButtonLabel: () => + i18n.translate('xpack.canvas.varConfigDeleteVar.deleteButtonLabel', { + defaultMessage: 'Delete variable', + }), + getTitle: () => + i18n.translate('xpack.canvas.varConfigDeleteVar.titleLabel', { + defaultMessage: 'Delete variable?', + }), + getWarningDescription: () => + i18n.translate('xpack.canvas.varConfigDeleteVar.warningDescription', { + defaultMessage: + 'Deleting this variable may adversely affect the workpad. Are you sure you wish to continue?', + }), + }, + VarConfigEditVar: { + getAddTitle: () => + i18n.translate('xpack.canvas.varConfigEditVar.addTitleLabel', { + defaultMessage: 'Add variable', + }), + getCancelButtonLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), + getDuplicateNameError: () => + i18n.translate('xpack.canvas.varConfigEditVar.duplicateNameError', { + defaultMessage: 'Variable name already in use', + }), + getEditTitle: () => + i18n.translate('xpack.canvas.varConfigEditVar.editTitleLabel', { + defaultMessage: 'Edit variable', + }), + getEditWarning: () => + i18n.translate('xpack.canvas.varConfigEditVar.editWarning', { + defaultMessage: 'Editing a variable in use may adversely affect your workpad', + }), + getNameFieldLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.nameFieldLabel', { + defaultMessage: 'Name', + }), + getSaveButtonLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.saveButtonLabel', { + defaultMessage: 'Save changes', + }), + getTypeBooleanLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.typeBooleanLabel', { + defaultMessage: 'Boolean', + }), + getTypeFieldLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.typeFieldLabel', { + defaultMessage: 'Type', + }), + getTypeNumberLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.typeNumberLabel', { + defaultMessage: 'Number', + }), + getTypeStringLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.typeStringLabel', { + defaultMessage: 'String', + }), + getValueFieldLabel: () => + i18n.translate('xpack.canvas.varConfigEditVar.valueFieldLabel', { + defaultMessage: 'Value', + }), + }, + VarConfigVarValueField: { + getFalseOption: () => + i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', { + defaultMessage: 'False', + }), + getTrueOption: () => + i18n.translate('xpack.canvas.varConfigVarValueField.trueOption', { + defaultMessage: 'True', + }), + }, WorkpadConfig: { getApplyStylesheetButtonLabel: () => i18n.translate('xpack.canvas.workpadConfig.applyStylesheetButtonLabel', { diff --git a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js index dfd99b18646a6..f356eedff19cf 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/arg_form.js +++ b/x-pack/plugins/canvas/public/components/arg_form/arg_form.js @@ -120,7 +120,7 @@ class ArgFormComponent extends PureComponent { ); return ( -
+
{ @@ -17,18 +17,16 @@ export const ArgLabel = (props) => { {expandable ? ( - - {label} - + {label} } extraAction={simpleArg} initialIsOpen={initialIsOpen} > -
{children}
+
{children}
) : ( simpleArg && ( diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js index 045e98bab870e..dcd933c2320cf 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js @@ -15,10 +15,13 @@ export const DatasourcePreview = compose( withState('datatable', 'setDatatable'), lifecycle({ componentDidMount() { - interpretAst({ - type: 'expression', - chain: [this.props.function], - }).then(this.props.setDatatable); + interpretAst( + { + type: 'expression', + chain: [this.props.function], + }, + {} + ).then(this.props.setDatatable); }, }), branch(({ datatable }) => !datatable, renderComponent(Loading)) diff --git a/x-pack/plugins/canvas/public/components/element_config/element_config.js b/x-pack/plugins/canvas/public/components/element_config/element_config.js deleted file mode 100644 index 5d710ef883548..0000000000000 --- a/x-pack/plugins/canvas/public/components/element_config/element_config.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiAccordion, EuiText, EuiSpacer } from '@elastic/eui'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { ComponentStrings } from '../../../i18n'; - -const { ElementConfig: strings } = ComponentStrings; - -export const ElementConfig = ({ elementStats }) => { - if (!elementStats) { - return null; - } - - const { total, ready, error } = elementStats; - const progress = total > 0 ? Math.round(((ready + error) / total) * 100) : 100; - - return ( - - {strings.getTitle()} - - } - initialIsOpen={false} - > - - - - - - - - - - - - - - - - - ); -}; - -ElementConfig.propTypes = { - elementStats: PropTypes.object, -}; diff --git a/x-pack/plugins/canvas/public/components/element_config/element_config.tsx b/x-pack/plugins/canvas/public/components/element_config/element_config.tsx new file mode 100644 index 0000000000000..c2fd827d62099 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/element_config/element_config.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiAccordion } from '@elastic/eui'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { ComponentStrings } from '../../../i18n'; +import { State } from '../../../types'; + +const { ElementConfig: strings } = ComponentStrings; + +interface Props { + elementStats: State['transient']['elementStats']; +} + +export const ElementConfig = ({ elementStats }: Props) => { + if (!elementStats) { + return null; + } + + const { total, ready, error } = elementStats; + const progress = total > 0 ? Math.round(((ready + error) / total) * 100) : 100; + + return ( +
+ +
+ + + + + + + + + + + + + + +
+
+
+ ); +}; + +ElementConfig.propTypes = { + elementStats: PropTypes.object, +}; diff --git a/x-pack/plugins/canvas/public/components/page_config/page_config.js b/x-pack/plugins/canvas/public/components/page_config/page_config.js index 51a4762fca501..c45536ac7b175 100644 --- a/x-pack/plugins/canvas/public/components/page_config/page_config.js +++ b/x-pack/plugins/canvas/public/components/page_config/page_config.js @@ -30,7 +30,7 @@ export const PageConfig = ({ }) => { return ( - +

{strings.getTitle()}

diff --git a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx index f89ab79a086cf..62673a5b38cc8 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx +++ b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx @@ -17,8 +17,6 @@ export const GlobalConfig: FunctionComponent = () => ( - - diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar.scss b/x-pack/plugins/canvas/public/components/sidebar/sidebar.scss index 338d515165e43..76d758197aa19 100644 --- a/x-pack/plugins/canvas/public/components/sidebar/sidebar.scss +++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar.scss @@ -31,12 +31,68 @@ &--isEmpty { border-bottom: none; } + + .canvasSidebar__expandable:last-child { + .canvasSidebar__accordion { + margin-bottom: (-$euiSizeS); + } + + .canvasSidebar__accordion:after { + content: none; + } + + .canvasSidebar__accordion.euiAccordion-isOpen:after { + display: none; + } + } } .canvasSidebar__panel-noMinWidth .euiButton { min-width: 0; } +.canvasSidebar__expandable + .canvasSidebar__expandable { + margin-top: 0; + + .canvasSidebar__accordion:before { + display: none; + } +} + +.canvasSidebar__accordion { + padding: $euiSizeM; + margin: 0 (-$euiSizeM); + background: $euiColorLightestShade; + position: relative; + + &.euiAccordion-isOpen { + background: transparent; + } + + &:before, + &:after { + content: ''; + height: 1px; + position: absolute; + left: 0; + width: 100%; + background: $euiColorLightShade; + } + + &:before { + top: 0; + } + + &:after { + bottom: 0; + } +} + +.canvasSidebar__accordionContent { + padding-top: $euiSize; + padding-left: $euiSizeXS + $euiSizeS + $euiSize; +} + @keyframes sidebarPop { 0% { opacity: 0; diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/delete_var.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/delete_var.stories.storyshot new file mode 100644 index 0000000000000..64f8cba665c15 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/delete_var.stories.storyshot @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/Variables/DeleteVar default 1`] = ` +Array [ +
+ +
, +
+
+
+
+
+
+ Deleting this variable may adversely affect the workpad. Are you sure you wish to continue? +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
, +] +`; diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/edit_var.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/edit_var.stories.storyshot new file mode 100644 index 0000000000000..65043e13e5143 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/edit_var.stories.storyshot @@ -0,0 +1,1236 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/Variables/EditVar edit variable (boolean) 1`] = ` +Array [ +
+ +
, +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + Select an option: +
+ +
+ + + + Boolean + +
+ , is selected +
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
, +] +`; + +exports[`Storyshots components/Variables/EditVar edit variable (number) 1`] = ` +Array [ +
+ +
, +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + Select an option: +
+ +
+ + + + Number + +
+ , is selected +
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
, +] +`; + +exports[`Storyshots components/Variables/EditVar edit variable (string) 1`] = ` +Array [ +
+ +
, +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + Select an option: +
+ +
+ + + + String + +
+ , is selected +
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
, +] +`; + +exports[`Storyshots components/Variables/EditVar new variable 1`] = ` +Array [ +
+ +
, +
+
+
+
+ +
+
+
+
+ +
+
+ + Select an option: +
+ +
+ + + + String + +
+ , is selected +
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
, +] +`; diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/var_config.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/var_config.stories.storyshot new file mode 100644 index 0000000000000..146f07a9d0118 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/__snapshots__/var_config.stories.storyshot @@ -0,0 +1,87 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/Variables/VarConfig default 1`] = ` +
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+`; diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/delete_var.stories.tsx b/x-pack/plugins/canvas/public/components/var_config/__examples__/delete_var.stories.tsx new file mode 100644 index 0000000000000..8f5b73d1f6ae9 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/delete_var.stories.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; + +import { CanvasVariable } from '../../../../types'; + +import { DeleteVar } from '../delete_var'; + +const variable: CanvasVariable = { + name: 'homeUrl', + value: 'https://elastic.co', + type: 'string', +}; + +storiesOf('components/Variables/DeleteVar', module).add('default', () => ( + +)); diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/edit_var.stories.tsx b/x-pack/plugins/canvas/public/components/var_config/__examples__/edit_var.stories.tsx new file mode 100644 index 0000000000000..0369c2c09a39c --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/edit_var.stories.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; + +import { CanvasVariable } from '../../../../types'; + +import { EditVar } from '../edit_var'; + +const variables: CanvasVariable[] = [ + { + name: 'homeUrl', + value: 'https://elastic.co', + type: 'string', + }, + { + name: 'bigNumber', + value: 1000, + type: 'number', + }, + { + name: 'zenMode', + value: true, + type: 'boolean', + }, +]; + +storiesOf('components/Variables/EditVar', module) + .add('new variable', () => ( + + )) + .add('edit variable (string)', () => ( + + )) + .add('edit variable (number)', () => ( + + )) + .add('edit variable (boolean)', () => ( + + )); diff --git a/x-pack/plugins/canvas/public/components/var_config/__examples__/var_config.stories.tsx b/x-pack/plugins/canvas/public/components/var_config/__examples__/var_config.stories.tsx new file mode 100644 index 0000000000000..ac5c97d122138 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/__examples__/var_config.stories.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; + +import { CanvasVariable } from '../../../../types'; + +import { VarConfig } from '../var_config'; + +const variables: CanvasVariable[] = [ + { + name: 'homeUrl', + value: 'https://elastic.co', + type: 'string', + }, + { + name: 'bigNumber', + value: 1000, + type: 'number', + }, + { + name: 'zenMode', + value: true, + type: 'boolean', + }, +]; + +storiesOf('components/Variables/VarConfig', module).add('default', () => ( + +)); diff --git a/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx new file mode 100644 index 0000000000000..fa1771a752848 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/delete_var.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { CanvasVariable } from '../../../types'; + +import { ComponentStrings } from '../../../i18n'; +const { VarConfigDeleteVar: strings } = ComponentStrings; + +import './var_panel.scss'; + +interface Props { + selectedVar: CanvasVariable; + onDelete: (v: CanvasVariable) => void; + onCancel: () => void; +} + +export const DeleteVar: FC = ({ selectedVar, onCancel, onDelete }) => { + return ( + +
+ +
+
+
+ + + + {strings.getWarningDescription()} + + + + + + + + + onDelete(selectedVar)} + iconType="trash" + > + {strings.getDeleteButtonLabel()} + + + + onCancel()}> + {strings.getCancelButtonLabel()} + + + +
+
+
+ ); +}; diff --git a/x-pack/plugins/canvas/public/components/var_config/edit_var.scss b/x-pack/plugins/canvas/public/components/var_config/edit_var.scss new file mode 100644 index 0000000000000..7d4a7a4c81ba1 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/edit_var.scss @@ -0,0 +1,8 @@ +.canvasEditVar__typeOption { + display: flex; + align-items: center; + + .canvasEditVar__tokenIcon { + margin-right: 15px; + } +} diff --git a/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx new file mode 100644 index 0000000000000..a1a5541431d26 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/edit_var.tsx @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, FC } from 'react'; +import { + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiToken, + EuiSuperSelect, + EuiForm, + EuiFormRow, + EuiFieldText, + EuiButton, + EuiButtonEmpty, + EuiSpacer, + EuiCallOut, +} from '@elastic/eui'; +import { CanvasVariable } from '../../../types'; + +import { VarValueField } from './var_value_field'; + +import { ComponentStrings } from '../../../i18n'; +const { VarConfigEditVar: strings } = ComponentStrings; + +import './edit_var.scss'; +import './var_panel.scss'; + +interface Props { + selectedVar: CanvasVariable | null; + variables: CanvasVariable[]; + onSave: (v: CanvasVariable) => void; + onCancel: () => void; +} + +const checkDupeName = (newName: string, oldName: string | null, variables: CanvasVariable[]) => { + const match = variables.find((v) => { + // If the new name matches an existing variable and that + // matched variable name isn't the old name, then there + // is a duplicate + return newName === v.name && (!oldName || v.name !== oldName); + }); + + return !!match; +}; + +export const EditVar: FC = ({ variables, selectedVar, onCancel, onSave }) => { + // If there isn't a selected variable, we're creating a new var + const isNew = selectedVar === null; + + const [type, setType] = useState(isNew ? 'string' : selectedVar!.type); + const [name, setName] = useState(isNew ? '' : selectedVar!.name); + const [value, setValue] = useState(isNew ? '' : selectedVar!.value); + + const hasDupeName = checkDupeName(name, selectedVar && selectedVar.name, variables); + + const typeOptions = [ + { + value: 'string', + inputDisplay: ( +
+ {' '} + {strings.getTypeStringLabel()} +
+ ), + }, + { + value: 'number', + inputDisplay: ( +
+ {' '} + {strings.getTypeNumberLabel()} +
+ ), + }, + { + value: 'boolean', + inputDisplay: ( +
+ {' '} + {strings.getTypeBooleanLabel()} +
+ ), + }, + ]; + + return ( + <> +
+ +
+
+ {!isNew && ( +
+ + +
+ )} + + + + { + // Only have these types possible in the dropdown + setType(v as CanvasVariable['type']); + + // Reset default value + if (v === 'boolean') { + // Just setting a default value + setValue(true); + } else if (v === 'number') { + // Setting default number + setValue(0); + } else { + setValue(''); + } + }} + compressed={true} + /> + + + setName(e.target.value)} + isInvalid={hasDupeName} + /> + + + setValue(v)} /> + + + + + + + + onSave({ + name, + value, + type, + }) + } + disabled={hasDupeName || !name} + iconType="save" + > + {strings.getSaveButtonLabel()} + + + + onCancel()}> + {strings.getCancelButtonLabel()} + + + + +
+ + ); +}; diff --git a/x-pack/plugins/canvas/public/components/var_config/index.tsx b/x-pack/plugins/canvas/public/components/var_config/index.tsx new file mode 100644 index 0000000000000..526037b79e0e0 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/index.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import copy from 'copy-to-clipboard'; +import { VarConfig as ChildComponent } from './var_config'; +import { + withKibana, + KibanaReactContextValue, + KibanaServices, +} from '../../../../../../src/plugins/kibana_react/public'; +import { CanvasServices } from '../../services'; + +import { ComponentStrings } from '../../../i18n'; + +import { CanvasVariable } from '../../../types'; + +const { VarConfig: strings } = ComponentStrings; + +interface Props { + kibana: KibanaReactContextValue<{ canvas: CanvasServices } & KibanaServices>; + + variables: CanvasVariable[]; + setVariables: (variables: CanvasVariable[]) => void; +} + +const WrappedComponent: FC = ({ kibana, variables, setVariables }) => { + const onDeleteVar = (v: CanvasVariable) => { + const index = variables.findIndex((targetVar: CanvasVariable) => { + return targetVar.name === v.name; + }); + if (index !== -1) { + const newVars = [...variables]; + newVars.splice(index, 1); + setVariables(newVars); + + kibana.services.canvas.notify.success(strings.getDeleteNotificationDescription()); + } + }; + + const onCopyVar = (v: CanvasVariable) => { + const snippetStr = `{var "${v.name}"}`; + copy(snippetStr, { debug: true }); + kibana.services.canvas.notify.success(strings.getCopyNotificationDescription()); + }; + + const onAddVar = (v: CanvasVariable) => { + setVariables([...variables, v]); + }; + + const onEditVar = (oldVar: CanvasVariable, newVar: CanvasVariable) => { + const existingVarIndex = variables.findIndex((v) => oldVar.name === v.name); + + const newVars = [...variables]; + newVars[existingVarIndex] = newVar; + + setVariables(newVars); + }; + + return ; +}; + +export const VarConfig = withKibana(WrappedComponent); diff --git a/x-pack/plugins/canvas/public/components/var_config/var_config.scss b/x-pack/plugins/canvas/public/components/var_config/var_config.scss new file mode 100644 index 0000000000000..19fe64e7422fd --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/var_config.scss @@ -0,0 +1,66 @@ +.canvasVarConfig__container { + width: 100%; + position: relative; + + &.canvasVarConfig-isEditMode { + .canvasVarConfig__innerContainer { + transform: translateX(-50%); + } + } +} + +.canvasVarConfig__list { + table { + background-color: transparent; + } + + thead tr th, + thead tr td { + border-bottom: none; + border-top: none; + } + + tbody tr td { + border-top: none; + border-bottom: none; + } + + tbody tr:hover { + background-color: transparent; + } + + tbody tr:last-child td { + border-bottom: none; + } +} + +.canvasVarConfig__innerContainer { + width: calc(200% + 48px); // Account for the extra padding + + position: relative; + + display: flex; + flex-direction: row; + align-content: stretch; + + .canvasVarConfig__editView { + margin-left: 0; + } + + .canvasVarConfig__listView { + margin-right: 0; + } +} + +.canvasVarConfig__editView { + width: 50%; + height: 100%; + + flex-shrink: 0; +} + +.canvasVarConfig__listView { + width: 50%; + + flex-shrink: 0; +} diff --git a/x-pack/plugins/canvas/public/components/var_config/var_config.tsx b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx new file mode 100644 index 0000000000000..6120130c77e24 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/var_config.tsx @@ -0,0 +1,230 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, FC } from 'react'; +import { + EuiAccordion, + EuiButtonIcon, + EuiToken, + EuiToolTip, + EuiText, + EuiInMemoryTable, + EuiBasicTableColumn, + EuiTableActionsColumnType, + EuiSpacer, + EuiButton, +} from '@elastic/eui'; + +import { CanvasVariable } from '../../../types'; +import { ComponentStrings } from '../../../i18n'; + +import { EditVar } from './edit_var'; +import { DeleteVar } from './delete_var'; + +import './var_config.scss'; + +const { VarConfig: strings } = ComponentStrings; + +enum PanelMode { + List, + Edit, + Delete, +} + +const typeToToken = { + number: 'tokenNumber', + boolean: 'tokenBoolean', + string: 'tokenString', +}; + +interface Props { + variables: CanvasVariable[]; + onCopyVar: (v: CanvasVariable) => void; + onDeleteVar: (v: CanvasVariable) => void; + onAddVar: (v: CanvasVariable) => void; + onEditVar: (oldVar: CanvasVariable, newVar: CanvasVariable) => void; +} + +export const VarConfig: FC = ({ + variables, + onCopyVar, + onDeleteVar, + onAddVar, + onEditVar, +}) => { + const [panelMode, setPanelMode] = useState(PanelMode.List); + const [selectedVar, setSelectedVar] = useState(null); + + const selectAndEditVar = (v: CanvasVariable) => { + setSelectedVar(v); + setPanelMode(PanelMode.Edit); + }; + + const selectAndDeleteVar = (v: CanvasVariable) => { + setSelectedVar(v); + setPanelMode(PanelMode.Delete); + }; + + const actions: EuiTableActionsColumnType['actions'] = [ + { + type: 'icon', + name: strings.getCopyActionButtonLabel(), + description: strings.getCopyActionTooltipLabel(), + icon: 'copyClipboard', + onClick: onCopyVar, + isPrimary: true, + }, + { + type: 'icon', + name: strings.getEditActionButtonLabel(), + description: '', + icon: 'pencil', + onClick: selectAndEditVar, + }, + { + type: 'icon', + name: strings.getDeleteActionButtonLabel(), + description: '', + icon: 'trash', + color: 'danger', + onClick: selectAndDeleteVar, + }, + ]; + + const varColumns: Array> = [ + { + field: 'type', + name: strings.getTableTypeLabel(), + sortable: true, + render: (varType: CanvasVariable['type'], _v: CanvasVariable) => { + return ; + }, + width: '50px', + }, + { + field: 'name', + name: strings.getTableNameLabel(), + sortable: true, + }, + { + field: 'value', + name: strings.getTableValueLabel(), + sortable: true, + truncateText: true, + render: (value: CanvasVariable['value'], _v: CanvasVariable) => { + return '' + value; + }, + }, + { + actions, + width: '60px', + }, + ]; + + return ( +
+
+ + {strings.getTitle()} + + } + extraAction={ + + { + setSelectedVar(null); + setPanelMode(PanelMode.Edit); + }} + /> + + } + > + {variables.length !== 0 && ( +
+ +
+ )} + {variables.length === 0 && ( +
+ + {strings.getEmptyDescription()} + + + setPanelMode(PanelMode.Edit)} + > + {strings.getAddButtonLabel()} + +
+ )} +
+
+ {panelMode === PanelMode.Edit && ( + { + if (!selectedVar) { + onAddVar(newVar); + } else { + onEditVar(selectedVar, newVar); + } + + setSelectedVar(null); + setPanelMode(PanelMode.List); + }} + onCancel={() => { + setSelectedVar(null); + setPanelMode(PanelMode.List); + }} + /> + )} + + {panelMode === PanelMode.Delete && selectedVar && ( + { + onDeleteVar(v); + + setSelectedVar(null); + setPanelMode(PanelMode.List); + }} + onCancel={() => { + setSelectedVar(null); + setPanelMode(PanelMode.List); + }} + /> + )} +
+
+
+ ); +}; diff --git a/x-pack/plugins/canvas/public/components/var_config/var_panel.scss b/x-pack/plugins/canvas/public/components/var_config/var_panel.scss new file mode 100644 index 0000000000000..84f92aab28146 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/var_panel.scss @@ -0,0 +1,31 @@ +.canvasVarHeader__triggerWrapper { + display: flex; + align-items: center; +} + +.canvasVarHeader__button { + @include euiFontSize; + text-align: left; + + width: 100%; + flex-grow: 1; + + display: flex; + align-items: center; +} + +.canvasVarHeader__iconWrapper { + width: $euiSize; + height: $euiSize; + + border-radius: $euiBorderRadius; + + margin-right: $euiSizeS; + margin-left: $euiSizeXS; + + flex-shrink: 0; +} + +.canvasVarHeader__anchor { + display: inline-block; +} \ No newline at end of file diff --git a/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx b/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx new file mode 100644 index 0000000000000..c86be4efec043 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/var_config/var_value_field.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFieldText, EuiFieldNumber, EuiButtonGroup } from '@elastic/eui'; +import { htmlIdGenerator } from '@elastic/eui'; + +import { CanvasVariable } from '../../../types'; + +import { ComponentStrings } from '../../../i18n'; +const { VarConfigVarValueField: strings } = ComponentStrings; + +interface Props { + type: CanvasVariable['type']; + value: CanvasVariable['value']; + onChange: (v: CanvasVariable['value']) => void; +} + +export const VarValueField: FC = ({ type, value, onChange }) => { + const idPrefix = htmlIdGenerator()(); + + const options = [ + { + id: `${idPrefix}-true`, + label: strings.getTrueOption(), + }, + { + id: `${idPrefix}-false`, + label: strings.getFalseOption(), + }, + ]; + + if (type === 'number') { + return ( + onChange(e.target.value)} + /> + ); + } else if (type === 'boolean') { + return ( + { + const val = id.replace(`${idPrefix}-`, '') === 'true'; + onChange(val); + }} + buttonSize="compressed" + isFullWidth + /> + ); + } + + return ( + onChange(e.target.value)} + /> + ); +}; diff --git a/x-pack/plugins/canvas/public/components/workpad_config/index.ts b/x-pack/plugins/canvas/public/components/workpad_config/index.ts index c69a1fd9b8137..bba08d7647e9e 100644 --- a/x-pack/plugins/canvas/public/components/workpad_config/index.ts +++ b/x-pack/plugins/canvas/public/components/workpad_config/index.ts @@ -7,11 +7,17 @@ import { connect } from 'react-redux'; import { get } from 'lodash'; -import { sizeWorkpad as setSize, setName, setWorkpadCSS } from '../../state/actions/workpad'; +import { + sizeWorkpad as setSize, + setName, + setWorkpadCSS, + updateWorkpadVariables, +} from '../../state/actions/workpad'; + import { getWorkpad } from '../../state/selectors/workpad'; import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants'; import { WorkpadConfig as Component } from './workpad_config'; -import { State } from '../../../types'; +import { State, CanvasVariable } from '../../../types'; const mapStateToProps = (state: State) => { const workpad = getWorkpad(state); @@ -23,6 +29,7 @@ const mapStateToProps = (state: State) => { height: get(workpad, 'height'), }, css: get(workpad, 'css', DEFAULT_WORKPAD_CSS), + variables: get(workpad, 'variables', []), }; }; @@ -30,6 +37,7 @@ const mapDispatchToProps = { setSize, setName, setWorkpadCSS, + setWorkpadVariables: (vars: CanvasVariable[]) => updateWorkpadVariables(vars), }; export const WorkpadConfig = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx index 7b7a1e08b2c5d..a7424882f1072 100644 --- a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx @@ -19,10 +19,13 @@ import { EuiToolTip, EuiTextArea, EuiAccordion, - EuiText, EuiButton, } from '@elastic/eui'; + +import { VarConfig } from '../var_config'; + import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants'; +import { CanvasVariable } from '../../../types'; import { ComponentStrings } from '../../../i18n'; const { WorkpadConfig: strings } = ComponentStrings; @@ -34,14 +37,16 @@ interface Props { }; name: string; css?: string; + variables: CanvasVariable[]; setSize: ({ height, width }: { height: number; width: number }) => void; setName: (name: string) => void; setWorkpadCSS: (css: string) => void; + setWorkpadVariables: (vars: CanvasVariable[]) => void; } export const WorkpadConfig: FunctionComponent = (props) => { const [css, setCSS] = useState(props.css); - const { size, name, setSize, setName, setWorkpadCSS } = props; + const { size, name, setSize, setName, setWorkpadCSS, variables, setWorkpadVariables } = props; const rotate = () => setSize({ width: size.height, height: size.width }); const badges = [ @@ -129,23 +134,25 @@ export const WorkpadConfig: FunctionComponent = (props) => {
-
+ + + +
- - {strings.getGlobalCSSLabel()} - + {strings.getGlobalCSSLabel()} } > -
+
F if (filterList && filterList.length) { const filterExpression = filterList.join(' | '); const filterAST = fromExpression(filterExpression); - return interpretAst(filterAST); + return interpretAst(filterAST, getWorkpadVariablesAsObject(getState())); } else { const filterType = initialize.typesRegistry.get('filter'); return filterType?.from(null, {}); diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index 07c0ca4b1ce15..12e07ed3535f6 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -15,8 +15,12 @@ interface Options { /** * Meant to be a replacement for plugins/interpreter/interpretAST */ -export async function interpretAst(ast: ExpressionAstExpression): Promise { - return await expressionsService.getService().execute(ast).getData(); +export async function interpretAst( + ast: ExpressionAstExpression, + variables: Record +): Promise { + const context = { variables }; + return await expressionsService.getService().execute(ast, null, context).getData(); } /** @@ -24,6 +28,7 @@ export async function interpretAst(ast: ExpressionAstExpression): Promise, options: Options = {} ): Promise { + const context = { variables }; + try { - const renderable = await expressionsService.getService().execute(ast, input).getData(); + const renderable = await expressionsService.getService().execute(ast, input, context).getData(); if (getType(renderable) === 'render') { return renderable; } if (options.castToRender) { - return runInterpreter(fromExpression('render'), renderable, { + return runInterpreter(fromExpression('render'), renderable, variables, { castToRender: false, }); } diff --git a/x-pack/plugins/canvas/public/lib/workpad_service.js b/x-pack/plugins/canvas/public/lib/workpad_service.js index 1617759e83dd8..2047e20424acc 100644 --- a/x-pack/plugins/canvas/public/lib/workpad_service.js +++ b/x-pack/plugins/canvas/public/lib/workpad_service.js @@ -21,6 +21,7 @@ const validKeys = [ 'assets', 'colors', 'css', + 'variables', 'height', 'id', 'isWriteable', @@ -61,6 +62,7 @@ export function create(workpad) { return fetch.post(getApiPath(), { ...sanitizeWorkpad({ ...workpad }), assets: workpad.assets || {}, + variables: workpad.variables || [], }); } @@ -73,7 +75,7 @@ export async function createFromTemplate(templateId) { export function get(workpadId) { return fetch.get(`${getApiPath()}/${workpadId}`).then(({ data: workpad }) => { // shim old workpads with new properties - return { css: DEFAULT_WORKPAD_CSS, ...workpad }; + return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad }; }); } diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index e89e62917da39..2ba011373c670 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -9,7 +9,13 @@ import immutable from 'object-path-immutable'; import { get, pick, cloneDeep, without } from 'lodash'; import { toExpression, safeElementFromExpression } from '@kbn/interpreter/common'; import { createThunk } from '../../lib/create_thunk'; -import { getPages, getNodeById, getNodes, getSelectedPageIndex } from '../selectors/workpad'; +import { + getPages, + getWorkpadVariablesAsObject, + getNodeById, + getNodes, + getSelectedPageIndex, +} from '../selectors/workpad'; import { getValue as getResolvedArgsValue } from '../selectors/resolved_args'; import { getDefaultElement } from '../defaults'; import { ErrorStrings } from '../../../i18n'; @@ -96,13 +102,15 @@ export const fetchContext = createThunk( return i < index; }); + const variables = getWorkpadVariablesAsObject(getState()); + // get context data from a partial AST return interpretAst( { ...element.ast, chain: astChain, }, - prevContextValue + variables ).then((value) => { dispatch( args.setValue({ @@ -114,7 +122,7 @@ export const fetchContext = createThunk( } ); -const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { +const fetchRenderableWithContextFn = ({ dispatch, getState }, element, ast, context) => { const argumentPath = [element.id, 'expressionRenderable']; dispatch( args.setLoading({ @@ -128,7 +136,9 @@ const fetchRenderableWithContextFn = ({ dispatch }, element, ast, context) => { value: renderable, }); - return runInterpreter(ast, context, { castToRender: true }) + const variables = getWorkpadVariablesAsObject(getState()); + + return runInterpreter(ast, context, variables, { castToRender: true }) .then((renderable) => { dispatch(getAction(renderable)); }) @@ -172,7 +182,9 @@ export const fetchAllRenderables = createThunk( const ast = element.ast || safeElementFromExpression(element.expression); const argumentPath = [element.id, 'expressionRenderable']; - return runInterpreter(ast, null, { castToRender: true }) + const variables = getWorkpadVariablesAsObject(getState()); + + return runInterpreter(ast, null, variables, { castToRender: true }) .then((renderable) => ({ path: argumentPath, value: renderable })) .catch((err) => { services.notify.getService().error(err); diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.ts b/x-pack/plugins/canvas/public/state/actions/workpad.ts index 419832e404594..7af55730f5787 100644 --- a/x-pack/plugins/canvas/public/state/actions/workpad.ts +++ b/x-pack/plugins/canvas/public/state/actions/workpad.ts @@ -10,7 +10,7 @@ import { createThunk } from '../../lib/create_thunk'; import { getWorkpadColors } from '../selectors/workpad'; // @ts-expect-error import { fetchAllRenderables } from './elements'; -import { CanvasWorkpad } from '../../../types'; +import { CanvasWorkpad, CanvasVariable } from '../../../types'; export const sizeWorkpad = createAction<{ height: number; width: number }>('sizeWorkpad'); export const setName = createAction('setName'); @@ -18,6 +18,7 @@ export const setWriteable = createAction('setWriteable'); export const setColors = createAction('setColors'); export const setRefreshInterval = createAction('setRefreshInterval'); export const setWorkpadCSS = createAction('setWorkpadCSS'); +export const setWorkpadVariables = createAction('setWorkpadVariables'); export const enableAutoplay = createAction('enableAutoplay'); export const setAutoplayInterval = createAction('setAutoplayInterval'); export const resetWorkpad = createAction('resetWorkpad'); @@ -38,6 +39,14 @@ export const removeColor = createThunk('removeColor', ({ dispatch, getState }, c dispatch(setColors(without(getWorkpadColors(getState()), color))); }); +export const updateWorkpadVariables = createThunk( + 'updateWorkpadVariables', + ({ dispatch }, vars) => { + dispatch(setWorkpadVariables(vars)); + dispatch(fetchAllRenderables()); + } +); + export const setWorkpad = createThunk( 'setWorkpad', ( diff --git a/x-pack/plugins/canvas/public/state/defaults.js b/x-pack/plugins/canvas/public/state/defaults.js index 13ff7102bcafe..5cffb5e865d64 100644 --- a/x-pack/plugins/canvas/public/state/defaults.js +++ b/x-pack/plugins/canvas/public/state/defaults.js @@ -81,6 +81,7 @@ export const getDefaultWorkpad = () => { '#FFFFFF', 'rgba(255,255,255,0)', // 'transparent' ], + variables: [], isWriteable: true, }; }; diff --git a/x-pack/plugins/canvas/public/state/reducers/workpad.js b/x-pack/plugins/canvas/public/state/reducers/workpad.js index 30f9c638a054f..9a0c30bdf1337 100644 --- a/x-pack/plugins/canvas/public/state/reducers/workpad.js +++ b/x-pack/plugins/canvas/public/state/reducers/workpad.js @@ -14,6 +14,7 @@ import { setName, setWriteable, setWorkpadCSS, + setWorkpadVariables, resetWorkpad, } from '../actions/workpad'; @@ -59,6 +60,10 @@ export const workpadReducer = handleActions( return { ...workpadState, css: payload }; }, + [setWorkpadVariables]: (workpadState, { payload }) => { + return { ...workpadState, variables: payload }; + }, + [resetWorkpad]: () => ({ ...getDefaultWorkpad() }), }, {} diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/plugins/canvas/public/state/selectors/workpad.ts index 83f4984b4a300..1d7ea05daaa61 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.ts @@ -10,7 +10,14 @@ import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/comm // @ts-expect-error untyped local import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; -import { State, CanvasWorkpad, CanvasPage, CanvasElement, ResolvedArgType } from '../../../types'; +import { + State, + CanvasWorkpad, + CanvasPage, + CanvasElement, + CanvasVariable, + ResolvedArgType, +} from '../../../types'; import { ExpressionContext, CanvasGroup, @@ -49,6 +56,23 @@ export function getWorkpadPersisted(state: State) { return getWorkpad(state); } +export function getWorkpadVariables(state: State) { + const workpad = getWorkpad(state); + return get(workpad, 'variables', []); +} + +export function getWorkpadVariablesAsObject(state: State) { + const variables = getWorkpadVariables(state); + if (variables.length === 0) { + return {}; + } + + return (variables as CanvasVariable[]).reduce( + (vars: Record, v: CanvasVariable) => ({ ...vars, [v.name]: v.value }), + {} + ); +} + export function getWorkpadInfo(state: State): WorkpadInfo { return { ...getWorkpad(state), @@ -326,7 +350,9 @@ export function getElements( return elements.map((el) => omit(el, ['ast'])); } - return elements.map(appendAst); + const elementAppendAst = (elem: CanvasElement) => appendAst(elem); + + return elements.map(elementAppendAst); } const augment = (type: string) => (n: T): T => ({ diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts index 0c31f517a74b3..5bbd2caa0cb99 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts @@ -51,12 +51,19 @@ export const WorkpadAssetSchema = schema.object({ value: schema.string(), }); +export const WorkpadVariable = schema.object({ + name: schema.string(), + value: schema.oneOf([schema.string(), schema.number(), schema.boolean()]), + type: schema.string(), +}); + export const WorkpadSchema = schema.object({ '@created': schema.maybe(schema.string()), '@timestamp': schema.maybe(schema.string()), assets: schema.maybe(schema.recordOf(schema.string(), WorkpadAssetSchema)), colors: schema.arrayOf(schema.string()), css: schema.string(), + variables: schema.arrayOf(WorkpadVariable), height: schema.number(), id: schema.string(), isWriteable: schema.maybe(schema.boolean()), diff --git a/x-pack/plugins/canvas/server/templates/pitch_presentation.ts b/x-pack/plugins/canvas/server/templates/pitch_presentation.ts index 95f0dc4c3da39..416d3aee2dd03 100644 --- a/x-pack/plugins/canvas/server/templates/pitch_presentation.ts +++ b/x-pack/plugins/canvas/server/templates/pitch_presentation.ts @@ -1644,5 +1644,6 @@ export const pitch: CanvasTemplate = { }, css: ".canvasPage h1, .canvasPage h2, .canvasPage h3, .canvasPage h4, .canvasPage h5 {\nfont-family: 'Futura';\ncolor: #444444;\n}\n\n.canvasPage h1 {\nfont-size: 112px;\nfont-weight: bold;\ncolor: #FFFFFF;\n}\n\n.canvasPage h2 {\nfont-size: 48px;\nfont-weight: bold;\n}\n\n.canvasPage h3 {\nfont-size: 30px;\nfont-weight: 300;\ntext-transform: uppercase;\ncolor: #FFFFFF;\n}\n\n.canvasPage h5 {\nfont-size: 24px;\nfont-style: italic;\n}", + variables: [], }, }; diff --git a/x-pack/plugins/canvas/server/templates/status_report.ts b/x-pack/plugins/canvas/server/templates/status_report.ts index b396ed784cbed..447e1f99afaee 100644 --- a/x-pack/plugins/canvas/server/templates/status_report.ts +++ b/x-pack/plugins/canvas/server/templates/status_report.ts @@ -17,6 +17,7 @@ export const status: CanvasTemplate = { height: 792, css: '.canvasPage h1, .canvasPage h2, .canvasPage h3, .canvasPage h4, .canvasPage h5, .canvasPage h6, .canvasPage li, .canvasPage p, .canvasPage th, .canvasPage td {\nfont-family: "Gill Sans" !important;\ncolor: #333333;\n}\n\n.canvasPage h1, .canvasPage h2 {\nfont-weight: 400;\n}\n\n.canvasPage h2 {\ntext-transform: uppercase;\ncolor: #1785B0;\n}\n\n.canvasMarkdown p,\n.canvasMarkdown li {\nfont-size: 18px;\n}\n\n.canvasMarkdown li {\nmargin-bottom: .75em;\n}\n\n.canvasMarkdown h3:not(:first-child) {\nmargin-top: 2em;\n}\n\n.canvasMarkdown a {\ncolor: #1785B0;\n}\n\n.canvasMarkdown th,\n.canvasMarkdown td {\npadding: .5em 1em;\n}\n\n.canvasMarkdown th {\nbackground-color: #FAFBFD;\n}\n\n.canvasMarkdown table,\n.canvasMarkdown th,\n.canvasMarkdown td {\nborder: 1px solid #e4e9f2;\n}', + variables: [], page: 0, pages: [ { diff --git a/x-pack/plugins/canvas/server/templates/summary_report.ts b/x-pack/plugins/canvas/server/templates/summary_report.ts index 1b32a80fa82c7..64f04eef4194e 100644 --- a/x-pack/plugins/canvas/server/templates/summary_report.ts +++ b/x-pack/plugins/canvas/server/templates/summary_report.ts @@ -493,5 +493,6 @@ export const summary: CanvasTemplate = { '@created': '2019-05-31T16:01:45.751Z', assets: {}, css: 'h3 {\ncolor: #343741;\nfont-weight: 400;\n}\n\nh5 {\ncolor: #69707D;\n}', + variables: [], }, }; diff --git a/x-pack/plugins/canvas/server/templates/theme_dark.ts b/x-pack/plugins/canvas/server/templates/theme_dark.ts index 8dce2c5eb9b6e..5822a17976cd3 100644 --- a/x-pack/plugins/canvas/server/templates/theme_dark.ts +++ b/x-pack/plugins/canvas/server/templates/theme_dark.ts @@ -17,6 +17,7 @@ export const dark: CanvasTemplate = { height: 720, page: 0, css: '', + variables: [], pages: [ { id: 'page-fda26a1f-c096-44e4-a149-cb99e1038a34', diff --git a/x-pack/plugins/canvas/server/templates/theme_light.ts b/x-pack/plugins/canvas/server/templates/theme_light.ts index fb654a2fd2954..d278e057bb441 100644 --- a/x-pack/plugins/canvas/server/templates/theme_light.ts +++ b/x-pack/plugins/canvas/server/templates/theme_light.ts @@ -14,6 +14,7 @@ export const light: CanvasTemplate = { template: { name: 'Light', css: '', + variables: [], width: 1080, height: 720, page: 0, diff --git a/x-pack/plugins/canvas/types/canvas.ts b/x-pack/plugins/canvas/types/canvas.ts index 2f20dc88fdec4..cc07f498f1eec 100644 --- a/x-pack/plugins/canvas/types/canvas.ts +++ b/x-pack/plugins/canvas/types/canvas.ts @@ -37,12 +37,19 @@ export interface CanvasPage { groups: CanvasGroup[]; } +export interface CanvasVariable { + name: string; + value: boolean | number | string; + type: 'boolean' | 'number' | 'string'; +} + export interface CanvasWorkpad { '@created': string; '@timestamp': string; assets: { [id: string]: CanvasAsset }; colors: string[]; css: string; + variables: CanvasVariable[]; height: number; id: string; isWriteable: boolean;