mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Feat: Workpad Templates (#23966)
* Added workpad manager which contains workpad_loader and workpad_templates * Fixed term filter in workpad_templates * design changes * Removed console logs Closes workpad manager modal after cloning template Fixed filtering workpad templates Removed console log Added sample templates Added more templates to test with Removed cloneDeep * case insensitive template search * Case insensitive tag order in popover * added descriptions and tags to sample data workpads * refine list of initial templates * remove sample data templates, make buttons bigger * Added template and tag registries * Fixed workpad loader resizing issue on home page * Moved tags to ui folder * Fixed template class * Fixed properties in templates to match workpad
This commit is contained in:
parent
f06ec83458
commit
350ce1805d
29 changed files with 1244 additions and 86 deletions
|
@ -30,4 +30,6 @@ export const pluginPaths = {
|
|||
modelUIs: ['uis', 'models'],
|
||||
viewUIs: ['uis', 'views'],
|
||||
argumentUIs: ['uis', 'arguments'],
|
||||
templates: ['templates'],
|
||||
tagUIs: ['uis', 'tags'],
|
||||
};
|
||||
|
|
11
x-pack/plugins/canvas/canvas_plugin_src/templates/index.js
Normal file
11
x-pack/plugins/canvas/canvas_plugin_src/templates/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const themeDark = require('./theme_dark.json');
|
||||
const themeLight = require('./theme_light.json');
|
||||
|
||||
// Registry expects a function that returns a spec object
|
||||
export const templateSpecs = [themeDark, themeLight].map(template => () => template);
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 'babel-polyfill';
|
||||
import { templateSpecs } from './index';
|
||||
|
||||
templateSpecs.forEach(canvas.register);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10
x-pack/plugins/canvas/canvas_plugin_src/uis/tags/index.js
Normal file
10
x-pack/plugins/canvas/canvas_plugin_src/uis/tags/index.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { presentation } from './presentation';
|
||||
|
||||
// Registry expects a function that returns a spec object
|
||||
export const tagSpecs = [presentation];
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const presentation = () => ({ name: 'presentation', color: '#1EA593' });
|
10
x-pack/plugins/canvas/canvas_plugin_src/uis/tags/register.js
Normal file
10
x-pack/plugins/canvas/canvas_plugin_src/uis/tags/register.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 'babel-polyfill';
|
||||
import { tagSpecs } from './index';
|
||||
|
||||
tagSpecs.forEach(canvas.register);
|
|
@ -6,16 +6,20 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
|
||||
import { WorkpadLoader } from '../../components/workpad_loader';
|
||||
import { WorkpadManager } from '../../components/workpad_manager';
|
||||
import { setDocTitle } from '../../lib/doc_title';
|
||||
|
||||
export const HomeApp = () => {
|
||||
setDocTitle('Canvas');
|
||||
return (
|
||||
<EuiPage restrictWidth style={{ width: '100%' }}>
|
||||
<EuiPage className="canvasHomeApp" restrictWidth>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent panelPaddingSize="none" horizontalPosition="center">
|
||||
<WorkpadLoader onClose={() => {}} />
|
||||
<EuiPageContent
|
||||
className="canvasHomeApp__content"
|
||||
panelPaddingSize="none"
|
||||
horizontalPosition="center"
|
||||
>
|
||||
<WorkpadManager onClose={() => {}} />
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
.canvasHomeApp__modalHeader {
|
||||
padding-right: 24px;
|
||||
.canvasHomeApp {
|
||||
width: 100%;
|
||||
|
||||
.canvasHomeApp__content {
|
||||
width: 100%;
|
||||
}
|
||||
.canvasHomeApp__modalHeader {
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import { getAppReady, getBasePath } from '../../state/selectors/app';
|
|||
import { appReady, appError } from '../../state/actions/app';
|
||||
import { loadPrivateBrowserFunctions } from '../../lib/load_private_browser_functions';
|
||||
import { elementsRegistry } from '../../lib/elements_registry';
|
||||
import { templatesRegistry } from '../../lib/templates_registry';
|
||||
import { tagsRegistry } from '../../lib/tags_registry';
|
||||
import { renderFunctionsRegistry } from '../../lib/render_functions_registry';
|
||||
import {
|
||||
argTypeRegistry,
|
||||
|
@ -40,6 +42,8 @@ const types = {
|
|||
modelUIs: modelRegistry,
|
||||
viewUIs: viewRegistry,
|
||||
argumentUIs: argTypeRegistry,
|
||||
templates: templatesRegistry,
|
||||
tags: tagsRegistry,
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -41,5 +41,5 @@ export const Toolbar = compose(
|
|||
},
|
||||
}),
|
||||
withState('tray', 'setTray', props => props.tray),
|
||||
withState('showWorkpadLoader', 'setShowWorkpadLoader', false)
|
||||
withState('showWorkpadManager', 'setShowWorkpadManager', false)
|
||||
)(Component);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { Navbar } from '../navbar';
|
||||
import { WorkpadLoader } from '../workpad_loader';
|
||||
import { WorkpadManager } from '../workpad_manager';
|
||||
import { PageManager } from '../page_manager';
|
||||
import { Expression } from '../expression';
|
||||
import { Tray } from './tray';
|
||||
|
@ -32,8 +32,8 @@ export const Toolbar = props => {
|
|||
selectedPageNumber,
|
||||
workpadName,
|
||||
totalPages,
|
||||
showWorkpadLoader,
|
||||
setShowWorkpadLoader,
|
||||
showWorkpadManager,
|
||||
setShowWorkpadManager,
|
||||
} = props;
|
||||
|
||||
const elementIsSelected = Boolean(selectedElement);
|
||||
|
@ -45,15 +45,15 @@ export const Toolbar = props => {
|
|||
setTray(exp);
|
||||
};
|
||||
|
||||
const closeWorkpadLoader = () => setShowWorkpadLoader(false);
|
||||
const openWorkpadLoader = () => setShowWorkpadLoader(true);
|
||||
const closeWorkpadManager = () => setShowWorkpadManager(false);
|
||||
const openWorkpadManager = () => setShowWorkpadManager(true);
|
||||
|
||||
const workpadLoader = (
|
||||
const workpadManager = (
|
||||
<EuiOverlayMask>
|
||||
<EuiModal onClose={closeWorkpadLoader} className="canvasModal--fixedSize" maxWidth="1000px">
|
||||
<WorkpadLoader onClose={closeWorkpadLoader} />
|
||||
<EuiModal onClose={closeWorkpadManager} className="canvasModal--fixedSize" maxWidth="1000px">
|
||||
<WorkpadManager onClose={closeWorkpadManager} />
|
||||
<EuiModalFooter>
|
||||
<EuiButton size="s" onClick={closeWorkpadLoader}>
|
||||
<EuiButton size="s" onClick={closeWorkpadManager}>
|
||||
Dismiss
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
|
@ -72,7 +72,7 @@ export const Toolbar = props => {
|
|||
<Navbar>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" className="canvasToolbar__controls">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="text" iconType="grid" onClick={() => openWorkpadLoader()}>
|
||||
<EuiButtonEmpty color="text" iconType="grid" onClick={() => openWorkpadManager()}>
|
||||
{workpadName}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
@ -116,7 +116,7 @@ export const Toolbar = props => {
|
|||
</EuiFlexGroup>
|
||||
</Navbar>
|
||||
|
||||
{showWorkpadLoader && workpadLoader}
|
||||
{showWorkpadManager && workpadManager}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -130,6 +130,6 @@ Toolbar.propTypes = {
|
|||
selectedPageNumber: PropTypes.number.isRequired,
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
selectedElement: PropTypes.object,
|
||||
showWorkpadLoader: PropTypes.bool.isRequired,
|
||||
setShowWorkpadLoader: PropTypes.func.isRequired,
|
||||
showWorkpadManager: PropTypes.bool.isRequired,
|
||||
setShowWorkpadManager: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -9,14 +9,7 @@ import PropTypes from 'prop-types';
|
|||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
export const WorkpadCreate = ({ createPending, onCreate, ...rest }) => (
|
||||
<EuiButton
|
||||
{...rest}
|
||||
iconType="plusInCircle"
|
||||
size="s"
|
||||
fill
|
||||
onClick={onCreate}
|
||||
isLoading={createPending}
|
||||
>
|
||||
<EuiButton {...rest} iconType="plusInCircle" fill onClick={onCreate} isLoading={createPending}>
|
||||
Create workpad
|
||||
</EuiButton>
|
||||
);
|
||||
|
|
|
@ -10,3 +10,14 @@
|
|||
.canvasWorkpad__dropzoneTable .euiTable {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.canvasWorkpad__dropzoneTable--tags {
|
||||
|
||||
.euiTableCellContent {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.euiHealth {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -10,21 +10,15 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBasicTable,
|
||||
EuiBetaBadge,
|
||||
EuiButtonIcon,
|
||||
EuiLink,
|
||||
EuiPagination,
|
||||
EuiSpacer,
|
||||
EuiButton,
|
||||
EuiToolTip,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiModalBody,
|
||||
EuiEmptyPrompt,
|
||||
} from '@elastic/eui';
|
||||
import { sortByOrder } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { documentationLinks } from '../../lib/documentation_links';
|
||||
import { ConfirmModal } from '../confirm_modal';
|
||||
import { Link } from '../link';
|
||||
import { Paginate } from '../paginate';
|
||||
|
@ -282,7 +276,6 @@ export class WorkpadLoader extends React.PureComponent {
|
|||
|
||||
let deleteButton = (
|
||||
<EuiButton
|
||||
size="s"
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={this.openRemoveConfirm}
|
||||
|
@ -293,7 +286,7 @@ export class WorkpadLoader extends React.PureComponent {
|
|||
);
|
||||
|
||||
const downloadButton = (
|
||||
<EuiButton size="s" color="secondary" onClick={this.downloadWorkpads} iconType="sortDown">
|
||||
<EuiButton color="secondary" onClick={this.downloadWorkpads} iconType="sortDown">
|
||||
{`Download (${selectedWorkpads.length})`}
|
||||
</EuiButton>
|
||||
);
|
||||
|
@ -347,62 +340,43 @@ export class WorkpadLoader extends React.PureComponent {
|
|||
<Paginate rows={sortedWorkpads}>
|
||||
{pagination => (
|
||||
<Fragment>
|
||||
<EuiModalHeader className="canvasHomeApp__modalHeader">
|
||||
<div style={{ width: '100%' }}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiModalHeaderTitle>Canvas workpads</EuiModalHeaderTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
label="Beta"
|
||||
tooltipContent="Canvas is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo."
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{selectedWorkpads.length > 0 && (
|
||||
<Fragment>
|
||||
<EuiFlexItem grow={false}>{downloadButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{deleteButton}</EuiFlexItem>
|
||||
</Fragment>
|
||||
)}
|
||||
<EuiFlexItem grow={1}>
|
||||
<WorkpadSearch
|
||||
onChange={text => {
|
||||
pagination.setPage(0);
|
||||
this.props.findWorkpads(text);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={documentationLinks.canvas} target="_blank">
|
||||
Docs
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{selectedWorkpads.length > 0 && (
|
||||
<Fragment>
|
||||
<EuiFlexItem grow={false}>{downloadButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{deleteButton}</EuiFlexItem>
|
||||
</Fragment>
|
||||
)}
|
||||
<EuiFlexItem grow={1}>
|
||||
<WorkpadSearch
|
||||
onChange={text => {
|
||||
pagination.setPage(0);
|
||||
this.props.findWorkpads(text);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexItem grow={false}>{uploadButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{createButton}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd" wrap>
|
||||
<EuiFlexItem grow={false}>{uploadButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{createButton}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
{createPending && <div>Creating Workpad...</div>}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{!createPending && isLoading && <div>Fetching Workpads...</div>}
|
||||
<EuiSpacer />
|
||||
|
||||
{!createPending && !isLoading && this.renderWorkpadTable(pagination)}
|
||||
{createPending && <div style={{ width: '100%' }}>Creating Workpad...</div>}
|
||||
|
||||
{confirmModal}
|
||||
</EuiModalBody>
|
||||
{!createPending &&
|
||||
isLoading && <div style={{ width: '100%' }}>Fetching Workpads...</div>}
|
||||
|
||||
{!createPending && !isLoading && this.renderWorkpadTable(pagination)}
|
||||
|
||||
{confirmModal}
|
||||
</Fragment>
|
||||
)}
|
||||
</Paginate>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
.canvasWorkpad__upload--compressed {
|
||||
|
||||
&.euiFilePicker--compressed.euiFilePicker {
|
||||
|
||||
.euiFilePicker__prompt {
|
||||
height: $euiSizeXXL;
|
||||
padding: $euiSizeM;
|
||||
padding-left: $euiSizeXXL;
|
||||
}
|
||||
.euiFilePicker__icon {
|
||||
top: $euiSizeM;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ export class WorkpadSearch extends React.PureComponent {
|
|||
render() {
|
||||
return (
|
||||
<EuiFieldSearch
|
||||
compressed
|
||||
placeholder="Find workpad"
|
||||
value={this.state.searchText}
|
||||
onChange={this.setSearchText}
|
||||
|
|
|
@ -13,6 +13,7 @@ export const WorkpadUpload = ({ onUpload, ...rest }) => (
|
|||
<EuiFilePicker
|
||||
{...rest}
|
||||
compressed
|
||||
className="canvasWorkpad__upload--compressed"
|
||||
initialPromptText="Import workpad JSON file"
|
||||
onChange={([file]) => uploadWorkpad(file, onUpload)}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { WorkpadManager } from './workpad_manager';
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiTabbedContent,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiModalBody,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBetaBadge,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { WorkpadLoader } from '../workpad_loader';
|
||||
import { WorkpadTemplates } from '../workpad_templates';
|
||||
import { documentationLinks } from '../../lib/documentation_links';
|
||||
|
||||
export const WorkpadManager = ({ onClose }) => {
|
||||
const tabs = [
|
||||
{
|
||||
id: 'workpadLoader',
|
||||
name: 'My Workpads',
|
||||
content: (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<WorkpadLoader onClose={onClose} />
|
||||
</Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'workpadTemplates',
|
||||
name: 'Templates',
|
||||
content: (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<WorkpadTemplates onClose={onClose} />
|
||||
</Fragment>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiModalHeader className="canvasHomeApp__modalHeader">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiModalHeaderTitle>Canvas workpads</EuiModalHeaderTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
label="Beta"
|
||||
tooltipContent="Canvas is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={documentationLinks.canvas} target="_blank">
|
||||
Docs
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
|
||||
</EuiModalBody>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
WorkpadManager.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { compose, getContext, withHandlers, withProps } from 'recompose';
|
||||
import * as workpadService from '../../lib/workpad_service';
|
||||
import { notify } from '../../lib/notify';
|
||||
import { getId } from '../../lib/get_id';
|
||||
import { templatesRegistry } from '../../lib/templates_registry';
|
||||
import { tagsRegistry } from '../../lib/tags_registry';
|
||||
import { WorkpadTemplates as Component } from './workpad_templates';
|
||||
|
||||
export const WorkpadTemplates = compose(
|
||||
getContext({
|
||||
router: PropTypes.object,
|
||||
}),
|
||||
withProps(() => ({
|
||||
templates: templatesRegistry.toJS(),
|
||||
uniqueTags: tagsRegistry.toJS(),
|
||||
})),
|
||||
withHandlers({
|
||||
// Clone workpad given an id
|
||||
cloneWorkpad: props => workpad => {
|
||||
workpad.id = getId('workpad');
|
||||
return workpadService
|
||||
.create(workpad)
|
||||
.then(() => props.router.navigateTo('loadWorkpad', { id: workpad.id, page: 1 }))
|
||||
.catch(err => notify.error(err, { title: `Couldn't clone workpad template` }));
|
||||
},
|
||||
})
|
||||
)(Component);
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBasicTable,
|
||||
EuiPagination,
|
||||
EuiSpacer,
|
||||
EuiHealth,
|
||||
EuiButtonEmpty,
|
||||
EuiSearchBar,
|
||||
} from '@elastic/eui';
|
||||
import { get, sortByOrder } from 'lodash';
|
||||
import { getId } from '../../lib/get_id';
|
||||
import { Paginate } from '../paginate';
|
||||
|
||||
export class WorkpadTemplates extends React.PureComponent {
|
||||
static propTypes = {
|
||||
cloneWorkpad: PropTypes.func.isRequired,
|
||||
templates: PropTypes.object,
|
||||
uniqueTags: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
sortField: 'name',
|
||||
sortDirection: 'asc',
|
||||
pageSize: 10,
|
||||
searchTerm: '',
|
||||
filterTags: [],
|
||||
};
|
||||
|
||||
onTableChange = ({ sort = {} }) => {
|
||||
const { field: sortField, direction: sortDirection } = sort;
|
||||
this.setState({
|
||||
sortField,
|
||||
sortDirection,
|
||||
});
|
||||
};
|
||||
|
||||
onSearch = ({ query }) => {
|
||||
const clauses = get(query, 'ast._clauses', []);
|
||||
|
||||
const filterTags = [];
|
||||
const searchTerms = [];
|
||||
|
||||
clauses.forEach(clause => {
|
||||
const { type, field, value } = clause;
|
||||
// extract terms from the query AST
|
||||
if (type === 'term') searchTerms.push(value);
|
||||
// extracts tags from the query AST
|
||||
else if (field === 'tags') filterTags.push(value);
|
||||
});
|
||||
|
||||
this.setState({ searchTerm: searchTerms.join(' '), filterTags });
|
||||
};
|
||||
|
||||
cloneTemplate = template => this.props.cloneWorkpad(template).then(() => this.props.onClose());
|
||||
|
||||
renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }) => {
|
||||
const { uniqueTags } = this.props;
|
||||
const { sortField, sortDirection } = this.state;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
name: 'Template Name',
|
||||
sortable: true,
|
||||
width: '30%',
|
||||
dataType: 'string',
|
||||
render: (name, template) => {
|
||||
const templateName = template.name.length ? template.name : <em>{template.id}</em>;
|
||||
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
onClick={() => this.cloneTemplate(template)}
|
||||
aria-label={`Clone workpad template "${templateName}"`}
|
||||
type="link"
|
||||
>
|
||||
{templateName}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'help',
|
||||
name: 'Description',
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
},
|
||||
{
|
||||
field: 'tags',
|
||||
name: 'Tags',
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
render: tags => {
|
||||
if (!tags) return 'No tags';
|
||||
return tags.map(tag => (
|
||||
<EuiHealth key={getId('tag')} color={get(uniqueTags, `${tag}.color`, '#666666')}>
|
||||
{tag}
|
||||
</EuiHealth>
|
||||
));
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiBasicTable
|
||||
compressed
|
||||
items={rows}
|
||||
itemId="id"
|
||||
columns={columns}
|
||||
sorting={sorting}
|
||||
onChange={this.onTableChange}
|
||||
className="canvasWorkpad__dropzoneTable canvasWorkpad__dropzoneTable--tags"
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination activePage={pageNumber} onPageClick={setPage} pageCount={totalPages} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderSearch = () => {
|
||||
let { uniqueTags } = this.props;
|
||||
const { searchTerm } = this.state;
|
||||
|
||||
uniqueTags = Object.values(uniqueTags);
|
||||
|
||||
const filters = [
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'tags',
|
||||
name: 'Tags',
|
||||
multiSelect: true,
|
||||
options: uniqueTags.map(({ name, color }) => ({
|
||||
value: name,
|
||||
name: name,
|
||||
view: (
|
||||
<EuiHealth key={getId('tag')} color={color}>
|
||||
{name}
|
||||
</EuiHealth>
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiSearchBar
|
||||
defaultQuery={searchTerm}
|
||||
box={{
|
||||
placeholder: 'Find template',
|
||||
incremental: true,
|
||||
}}
|
||||
filters={filters}
|
||||
onChange={this.onSearch}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { templates } = this.props;
|
||||
const { sortField, sortDirection, searchTerm, filterTags } = this.state;
|
||||
const sortedTemplates = sortByOrder(templates, [sortField, 'name'], [sortDirection, 'asc']);
|
||||
|
||||
const filteredTemplates = sortedTemplates.filter(({ name = '', help = '', tags = [] }) => {
|
||||
const tagMatch = filterTags.length
|
||||
? filterTags.every(filterTag => tags.indexOf(filterTag) > -1)
|
||||
: true;
|
||||
|
||||
const lowercaseSearch = searchTerm.toLowerCase();
|
||||
const textMatch = lowercaseSearch
|
||||
? name.toLowerCase().indexOf(lowercaseSearch) > -1 ||
|
||||
help.toLowerCase().indexOf(lowercaseSearch) > -1
|
||||
: true;
|
||||
|
||||
return tagMatch && textMatch;
|
||||
});
|
||||
|
||||
return (
|
||||
<Paginate rows={filteredTemplates}>
|
||||
{pagination => (
|
||||
<Fragment>
|
||||
{this.renderSearch()}
|
||||
<EuiSpacer />
|
||||
{this.renderWorkpadTable(pagination)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Paginate>
|
||||
);
|
||||
}
|
||||
}
|
13
x-pack/plugins/canvas/public/lib/tag.js
Normal file
13
x-pack/plugins/canvas/public/lib/tag.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export function Tag(config) {
|
||||
// The name of the tag
|
||||
this.name = config.name;
|
||||
|
||||
// color of the tag to display in a list
|
||||
this.color = config.color;
|
||||
}
|
16
x-pack/plugins/canvas/public/lib/tags_registry.js
Normal file
16
x-pack/plugins/canvas/public/lib/tags_registry.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { Registry } from '@kbn/interpreter/common';
|
||||
import { Tag } from './tag';
|
||||
|
||||
class TagRegistry extends Registry {
|
||||
wrapper(obj) {
|
||||
return new Tag(obj);
|
||||
}
|
||||
}
|
||||
|
||||
export const tagsRegistry = new TagRegistry();
|
49
x-pack/plugins/canvas/public/lib/template.js
Normal file
49
x-pack/plugins/canvas/public/lib/template.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { tagsRegistry } from '../lib/tags_registry';
|
||||
import { getDefaultWorkpad } from '../state/defaults';
|
||||
import { getId } from './get_id';
|
||||
|
||||
const defaultWorkpad = getDefaultWorkpad();
|
||||
|
||||
export function Template(config) {
|
||||
// The name of the template
|
||||
this.name = config.name;
|
||||
|
||||
// Use this to set a more friendly name
|
||||
this.displayName = config.displayName || this.name;
|
||||
|
||||
this.id = config.id || getId('workpad');
|
||||
|
||||
// A sentence or few about what this template contains
|
||||
this.help = config.help || '';
|
||||
|
||||
// Tags for categorizing the template
|
||||
this.tags = config.tags || [];
|
||||
|
||||
this.tags.forEach(tag => {
|
||||
if (!tagsRegistry.get(tag)) tagsRegistry.register(() => ({ name: tag, color: '#666666' }));
|
||||
});
|
||||
|
||||
this.width = config.width || defaultWorkpad.width;
|
||||
|
||||
this.height = config.height || defaultWorkpad.height;
|
||||
|
||||
this.page = config.page || defaultWorkpad.page;
|
||||
|
||||
this.pages = config.pages || defaultWorkpad.pages;
|
||||
|
||||
this.colors = config.colors || defaultWorkpad.colors;
|
||||
|
||||
this['@timestamp'] = config['@timestamp'] || defaultWorkpad['@timestamp'];
|
||||
|
||||
this['@created'] = config['@created'] || defaultWorkpad['@created'];
|
||||
|
||||
this.assets = config.assets || {};
|
||||
|
||||
this.css = config.css || defaultWorkpad.css;
|
||||
}
|
16
x-pack/plugins/canvas/public/lib/templates_registry.js
Normal file
16
x-pack/plugins/canvas/public/lib/templates_registry.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { Registry } from '@kbn/interpreter/common';
|
||||
import { Template } from './template';
|
||||
|
||||
class TemplateRegistry extends Registry {
|
||||
wrapper(obj) {
|
||||
return new Template(obj);
|
||||
}
|
||||
}
|
||||
|
||||
export const templatesRegistry = new TemplateRegistry();
|
|
@ -52,5 +52,6 @@
|
|||
@import '../components/toolbar/toolbar';
|
||||
@import '../components/toolbar/tray/tray';
|
||||
@import '../components/workpad/workpad';
|
||||
@import '../components/workpad_loader/workpad_loader';
|
||||
@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
|
||||
@import '../components/workpad_page/workpad_page';
|
||||
|
|
|
@ -26,6 +26,8 @@ export function getWebpackConfig({ devtool, watch } = {}) {
|
|||
'uis/arguments/all': path.join(sourceDir, 'uis/arguments/register.js'),
|
||||
'functions/browser/all': path.join(sourceDir, 'functions/browser/register.js'),
|
||||
'functions/common/all': path.join(sourceDir, 'functions/common/register.js'),
|
||||
'templates/all': path.join(sourceDir, 'templates/register.js'),
|
||||
'tags/all': path.join(sourceDir, 'uis/tags/register.js'),
|
||||
},
|
||||
|
||||
// there were problems with the node and web targets since this code is actually
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue