mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[canvas] New Home Page (#102446)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f422cbdcf1
commit
2b0f1256dd
73 changed files with 2157 additions and 2289 deletions
|
@ -1166,12 +1166,6 @@ export const ComponentStrings = {
|
|||
description: 'This is referring to the dimensions of U.S. standard letter paper.',
|
||||
}),
|
||||
},
|
||||
WorkpadCreate: {
|
||||
getWorkpadCreateButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadCreate.createButtonLabel', {
|
||||
defaultMessage: 'Create workpad',
|
||||
}),
|
||||
},
|
||||
WorkpadHeader: {
|
||||
getAddElementButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeader.addElementButtonLabel', {
|
||||
|
@ -1546,219 +1540,4 @@ export const ComponentStrings = {
|
|||
defaultMessage: 'Reset',
|
||||
}),
|
||||
},
|
||||
WorkpadLoader: {
|
||||
getClonedWorkpadName: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.clonedWorkpadName', {
|
||||
defaultMessage: 'Copy of {workpadName}',
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
description:
|
||||
'This suffix is added to the end of the name of a cloned workpad to indicate that this ' +
|
||||
'new workpad is a copy of the original workpad. Example: "Copy of Sales Pitch"',
|
||||
}),
|
||||
getCloneToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.cloneTooltip', {
|
||||
defaultMessage: 'Clone workpad',
|
||||
}),
|
||||
getCreateWorkpadLoadingDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.createWorkpadLoadingDescription', {
|
||||
defaultMessage: 'Creating workpad...',
|
||||
description:
|
||||
'This message appears while the user is waiting for a new workpad to be created',
|
||||
}),
|
||||
getDeleteButtonAriaLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteButtonAriaLabel', {
|
||||
defaultMessage: 'Delete {numberOfWorkpads} workpads',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteButtonLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete ({numberOfWorkpads})',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteModalConfirmButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
}),
|
||||
getDeleteModalDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteModalDescription', {
|
||||
defaultMessage: `You can't recover deleted workpads.`,
|
||||
}),
|
||||
getDeleteMultipleWorkpadModalTitle: (numberOfWorkpads: string) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle', {
|
||||
defaultMessage: 'Delete {numberOfWorkpads} workpads?',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteSingleWorkpadModalTitle: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle', {
|
||||
defaultMessage: `Delete workpad '{workpadName}'?`,
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
}),
|
||||
getEmptyPromptGettingStartedDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription', {
|
||||
defaultMessage:
|
||||
'Create a new workpad, start from a template, or import a workpad {JSON} file by dropping it here.',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
getEmptyPromptNewUserDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.emptyPromptNewUserDescription', {
|
||||
defaultMessage: 'New to {CANVAS}?',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
getEmptyPromptTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.emptyPromptTitle', {
|
||||
defaultMessage: 'Add your first workpad',
|
||||
}),
|
||||
getExportButtonAriaLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.exportButtonAriaLabel', {
|
||||
defaultMessage: 'Export {numberOfWorkpads} workpads',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getExportButtonLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.exportButtonLabel', {
|
||||
defaultMessage: 'Export ({numberOfWorkpads})',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getExportToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.exportTooltip', {
|
||||
defaultMessage: 'Export workpad',
|
||||
}),
|
||||
getFetchLoadingDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.fetchLoadingDescription', {
|
||||
defaultMessage: 'Fetching workpads...',
|
||||
description:
|
||||
'This message appears while the user is waiting for their list of workpads to load',
|
||||
}),
|
||||
getFilePickerPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.filePickerPlaceholder', {
|
||||
defaultMessage: 'Import workpad {JSON} file',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
getLoadWorkpadArialLabel: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.loadWorkpadArialLabel', {
|
||||
defaultMessage: `Load workpad '{workpadName}'`,
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
}),
|
||||
getNoPermissionToCloneToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.noPermissionToCloneToolTip', {
|
||||
defaultMessage: `You don't have permission to clone workpads`,
|
||||
}),
|
||||
getNoPermissionToCreateToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.noPermissionToCreateToolTip', {
|
||||
defaultMessage: `You don't have permission to create workpads`,
|
||||
}),
|
||||
getNoPermissionToDeleteToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.noPermissionToDeleteToolTip', {
|
||||
defaultMessage: `You don't have permission to delete workpads`,
|
||||
}),
|
||||
getNoPermissionToUploadToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.noPermissionToUploadToolTip', {
|
||||
defaultMessage: `You don't have permission to upload workpads`,
|
||||
}),
|
||||
getSampleDataLinkLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.sampleDataLinkLabel', {
|
||||
defaultMessage: 'Add your first workpad',
|
||||
}),
|
||||
getTableCreatedColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.table.createdColumnTitle', {
|
||||
defaultMessage: 'Created',
|
||||
description: 'This column in the table contains the date/time the workpad was created.',
|
||||
}),
|
||||
getTableNameColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.table.nameColumnTitle', {
|
||||
defaultMessage: 'Workpad name',
|
||||
}),
|
||||
getTableUpdatedColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.table.updatedColumnTitle', {
|
||||
defaultMessage: 'Updated',
|
||||
description:
|
||||
'This column in the table contains the date/time the workpad was last updated.',
|
||||
}),
|
||||
getTableActionsColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadLoader.table.actionsColumnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
description:
|
||||
'This column in the table contains the actions that can be taken on a workpad.',
|
||||
}),
|
||||
},
|
||||
WorkpadManager: {
|
||||
getModalTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadManager.modalTitle', {
|
||||
defaultMessage: '{CANVAS} workpads',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
getMyWorkpadsTabLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadManager.myWorkpadsTabLabel', {
|
||||
defaultMessage: 'My workpads',
|
||||
}),
|
||||
getWorkpadTemplatesTabLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadManager.workpadTemplatesTabLabel', {
|
||||
defaultMessage: 'Templates',
|
||||
description: 'The label for the tab that displays a list of designed workpad templates.',
|
||||
}),
|
||||
},
|
||||
WorkpadSearch: {
|
||||
getWorkpadSearchPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadSearch.searchPlaceholder', {
|
||||
defaultMessage: 'Find workpad',
|
||||
}),
|
||||
},
|
||||
WorkpadTemplates: {
|
||||
getCloneTemplateLinkAriaLabel: (templateName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel', {
|
||||
defaultMessage: `Clone workpad template '{templateName}'`,
|
||||
values: {
|
||||
templateName,
|
||||
},
|
||||
}),
|
||||
getTableDescriptionColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.descriptionColumnTitle', {
|
||||
defaultMessage: 'Description',
|
||||
}),
|
||||
getTableNameColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.nameColumnTitle', {
|
||||
defaultMessage: 'Template name',
|
||||
}),
|
||||
getTableTagsColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.tagsColumnTitle', {
|
||||
defaultMessage: 'Tags',
|
||||
description:
|
||||
'This column contains relevant tags that indicate what type of template ' +
|
||||
'is displayed. For example: "report", "presentation", etc.',
|
||||
}),
|
||||
getTemplateSearchPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplate.searchPlaceholder', {
|
||||
defaultMessage: 'Find template',
|
||||
}),
|
||||
getCreatingTemplateLabel: (templateName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTemplate.creatingTemplateLabel', {
|
||||
defaultMessage: `Creating from template '{templateName}'`,
|
||||
values: {
|
||||
templateName,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CANVAS, JSON } from './constants';
|
||||
|
||||
export const ErrorStrings = {
|
||||
actionsElements: {
|
||||
|
@ -93,54 +92,10 @@ export const ErrorStrings = {
|
|||
},
|
||||
}),
|
||||
},
|
||||
WorkpadFileUpload: {
|
||||
getAcceptJSONOnlyErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage', {
|
||||
defaultMessage: 'Only {JSON} files are accepted',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
getFileUploadFailureWithFileNameErrorMessage: (fileName: string) =>
|
||||
i18n.translate('xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage', {
|
||||
defaultMessage: `Couldn't upload '{fileName}'`,
|
||||
values: {
|
||||
fileName,
|
||||
},
|
||||
}),
|
||||
getFileUploadFailureWithoutFileNameErrorMessage: () =>
|
||||
i18n.translate(
|
||||
'xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage',
|
||||
{
|
||||
defaultMessage: `Couldn't upload file`,
|
||||
}
|
||||
),
|
||||
getMissingPropertiesErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage', {
|
||||
defaultMessage:
|
||||
'Some properties required for a {CANVAS} workpad are missing. Edit your {JSON} file to provide the correct property values, and try again.',
|
||||
values: {
|
||||
CANVAS,
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
},
|
||||
WorkpadLoader: {
|
||||
getCloneFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadLoader.cloneFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't clone workpad`,
|
||||
}),
|
||||
getDeleteFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadLoader.deleteFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't delete all workpads`,
|
||||
}),
|
||||
getFindFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadLoader.findFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't find workpad`,
|
||||
}),
|
||||
getUploadFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadLoader.uploadFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't upload workpad`,
|
||||
WorkpadDropzone: {
|
||||
getTooManyFilesErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.workpadDropzone.tooManyFilesErrorMessage', {
|
||||
defaultMessage: 'One one file can be uploaded at a time',
|
||||
}),
|
||||
},
|
||||
workpadRoutes: {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaPageTemplate } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { withSuspense } from '../../../../../../src/plugins/presentation_util/public';
|
||||
|
||||
import { WorkpadCreate } from './workpad_create';
|
||||
import { LazyWorkpadTemplates } from './workpad_templates';
|
||||
import { LazyMyWorkpads } from './my_workpads';
|
||||
|
||||
export type HomePageTab = 'workpads' | 'templates';
|
||||
|
||||
export interface Props {
|
||||
activeTab?: HomePageTab;
|
||||
}
|
||||
|
||||
const WorkpadTemplates = withSuspense(LazyWorkpadTemplates);
|
||||
const MyWorkpads = withSuspense(LazyMyWorkpads);
|
||||
|
||||
export const Home = ({ activeTab = 'workpads' }: Props) => {
|
||||
const [tab, setTab] = useState(activeTab);
|
||||
|
||||
return (
|
||||
<KibanaPageTemplate
|
||||
pageHeader={{
|
||||
pageTitle: 'Canvas',
|
||||
rightSideItems: [<WorkpadCreate />],
|
||||
bottomBorder: true,
|
||||
tabs: [
|
||||
{
|
||||
label: strings.getMyWorkpadsTabLabel(),
|
||||
id: 'myWorkpads',
|
||||
isSelected: tab === 'workpads',
|
||||
onClick: () => setTab('workpads'),
|
||||
},
|
||||
{
|
||||
label: strings.getWorkpadTemplatesTabLabel(),
|
||||
id: 'workpadTemplates',
|
||||
'data-test-subj': 'workpadTemplates',
|
||||
isSelected: tab === 'templates',
|
||||
onClick: () => setTab('templates'),
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
{tab === 'workpads' ? <MyWorkpads /> : <WorkpadTemplates />}
|
||||
</KibanaPageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getMyWorkpadsTabLabel: () =>
|
||||
i18n.translate('xpack.canvas.home.myWorkpadsTabLabel', {
|
||||
defaultMessage: 'My workpads',
|
||||
}),
|
||||
getWorkpadTemplatesTabLabel: () =>
|
||||
i18n.translate('xpack.canvas.home.workpadTemplatesTabLabel', {
|
||||
defaultMessage: 'Templates',
|
||||
description: 'The label for the tab that displays a list of designed workpad templates.',
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import {
|
||||
reduxDecorator,
|
||||
getAddonPanelParameters,
|
||||
servicesContextDecorator,
|
||||
getDisableStoryshotsParameter,
|
||||
} from '../../../storybook';
|
||||
|
||||
import { Home } from './home.component';
|
||||
|
||||
export default {
|
||||
title: 'Home/Home Page',
|
||||
argTypes: {},
|
||||
decorators: [reduxDecorator()],
|
||||
parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
|
||||
};
|
||||
|
||||
export const NoContent = () => <Home />;
|
||||
export const HasContent = () => <Home />;
|
||||
|
||||
NoContent.decorators = [servicesContextDecorator()];
|
||||
HasContent.decorators = [servicesContextDecorator({ findWorkpads: 5, findTemplates: true })];
|
33
x-pack/plugins/canvas/public/components/home/home.tsx
Normal file
33
x-pack/plugins/canvas/public/components/home/home.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 React, { useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { getBaseBreadcrumb } from '../../lib/breadcrumbs';
|
||||
import { resetWorkpad } from '../../state/actions/workpad';
|
||||
import { Home as Component } from './home.component';
|
||||
import { usePlatformService } from '../../services';
|
||||
|
||||
export const Home = () => {
|
||||
const { setBreadcrumbs } = usePlatformService();
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounted) {
|
||||
dispatch(resetWorkpad());
|
||||
setIsMounted(true);
|
||||
}
|
||||
}, [dispatch, isMounted, setIsMounted]);
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([getBaseBreadcrumb()]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
return <Component />;
|
||||
};
|
15
x-pack/plugins/canvas/public/components/home/hooks/index.ts
Normal file
15
x-pack/plugins/canvas/public/components/home/hooks/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { useCloneWorkpad } from './use_clone_workpad';
|
||||
export { useCreateWorkpad } from './use_create_workpad';
|
||||
export { useDeleteWorkpads } from './use_delete_workpad';
|
||||
export { useDownloadWorkpad } from './use_download_workpad';
|
||||
export { useFindTemplates, useFindTemplatesOnMount } from './use_find_templates';
|
||||
export { useFindWorkpads, useFindWorkpadsOnMount } from './use_find_workpad';
|
||||
export { useImportWorkpad } from './use_upload_workpad';
|
||||
export { useCreateFromTemplate } from './use_create_from_template';
|
|
@ -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 { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
import { getId } from '../../../lib/get_id';
|
||||
|
||||
export const useCloneWorkpad = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
const history = useHistory();
|
||||
|
||||
return useCallback(
|
||||
async (workpadId: string) => {
|
||||
try {
|
||||
let workpad = await workpadService.get(workpadId);
|
||||
|
||||
workpad = {
|
||||
...workpad,
|
||||
name: strings.getClonedWorkpadName(workpad.name),
|
||||
id: getId('workpad'),
|
||||
};
|
||||
|
||||
await workpadService.create(workpad);
|
||||
|
||||
history.push(`/workpad/${workpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, { title: errors.getCloneFailureErrorMessage() });
|
||||
}
|
||||
},
|
||||
[notifyService, workpadService, history]
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getClonedWorkpadName: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.useCloneWorkpad.clonedWorkpadName', {
|
||||
defaultMessage: 'Copy of {workpadName}',
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
description:
|
||||
'This suffix is added to the end of the name of a cloned workpad to indicate that this ' +
|
||||
'new workpad is a copy of the original workpad. Example: "Copy of Sales Pitch"',
|
||||
}),
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getCloneFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't clone workpad`,
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { CanvasTemplate } from '../../../../types';
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
|
||||
export const useCreateFromTemplate = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
const history = useHistory();
|
||||
|
||||
return useCallback(
|
||||
async (template: CanvasTemplate) => {
|
||||
try {
|
||||
const result = await workpadService.createFromTemplate(template.id);
|
||||
history.push(`/workpad/${result.id}/page/1`);
|
||||
} catch (e) {
|
||||
notifyService.error(e, {
|
||||
title: `Couldn't create workpad from template`,
|
||||
});
|
||||
}
|
||||
},
|
||||
[workpadService, notifyService, history]
|
||||
);
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
// @ts-expect-error
|
||||
import { getDefaultWorkpad } from '../../../state/defaults';
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
|
||||
import type { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
export const useCreateWorkpad = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
const history = useHistory();
|
||||
|
||||
return useCallback(
|
||||
async (_workpad?: CanvasWorkpad | null) => {
|
||||
const workpad = _workpad || (getDefaultWorkpad() as CanvasWorkpad);
|
||||
|
||||
try {
|
||||
await workpadService.create(workpad);
|
||||
history.push(`/workpad/${workpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, {
|
||||
title: errors.getUploadFailureErrorMessage(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
},
|
||||
[notifyService, history, workpadService]
|
||||
);
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getUploadFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't upload workpad`,
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
|
||||
export const useDeleteWorkpads = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
|
||||
return useCallback(
|
||||
async (workpadIds: string[]) => {
|
||||
const removedWorkpads = workpadIds.map(async (id) => {
|
||||
try {
|
||||
await workpadService.remove(id);
|
||||
return { id, err: null };
|
||||
} catch (err) {
|
||||
return { id, err };
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(removedWorkpads).then((results) => {
|
||||
const [passes, errored] = results.reduce<[string[], string[]]>(
|
||||
([passesArr, errorsArr], result) => {
|
||||
if (result.err) {
|
||||
errorsArr.push(result.id);
|
||||
} else {
|
||||
passesArr.push(result.id);
|
||||
}
|
||||
|
||||
return [passesArr, errorsArr];
|
||||
},
|
||||
[[], []]
|
||||
);
|
||||
|
||||
const removedIds = workpadIds.filter((id) => passes.includes(id));
|
||||
|
||||
if (errored.length > 0) {
|
||||
notifyService.error(errors.getDeleteFailureErrorMessage());
|
||||
}
|
||||
|
||||
return {
|
||||
removedIds,
|
||||
errored,
|
||||
};
|
||||
});
|
||||
},
|
||||
[workpadService, notifyService]
|
||||
);
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getDeleteFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't delete all workpads`,
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { downloadWorkpad as downloadWorkpadFn } from '../../../lib/download_workpad';
|
||||
|
||||
export const useDownloadWorkpad = () =>
|
||||
useCallback((workpadId: string) => downloadWorkpadFn(workpadId), []);
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { useState, useCallback } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
|
||||
import { useWorkpadService } from '../../../services';
|
||||
import { TemplateFindResponse } from '../../../services/workpad';
|
||||
|
||||
const emptyResponse = { templates: [] };
|
||||
|
||||
export const useFindTemplates = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
return useCallback(async () => await workpadService.findTemplates(), [workpadService]);
|
||||
};
|
||||
|
||||
export const useFindTemplatesOnMount = (): [boolean, TemplateFindResponse] => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const findTemplates = useFindTemplates();
|
||||
const [templateResponse, setTemplateResponse] = useState<TemplateFindResponse>(emptyResponse);
|
||||
|
||||
const fetchTemplates = useCallback(async () => {
|
||||
const foundTemplates = await findTemplates();
|
||||
setTemplateResponse(foundTemplates || emptyResponse);
|
||||
setIsMounted(true);
|
||||
}, [findTemplates]);
|
||||
|
||||
useMount(() => {
|
||||
fetchTemplates();
|
||||
return () => setIsMounted(false);
|
||||
});
|
||||
|
||||
return [isMounted, templateResponse];
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { useState, useCallback } from 'react';
|
||||
import useMount from 'react-use/lib/useMount';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { WorkpadFindResponse } from '../../../services/workpad';
|
||||
|
||||
import { useNotifyService, useWorkpadService } from '../../../services';
|
||||
const emptyResponse = { total: 0, workpads: [] };
|
||||
|
||||
export const useFindWorkpads = () => {
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
|
||||
return useCallback(
|
||||
async (text = '') => {
|
||||
try {
|
||||
return await workpadService.find(text);
|
||||
} catch (err) {
|
||||
notifyService.error(err, { title: errors.getFindFailureErrorMessage() });
|
||||
}
|
||||
},
|
||||
[notifyService, workpadService]
|
||||
);
|
||||
};
|
||||
|
||||
export const useFindWorkpadsOnMount = (): [boolean, WorkpadFindResponse] => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const findWorkpads = useFindWorkpads();
|
||||
const [workpadResponse, setWorkpadResponse] = useState<WorkpadFindResponse>(emptyResponse);
|
||||
|
||||
const fetchWorkpads = useCallback(async () => {
|
||||
const foundWorkpads = await findWorkpads();
|
||||
setWorkpadResponse(foundWorkpads || emptyResponse);
|
||||
setIsMounted(true);
|
||||
}, [findWorkpads]);
|
||||
|
||||
useMount(() => {
|
||||
fetchWorkpads();
|
||||
return () => setIsMounted(false);
|
||||
});
|
||||
|
||||
return [isMounted, workpadResponse];
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getFindFailureErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useFindWorkpads.findFailureErrorMessage', {
|
||||
defaultMessage: `Couldn't find workpad`,
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { CANVAS, JSON as JSONString } from '../../../../i18n/constants';
|
||||
import { useNotifyService } from '../../../services';
|
||||
import { getId } from '../../../lib/get_id';
|
||||
import type { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
export const useImportWorkpad = () => {
|
||||
const notifyService = useNotifyService();
|
||||
|
||||
return useCallback(
|
||||
(file?: File, onComplete: (workpad?: CanvasWorkpad) => void = () => {}) => {
|
||||
if (!file) {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (get(file, 'type') !== 'application/json') {
|
||||
notifyService.warning(errors.getAcceptJSONOnlyErrorMessage(), {
|
||||
title: file.name
|
||||
? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
|
||||
: errors.getFileUploadFailureWithoutFileNameErrorMessage(),
|
||||
});
|
||||
onComplete();
|
||||
}
|
||||
|
||||
// TODO: Clean up this file, this loading stuff can, and should be, abstracted
|
||||
const reader = new FileReader();
|
||||
|
||||
// handle reading the uploaded file
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const workpad = JSON.parse(reader.result as string); // Type-casting because we catch below.
|
||||
workpad.id = getId('workpad');
|
||||
|
||||
// sanity check for workpad object
|
||||
if (!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets) {
|
||||
onComplete();
|
||||
throw new Error(errors.getMissingPropertiesErrorMessage());
|
||||
}
|
||||
|
||||
onComplete(workpad);
|
||||
} catch (e) {
|
||||
notifyService.error(e, {
|
||||
title: file.name
|
||||
? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
|
||||
: errors.getFileUploadFailureWithoutFileNameErrorMessage(),
|
||||
});
|
||||
onComplete();
|
||||
}
|
||||
};
|
||||
|
||||
// read the uploaded file
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[notifyService]
|
||||
);
|
||||
};
|
||||
|
||||
const errors = {
|
||||
getFileUploadFailureWithoutFileNameErrorMessage: () =>
|
||||
i18n.translate(
|
||||
'xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage',
|
||||
{
|
||||
defaultMessage: `Couldn't upload file`,
|
||||
}
|
||||
),
|
||||
getFileUploadFailureWithFileNameErrorMessage: (fileName: string) =>
|
||||
i18n.translate('xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage', {
|
||||
defaultMessage: `Couldn't upload '{fileName}'`,
|
||||
values: {
|
||||
fileName,
|
||||
},
|
||||
}),
|
||||
getMissingPropertiesErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage', {
|
||||
defaultMessage:
|
||||
'Some properties required for a {CANVAS} workpad are missing. Edit your {JSON} file to provide the correct property values, and try again.',
|
||||
values: {
|
||||
CANVAS,
|
||||
JSON: JSONString,
|
||||
},
|
||||
}),
|
||||
getAcceptJSONOnlyErrorMessage: () =>
|
||||
i18n.translate('xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage', {
|
||||
defaultMessage: 'Only {JSON} files are accepted',
|
||||
values: {
|
||||
JSON: JSONString,
|
||||
},
|
||||
}),
|
||||
};
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { WorkpadManager } from './workpad_manager';
|
||||
export { Home } from './home';
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { HomeEmptyPrompt } from './empty_prompt';
|
||||
import { getDisableStoryshotsParameter } from '../../../../storybook';
|
||||
|
||||
export default {
|
||||
title: 'Home/Empty Prompt',
|
||||
argTypes: {},
|
||||
parameters: { ...getDisableStoryshotsParameter() },
|
||||
};
|
||||
|
||||
export const EmptyPrompt = () => <HomeEmptyPrompt />;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { CANVAS, JSON } from '../../../../i18n/constants';
|
||||
|
||||
export const HomeEmptyPrompt = () => (
|
||||
<EuiFlexGroup justifyContent="spaceAround" alignItems="center" style={{ minHeight: 600 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel color="subdued" borderRadius="none" hasShadow={false}>
|
||||
<EuiEmptyPrompt
|
||||
color="subdued"
|
||||
iconType="importAction"
|
||||
title={<h2>{strings.getEmptyPromptTitle()}</h2>}
|
||||
titleSize="m"
|
||||
body={
|
||||
<Fragment>
|
||||
<p>{strings.getEmptyPromptGettingStartedDescription()}</p>
|
||||
<p>
|
||||
{strings.getEmptyPromptNewUserDescription()}{' '}
|
||||
<EuiLink href="home#/tutorial_directory/sampleData">
|
||||
{strings.getSampleDataLinkLabel()}
|
||||
</EuiLink>
|
||||
.
|
||||
</p>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const strings = {
|
||||
getEmptyPromptGettingStartedDescription: () =>
|
||||
i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription', {
|
||||
defaultMessage:
|
||||
'Create a new workpad, start from a template, or import a workpad {JSON} file by dropping it here.',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
getEmptyPromptNewUserDescription: () =>
|
||||
i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription', {
|
||||
defaultMessage: 'New to {CANVAS}?',
|
||||
values: {
|
||||
CANVAS,
|
||||
},
|
||||
}),
|
||||
getEmptyPromptTitle: () =>
|
||||
i18n.translate('xpack.canvas.homeEmptyPrompt.emptyPromptTitle', {
|
||||
defaultMessage: 'Add your first workpad',
|
||||
}),
|
||||
getSampleDataLinkLabel: () =>
|
||||
i18n.translate('xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel', {
|
||||
defaultMessage: 'Add your first workpad',
|
||||
}),
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const LazyMyWorkpads = React.lazy(() => import('./my_workpads'));
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
export const Loading = () => (
|
||||
<EuiFlexGroup justifyContent="spaceAround" alignItems="center" style={{ minHeight: 600 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { FoundWorkpad } from '../../../services/workpad';
|
||||
import { UploadDropzone } from './upload_dropzone';
|
||||
import { HomeEmptyPrompt } from './empty_prompt';
|
||||
import { WorkpadTable } from './workpad_table';
|
||||
|
||||
export interface Props {
|
||||
workpads: FoundWorkpad[];
|
||||
}
|
||||
|
||||
export const MyWorkpads = ({ workpads }: Props) => {
|
||||
if (workpads.length === 0) {
|
||||
return (
|
||||
<UploadDropzone>
|
||||
<EuiFlexGroup justifyContent="spaceAround" alignItems="center" style={{ minHeight: 600 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<HomeEmptyPrompt />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</UploadDropzone>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UploadDropzone>
|
||||
<WorkpadTable />
|
||||
</UploadDropzone>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
reduxDecorator,
|
||||
getAddonPanelParameters,
|
||||
servicesContextDecorator,
|
||||
getDisableStoryshotsParameter,
|
||||
} from '../../../../storybook';
|
||||
import { getSomeWorkpads } from '../../../services/stubs/workpad';
|
||||
|
||||
import { MyWorkpads, WorkpadsContext } from './my_workpads';
|
||||
import { MyWorkpads as MyWorkpadsComponent } from './my_workpads.component';
|
||||
|
||||
export default {
|
||||
title: 'Home/My Workpads',
|
||||
argTypes: {},
|
||||
decorators: [reduxDecorator()],
|
||||
parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
|
||||
};
|
||||
|
||||
export const NoWorkpads = () => {
|
||||
return <MyWorkpads />;
|
||||
};
|
||||
|
||||
export const HasWorkpads = () => {
|
||||
return (
|
||||
<EuiPanel>
|
||||
<MyWorkpads />
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
NoWorkpads.decorators = [servicesContextDecorator()];
|
||||
HasWorkpads.decorators = [servicesContextDecorator({ findWorkpads: 5 })];
|
||||
|
||||
export const Component = ({ workpadCount }: { workpadCount: number }) => {
|
||||
const [workpads, setWorkpads] = useState(getSomeWorkpads(workpadCount));
|
||||
|
||||
return (
|
||||
<WorkpadsContext.Provider value={{ workpads, setWorkpads }}>
|
||||
<EuiPanel>
|
||||
<MyWorkpadsComponent {...{ workpads }} />
|
||||
</EuiPanel>
|
||||
</WorkpadsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Component.args = { workpadCount: 5 };
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 React, { useState, useEffect, createContext, Dispatch, SetStateAction } from 'react';
|
||||
import { useFindWorkpadsOnMount } from './../hooks';
|
||||
import { FoundWorkpad } from '../../../services/workpad';
|
||||
import { Loading } from './loading';
|
||||
import { MyWorkpads as Component } from './my_workpads.component';
|
||||
|
||||
interface Context {
|
||||
workpads: FoundWorkpad[];
|
||||
setWorkpads: Dispatch<SetStateAction<FoundWorkpad[]>>;
|
||||
}
|
||||
|
||||
export const WorkpadsContext = createContext<Context | null>(null);
|
||||
|
||||
export const MyWorkpads = () => {
|
||||
const [isMounted, workpadResponse] = useFindWorkpadsOnMount();
|
||||
const [workpads, setWorkpads] = useState(workpadResponse.workpads);
|
||||
|
||||
useEffect(() => {
|
||||
setWorkpads(workpadResponse.workpads);
|
||||
}, [workpadResponse]);
|
||||
|
||||
if (!isMounted) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<WorkpadsContext.Provider value={{ workpads, setWorkpads }}>
|
||||
<Component {...{ workpads }} />
|
||||
</WorkpadsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// required for dynamic import using React.lazy()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default MyWorkpads;
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 React, { FC } from 'react';
|
||||
// @ts-expect-error untyped library
|
||||
import Dropzone from 'react-dropzone';
|
||||
|
||||
import './upload_dropzone.scss';
|
||||
|
||||
export interface Props {
|
||||
disabled?: boolean;
|
||||
onDrop?: (files: FileList) => void;
|
||||
}
|
||||
|
||||
export const UploadDropzone: FC<Props> = ({ onDrop = () => {}, disabled, children }) => {
|
||||
return (
|
||||
<Dropzone
|
||||
{...{ onDrop, disabled }}
|
||||
disableClick
|
||||
className="canvasWorkpad__dropzone"
|
||||
activeClassName="canvasWorkpad__dropzone--active"
|
||||
>
|
||||
{children}
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
.canvasWorkpad__dropzone {
|
||||
border: 2px dashed transparent;
|
||||
}
|
||||
|
||||
.canvasWorkpad__dropzone--active {
|
||||
background-color: $euiColorLightestShade;
|
||||
border-color: $euiColorLightShade;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 React, { FC, useState } from 'react';
|
||||
// @ts-expect-error untyped library
|
||||
import Dropzone from 'react-dropzone';
|
||||
|
||||
import { useNotifyService } from '../../../services';
|
||||
import { ErrorStrings } from '../../../../i18n';
|
||||
import { useImportWorkpad, useCreateWorkpad } from '../hooks';
|
||||
import { CanvasWorkpad } from '../../../../types';
|
||||
|
||||
import { UploadDropzone as Component } from './upload_dropzone.component';
|
||||
|
||||
const { WorkpadDropzone: errors } = ErrorStrings;
|
||||
|
||||
export const UploadDropzone: FC = ({ children }) => {
|
||||
const notify = useNotifyService();
|
||||
const uploadWorkpad = useImportWorkpad();
|
||||
const createWorkpad = useCreateWorkpad();
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
|
||||
const onComplete = async (workpad?: CanvasWorkpad) => {
|
||||
if (!workpad) {
|
||||
setIsDisabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await createWorkpad(workpad);
|
||||
};
|
||||
|
||||
const onDrop = (files: FileList) => {
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.length > 1) {
|
||||
notify.warning(errors.getTooManyFilesErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDisabled(true);
|
||||
uploadWorkpad(files[0], onComplete);
|
||||
};
|
||||
|
||||
return (
|
||||
<Component disabled={isDisabled} {...{ onDrop }}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFilePicker, EuiFilePickerProps } from '@elastic/eui';
|
||||
|
||||
import { JSON } from '../../../../i18n/constants';
|
||||
export interface Props {
|
||||
canUserWrite: boolean;
|
||||
onImportWorkpad?: EuiFilePickerProps['onChange'];
|
||||
uniqueKey?: string | number;
|
||||
}
|
||||
|
||||
export const WorkpadImport = ({ uniqueKey, canUserWrite, onImportWorkpad = () => {} }: Props) => (
|
||||
<EuiFilePicker
|
||||
display="default"
|
||||
className="canvasWorkpad__upload--compressed"
|
||||
aria-label={strings.getFilePickerPlaceholder()}
|
||||
initialPromptText={strings.getFilePickerPlaceholder()}
|
||||
onChange={onImportWorkpad}
|
||||
key={uniqueKey}
|
||||
accept="application/json"
|
||||
disabled={!canUserWrite}
|
||||
/>
|
||||
);
|
||||
|
||||
const strings = {
|
||||
getFilePickerPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadImport.filePickerPlaceholder', {
|
||||
defaultMessage: 'Import workpad {JSON} file',
|
||||
values: {
|
||||
JSON,
|
||||
},
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
|
||||
import type { State } from '../../../../types';
|
||||
|
||||
import { useImportWorkpad } from '../hooks';
|
||||
import { WorkpadImport as Component, Props as ComponentProps } from './workpad_import.component';
|
||||
|
||||
type Props = Omit<ComponentProps, 'canUserWrite' | 'onImportWorkpad'>;
|
||||
|
||||
export const WorkpadImport = (props: Props) => {
|
||||
const importWorkpad = useImportWorkpad();
|
||||
const [uniqueKey, setUniqueKey] = useState(Date.now());
|
||||
|
||||
const { canUserWrite } = useSelector((state: State) => ({
|
||||
canUserWrite: canUserWriteSelector(state),
|
||||
}));
|
||||
|
||||
const onImportWorkpad: ComponentProps['onImportWorkpad'] = (files) => {
|
||||
if (files) {
|
||||
importWorkpad(files[0]);
|
||||
}
|
||||
setUniqueKey(Date.now());
|
||||
};
|
||||
|
||||
return <Component {...{ ...props, uniqueKey, onImportWorkpad, canUserWrite }} />;
|
||||
};
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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 React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiInMemoryTable,
|
||||
EuiInMemoryTableProps,
|
||||
EuiTableActionsColumnType,
|
||||
EuiBasicTableColumn,
|
||||
EuiToolTip,
|
||||
EuiButtonIcon,
|
||||
EuiTableSelectionType,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import moment from 'moment';
|
||||
|
||||
import { RoutingLink } from '../../routing';
|
||||
import { FoundWorkpad } from '../../../services/workpad';
|
||||
import { WorkpadTableTools } from './workpad_table_tools';
|
||||
import { WorkpadImport } from './workpad_import';
|
||||
|
||||
export interface Props {
|
||||
workpads: FoundWorkpad[];
|
||||
canUserWrite: boolean;
|
||||
dateFormat: string;
|
||||
onExportWorkpad: (ids: string) => void;
|
||||
onCloneWorkpad: (id: string) => void;
|
||||
}
|
||||
|
||||
const getDisplayName = (name: string, workpadId: string, loadedWorkpadId?: string) => {
|
||||
const workpadName = name.length ? <span>{name}</span> : <em>{workpadId}</em>;
|
||||
return workpadId === loadedWorkpadId ? <strong>{workpadName}</strong> : workpadName;
|
||||
};
|
||||
|
||||
export const WorkpadTable = ({
|
||||
workpads,
|
||||
canUserWrite,
|
||||
dateFormat,
|
||||
onExportWorkpad: onExport,
|
||||
onCloneWorkpad,
|
||||
}: Props) => {
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const formatDate = (date: string) => date && moment(date).format(dateFormat);
|
||||
|
||||
const selection: EuiTableSelectionType<FoundWorkpad> = {
|
||||
onSelectionChange: (selectedWorkpads) => {
|
||||
setSelectedIds(selectedWorkpads.map((workpad) => workpad.id).filter((id) => !!id));
|
||||
},
|
||||
};
|
||||
|
||||
const actions: EuiTableActionsColumnType<any>['actions'] = [
|
||||
{
|
||||
render: (workpad: FoundWorkpad) => (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={strings.getExportToolTip()}>
|
||||
<EuiButtonIcon
|
||||
iconType="exportAction"
|
||||
onClick={() => onExport(workpad.id)}
|
||||
aria-label={strings.getExportToolTip()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
canUserWrite ? strings.getCloneToolTip() : strings.getNoPermissionToCloneToolTip()
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="copy"
|
||||
onClick={() => onCloneWorkpad(workpad.id)}
|
||||
aria-label={strings.getCloneToolTip()}
|
||||
disabled={!canUserWrite}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const search: EuiInMemoryTableProps<FoundWorkpad>['search'] = {
|
||||
toolsLeft:
|
||||
selectedIds.length > 0 ? <WorkpadTableTools selectedWorkpadIds={selectedIds} /> : undefined,
|
||||
toolsRight: <WorkpadImport />,
|
||||
box: {
|
||||
schema: true,
|
||||
incremental: true,
|
||||
placeholder: strings.getWorkpadSearchPlaceholder(),
|
||||
'data-test-subj': 'tableListSearchBox',
|
||||
},
|
||||
};
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<FoundWorkpad>> = [
|
||||
{
|
||||
field: 'name',
|
||||
name: strings.getTableNameColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'string',
|
||||
render: (name, workpad) => (
|
||||
<RoutingLink
|
||||
data-test-subj="canvasWorkpadTableWorkpad"
|
||||
to={`/workpad/${workpad.id}`}
|
||||
aria-label={strings.getLoadWorkpadArialLabel(name.length ? name : workpad.id)}
|
||||
>
|
||||
{getDisplayName(name, workpad.id)}
|
||||
</RoutingLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: '@created',
|
||||
name: strings.getTableCreatedColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'date',
|
||||
width: '20%',
|
||||
render: (date: string) => formatDate(date),
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
name: strings.getTableUpdatedColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'date',
|
||||
width: '20%',
|
||||
render: (date: string) => formatDate(date),
|
||||
},
|
||||
{ name: strings.getTableActionsColumnTitle(), actions, width: '100px' },
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable
|
||||
itemId="id"
|
||||
items={workpads}
|
||||
columns={columns}
|
||||
message={strings.getNoWorkpadsFoundMessage()}
|
||||
search={search}
|
||||
sorting={{
|
||||
sort: {
|
||||
field: '@timestamp',
|
||||
direction: 'desc',
|
||||
},
|
||||
}}
|
||||
pagination={true}
|
||||
selection={selection}
|
||||
data-test-subj="canvasWorkpadTable"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getCloneToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.cloneTooltip', {
|
||||
defaultMessage: 'Clone workpad',
|
||||
}),
|
||||
getExportToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.exportTooltip', {
|
||||
defaultMessage: 'Export workpad',
|
||||
}),
|
||||
getLoadWorkpadArialLabel: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTable.loadWorkpadArialLabel', {
|
||||
defaultMessage: `Load workpad '{workpadName}'`,
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
}),
|
||||
getNoPermissionToCloneToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.noPermissionToCloneToolTip', {
|
||||
defaultMessage: `You don't have permission to clone workpads`,
|
||||
}),
|
||||
getNoWorkpadsFoundMessage: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.noWorkpadsFoundMessage', {
|
||||
defaultMessage: 'No workpads matched your search.',
|
||||
}),
|
||||
getWorkpadSearchPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.searchPlaceholder', {
|
||||
defaultMessage: 'Find workpad',
|
||||
}),
|
||||
getTableCreatedColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.table.createdColumnTitle', {
|
||||
defaultMessage: 'Created',
|
||||
description: 'This column in the table contains the date/time the workpad was created.',
|
||||
}),
|
||||
getTableNameColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.table.nameColumnTitle', {
|
||||
defaultMessage: 'Workpad name',
|
||||
}),
|
||||
getTableUpdatedColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.table.updatedColumnTitle', {
|
||||
defaultMessage: 'Updated',
|
||||
description: 'This column in the table contains the date/time the workpad was last updated.',
|
||||
}),
|
||||
getTableActionsColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTable.table.actionsColumnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
description: 'This column in the table contains the actions that can be taken on a workpad.',
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 React, { useState, useEffect } from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import {
|
||||
reduxDecorator,
|
||||
getAddonPanelParameters,
|
||||
getDisableStoryshotsParameter,
|
||||
} from '../../../../storybook';
|
||||
import { getSomeWorkpads } from '../../../services/stubs/workpad';
|
||||
|
||||
import { WorkpadTable } from './workpad_table';
|
||||
import { WorkpadTable as WorkpadTableComponent } from './workpad_table.component';
|
||||
import { WorkpadsContext } from './my_workpads';
|
||||
|
||||
export default {
|
||||
title: 'Home/Workpad Table',
|
||||
argTypes: {},
|
||||
decorators: [reduxDecorator()],
|
||||
parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
|
||||
};
|
||||
|
||||
export const NoWorkpads = () => {
|
||||
const [workpads, setWorkpads] = useState(getSomeWorkpads(0));
|
||||
|
||||
return (
|
||||
<WorkpadsContext.Provider value={{ workpads, setWorkpads }}>
|
||||
<EuiPanel>
|
||||
<WorkpadTable />
|
||||
</EuiPanel>
|
||||
</WorkpadsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const HasWorkpads = () => {
|
||||
const [workpads, setWorkpads] = useState(getSomeWorkpads(5));
|
||||
|
||||
return (
|
||||
<WorkpadsContext.Provider value={{ workpads, setWorkpads }}>
|
||||
<EuiPanel>
|
||||
<WorkpadTable />
|
||||
</EuiPanel>
|
||||
</WorkpadsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const Component = ({
|
||||
workpadCount,
|
||||
canUserWrite,
|
||||
dateFormat,
|
||||
}: {
|
||||
workpadCount: number;
|
||||
canUserWrite: boolean;
|
||||
dateFormat: string;
|
||||
}) => {
|
||||
const [workpads, setWorkpads] = useState(getSomeWorkpads(workpadCount));
|
||||
|
||||
useEffect(() => {
|
||||
setWorkpads(getSomeWorkpads(workpadCount));
|
||||
}, [workpadCount]);
|
||||
|
||||
return (
|
||||
<WorkpadsContext.Provider value={{ workpads, setWorkpads }}>
|
||||
<EuiPanel>
|
||||
<WorkpadTableComponent
|
||||
{...{ workpads, canUserWrite, dateFormat }}
|
||||
onCloneWorkpad={action('onCloneWorkpad')}
|
||||
onExportWorkpad={action('onExportWorkpad')}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</WorkpadsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Component.args = { workpadCount: 5, canUserWrite: true, dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS' };
|
||||
Component.argTypes = {};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 React, { useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
|
||||
import type { State } from '../../../../types';
|
||||
import { usePlatformService } from '../../../services';
|
||||
import { useCloneWorkpad, useDownloadWorkpad } from '../hooks';
|
||||
|
||||
import { WorkpadTable as Component } from './workpad_table.component';
|
||||
import { WorkpadsContext } from './my_workpads';
|
||||
|
||||
export const WorkpadTable = () => {
|
||||
const platformService = usePlatformService();
|
||||
const onCloneWorkpad = useCloneWorkpad();
|
||||
const onExportWorkpad = useDownloadWorkpad();
|
||||
const context = useContext(WorkpadsContext);
|
||||
|
||||
const { canUserWrite } = useSelector((state: State) => ({
|
||||
canUserWrite: canUserWriteSelector(state),
|
||||
}));
|
||||
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { workpads } = context;
|
||||
|
||||
const dateFormat = platformService.getUISetting('dateFormat');
|
||||
|
||||
return <Component {...{ workpads, dateFormat, canUserWrite, onCloneWorkpad, onExportWorkpad }} />;
|
||||
};
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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 React, { useState, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton, EuiToolTip, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
|
||||
import { ConfirmModal } from '../../confirm_modal';
|
||||
import { FoundWorkpad } from '../../../services/workpad';
|
||||
|
||||
export interface Props {
|
||||
workpads: FoundWorkpad[];
|
||||
canUserWrite: boolean;
|
||||
selectedWorkpadIds: string[];
|
||||
onDeleteWorkpads: (ids: string[]) => void;
|
||||
onExportWorkpads: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
export const WorkpadTableTools = ({
|
||||
workpads,
|
||||
canUserWrite,
|
||||
selectedWorkpadIds,
|
||||
onDeleteWorkpads,
|
||||
onExportWorkpads,
|
||||
}: Props) => {
|
||||
const [isDeletePending, setIsDeletePending] = useState(false);
|
||||
|
||||
const openRemoveConfirm = () => setIsDeletePending(true);
|
||||
const closeRemoveConfirm = () => setIsDeletePending(false);
|
||||
|
||||
let deleteButton = (
|
||||
<EuiButton
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={openRemoveConfirm}
|
||||
disabled={!canUserWrite}
|
||||
aria-label={strings.getDeleteButtonAriaLabel(selectedWorkpadIds.length)}
|
||||
data-test-subj="deleteWorkpadButton"
|
||||
>
|
||||
{strings.getDeleteButtonLabel(selectedWorkpadIds.length)}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
const downloadButton = (
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
onClick={() => onExportWorkpads(selectedWorkpadIds)}
|
||||
iconType="exportAction"
|
||||
aria-label={strings.getExportButtonAriaLabel(selectedWorkpadIds.length)}
|
||||
>
|
||||
{strings.getExportButtonLabel(selectedWorkpadIds.length)}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
if (!canUserWrite) {
|
||||
deleteButton = (
|
||||
<EuiToolTip content={strings.getNoPermissionToDeleteToolTip()}>{deleteButton}</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
const modalTitle =
|
||||
selectedWorkpadIds.length === 1
|
||||
? strings.getDeleteSingleWorkpadModalTitle(
|
||||
workpads.find((workpad) => workpad.id === selectedWorkpadIds[0])?.name || ''
|
||||
)
|
||||
: strings.getDeleteMultipleWorkpadModalTitle(selectedWorkpadIds.length + '');
|
||||
|
||||
const confirmModal = (
|
||||
<ConfirmModal
|
||||
isOpen={isDeletePending}
|
||||
title={modalTitle}
|
||||
message={strings.getDeleteModalDescription()}
|
||||
confirmButtonText={strings.getDeleteModalConfirmButtonLabel()}
|
||||
onConfirm={() => {
|
||||
onDeleteWorkpads(selectedWorkpadIds);
|
||||
closeRemoveConfirm();
|
||||
}}
|
||||
onCancel={closeRemoveConfirm}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>{downloadButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{deleteButton}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{confirmModal}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getDeleteButtonAriaLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteButtonAriaLabel', {
|
||||
defaultMessage: 'Delete {numberOfWorkpads} workpads',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteButtonLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete ({numberOfWorkpads})',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteModalConfirmButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
}),
|
||||
getDeleteModalDescription: () =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteModalDescription', {
|
||||
defaultMessage: `You can't recover deleted workpads.`,
|
||||
}),
|
||||
getDeleteMultipleWorkpadModalTitle: (numberOfWorkpads: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle', {
|
||||
defaultMessage: 'Delete {numberOfWorkpads} workpads?',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getDeleteSingleWorkpadModalTitle: (workpadName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle', {
|
||||
defaultMessage: `Delete workpad '{workpadName}'?`,
|
||||
values: {
|
||||
workpadName,
|
||||
},
|
||||
}),
|
||||
getExportButtonAriaLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.exportButtonAriaLabel', {
|
||||
defaultMessage: 'Export {numberOfWorkpads} workpads',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getExportButtonLabel: (numberOfWorkpads: number) =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.exportButtonLabel', {
|
||||
defaultMessage: 'Export ({numberOfWorkpads})',
|
||||
values: {
|
||||
numberOfWorkpads,
|
||||
},
|
||||
}),
|
||||
getNoPermissionToCreateToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.noPermissionToCreateToolTip', {
|
||||
defaultMessage: `You don't have permission to create workpads`,
|
||||
}),
|
||||
getNoPermissionToDeleteToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip', {
|
||||
defaultMessage: `You don't have permission to delete workpads`,
|
||||
}),
|
||||
getNoPermissionToUploadToolTip: () =>
|
||||
i18n.translate('xpack.canvas.workpadTableTools.noPermissionToUploadToolTip', {
|
||||
defaultMessage: `You don't have permission to upload workpads`,
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 React, { useContext } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { canUserWrite as canUserWriteSelector } from '../../../state/selectors/app';
|
||||
import type { State } from '../../../../types';
|
||||
import { useDeleteWorkpads, useDownloadWorkpad } from '../hooks';
|
||||
|
||||
import {
|
||||
WorkpadTableTools as Component,
|
||||
Props as ComponentProps,
|
||||
} from './workpad_table_tools.component';
|
||||
import { WorkpadsContext } from './my_workpads';
|
||||
|
||||
export type Props = Pick<ComponentProps, 'selectedWorkpadIds'>;
|
||||
|
||||
export const WorkpadTableTools = ({ selectedWorkpadIds }: Props) => {
|
||||
const deleteWorkpads = useDeleteWorkpads();
|
||||
const downloadWorkpad = useDownloadWorkpad();
|
||||
const context = useContext(WorkpadsContext);
|
||||
|
||||
const { canUserWrite } = useSelector((state: State) => ({
|
||||
canUserWrite: canUserWriteSelector(state),
|
||||
}));
|
||||
|
||||
if (context === null || selectedWorkpadIds.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { workpads, setWorkpads } = context;
|
||||
|
||||
const onExport = () => selectedWorkpadIds.map((id) => downloadWorkpad(id));
|
||||
const onDelete = async () => {
|
||||
const { removedIds } = await deleteWorkpads(selectedWorkpadIds);
|
||||
setWorkpads(workpads.filter((workpad) => !removedIds.includes(workpad.id)));
|
||||
};
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...{ workpads, selectedWorkpadIds, canUserWrite }}
|
||||
onDeleteWorkpads={onDelete}
|
||||
onExportWorkpads={onExport}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { EuiButtonPropsForButton } from '@elastic/eui/src/components/button/button';
|
||||
|
||||
export interface Props
|
||||
extends Omit<EuiButtonPropsForButton, 'iconType' | 'fill' | 'data-test-subj' | 'children'> {
|
||||
canUserWrite: boolean;
|
||||
}
|
||||
|
||||
export const WorkpadCreate = ({ canUserWrite, disabled, ...rest }: Props) => {
|
||||
return (
|
||||
<EuiButton
|
||||
{...{ ...rest }}
|
||||
iconType="plusInCircleFilled"
|
||||
fill
|
||||
disabled={!canUserWrite && !disabled}
|
||||
data-test-subj="create-workpad-button"
|
||||
>
|
||||
{strings.getWorkpadCreateButtonLabel()}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getWorkpadCreateButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadCreate.createButtonLabel', {
|
||||
defaultMessage: 'Create workpad',
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { canUserWrite as canUserWriteSelector } from '../../state/selectors/app';
|
||||
import type { State } from '../../../types';
|
||||
|
||||
import { useCreateWorkpad } from './hooks';
|
||||
import { WorkpadCreate as Component, Props as ComponentProps } from './workpad_create.component';
|
||||
|
||||
type Props = Omit<ComponentProps, 'canUserWrite' | 'onClick'>;
|
||||
|
||||
export const WorkpadCreate = (props: Props) => {
|
||||
const createWorkpad = useCreateWorkpad();
|
||||
|
||||
const { canUserWrite } = useSelector((state: State) => ({
|
||||
canUserWrite: canUserWriteSelector(state),
|
||||
}));
|
||||
|
||||
const onClick: ComponentProps['onClick'] = async () => {
|
||||
await createWorkpad();
|
||||
};
|
||||
|
||||
return <Component {...{ ...props, onClick, canUserWrite }} />;
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const LazyWorkpadTemplates = React.lazy(() => import('./workpad_templates'));
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { uniq } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiInMemoryTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiButtonEmpty,
|
||||
EuiSearchBarProps,
|
||||
SearchFilterConfig,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { CanvasTemplate } from '../../../../types';
|
||||
import { tagsRegistry } from '../../../lib/tags_registry';
|
||||
import { TagList } from '../../tag_list';
|
||||
|
||||
export interface Props {
|
||||
templates: CanvasTemplate[];
|
||||
onCreateWorkpad: (template: CanvasTemplate) => void;
|
||||
}
|
||||
|
||||
export const WorkpadTemplates = ({ templates, onCreateWorkpad }: Props) => {
|
||||
const columns: Array<EuiBasicTableColumn<CanvasTemplate>> = [
|
||||
{
|
||||
field: 'name',
|
||||
name: strings.getTableNameColumnTitle(),
|
||||
sortable: true,
|
||||
width: '30%',
|
||||
dataType: 'string',
|
||||
render: (name: string, template) => {
|
||||
const templateName = name.length ? name : 'Unnamed Template';
|
||||
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
onClick={() => onCreateWorkpad(template)}
|
||||
aria-label={strings.getCloneTemplateLinkAriaLabel(templateName)}
|
||||
type="button"
|
||||
>
|
||||
{templateName}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'help',
|
||||
name: strings.getTableDescriptionColumnTitle(),
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
},
|
||||
{
|
||||
field: 'tags',
|
||||
name: strings.getTableTagsColumnTitle(),
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
render: (tags: string[]) => <TagList tags={tags} tagType="health" />,
|
||||
},
|
||||
];
|
||||
|
||||
let uniqueTagNames: string[] = [];
|
||||
|
||||
templates.forEach((template) => {
|
||||
const { tags } = template;
|
||||
tags.forEach((tag) => uniqueTagNames.push(tag));
|
||||
uniqueTagNames = uniq(uniqueTagNames);
|
||||
});
|
||||
|
||||
const uniqueTags = uniqueTagNames.map(
|
||||
(name) =>
|
||||
tagsRegistry.get(name) || {
|
||||
color: undefined,
|
||||
name,
|
||||
}
|
||||
);
|
||||
|
||||
const filters: SearchFilterConfig[] = [
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'tags',
|
||||
name: 'Tags',
|
||||
multiSelect: true,
|
||||
options: uniqueTags.map((tag) => ({
|
||||
value: tag.name,
|
||||
name: tag.name,
|
||||
view: <TagList tags={[tag.name]} tagType="health" />,
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const search: EuiSearchBarProps = {
|
||||
box: {
|
||||
incremental: true,
|
||||
schema: true,
|
||||
},
|
||||
filters,
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable
|
||||
itemId="id"
|
||||
items={templates}
|
||||
columns={columns}
|
||||
search={search}
|
||||
sorting={{
|
||||
sort: {
|
||||
field: 'name',
|
||||
direction: 'asc',
|
||||
},
|
||||
}}
|
||||
pagination={true}
|
||||
data-test-subj="canvasTemplatesTable"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const strings = {
|
||||
getCloneTemplateLinkAriaLabel: (templateName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel', {
|
||||
defaultMessage: `Clone workpad template '{templateName}'`,
|
||||
values: {
|
||||
templateName,
|
||||
},
|
||||
}),
|
||||
getTableDescriptionColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.descriptionColumnTitle', {
|
||||
defaultMessage: 'Description',
|
||||
}),
|
||||
getTableNameColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.nameColumnTitle', {
|
||||
defaultMessage: 'Template name',
|
||||
}),
|
||||
getTableTagsColumnTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.table.tagsColumnTitle', {
|
||||
defaultMessage: 'Tags',
|
||||
description:
|
||||
'This column contains relevant tags that indicate what type of template ' +
|
||||
'is displayed. For example: "report", "presentation", etc.',
|
||||
}),
|
||||
getTemplateSearchPlaceholder: () =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.searchPlaceholder', {
|
||||
defaultMessage: 'Find template',
|
||||
}),
|
||||
getCreatingTemplateLabel: (templateName: string) =>
|
||||
i18n.translate('xpack.canvas.workpadTemplates.creatingTemplateLabel', {
|
||||
defaultMessage: `Creating from template '{templateName}'`,
|
||||
values: {
|
||||
templateName,
|
||||
},
|
||||
}),
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
reduxDecorator,
|
||||
getAddonPanelParameters,
|
||||
servicesContextDecorator,
|
||||
getDisableStoryshotsParameter,
|
||||
} from '../../../../storybook';
|
||||
import { getSomeTemplates } from '../../../services/stubs/workpad';
|
||||
|
||||
import { WorkpadTemplates } from './workpad_templates';
|
||||
import { WorkpadTemplates as WorkpadTemplatesComponent } from './workpad_templates.component';
|
||||
|
||||
export default {
|
||||
title: 'Home/Workpad Templates',
|
||||
argTypes: {},
|
||||
decorators: [reduxDecorator()],
|
||||
parameters: { ...getAddonPanelParameters(), ...getDisableStoryshotsParameter() },
|
||||
};
|
||||
|
||||
export const NoTemplates = () => {
|
||||
return (
|
||||
<EuiPanel>
|
||||
<WorkpadTemplates />
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const HasTemplates = () => {
|
||||
return (
|
||||
<EuiPanel>
|
||||
<WorkpadTemplates />
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
NoTemplates.decorators = [servicesContextDecorator()];
|
||||
HasTemplates.decorators = [servicesContextDecorator({ findTemplates: true })];
|
||||
|
||||
export const Component = ({ hasTemplates }: { hasTemplates: boolean }) => {
|
||||
return (
|
||||
<EuiPanel>
|
||||
<WorkpadTemplatesComponent
|
||||
onCreateWorkpad={action('onCreateWorkpad')}
|
||||
templates={hasTemplates ? getSomeTemplates().templates : []}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
Component.args = {
|
||||
hasTemplates: true,
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
import { useCreateFromTemplate, useFindTemplatesOnMount } from '../hooks';
|
||||
|
||||
import { WorkpadTemplates as Component } from './workpad_templates.component';
|
||||
|
||||
export const WorkpadTemplates = () => {
|
||||
const [isMounted, templateResponse] = useFindTemplatesOnMount();
|
||||
const onCreateWorkpad = useCreateFromTemplate();
|
||||
|
||||
if (!isMounted) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceAround" alignItems="center" style={{ minHeight: 600 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
const { templates } = templateResponse;
|
||||
|
||||
return <Component {...{ templates, onCreateWorkpad }} />;
|
||||
};
|
||||
|
||||
// required for dynamic import using React.lazy()
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default WorkpadTemplates;
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
|
||||
// @ts-expect-error untyped local
|
||||
import { WorkpadManager } from '../workpad_manager';
|
||||
import { Home } from '../home';
|
||||
// @ts-expect-error untyped local
|
||||
import { setDocTitle } from '../../lib/doc_title';
|
||||
|
||||
|
@ -19,17 +17,5 @@ export interface Props {
|
|||
export const HomeApp: FC<Props> = ({ onLoad = () => {} }) => {
|
||||
onLoad();
|
||||
setDocTitle('Canvas');
|
||||
return (
|
||||
<EuiPage className="canvasHomeApp" restrictWidth>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent
|
||||
className="canvasHomeApp__content"
|
||||
panelPaddingSize="none"
|
||||
horizontalPosition="center"
|
||||
>
|
||||
<WorkpadManager onClose={() => {}} />
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
return <Home />;
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ storiesOf('components/Toolbar', module)
|
|||
isWriteable={true}
|
||||
selectedPageNumber={1}
|
||||
totalPages={1}
|
||||
workpadId={'abc'}
|
||||
workpadName={'My Canvas Workpad'}
|
||||
/>
|
||||
))
|
||||
|
@ -28,7 +27,6 @@ storiesOf('components/Toolbar', module)
|
|||
selectedElement={getDefaultElement()}
|
||||
selectedPageNumber={1}
|
||||
totalPages={1}
|
||||
workpadId={'abc'}
|
||||
workpadName={'My Canvas Workpad'}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -7,17 +7,8 @@
|
|||
|
||||
import React, { FC, useState, useContext, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiModal,
|
||||
EuiModalFooter,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
// @ts-expect-error untyped local
|
||||
import { WorkpadManager } from '../workpad_manager';
|
||||
import { PageManager } from '../page_manager';
|
||||
import { Expression } from '../expression';
|
||||
import { Tray } from './tray';
|
||||
|
@ -37,7 +28,6 @@ export interface Props {
|
|||
selectedElement?: CanvasElement;
|
||||
selectedPageNumber: number;
|
||||
totalPages: number;
|
||||
workpadId: string;
|
||||
workpadName: string;
|
||||
}
|
||||
|
||||
|
@ -46,11 +36,9 @@ export const Toolbar: FC<Props> = ({
|
|||
selectedElement,
|
||||
selectedPageNumber,
|
||||
totalPages,
|
||||
workpadId,
|
||||
workpadName,
|
||||
}) => {
|
||||
const [activeTray, setActiveTray] = useState<TrayType | null>(null);
|
||||
const [showWorkpadManager, setShowWorkpadManager] = useState(false);
|
||||
const { getUrl, previousPage } = useContext(WorkpadRoutingContext);
|
||||
|
||||
// While the tray doesn't get activated if the workpad isn't writeable,
|
||||
|
@ -75,20 +63,6 @@ export const Toolbar: FC<Props> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const closeWorkpadManager = () => setShowWorkpadManager(false);
|
||||
const openWorkpadManager = () => setShowWorkpadManager(true);
|
||||
|
||||
const workpadManager = (
|
||||
<EuiModal onClose={closeWorkpadManager} className="canvasModal--fixedSize" maxWidth="1000px">
|
||||
<WorkpadManager onClose={closeWorkpadManager} />
|
||||
<EuiModalFooter>
|
||||
<EuiButton size="s" onClick={closeWorkpadManager}>
|
||||
{strings.getWorkpadManagerCloseButtonLabel()}
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
|
||||
const trays = {
|
||||
pageManager: <PageManager onPreviousPage={previousPage} />,
|
||||
expression: !elementIsSelected ? null : <Expression done={() => setActiveTray(null)} />,
|
||||
|
@ -99,12 +73,6 @@ export const Toolbar: FC<Props> = ({
|
|||
{activeTray !== null && <Tray done={() => setActiveTray(null)}>{trays[activeTray]}</Tray>}
|
||||
<div className="canvasToolbar__container">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" className="canvasToolbar__controls">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="text" iconType="grid" onClick={() => openWorkpadManager()}>
|
||||
{workpadName}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} />
|
||||
<EuiFlexItem grow={false}>
|
||||
<RoutingButtonIcon
|
||||
color="text"
|
||||
|
@ -143,7 +111,6 @@ export const Toolbar: FC<Props> = ({
|
|||
)}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
{showWorkpadManager && workpadManager}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -153,6 +120,5 @@ Toolbar.propTypes = {
|
|||
selectedElement: PropTypes.object,
|
||||
selectedPageNumber: PropTypes.number.isRequired,
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
workpadId: PropTypes.string.isRequired,
|
||||
workpadName: PropTypes.string.isRequired,
|
||||
};
|
||||
|
|
|
@ -1,173 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
import moment from 'moment';
|
||||
// @ts-expect-error
|
||||
import { getDefaultWorkpad } from '../../state/defaults';
|
||||
import { canUserWrite as canUserWriteSelector } from '../../state/selectors/app';
|
||||
import { getWorkpad } from '../../state/selectors/workpad';
|
||||
import { getId } from '../../lib/get_id';
|
||||
import { downloadWorkpad } from '../../lib/download_workpad';
|
||||
import { ComponentStrings, ErrorStrings } from '../../../i18n';
|
||||
import { State, CanvasWorkpad } from '../../../types';
|
||||
import { useNotifyService, useWorkpadService, usePlatformService } from '../../services';
|
||||
// @ts-expect-error
|
||||
import { WorkpadLoader as Component } from './workpad_loader';
|
||||
|
||||
const { WorkpadLoader: strings } = ComponentStrings;
|
||||
const { WorkpadLoader: errors } = ErrorStrings;
|
||||
|
||||
type WorkpadStatePromise = ReturnType<ReturnType<typeof useWorkpadService>['find']>;
|
||||
type WorkpadState = WorkpadStatePromise extends PromiseLike<infer U> ? U : never;
|
||||
|
||||
export const WorkpadLoader: FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
const fromState = useSelector((state: State) => ({
|
||||
workpadId: getWorkpad(state).id,
|
||||
canUserWrite: canUserWriteSelector(state),
|
||||
}));
|
||||
|
||||
const [workpadsState, setWorkpadsState] = useState<WorkpadState | null>(null);
|
||||
const workpadService = useWorkpadService();
|
||||
const notifyService = useNotifyService();
|
||||
const platformService = usePlatformService();
|
||||
const history = useHistory();
|
||||
|
||||
const createWorkpad = useCallback(
|
||||
async (_workpad: CanvasWorkpad | null | undefined) => {
|
||||
const workpad = _workpad || getDefaultWorkpad();
|
||||
if (workpad != null) {
|
||||
try {
|
||||
await workpadService.create(workpad);
|
||||
history.push(`/workpad/${workpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, {
|
||||
title: errors.getUploadFailureErrorMessage(),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
[workpadService, notifyService, history]
|
||||
);
|
||||
|
||||
const findWorkpads = useCallback(
|
||||
async (text) => {
|
||||
try {
|
||||
const fetchedWorkpads = await workpadService.find(text);
|
||||
setWorkpadsState(fetchedWorkpads);
|
||||
} catch (err) {
|
||||
notifyService.error(err, { title: errors.getFindFailureErrorMessage() });
|
||||
}
|
||||
},
|
||||
[notifyService, workpadService]
|
||||
);
|
||||
|
||||
const onDownloadWorkpad = useCallback((workpadId: string) => downloadWorkpad(workpadId), []);
|
||||
|
||||
const cloneWorkpad = useCallback(
|
||||
async (workpadId: string) => {
|
||||
try {
|
||||
const workpad = await workpadService.get(workpadId);
|
||||
workpad.name = strings.getClonedWorkpadName(workpad.name);
|
||||
workpad.id = getId('workpad');
|
||||
await workpadService.create(workpad);
|
||||
history.push(`/workpad/${workpad.id}/page/1`);
|
||||
} catch (err) {
|
||||
notifyService.error(err, { title: errors.getCloneFailureErrorMessage() });
|
||||
}
|
||||
},
|
||||
[notifyService, workpadService, history]
|
||||
);
|
||||
|
||||
const removeWorkpads = useCallback(
|
||||
(workpadIds: string[]) => {
|
||||
if (workpadsState === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removedWorkpads = workpadIds.map(async (id) => {
|
||||
try {
|
||||
await workpadService.remove(id);
|
||||
return { id, err: null };
|
||||
} catch (err) {
|
||||
return { id, err };
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(removedWorkpads).then((results) => {
|
||||
let redirectHome = false;
|
||||
|
||||
const [passes, errored] = results.reduce<[string[], string[]]>(
|
||||
([passesArr, errorsArr], result) => {
|
||||
if (result.id === fromState.workpadId && !result.err) {
|
||||
redirectHome = true;
|
||||
}
|
||||
|
||||
if (result.err) {
|
||||
errorsArr.push(result.id);
|
||||
} else {
|
||||
passesArr.push(result.id);
|
||||
}
|
||||
|
||||
return [passesArr, errorsArr];
|
||||
},
|
||||
[[], []]
|
||||
);
|
||||
|
||||
const remainingWorkpads = workpadsState.workpads.filter(({ id }) => !passes.includes(id));
|
||||
|
||||
const workpadState = {
|
||||
total: remainingWorkpads.length,
|
||||
workpads: remainingWorkpads,
|
||||
};
|
||||
|
||||
if (errored.length > 0) {
|
||||
notifyService.error(errors.getDeleteFailureErrorMessage());
|
||||
}
|
||||
|
||||
setWorkpadsState(workpadState);
|
||||
|
||||
if (redirectHome) {
|
||||
history.push('/');
|
||||
}
|
||||
|
||||
return errored;
|
||||
});
|
||||
},
|
||||
[history, workpadService, fromState.workpadId, workpadsState, notifyService]
|
||||
);
|
||||
|
||||
const formatDate = useCallback(
|
||||
(date: any) => {
|
||||
const dateFormat = platformService.getUISetting('dateFormat');
|
||||
return date && moment(date).format(dateFormat);
|
||||
},
|
||||
[platformService]
|
||||
);
|
||||
|
||||
const { workpadId, canUserWrite } = fromState;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...{
|
||||
downloadWorkpad: onDownloadWorkpad,
|
||||
workpads: workpadsState,
|
||||
workpadId,
|
||||
canUserWrite,
|
||||
cloneWorkpad,
|
||||
createWorkpad,
|
||||
findWorkpads,
|
||||
removeWorkpads,
|
||||
formatDate,
|
||||
onClose,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,52 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { getId } from '../../lib/get_id';
|
||||
import { ErrorStrings } from '../../../i18n';
|
||||
|
||||
const { WorkpadFileUpload: errors } = ErrorStrings;
|
||||
|
||||
export const uploadWorkpad = (file, onUpload, notify) => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get(file, 'type') !== 'application/json') {
|
||||
return notify.warning(errors.getAcceptJSONOnlyErrorMessage(), {
|
||||
title: file.name
|
||||
? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
|
||||
: errors.getFileUploadFailureWithoutFileNameErrorMessage(),
|
||||
});
|
||||
}
|
||||
// TODO: Clean up this file, this loading stuff can, and should be, abstracted
|
||||
const reader = new FileReader();
|
||||
|
||||
// handle reading the uploaded file
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const workpad = JSON.parse(reader.result);
|
||||
workpad.id = getId('workpad');
|
||||
|
||||
// sanity check for workpad object
|
||||
if (!Array.isArray(workpad.pages) || workpad.pages.length === 0 || !workpad.assets) {
|
||||
throw new Error(errors.getMissingPropertiesErrorMessage());
|
||||
}
|
||||
|
||||
onUpload(workpad);
|
||||
} catch (e) {
|
||||
notify.error(e, {
|
||||
title: file.name
|
||||
? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
|
||||
: errors.getFileUploadFailureWithoutFileNameErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// read the uploaded file
|
||||
reader.readAsText(file);
|
||||
};
|
|
@ -1,31 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
|
||||
const { WorkpadCreate: strings } = ComponentStrings;
|
||||
|
||||
export const WorkpadCreate = ({ createPending, onCreate, ...rest }) => (
|
||||
<EuiButton
|
||||
{...rest}
|
||||
iconType="plusInCircle"
|
||||
fill
|
||||
onClick={onCreate}
|
||||
isLoading={createPending}
|
||||
data-test-subj="create-workpad-button"
|
||||
>
|
||||
{strings.getWorkpadCreateButtonLabel()}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
WorkpadCreate.propTypes = {
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
createPending: PropTypes.bool,
|
||||
};
|
|
@ -1,31 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, withHandlers } from 'recompose';
|
||||
import { uploadWorkpad } from '../upload_workpad';
|
||||
import { ErrorStrings } from '../../../../i18n';
|
||||
import { WorkpadDropzone as Component } from './workpad_dropzone';
|
||||
|
||||
const { WorkpadFileUpload: errors } = ErrorStrings;
|
||||
|
||||
export const WorkpadDropzone = compose(
|
||||
withHandlers(({ notify }) => ({
|
||||
onDropAccepted: ({ onUpload }) => ([file]) => uploadWorkpad(file, onUpload),
|
||||
onDropRejected: () => ([file]) => {
|
||||
notify.warning(errors.getAcceptJSONOnlyErrorMessage(), {
|
||||
title: file.name
|
||||
? errors.getFileUploadFailureWithFileNameErrorMessage(file.name)
|
||||
: errors.getFileUploadFailureWithoutFileNameErrorMessage(),
|
||||
});
|
||||
},
|
||||
}))
|
||||
)(Component);
|
||||
|
||||
WorkpadDropzone.propTypes = {
|
||||
onUpload: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,31 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dropzone from 'react-dropzone';
|
||||
|
||||
export const WorkpadDropzone = ({ onDropAccepted, onDropRejected, disabled, children }) => (
|
||||
<Dropzone
|
||||
accept="application/json"
|
||||
onDropAccepted={onDropAccepted}
|
||||
onDropRejected={onDropRejected}
|
||||
disableClick
|
||||
disabled={disabled}
|
||||
className="canvasWorkpad__dropzone"
|
||||
activeClassName="canvasWorkpad__dropzone--active"
|
||||
>
|
||||
{children}
|
||||
</Dropzone>
|
||||
);
|
||||
|
||||
WorkpadDropzone.propTypes = {
|
||||
onDropAccepted: PropTypes.func.isRequired,
|
||||
onDropRejected: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
.canvasWorkpad__dropzone {
|
||||
border: 2px dashed transparent;
|
||||
}
|
||||
|
||||
.canvasWorkpad__dropzone--active {
|
||||
background-color: $euiColorLightestShade;
|
||||
border-color: $euiColorLightShade;
|
||||
}
|
||||
|
||||
.canvasWorkpad__dropzoneTable .euiTable {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.canvasWorkpad__dropzoneTable--tags {
|
||||
.euiTableCellContent {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.euiHealth {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,426 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBasicTable,
|
||||
EuiButtonIcon,
|
||||
EuiPagination,
|
||||
EuiSpacer,
|
||||
EuiButton,
|
||||
EuiToolTip,
|
||||
EuiEmptyPrompt,
|
||||
EuiFilePicker,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { orderBy } from 'lodash';
|
||||
import { ConfirmModal } from '../confirm_modal';
|
||||
import { RoutingLink } from '../routing';
|
||||
import { Paginate } from '../paginate';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
import { WorkpadDropzone } from './workpad_dropzone';
|
||||
import { WorkpadCreate } from './workpad_create';
|
||||
import { WorkpadSearch } from './workpad_search';
|
||||
import { uploadWorkpad } from './upload_workpad';
|
||||
|
||||
const { WorkpadLoader: strings } = ComponentStrings;
|
||||
|
||||
const getDisplayName = (name, workpad, loadedWorkpad) => {
|
||||
const workpadName = name.length ? name : <em>{workpad.id}</em>;
|
||||
return workpad.id === loadedWorkpad ? <strong>{workpadName}</strong> : workpadName;
|
||||
};
|
||||
|
||||
export class WorkpadLoader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
workpadId: PropTypes.string.isRequired,
|
||||
canUserWrite: PropTypes.bool.isRequired,
|
||||
createWorkpad: PropTypes.func.isRequired,
|
||||
findWorkpads: PropTypes.func.isRequired,
|
||||
downloadWorkpad: PropTypes.func.isRequired,
|
||||
cloneWorkpad: PropTypes.func.isRequired,
|
||||
removeWorkpads: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
workpads: PropTypes.object,
|
||||
formatDate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
createPending: false,
|
||||
deletingWorkpad: false,
|
||||
sortField: '@timestamp',
|
||||
sortDirection: 'desc',
|
||||
selectedWorkpads: [],
|
||||
pageSize: 10,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
// on component load, kick off the workpad search
|
||||
this.props.findWorkpads();
|
||||
|
||||
// keep track of whether or not the component is mounted, to prevent rogue setState calls
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
// the workpadId prop will change when a is created or loaded, close the toolbar when it does
|
||||
const { workpadId, onClose } = this.props;
|
||||
if (workpadId !== newProps.workpadId) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
// create new empty workpad
|
||||
createWorkpad = async () => {
|
||||
this.setState({ createPending: true });
|
||||
await this.props.createWorkpad();
|
||||
this._isMounted && this.setState({ createPending: false });
|
||||
};
|
||||
|
||||
// create new workpad from uploaded JSON
|
||||
onUpload = async (workpad) => {
|
||||
this.setState({ createPending: true });
|
||||
await this.props.createWorkpad(workpad);
|
||||
this._isMounted && this.setState({ createPending: false });
|
||||
};
|
||||
|
||||
// clone existing workpad
|
||||
cloneWorkpad = async (workpad) => {
|
||||
this.setState({ createPending: true });
|
||||
await this.props.cloneWorkpad(workpad.id);
|
||||
this._isMounted && this.setState({ createPending: false });
|
||||
};
|
||||
|
||||
// Workpad remove methods
|
||||
openRemoveConfirm = () => this.setState({ deletingWorkpad: true });
|
||||
|
||||
closeRemoveConfirm = () => this.setState({ deletingWorkpad: false });
|
||||
|
||||
removeWorkpads = () => {
|
||||
const { selectedWorkpads } = this.state;
|
||||
|
||||
this.props.removeWorkpads(selectedWorkpads.map(({ id }) => id)).then((remainingIds) => {
|
||||
const remainingWorkpads =
|
||||
remainingIds.length > 0
|
||||
? selectedWorkpads.filter(({ id }) => remainingIds.includes(id))
|
||||
: [];
|
||||
|
||||
this._isMounted &&
|
||||
this.setState({
|
||||
deletingWorkpad: false,
|
||||
selectedWorkpads: remainingWorkpads,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// downloads selected workpads as JSON files
|
||||
downloadWorkpads = () => {
|
||||
this.state.selectedWorkpads.forEach(({ id }) => this.props.downloadWorkpad(id));
|
||||
};
|
||||
|
||||
onSelectionChange = (selectedWorkpads) => {
|
||||
this.setState({ selectedWorkpads });
|
||||
};
|
||||
|
||||
onTableChange = ({ sort = {} }) => {
|
||||
const { field: sortField, direction: sortDirection } = sort;
|
||||
this.setState({
|
||||
sortField,
|
||||
sortDirection,
|
||||
});
|
||||
};
|
||||
|
||||
renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }) => {
|
||||
const { sortField, sortDirection } = this.state;
|
||||
const { canUserWrite, createPending, workpadId: loadedWorkpad } = this.props;
|
||||
|
||||
const actions = [
|
||||
{
|
||||
render: (workpad) => (
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={strings.getExportToolTip()}>
|
||||
<EuiButtonIcon
|
||||
iconType="exportAction"
|
||||
onClick={() => this.props.downloadWorkpad(workpad.id)}
|
||||
aria-label={strings.getExportToolTip()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={
|
||||
canUserWrite ? strings.getCloneToolTip() : strings.getNoPermissionToCloneToolTip()
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="copy"
|
||||
onClick={() => this.cloneWorkpad(workpad)}
|
||||
aria-label={strings.getCloneToolTip()}
|
||||
disabled={!canUserWrite}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
name: strings.getTableNameColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'string',
|
||||
render: (name, workpad) => {
|
||||
const workpadName = getDisplayName(name, workpad, loadedWorkpad);
|
||||
|
||||
return (
|
||||
<RoutingLink
|
||||
data-test-subj="canvasWorkpadLoaderWorkpad"
|
||||
to={`/workpad/${workpad.id}`}
|
||||
aria-label={strings.getLoadWorkpadArialLabel()}
|
||||
>
|
||||
{workpadName}
|
||||
</RoutingLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: '@created',
|
||||
name: strings.getTableCreatedColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'date',
|
||||
width: '20%',
|
||||
render: (date) => this.props.formatDate(date),
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
name: strings.getTableUpdatedColumnTitle(),
|
||||
sortable: true,
|
||||
dataType: 'date',
|
||||
width: '20%',
|
||||
render: (date) => this.props.formatDate(date),
|
||||
},
|
||||
{ name: strings.getTableActionsColumnTitle(), actions, width: '100px' },
|
||||
];
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sortField,
|
||||
direction: sortDirection,
|
||||
},
|
||||
};
|
||||
|
||||
const selection = {
|
||||
itemId: 'id',
|
||||
onSelectionChange: this.onSelectionChange,
|
||||
};
|
||||
|
||||
const emptyTable = (
|
||||
<EuiEmptyPrompt
|
||||
iconType="importAction"
|
||||
title={<h2>{strings.getEmptyPromptTitle()}</h2>}
|
||||
titleSize="s"
|
||||
body={
|
||||
<Fragment>
|
||||
<p>{strings.getEmptyPromptGettingStartedDescription()}</p>
|
||||
<p>
|
||||
{strings.getEmptyPromptNewUserDescription()}{' '}
|
||||
<EuiLink href="home#/tutorial_directory/sampleData">
|
||||
{strings.getSampleDataLinkLabel()}
|
||||
</EuiLink>
|
||||
.
|
||||
</p>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<WorkpadDropzone
|
||||
onUpload={this.onUpload}
|
||||
disabled={createPending || !canUserWrite}
|
||||
notify={this.props.notify}
|
||||
>
|
||||
<EuiBasicTable
|
||||
items={rows}
|
||||
itemId="id"
|
||||
columns={columns}
|
||||
sorting={sorting}
|
||||
noItemsMessage={emptyTable}
|
||||
onChange={this.onTableChange}
|
||||
isSelectable
|
||||
selection={selection}
|
||||
className="canvasWorkpad__dropzoneTable"
|
||||
data-test-subj="canvasWorkpadLoaderTable"
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{rows.length > 0 && (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination
|
||||
activePage={pageNumber}
|
||||
onPageClick={setPage}
|
||||
pageCount={totalPages}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</WorkpadDropzone>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
deletingWorkpad,
|
||||
createPending,
|
||||
selectedWorkpads,
|
||||
sortField,
|
||||
sortDirection,
|
||||
} = this.state;
|
||||
const { canUserWrite } = this.props;
|
||||
const isLoading = this.props.workpads == null;
|
||||
|
||||
let createButton = (
|
||||
<WorkpadCreate
|
||||
createPending={createPending}
|
||||
onCreate={this.createWorkpad}
|
||||
disabled={!canUserWrite}
|
||||
/>
|
||||
);
|
||||
|
||||
let deleteButton = (
|
||||
<EuiButton
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={this.openRemoveConfirm}
|
||||
disabled={!canUserWrite}
|
||||
aria-label={strings.getDeleteButtonAriaLabel(selectedWorkpads.length)}
|
||||
data-test-subj="deleteWorkpadButton"
|
||||
>
|
||||
{strings.getDeleteButtonLabel(selectedWorkpads.length)}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
const downloadButton = (
|
||||
<EuiButton
|
||||
color="secondary"
|
||||
onClick={this.downloadWorkpads}
|
||||
iconType="exportAction"
|
||||
aria-label={strings.getExportButtonAriaLabel(selectedWorkpads.length)}
|
||||
>
|
||||
{strings.getExportButtonLabel(selectedWorkpads.length)}
|
||||
</EuiButton>
|
||||
);
|
||||
|
||||
let uploadButton = (
|
||||
<EuiFilePicker
|
||||
display="default"
|
||||
compressed
|
||||
className="canvasWorkpad__upload--compressed"
|
||||
aria-label={strings.getFilePickerPlaceholder()}
|
||||
initialPromptText={strings.getFilePickerPlaceholder()}
|
||||
onChange={([file]) => uploadWorkpad(file, this.onUpload, this.props.notify)}
|
||||
accept="application/json"
|
||||
disabled={createPending || !canUserWrite}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!canUserWrite) {
|
||||
createButton = (
|
||||
<EuiToolTip content={strings.getNoPermissionToCreateToolTip()}>{createButton}</EuiToolTip>
|
||||
);
|
||||
deleteButton = (
|
||||
<EuiToolTip content={strings.getNoPermissionToDeleteToolTip()}>{deleteButton}</EuiToolTip>
|
||||
);
|
||||
uploadButton = (
|
||||
<EuiToolTip content={strings.getNoPermissionToUploadToolTip()}>{uploadButton}</EuiToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
const modalTitle =
|
||||
selectedWorkpads.length === 1
|
||||
? strings.getDeleteSingleWorkpadModalTitle(selectedWorkpads[0].name)
|
||||
: strings.getDeleteMultipleWorkpadModalTitle(selectedWorkpads.length);
|
||||
|
||||
const confirmModal = (
|
||||
<ConfirmModal
|
||||
isOpen={deletingWorkpad}
|
||||
title={modalTitle}
|
||||
message={strings.getDeleteModalDescription()}
|
||||
confirmButtonText={strings.getDeleteModalConfirmButtonLabel()}
|
||||
onConfirm={this.removeWorkpads}
|
||||
onCancel={this.closeRemoveConfirm}
|
||||
/>
|
||||
);
|
||||
|
||||
let sortedWorkpads = [];
|
||||
|
||||
if (!createPending && !isLoading) {
|
||||
const { workpads } = this.props.workpads;
|
||||
sortedWorkpads = orderBy(workpads, [sortField, '@timestamp'], [sortDirection, 'desc']);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paginate rows={sortedWorkpads}>
|
||||
{(pagination) => (
|
||||
<Fragment>
|
||||
<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>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
{createPending && (
|
||||
<div style={{ width: '100%' }}>{strings.getCreateWorkpadLoadingDescription()}</div>
|
||||
)}
|
||||
|
||||
{!createPending && isLoading && (
|
||||
<div style={{ width: '100%' }}>{strings.getFetchLoadingDescription()}</div>
|
||||
)}
|
||||
|
||||
{!createPending && !isLoading && this.renderWorkpadTable(pagination)}
|
||||
|
||||
{confirmModal}
|
||||
</Fragment>
|
||||
)}
|
||||
</Paginate>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
.canvasWorkpad__upload--compressed {
|
||||
|
||||
&.euiFilePicker--compressed.euiFilePicker {
|
||||
.euiFilePicker__prompt {
|
||||
height: $euiSizeXXL;
|
||||
padding: $euiSizeM;
|
||||
padding-left: $euiSizeXXL;
|
||||
}
|
||||
|
||||
.euiFilePicker__icon {
|
||||
top: $euiSizeM;
|
||||
}
|
||||
}
|
||||
|
||||
// The file picker input is being used moreso as a button, outside of a form,
|
||||
// and thus the need to override the default max-width of form inputs.
|
||||
// An issue has been opened in EUI to consider creating a button
|
||||
// version of the file picker - https://github.com/elastic/eui/issues/1987
|
||||
|
||||
.euiFilePicker__wrap {
|
||||
@include euiBreakpoint('xs', 's') {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFieldSearch } from '@elastic/eui';
|
||||
import { debounce } from 'lodash';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
|
||||
const { WorkpadSearch: strings } = ComponentStrings;
|
||||
export class WorkpadSearch extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
initialText: PropTypes.string,
|
||||
};
|
||||
|
||||
state = {
|
||||
searchText: this.props.initialText || '',
|
||||
};
|
||||
|
||||
triggerChange = debounce(this.props.onChange, 150);
|
||||
|
||||
setSearchText = (ev) => {
|
||||
const text = ev.target.value;
|
||||
this.setState({ searchText: text });
|
||||
this.triggerChange(text);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EuiFieldSearch
|
||||
placeholder={strings.getWorkpadSearchPlaceholder()}
|
||||
value={this.state.searchText}
|
||||
onChange={this.setSearchText}
|
||||
fullWidth
|
||||
incremental
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,69 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiTabbedContent,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiModalBody,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { WorkpadLoader } from '../workpad_loader';
|
||||
import { WorkpadTemplates } from '../workpad_templates';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
|
||||
const { WorkpadManager: strings } = ComponentStrings;
|
||||
|
||||
export const WorkpadManager = ({ onClose }) => {
|
||||
const tabs = [
|
||||
{
|
||||
id: 'workpadLoader',
|
||||
name: strings.getMyWorkpadsTabLabel(),
|
||||
content: (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<WorkpadLoader onClose={onClose} />
|
||||
</Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'workpadTemplates',
|
||||
name: strings.getWorkpadTemplatesTabLabel(),
|
||||
'data-test-subj': 'workpadTemplates',
|
||||
content: (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<WorkpadTemplates onClose={onClose} />
|
||||
</Fragment>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiModalHeader className="canvasHomeApp__modalHeader">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiModalHeaderTitle>
|
||||
<h1>{strings.getModalTitle()}</h1>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody className="canvasHomeApp__modalBody">
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
|
||||
</EuiModalBody>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
WorkpadManager.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
};
|
|
@ -1,564 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots components/WorkpadTemplates default 1`] = `
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"width": "500px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive euiFlexGroup--wrap"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiSearchBar__searchHolder"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--fullWidth"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
aria-label="This is a search bar. As you type, the results lower in the page will automatically filter."
|
||||
className="euiFieldSearch euiFieldSearch--fullWidth"
|
||||
defaultValue=""
|
||||
onKeyUp={[Function]}
|
||||
placeholder="Find template"
|
||||
type="search"
|
||||
/>
|
||||
<div
|
||||
className="euiFormControlLayoutIcons"
|
||||
>
|
||||
<span
|
||||
className="euiFormControlLayoutCustomIcon"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="euiFormControlLayoutCustomIcon__icon"
|
||||
data-euiicon-type="search"
|
||||
size="m"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero euiSearchBar__filtersHolder"
|
||||
>
|
||||
<div
|
||||
className="euiFilterGroup"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownCenter"
|
||||
id="field_value_selection_0"
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--text euiFilterButton euiFilterButton--hasIcon"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
size="m"
|
||||
/>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
<span
|
||||
className="euiFilterButton__textShift"
|
||||
data-text="Tags"
|
||||
title="Tags"
|
||||
>
|
||||
Tags
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
<div
|
||||
className="euiBasicTable canvasWorkpad__dropzoneTable canvasWorkpad__dropzoneTable--tags"
|
||||
data-test-subj="canvasTemplatesTable"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="euiTableHeaderMobile"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
/>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiTableSortMobile"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownRight"
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall euiButtonEmpty--flushRight"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
size="s"
|
||||
/>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Sorting
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
className="euiTable euiTable--compressed euiTable--responsive"
|
||||
id="generated-id"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<caption
|
||||
className="euiScreenReaderOnly euiTableCaption"
|
||||
/>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
aria-live="polite"
|
||||
aria-sort="ascending"
|
||||
className="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_name_0"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="euiTableHeaderButton euiTableHeaderButton-isSorted"
|
||||
data-test-subj="tableHeaderSortButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
Template name
|
||||
</span>
|
||||
<span
|
||||
className="euiTableSortIcon"
|
||||
data-euiicon-type="sortUp"
|
||||
size="m"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</th>
|
||||
<th
|
||||
className="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_help_1"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
Description
|
||||
</span>
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
className="euiTableHeaderCell"
|
||||
data-test-subj="tableHeaderCell_tags_2"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
Tags
|
||||
</span>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
className="euiTableRow"
|
||||
>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Template name
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<button
|
||||
aria-label="Clone workpad template 'test1'"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
test1
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Description
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
This is a test template
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Tags
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<div
|
||||
className="euiHealth euiHealth--textSizeS"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
color="#666666"
|
||||
data-euiicon-type="dot"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
tag1
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiHealth euiHealth--textSizeS"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
color="#666666"
|
||||
data-euiicon-type="dot"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
tag2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
className="euiTableRow"
|
||||
>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Template name
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<button
|
||||
aria-label="Clone workpad template 'test2'"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
test2
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Description
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent"
|
||||
>
|
||||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
This is a second test template
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
className="euiTableRowCell"
|
||||
style={
|
||||
Object {
|
||||
"width": "30%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiTableRowCell__mobileHeader euiTableRowCell--hideForDesktop"
|
||||
>
|
||||
Tags
|
||||
</div>
|
||||
<div
|
||||
className="euiTableCellContent euiTableCellContent--overflowingContent"
|
||||
>
|
||||
<div
|
||||
className="euiHealth euiHealth--textSizeS"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
color="#666666"
|
||||
data-euiicon-type="dot"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
tag2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiHealth euiHealth--textSizeS"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
color="#666666"
|
||||
data-euiicon-type="dot"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
tag3
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--l"
|
||||
/>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--justifyContentFlexEnd euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<nav
|
||||
className="euiPagination"
|
||||
>
|
||||
<button
|
||||
aria-label="Previous page"
|
||||
className="euiButtonIcon euiButtonIcon--text euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="pagination-button-previous"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowLeft"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
<ul
|
||||
className="euiPagination__list"
|
||||
>
|
||||
<li
|
||||
className="euiPagination__item"
|
||||
>
|
||||
<button
|
||||
aria-current={true}
|
||||
aria-label="Page 1 of 1"
|
||||
className="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--small euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile"
|
||||
data-test-subj="pagination-button-0"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
aria-label="Next page"
|
||||
className="euiButtonIcon euiButtonIcon--text euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="pagination-button-next"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowRight"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,45 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { WorkpadTemplates } from '../workpad_templates';
|
||||
import { CanvasTemplate } from '../../../../types';
|
||||
|
||||
const templates: Record<string, CanvasTemplate> = {
|
||||
test1: {
|
||||
id: 'test1-id',
|
||||
name: 'test1',
|
||||
help: 'This is a test template',
|
||||
tags: ['tag1', 'tag2'],
|
||||
template_key: 'test1-key',
|
||||
},
|
||||
test2: {
|
||||
id: 'test2-id',
|
||||
name: 'test2',
|
||||
help: 'This is a second test template',
|
||||
tags: ['tag2', 'tag3'],
|
||||
template_key: 'test2-key',
|
||||
},
|
||||
};
|
||||
|
||||
storiesOf('components/WorkpadTemplates', module)
|
||||
.addDecorator((story) => <div style={{ width: '500px' }}>{story()}</div>)
|
||||
.add('default', () => {
|
||||
const onCreateFromTemplateAction = action('onCreateFromTemplate');
|
||||
return (
|
||||
<WorkpadTemplates
|
||||
templates={templates}
|
||||
onClose={action('onClose')}
|
||||
onCreateFromTemplate={(template) => {
|
||||
onCreateFromTemplateAction(template);
|
||||
return Promise.resolve();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
|
@ -1,86 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState, useEffect, FunctionComponent } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { ComponentStrings } from '../../../i18n/components';
|
||||
// @ts-expect-error
|
||||
import * as workpadService from '../../lib/workpad_service';
|
||||
import { WorkpadTemplates as Component } from './workpad_templates';
|
||||
import { CanvasTemplate } from '../../../types';
|
||||
import { list } from '../../lib/template_service';
|
||||
import { applyTemplateStrings } from '../../../i18n/templates/apply_strings';
|
||||
import { useNotifyService, useServices } from '../../services';
|
||||
|
||||
interface WorkpadTemplatesProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const Creating: FunctionComponent<{ name: string }> = ({ name }) => (
|
||||
<div>
|
||||
<EuiLoadingSpinner size="l" />{' '}
|
||||
{ComponentStrings.WorkpadTemplates.getCreatingTemplateLabel(name)}
|
||||
</div>
|
||||
);
|
||||
export const WorkpadTemplates: FunctionComponent<WorkpadTemplatesProps> = ({ onClose }) => {
|
||||
const history = useHistory();
|
||||
const services = useServices();
|
||||
|
||||
const [templates, setTemplates] = useState<CanvasTemplate[] | undefined>(undefined);
|
||||
const [creatingFromTemplateName, setCreatingFromTemplateName] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const { error } = useNotifyService();
|
||||
|
||||
useEffect(() => {
|
||||
if (!templates) {
|
||||
(async () => {
|
||||
const fetchedTemplates = await list();
|
||||
setTemplates(applyTemplateStrings(fetchedTemplates));
|
||||
})();
|
||||
}
|
||||
}, [templates]);
|
||||
|
||||
let templateProp: Record<string, CanvasTemplate> = {};
|
||||
|
||||
if (templates) {
|
||||
templateProp = templates.reduce<Record<string, any>>((reduction, template) => {
|
||||
reduction[template.name] = template;
|
||||
return reduction;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const createFromTemplate = useCallback(
|
||||
async (template: CanvasTemplate) => {
|
||||
setCreatingFromTemplateName(template.name);
|
||||
try {
|
||||
const result = await services.workpad.createFromTemplate(template.id);
|
||||
history.push(`/workpad/${result.id}/page/1`);
|
||||
} catch (e) {
|
||||
setCreatingFromTemplateName(undefined);
|
||||
error(e, {
|
||||
title: `Couldn't create workpad from template`,
|
||||
});
|
||||
}
|
||||
},
|
||||
[services.workpad, error, history]
|
||||
);
|
||||
|
||||
if (creatingFromTemplateName) {
|
||||
return <Creating name={creatingFromTemplateName} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Component
|
||||
onClose={onClose}
|
||||
templates={templateProp}
|
||||
onCreateFromTemplate={createFromTemplate}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,215 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBasicTable,
|
||||
EuiPagination,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiSearchBar,
|
||||
EuiTableSortingType,
|
||||
Direction,
|
||||
SortDirection,
|
||||
} from '@elastic/eui';
|
||||
import { orderBy } from 'lodash';
|
||||
// @ts-ignore untyped local
|
||||
import { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { Paginate, PaginateChildProps } from '../paginate';
|
||||
import { TagList } from '../tag_list';
|
||||
import { getTagsFilter } from '../../lib/get_tags_filter';
|
||||
// @ts-expect-error
|
||||
import { extractSearch } from '../../lib/extract_search';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
import { CanvasTemplate } from '../../../types';
|
||||
|
||||
interface TableChange<T> {
|
||||
page?: {
|
||||
index: number;
|
||||
size: number;
|
||||
};
|
||||
sort?: {
|
||||
field: keyof T;
|
||||
direction: Direction;
|
||||
};
|
||||
}
|
||||
|
||||
const { WorkpadTemplates: strings } = ComponentStrings;
|
||||
|
||||
interface WorkpadTemplatesProps {
|
||||
onCreateFromTemplate: (template: CanvasTemplate) => Promise<void>;
|
||||
onClose: () => void;
|
||||
templates: Record<string, CanvasTemplate>;
|
||||
}
|
||||
|
||||
interface WorkpadTemplatesState {
|
||||
sortField: string;
|
||||
sortDirection: Direction;
|
||||
pageSize: number;
|
||||
searchTerm: string;
|
||||
filterTags: string[];
|
||||
}
|
||||
|
||||
export class WorkpadTemplates extends React.PureComponent<
|
||||
WorkpadTemplatesProps,
|
||||
WorkpadTemplatesState
|
||||
> {
|
||||
static propTypes = {
|
||||
onCreateFromTemplate: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
templates: PropTypes.object,
|
||||
};
|
||||
|
||||
state = {
|
||||
sortField: 'name',
|
||||
sortDirection: SortDirection.ASC,
|
||||
pageSize: 10,
|
||||
searchTerm: '',
|
||||
filterTags: [],
|
||||
};
|
||||
|
||||
tagType: 'health' = 'health';
|
||||
|
||||
onTableChange = (tableChange: TableChange<CanvasTemplate>) => {
|
||||
if (tableChange.sort) {
|
||||
const { field: sortField, direction: sortDirection } = tableChange.sort;
|
||||
this.setState({
|
||||
sortField,
|
||||
sortDirection,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSearch = ({ queryText = '' }) => this.setState(extractSearch(queryText));
|
||||
|
||||
cloneTemplate = (template: CanvasTemplate) =>
|
||||
this.props.onCreateFromTemplate(template).then(() => this.props.onClose());
|
||||
|
||||
renderWorkpadTable = ({ rows, pageNumber, totalPages, setPage }: PaginateChildProps) => {
|
||||
const { sortField, sortDirection } = this.state;
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<CanvasTemplate>> = [
|
||||
{
|
||||
field: 'name',
|
||||
name: strings.getTableNameColumnTitle(),
|
||||
sortable: true,
|
||||
width: '30%',
|
||||
dataType: 'string',
|
||||
render: (name: string, template) => {
|
||||
const templateName = name.length ? name : 'Unnamed Template';
|
||||
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
onClick={() => this.cloneTemplate(template)}
|
||||
aria-label={strings.getCloneTemplateLinkAriaLabel(templateName)}
|
||||
type="button"
|
||||
>
|
||||
{templateName}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'help',
|
||||
name: strings.getTableDescriptionColumnTitle(),
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
},
|
||||
{
|
||||
field: 'tags',
|
||||
name: strings.getTableTagsColumnTitle(),
|
||||
sortable: false,
|
||||
dataType: 'string',
|
||||
width: '30%',
|
||||
render: (tags: string[]) => <TagList tags={tags} tagType={this.tagType} />,
|
||||
},
|
||||
];
|
||||
|
||||
const sorting: EuiTableSortingType<any> = {
|
||||
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"
|
||||
data-test-subj="canvasTemplatesTable"
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{rows.length > 0 && (
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination activePage={pageNumber} onPageClick={setPage} pageCount={totalPages} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
renderSearch = () => {
|
||||
const { searchTerm } = this.state;
|
||||
const filters = [getTagsFilter(this.tagType)];
|
||||
|
||||
return (
|
||||
<EuiSearchBar
|
||||
defaultQuery={searchTerm}
|
||||
box={{
|
||||
placeholder: strings.getTemplateSearchPlaceholder(),
|
||||
incremental: true,
|
||||
}}
|
||||
filters={filters}
|
||||
onChange={this.onSearch}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { templates } = this.props;
|
||||
const { sortField, sortDirection, searchTerm, filterTags } = this.state;
|
||||
const sortedTemplates = orderBy(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: PaginateChildProps) => (
|
||||
<Fragment>
|
||||
{this.renderSearch()}
|
||||
<EuiSpacer />
|
||||
{this.renderWorkpadTable(pagination)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Paginate>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,39 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import { SearchFilterConfig } from '@elastic/eui';
|
||||
import { Tag } from '../components/tag';
|
||||
import { getId } from './get_id';
|
||||
import { tagsRegistry } from './tags_registry';
|
||||
import { ComponentStrings } from '../../i18n';
|
||||
|
||||
const { WorkpadTemplates: strings } = ComponentStrings;
|
||||
|
||||
// EUI helper function
|
||||
// generates the FieldValueSelectionFilter object for EuiSearchBar for tag filtering
|
||||
export const getTagsFilter = (type: 'health' | 'badge'): SearchFilterConfig => {
|
||||
const uniqueTags = sortBy(Object.values(tagsRegistry.toJS()), 'name');
|
||||
const filterType = 'field_value_selection';
|
||||
|
||||
return {
|
||||
type: filterType,
|
||||
field: 'tag',
|
||||
name: strings.getTableTagsColumnTitle(),
|
||||
multiSelect: true,
|
||||
options: uniqueTags.map(({ name, color }) => ({
|
||||
value: name,
|
||||
name,
|
||||
view: (
|
||||
<div>
|
||||
<Tag key={getId('tag')} color={color} name={name} type={type} />
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
};
|
||||
};
|
|
@ -34,7 +34,7 @@ export type CanvasServiceFactory<Service> = (
|
|||
appUpdater: BehaviorSubject<AppUpdater>
|
||||
) => Service | Promise<Service>;
|
||||
|
||||
class CanvasServiceProvider<Service> {
|
||||
export class CanvasServiceProvider<Service> {
|
||||
private factory: CanvasServiceFactory<Service>;
|
||||
private service: Service | undefined;
|
||||
|
||||
|
|
|
@ -9,13 +9,19 @@ import { PlatformService } from '../platform';
|
|||
|
||||
const noop = (..._args: any[]): any => {};
|
||||
|
||||
const uiSettings: Record<string, any> = {
|
||||
dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
|
||||
};
|
||||
|
||||
const getUISetting = (setting: string) => uiSettings[setting];
|
||||
|
||||
export const platformService: PlatformService = {
|
||||
getBasePath: () => '/base/path',
|
||||
getBasePathInterface: noop,
|
||||
getDocLinkVersion: () => 'dockLinkVersion',
|
||||
getElasticWebsiteUrl: () => 'https://elastic.co',
|
||||
getHasWriteAccess: () => true,
|
||||
getUISetting: noop,
|
||||
getUISetting,
|
||||
setBreadcrumbs: noop,
|
||||
setRecentlyAccessed: noop,
|
||||
getSavedObjects: noop,
|
||||
|
|
|
@ -5,17 +5,95 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { WorkpadService } from '../workpad';
|
||||
import { CanvasWorkpad } from '../../../types';
|
||||
import moment from 'moment';
|
||||
|
||||
export const workpadService: WorkpadService = {
|
||||
get: (id: string) => Promise.resolve({} as CanvasWorkpad),
|
||||
create: (workpad) => Promise.resolve({} as CanvasWorkpad),
|
||||
createFromTemplate: (templateId: string) => Promise.resolve({} as CanvasWorkpad),
|
||||
find: (term: string) =>
|
||||
Promise.resolve({
|
||||
// @ts-expect-error
|
||||
import { getDefaultWorkpad } from '../../state/defaults';
|
||||
import { WorkpadService } from '../workpad';
|
||||
import { getId } from '../../lib/get_id';
|
||||
import { CanvasTemplate } from '../../../types';
|
||||
|
||||
const TIMEOUT = 500;
|
||||
|
||||
const promiseTimeout = (time: number) => () => new Promise((resolve) => setTimeout(resolve, time));
|
||||
const getName = () => {
|
||||
const lorem = 'Lorem ipsum dolor sit amet consectetur adipiscing elit Fusce lobortis aliquet arcu ut turpis duis'.split(
|
||||
' '
|
||||
);
|
||||
return [1, 2, 3].map(() => lorem[Math.floor(Math.random() * lorem.length)]).join(' ');
|
||||
};
|
||||
|
||||
const randomDate = (
|
||||
start: Date = moment().toDate(),
|
||||
end: Date = moment().subtract(7, 'days').toDate()
|
||||
) => new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString();
|
||||
|
||||
const templates: CanvasTemplate[] = [
|
||||
{
|
||||
id: 'test1-id',
|
||||
name: 'test1',
|
||||
help: 'This is a test template',
|
||||
tags: ['tag1', 'tag2'],
|
||||
template_key: 'test1-key',
|
||||
},
|
||||
{
|
||||
id: 'test2-id',
|
||||
name: 'test2',
|
||||
help: 'This is a second test template',
|
||||
tags: ['tag2', 'tag3'],
|
||||
template_key: 'test2-key',
|
||||
},
|
||||
];
|
||||
|
||||
export const getSomeWorkpads = (count = 3) =>
|
||||
Array.from({ length: count }, () => ({
|
||||
'@created': randomDate(
|
||||
moment().subtract(3, 'days').toDate(),
|
||||
moment().subtract(10, 'days').toDate()
|
||||
),
|
||||
'@timestamp': randomDate(),
|
||||
id: getId('workpad'),
|
||||
name: getName(),
|
||||
}));
|
||||
|
||||
export const findSomeWorkpads = (count = 3, timeout = TIMEOUT) => (_term: string) => {
|
||||
return Promise.resolve()
|
||||
.then(promiseTimeout(timeout))
|
||||
.then(() => ({
|
||||
total: count,
|
||||
workpads: getSomeWorkpads(count),
|
||||
}));
|
||||
};
|
||||
|
||||
export const findNoWorkpads = (timeout = TIMEOUT) => (_term: string) => {
|
||||
return Promise.resolve()
|
||||
.then(promiseTimeout(timeout))
|
||||
.then(() => ({
|
||||
total: 0,
|
||||
workpads: [],
|
||||
}),
|
||||
remove: (id: string) => Promise.resolve(undefined),
|
||||
}));
|
||||
};
|
||||
|
||||
export const findSomeTemplates = (timeout = TIMEOUT) => () => {
|
||||
return Promise.resolve()
|
||||
.then(promiseTimeout(timeout))
|
||||
.then(() => getSomeTemplates());
|
||||
};
|
||||
|
||||
export const findNoTemplates = (timeout = TIMEOUT) => () => {
|
||||
return Promise.resolve()
|
||||
.then(promiseTimeout(timeout))
|
||||
.then(() => getNoTemplates());
|
||||
};
|
||||
|
||||
export const getNoTemplates = () => ({ templates: [] });
|
||||
export const getSomeTemplates = () => ({ templates });
|
||||
|
||||
export const workpadService: WorkpadService = {
|
||||
get: (id: string) => Promise.resolve({ ...getDefaultWorkpad(), id }),
|
||||
findTemplates: findNoTemplates(),
|
||||
create: (workpad) => Promise.resolve(workpad),
|
||||
createFromTemplate: (_templateId: string) => Promise.resolve(getDefaultWorkpad()),
|
||||
find: findNoWorkpads(),
|
||||
remove: (id: string) => Promise.resolve(),
|
||||
};
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { API_ROUTE_WORKPAD, DEFAULT_WORKPAD_CSS } from '../../common/lib/constants';
|
||||
import { CanvasWorkpad } from '../../types';
|
||||
import {
|
||||
API_ROUTE_WORKPAD,
|
||||
DEFAULT_WORKPAD_CSS,
|
||||
API_ROUTE_TEMPLATES,
|
||||
} from '../../common/lib/constants';
|
||||
import { CanvasWorkpad, CanvasTemplate } from '../../types';
|
||||
import { CanvasServiceFactory } from './';
|
||||
|
||||
/*
|
||||
|
@ -40,9 +44,15 @@ const sanitizeWorkpad = function (workpad: CanvasWorkpad) {
|
|||
return workpad;
|
||||
};
|
||||
|
||||
interface WorkpadFindResponse {
|
||||
export type FoundWorkpads = Array<Pick<CanvasWorkpad, 'name' | 'id' | '@timestamp' | '@created'>>;
|
||||
export type FoundWorkpad = FoundWorkpads[number];
|
||||
export interface WorkpadFindResponse {
|
||||
total: number;
|
||||
workpads: Array<Pick<CanvasWorkpad, 'name' | 'id' | '@timestamp' | '@created'>>;
|
||||
workpads: FoundWorkpads;
|
||||
}
|
||||
|
||||
export interface TemplateFindResponse {
|
||||
templates: CanvasTemplate[];
|
||||
}
|
||||
|
||||
export interface WorkpadService {
|
||||
|
@ -51,6 +61,7 @@ export interface WorkpadService {
|
|||
createFromTemplate: (templateId: string) => Promise<CanvasWorkpad>;
|
||||
find: (term: string) => Promise<WorkpadFindResponse>;
|
||||
remove: (id: string) => Promise<void>;
|
||||
findTemplates: () => Promise<TemplateFindResponse>;
|
||||
}
|
||||
|
||||
export const workpadServiceFactory: CanvasServiceFactory<WorkpadService> = (
|
||||
|
@ -82,7 +93,9 @@ export const workpadServiceFactory: CanvasServiceFactory<WorkpadService> = (
|
|||
body: JSON.stringify({ templateId }),
|
||||
});
|
||||
},
|
||||
findTemplates: async () => coreStart.http.get(API_ROUTE_TEMPLATES),
|
||||
find: (searchTerm: string) => {
|
||||
// TODO: this shouldn't be necessary. Check for usage.
|
||||
const validSearchTerm = typeof searchTerm === 'string' && searchTerm.length > 0;
|
||||
|
||||
return coreStart.http.get(`${getApiPath()}/find`, {
|
||||
|
|
|
@ -40,8 +40,6 @@
|
|||
@import '../components/workpad_header/element_menu/element_menu';
|
||||
@import '../components/workpad_header/share_menu/share_menu';
|
||||
@import '../components/workpad_header/view_menu/view_menu';
|
||||
@import '../components/workpad_loader/workpad_loader';
|
||||
@import '../components/workpad_loader/workpad_dropzone/workpad_dropzone';
|
||||
@import '../components/workpad_page/workpad_page';
|
||||
@import '../components/workpad_page/workpad_interactive_page/workpad_interactive_page';
|
||||
@import '../components/workpad_page/workpad_static_page/workpad_static_page';
|
||||
|
|
|
@ -11,6 +11,7 @@ import { kibanaContextDecorator } from './kibana_decorator';
|
|||
import { servicesContextDecorator } from './services_decorator';
|
||||
|
||||
export { reduxDecorator } from './redux_decorator';
|
||||
export { servicesContextDecorator } from './services_decorator';
|
||||
|
||||
export const addDecorators = () => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
|
@ -20,5 +21,5 @@ export const addDecorators = () => {
|
|||
|
||||
addDecorator(kibanaContextDecorator);
|
||||
addDecorator(routerContextDecorator);
|
||||
addDecorator(servicesContextDecorator);
|
||||
addDecorator(servicesContextDecorator());
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ elementsRegistry.register(image);
|
|||
import { getInitialState, getReducer, getMiddleware, patchDispatch } from '../addon/src/state';
|
||||
export { ADDON_ID, ACTIONS_PANEL_ID } from '../addon/src/constants';
|
||||
|
||||
interface Params {
|
||||
export interface Params {
|
||||
workpad?: CanvasWorkpad;
|
||||
elements?: CanvasElement[];
|
||||
assets?: CanvasAsset[];
|
||||
|
|
|
@ -7,8 +7,40 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { ServicesProvider } from '../../public/services';
|
||||
import {
|
||||
CanvasServiceFactory,
|
||||
CanvasServiceProvider,
|
||||
ServicesProvider,
|
||||
} from '../../public/services';
|
||||
import {
|
||||
findNoWorkpads,
|
||||
findSomeWorkpads,
|
||||
workpadService,
|
||||
findSomeTemplates,
|
||||
findNoTemplates,
|
||||
} from '../../public/services/stubs/workpad';
|
||||
import { WorkpadService } from '../../public/services/workpad';
|
||||
|
||||
export const servicesContextDecorator = (story: Function) => (
|
||||
<ServicesProvider>{story()}</ServicesProvider>
|
||||
);
|
||||
interface Params {
|
||||
findWorkpads?: number;
|
||||
findTemplates?: boolean;
|
||||
}
|
||||
|
||||
export const servicesContextDecorator = ({
|
||||
findWorkpads = 0,
|
||||
findTemplates: findTemplatesOption = false,
|
||||
}: Params = {}) => {
|
||||
const workpadServiceFactory: CanvasServiceFactory<WorkpadService> = (): WorkpadService => ({
|
||||
...workpadService,
|
||||
find: findWorkpads > 0 ? findSomeWorkpads(findWorkpads) : findNoWorkpads(),
|
||||
findTemplates: findTemplatesOption ? findSomeTemplates() : findNoTemplates(),
|
||||
});
|
||||
|
||||
const workpad = new CanvasServiceProvider(workpadServiceFactory);
|
||||
// @ts-expect-error This is a hack at the moment, until we can get Canvas moved over to the new services architecture.
|
||||
workpad.start();
|
||||
|
||||
return (story: Function) => (
|
||||
<ServicesProvider providers={{ workpad }}>{story()}</ServicesProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,3 +10,8 @@ import { ACTIONS_PANEL_ID } from './addon/src/constants';
|
|||
export * from './decorators';
|
||||
export { ACTIONS_PANEL_ID } from './addon/src/constants';
|
||||
export const getAddonPanelParameters = () => ({ options: { selectedPanel: ACTIONS_PANEL_ID } });
|
||||
export const getDisableStoryshotsParameter = () => ({
|
||||
storyshots: {
|
||||
disable: true,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -53,6 +53,11 @@ const canvasWebpack = {
|
|||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'src/plugins': resolve(KIBANA_ROOT, 'src/plugins'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Home/Empty Prompt Empty Prompt 1`] = `
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceAround euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
style={
|
||||
Object {
|
||||
"minHeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusNone euiPanel--subdued euiPanel--noShadow euiPanel--noBorder"
|
||||
>
|
||||
<div
|
||||
className="euiEmptyPrompt"
|
||||
color="subdued"
|
||||
>
|
||||
<span
|
||||
color="subdued"
|
||||
data-euiicon-type="importAction"
|
||||
size="xxl"
|
||||
/>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<h2
|
||||
className="euiTitle euiTitle--medium"
|
||||
>
|
||||
Add your first workpad
|
||||
</h2>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p>
|
||||
Create a new workpad, start from a template, or import a workpad JSON file by dropping it here.
|
||||
</p>
|
||||
<p>
|
||||
New to Canvas?
|
||||
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="home#/tutorial_directory/sampleData"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Add your first workpad
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -90,6 +90,11 @@ import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer'
|
|||
jest.mock('@elastic/eui/test-env/components/observer/observer');
|
||||
EuiObserver.mockImplementation(() => 'EuiObserver');
|
||||
|
||||
// @ts-expect-error untyped library
|
||||
import Dropzone from 'react-dropzone';
|
||||
jest.mock('react-dropzone');
|
||||
Dropzone.mockImplementation(() => 'Dropzone');
|
||||
|
||||
// This element uses a `ref` and cannot be rendered by Jest snapshots.
|
||||
import { RenderedElement } from '../shareable_runtime/components/rendered_element';
|
||||
jest.mock('../shareable_runtime/components/rendered_element');
|
||||
|
@ -111,7 +116,7 @@ addSerializer(styleSheetSerializer);
|
|||
|
||||
// Initialize Storyshots and build the Jest Snapshots
|
||||
initStoryshots({
|
||||
configPath: path.resolve(__dirname, './../storybook'),
|
||||
configPath: path.resolve(__dirname),
|
||||
framework: 'react',
|
||||
test: multiSnapshotWithOptions({}),
|
||||
// Don't snapshot tests that start with 'redux'
|
||||
|
|
|
@ -6107,18 +6107,18 @@
|
|||
"xpack.canvas.error.esService.indicesFetchErrorMessage": "Elasticsearch インデックスを取得できませんでした",
|
||||
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "「{functionName}」のレンダリングが失敗しました",
|
||||
"xpack.canvas.error.repeatImage.missingMaxArgument": "{emptyImageArgument} を指定する場合は、{maxArgument} を設定する必要があります",
|
||||
"xpack.canvas.error.workpadLoader.cloneFailureErrorMessage": "ワークパッドのクローンを作成できませんでした",
|
||||
"xpack.canvas.error.workpadLoader.deleteFailureErrorMessage": "すべてのワークパッドを削除できませんでした",
|
||||
"xpack.canvas.error.workpadLoader.findFailureErrorMessage": "ワークパッドが見つかりませんでした",
|
||||
"xpack.canvas.error.workpadLoader.uploadFailureErrorMessage": "ワークパッドをアップロードできませんでした",
|
||||
"xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "ワークパッドのクローンを作成できませんでした",
|
||||
"xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "ワークパッドをアップロードできませんでした",
|
||||
"xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "すべてのワークパッドを削除できませんでした",
|
||||
"xpack.canvas.error.useFindWorkpads.findFailureErrorMessage": "ワークパッドが見つかりませんでした",
|
||||
"xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage": "{JSON} 個のファイルしか受け付けられませんでした",
|
||||
"xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage": "ファイルをアップロードできませんでした",
|
||||
"xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} ワークパッドに必要なプロパティの一部が欠けています。 {JSON} ファイルを編集して正しいプロパティ値を入力し、再試行してください。",
|
||||
"xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "ワークパッドを作成できませんでした",
|
||||
"xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "ID でワークパッドを読み込めませんでした",
|
||||
"xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage": "{JSON} 個のファイルしか受け付けられませんでした",
|
||||
"xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage": "ファイルをアップロードできませんでした",
|
||||
"xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage": "{CANVAS} ワークパッドに必要なプロパティの一部が欠けています。 {JSON} ファイルを編集して正しいプロパティ値を入力し、再試行してください。",
|
||||
"xpack.canvas.errorComponent.description": "表現が失敗し次のメッセージが返されました:",
|
||||
"xpack.canvas.errorComponent.title": "おっと!表現が失敗しました",
|
||||
"xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage": "「{fileName}」をアップロードできませんでした",
|
||||
"xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "「{fileName}」をアップロードできませんでした",
|
||||
"xpack.canvas.expression.cancelButtonLabel": "キャンセル",
|
||||
"xpack.canvas.expression.closeButtonLabel": "閉じる",
|
||||
"xpack.canvas.expression.learnLinkText": "表現構文の詳細",
|
||||
|
@ -6452,6 +6452,12 @@
|
|||
"xpack.canvas.helpMenu.description": "{CANVAS} に関する情報",
|
||||
"xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} ドキュメント",
|
||||
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "キーボードショートカット",
|
||||
"xpack.canvas.home.myWorkpadsTabLabel": "マイワークパッド",
|
||||
"xpack.canvas.home.workpadTemplatesTabLabel": "テンプレート",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription": "新規ワークパッドを作成、テンプレートで開始、またはワークパッド {JSON} ファイルをここにドロップしてインポートします。",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription": "{CANVAS} を初めて使用する場合",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptTitle": "初の’ワークパッドを追加しましょう",
|
||||
"xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel": "初の’ワークパッドを追加しましょう",
|
||||
"xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText": "前に移動",
|
||||
"xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText": "表面に移動",
|
||||
"xpack.canvas.keyboardShortcuts.cloneShortcutHelpText": "クローンを作成",
|
||||
|
@ -6898,6 +6904,7 @@
|
|||
"xpack.canvas.units.quickRange.last90Days": "過去90日間",
|
||||
"xpack.canvas.units.quickRange.today": "今日",
|
||||
"xpack.canvas.units.quickRange.yesterday": "昨日",
|
||||
"xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} のコピー",
|
||||
"xpack.canvas.varConfig.addButtonLabel": "変数の追加",
|
||||
"xpack.canvas.varConfig.addTooltipLabel": "変数の追加",
|
||||
"xpack.canvas.varConfig.copyActionButtonLabel": "スニペットをコピー",
|
||||
|
@ -7024,40 +7031,30 @@
|
|||
"xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "ズーム",
|
||||
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "リセット",
|
||||
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
|
||||
"xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} のコピー",
|
||||
"xpack.canvas.workpadLoader.cloneTooltip": "ワークパッドのクローンを作成します",
|
||||
"xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "ワークパッドを作成中...",
|
||||
"xpack.canvas.workpadLoader.deleteButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドを削除",
|
||||
"xpack.canvas.workpadLoader.deleteButtonLabel": " ({numberOfWorkpads}) ワークパッドを削除",
|
||||
"xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel": "削除",
|
||||
"xpack.canvas.workpadLoader.deleteModalDescription": "削除されたワークパッドは復元できません。",
|
||||
"xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle": "{numberOfWorkpads} 個のワークパッドを削除しますか?",
|
||||
"xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle": "ワークパッド「{workpadName}」削除しますか?",
|
||||
"xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription": "新規ワークパッドを作成、テンプレートで開始、またはワークパッド {JSON} ファイルをここにドロップしてインポートします。",
|
||||
"xpack.canvas.workpadLoader.emptyPromptNewUserDescription": "{CANVAS} を初めて使用する場合",
|
||||
"xpack.canvas.workpadLoader.emptyPromptTitle": "初の’ワークパッドを追加しましょう",
|
||||
"xpack.canvas.workpadLoader.exportButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドをエクスポート",
|
||||
"xpack.canvas.workpadLoader.exportButtonLabel": "エクスポート ({numberOfWorkpads}) ",
|
||||
"xpack.canvas.workpadLoader.exportTooltip": "ワークパッドをエクスポート",
|
||||
"xpack.canvas.workpadLoader.fetchLoadingDescription": "ワークパッドを取得中...",
|
||||
"xpack.canvas.workpadLoader.filePickerPlaceholder": "ワークパッド {JSON} ファイルをインポート",
|
||||
"xpack.canvas.workpadLoader.loadWorkpadArialLabel": "ワークパッド「{workpadName}」を読み込む",
|
||||
"xpack.canvas.workpadLoader.noPermissionToCloneToolTip": "ワークパッドのクローンを作成するパーミッションがありません",
|
||||
"xpack.canvas.workpadLoader.noPermissionToCreateToolTip": "ワークパッドを作成するパーミッションがありません",
|
||||
"xpack.canvas.workpadLoader.noPermissionToDeleteToolTip": "ワークパッドを削除するパーミッションがありません",
|
||||
"xpack.canvas.workpadLoader.noPermissionToUploadToolTip": "ワークパッドを更新するパーミッションがありません",
|
||||
"xpack.canvas.workpadLoader.sampleDataLinkLabel": "初の’ワークパッドを追加しましょう",
|
||||
"xpack.canvas.workpadLoader.table.actionsColumnTitle": "アクション",
|
||||
"xpack.canvas.workpadLoader.table.createdColumnTitle": "作成済み",
|
||||
"xpack.canvas.workpadLoader.table.nameColumnTitle": "ワークパッド名",
|
||||
"xpack.canvas.workpadLoader.table.updatedColumnTitle": "更新しました",
|
||||
"xpack.canvas.workpadManager.modalTitle": "{CANVAS} ワークパッド",
|
||||
"xpack.canvas.workpadManager.myWorkpadsTabLabel": "マイワークパッド",
|
||||
"xpack.canvas.workpadManager.workpadTemplatesTabLabel": "テンプレート",
|
||||
"xpack.canvas.workpadSearch.searchPlaceholder": "ワークパッドを検索",
|
||||
"xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel": "ワークパッドテンプレート「{templateName}」のクローンを作成",
|
||||
"xpack.canvas.workpadTemplate.creatingTemplateLabel": "テンプレート「{templateName}」から作成しています",
|
||||
"xpack.canvas.workpadTemplate.searchPlaceholder": "テンプレートを検索",
|
||||
"xpack.canvas.workpadImport.filePickerPlaceholder": "ワークパッド {JSON} ファイルをインポート",
|
||||
"xpack.canvas.workpadTable.cloneTooltip": "ワークパッドのクローンを作成します",
|
||||
"xpack.canvas.workpadTable.exportTooltip": "ワークパッドをエクスポート",
|
||||
"xpack.canvas.workpadTable.loadWorkpadArialLabel": "ワークパッド「{workpadName}」を読み込む",
|
||||
"xpack.canvas.workpadTable.noPermissionToCloneToolTip": "ワークパッドのクローンを作成するパーミッションがありません",
|
||||
"xpack.canvas.workpadTable.searchPlaceholder": "ワークパッドを検索",
|
||||
"xpack.canvas.workpadTable.table.actionsColumnTitle": "アクション",
|
||||
"xpack.canvas.workpadTable.table.createdColumnTitle": "作成済み",
|
||||
"xpack.canvas.workpadTable.table.nameColumnTitle": "ワークパッド名",
|
||||
"xpack.canvas.workpadTable.table.updatedColumnTitle": "更新しました",
|
||||
"xpack.canvas.workpadTableTools.deleteButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドを削除",
|
||||
"xpack.canvas.workpadTableTools.deleteButtonLabel": " ({numberOfWorkpads}) ワークパッドを削除",
|
||||
"xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel": "削除",
|
||||
"xpack.canvas.workpadTableTools.deleteModalDescription": "削除されたワークパッドは復元できません。",
|
||||
"xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle": "{numberOfWorkpads} 個のワークパッドを削除しますか?",
|
||||
"xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle": "ワークパッド「{workpadName}」削除しますか?",
|
||||
"xpack.canvas.workpadTableTools.exportButtonAriaLabel": "{numberOfWorkpads} 個のワークパッドをエクスポート",
|
||||
"xpack.canvas.workpadTableTools.exportButtonLabel": "エクスポート ({numberOfWorkpads}) ",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToCreateToolTip": "ワークパッドを作成するパーミッションがありません",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip": "ワークパッドを削除するパーミッションがありません",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToUploadToolTip": "ワークパッドを更新するパーミッションがありません",
|
||||
"xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel": "ワークパッドテンプレート「{templateName}」のクローンを作成",
|
||||
"xpack.canvas.workpadTemplates.creatingTemplateLabel": "テンプレート「{templateName}」から作成しています",
|
||||
"xpack.canvas.workpadTemplates.searchPlaceholder": "テンプレートを検索",
|
||||
"xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "説明",
|
||||
"xpack.canvas.workpadTemplates.table.nameColumnTitle": "テンプレート名",
|
||||
"xpack.canvas.workpadTemplates.table.tagsColumnTitle": "タグ",
|
||||
|
|
|
@ -6146,18 +6146,18 @@
|
|||
"xpack.canvas.error.esService.indicesFetchErrorMessage": "无法提取 Elasticsearch 索引",
|
||||
"xpack.canvas.error.RenderWithFn.renderErrorMessage": "呈现“{functionName}”失败。",
|
||||
"xpack.canvas.error.repeatImage.missingMaxArgument": "如果提供 {emptyImageArgument},则必须设置 {maxArgument}",
|
||||
"xpack.canvas.error.workpadLoader.cloneFailureErrorMessage": "无法克隆 Workpad",
|
||||
"xpack.canvas.error.workpadLoader.deleteFailureErrorMessage": "无法删除所有 Workpad",
|
||||
"xpack.canvas.error.workpadLoader.findFailureErrorMessage": "无法查找 Workpad",
|
||||
"xpack.canvas.error.workpadLoader.uploadFailureErrorMessage": "无法上传 Workpad",
|
||||
"xpack.canvas.error.useCloneWorkpad.cloneFailureErrorMessage": "无法克隆 Workpad",
|
||||
"xpack.canvas.error.useCreateWorkpad.uploadFailureErrorMessage": "无法上传 Workpad",
|
||||
"xpack.canvas.error.useDeleteWorkpads.deleteFailureErrorMessage": "无法删除所有 Workpad",
|
||||
"xpack.canvas.error.useFindWorkpads.findFailureErrorMessage": "无法查找 Workpad",
|
||||
"xpack.canvas.error.useImportWorkpad.acceptJSONOnlyErrorMessage": "仅接受 {JSON} 文件",
|
||||
"xpack.canvas.error.useImportWorkpad.fileUploadFailureWithoutFileNameErrorMessage": "无法上传文件",
|
||||
"xpack.canvas.error.useImportWorkpad.missingPropertiesErrorMessage": "{CANVAS} Workpad 所需的某些属性缺失。 编辑 {JSON} 文件以提供正确的属性值,然后重试。",
|
||||
"xpack.canvas.error.workpadRoutes.createFailureErrorMessage": "无法创建 Workpad",
|
||||
"xpack.canvas.error.workpadRoutes.loadFailureErrorMessage": "无法加载具有以下 ID 的 Workpad",
|
||||
"xpack.canvas.error.workpadUpload.acceptJSONOnlyErrorMessage": "仅接受 {JSON} 文件",
|
||||
"xpack.canvas.error.workpadUpload.fileUploadFailureWithoutFileNameErrorMessage": "无法上传文件",
|
||||
"xpack.canvas.error.workpadUpload.missingPropertiesErrorMessage": "{CANVAS} Workpad 所需的某些属性缺失。 编辑 {JSON} 文件以提供正确的属性值,然后重试。",
|
||||
"xpack.canvas.errorComponent.description": "表达式失败,并显示消息:",
|
||||
"xpack.canvas.errorComponent.title": "哎哟!表达式失败",
|
||||
"xpack.canvas.errors.workpadUpload.fileUploadFileWithFileNameErrorMessage": "无法上传“{fileName}”",
|
||||
"xpack.canvas.errors.useImportWorkpad.fileUploadFileWithFileNameErrorMessage": "无法上传“{fileName}”",
|
||||
"xpack.canvas.expression.cancelButtonLabel": "取消",
|
||||
"xpack.canvas.expression.closeButtonLabel": "关闭",
|
||||
"xpack.canvas.expression.learnLinkText": "学习表达式语法",
|
||||
|
@ -6492,6 +6492,12 @@
|
|||
"xpack.canvas.helpMenu.description": "有关 {CANVAS} 特定信息",
|
||||
"xpack.canvas.helpMenu.documentationLinkLabel": "{CANVAS} 文档",
|
||||
"xpack.canvas.helpMenu.keyboardShortcutsLinkLabel": "快捷键",
|
||||
"xpack.canvas.home.myWorkpadsTabLabel": "我的 Workpad",
|
||||
"xpack.canvas.home.workpadTemplatesTabLabel": "模板",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptGettingStartedDescription": "创建新的 Workpad、从模板入手或通过将 Workpad {JSON} 文件拖放到此处来导入。",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptNewUserDescription": "{CANVAS} 新手?",
|
||||
"xpack.canvas.homeEmptyPrompt.emptyPromptTitle": "添加您的首个 Workpad",
|
||||
"xpack.canvas.homeEmptyPrompt.sampleDataLinkLabel": "添加您的首个 Workpad",
|
||||
"xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText": "前移",
|
||||
"xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText": "置前",
|
||||
"xpack.canvas.keyboardShortcuts.cloneShortcutHelpText": "克隆",
|
||||
|
@ -6942,6 +6948,7 @@
|
|||
"xpack.canvas.units.time.hours": "{hours, plural, other {# 小时}}",
|
||||
"xpack.canvas.units.time.minutes": "{minutes, plural, other {# 分钟}}",
|
||||
"xpack.canvas.units.time.seconds": "{seconds, plural, other {# 秒}}",
|
||||
"xpack.canvas.useCloneWorkpad.clonedWorkpadName": "{workpadName} 副本",
|
||||
"xpack.canvas.varConfig.addButtonLabel": "添加变量",
|
||||
"xpack.canvas.varConfig.addTooltipLabel": "添加变量",
|
||||
"xpack.canvas.varConfig.copyActionButtonLabel": "复制代码片段",
|
||||
|
@ -7072,40 +7079,30 @@
|
|||
"xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "缩放",
|
||||
"xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "重置",
|
||||
"xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%",
|
||||
"xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} 副本",
|
||||
"xpack.canvas.workpadLoader.cloneTooltip": "克隆 Workpad",
|
||||
"xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "正在创建 Workpad......",
|
||||
"xpack.canvas.workpadLoader.deleteButtonAriaLabel": "删除 {numberOfWorkpads} 个 Workpad",
|
||||
"xpack.canvas.workpadLoader.deleteButtonLabel": "删除 ({numberOfWorkpads})",
|
||||
"xpack.canvas.workpadLoader.deleteModalConfirmButtonLabel": "删除",
|
||||
"xpack.canvas.workpadLoader.deleteModalDescription": "您无法恢复删除的 Workpad。",
|
||||
"xpack.canvas.workpadLoader.deleteMultipleWorkpadsModalTitle": "删除 {numberOfWorkpads} 个 Workpad?",
|
||||
"xpack.canvas.workpadLoader.deleteSingleWorkpadModalTitle": "删除 Workpad“{workpadName}”?",
|
||||
"xpack.canvas.workpadLoader.emptyPromptGettingStartedDescription": "创建新的 Workpad、从模板入手或通过将 Workpad {JSON} 文件拖放到此处来导入。",
|
||||
"xpack.canvas.workpadLoader.emptyPromptNewUserDescription": "{CANVAS} 新手?",
|
||||
"xpack.canvas.workpadLoader.emptyPromptTitle": "添加您的首个 Workpad",
|
||||
"xpack.canvas.workpadLoader.exportButtonAriaLabel": "导出 {numberOfWorkpads} 个 Workpad",
|
||||
"xpack.canvas.workpadLoader.exportButtonLabel": "导出 ({numberOfWorkpads})",
|
||||
"xpack.canvas.workpadLoader.exportTooltip": "导出 Workpad",
|
||||
"xpack.canvas.workpadLoader.fetchLoadingDescription": "正在获取 Workpad......",
|
||||
"xpack.canvas.workpadLoader.filePickerPlaceholder": "导入 Workpad {JSON} 文件",
|
||||
"xpack.canvas.workpadLoader.loadWorkpadArialLabel": "加载 Workpad“{workpadName}”",
|
||||
"xpack.canvas.workpadLoader.noPermissionToCloneToolTip": "您无权克隆 Workpad",
|
||||
"xpack.canvas.workpadLoader.noPermissionToCreateToolTip": "您无权创建 Workpad",
|
||||
"xpack.canvas.workpadLoader.noPermissionToDeleteToolTip": "您无权删除 Workpad",
|
||||
"xpack.canvas.workpadLoader.noPermissionToUploadToolTip": "您无权上传 Workpad",
|
||||
"xpack.canvas.workpadLoader.sampleDataLinkLabel": "添加您的首个 Workpad",
|
||||
"xpack.canvas.workpadLoader.table.actionsColumnTitle": "操作",
|
||||
"xpack.canvas.workpadLoader.table.createdColumnTitle": "创建时间",
|
||||
"xpack.canvas.workpadLoader.table.nameColumnTitle": "Workpad 名称",
|
||||
"xpack.canvas.workpadLoader.table.updatedColumnTitle": "更新时间",
|
||||
"xpack.canvas.workpadManager.modalTitle": "{CANVAS} Workpad",
|
||||
"xpack.canvas.workpadManager.myWorkpadsTabLabel": "我的 Workpad",
|
||||
"xpack.canvas.workpadManager.workpadTemplatesTabLabel": "模板",
|
||||
"xpack.canvas.workpadSearch.searchPlaceholder": "查找 Workpad",
|
||||
"xpack.canvas.workpadTemplate.cloneTemplateLinkAriaLabel": "克隆 Workpad 模板“{templateName}”",
|
||||
"xpack.canvas.workpadTemplate.creatingTemplateLabel": "正在从模板“{templateName}”创建",
|
||||
"xpack.canvas.workpadTemplate.searchPlaceholder": "查找模板",
|
||||
"xpack.canvas.workpadImport.filePickerPlaceholder": "导入 Workpad {JSON} 文件",
|
||||
"xpack.canvas.workpadTable.searchPlaceholder": "查找 Workpad",
|
||||
"xpack.canvas.workpadTable.cloneTooltip": "克隆 Workpad",
|
||||
"xpack.canvas.workpadTable.exportTooltip": "导出 Workpad",
|
||||
"xpack.canvas.workpadTable.loadWorkpadArialLabel": "加载 Workpad“{workpadName}”",
|
||||
"xpack.canvas.workpadTable.noPermissionToCloneToolTip": "您无权克隆 Workpad",
|
||||
"xpack.canvas.workpadTable.table.actionsColumnTitle": "操作",
|
||||
"xpack.canvas.workpadTable.table.createdColumnTitle": "创建时间",
|
||||
"xpack.canvas.workpadTable.table.nameColumnTitle": "Workpad 名称",
|
||||
"xpack.canvas.workpadTable.table.updatedColumnTitle": "更新时间",
|
||||
"xpack.canvas.workpadTableTools.deleteButtonAriaLabel": "删除 {numberOfWorkpads} 个 Workpad",
|
||||
"xpack.canvas.workpadTableTools.deleteButtonLabel": "删除 ({numberOfWorkpads})",
|
||||
"xpack.canvas.workpadTableTools.deleteModalConfirmButtonLabel": "删除",
|
||||
"xpack.canvas.workpadTableTools.deleteModalDescription": "您无法恢复删除的 Workpad。",
|
||||
"xpack.canvas.workpadTableTools.deleteMultipleWorkpadsModalTitle": "删除 {numberOfWorkpads} 个 Workpad?",
|
||||
"xpack.canvas.workpadTableTools.deleteSingleWorkpadModalTitle": "删除 Workpad“{workpadName}”?",
|
||||
"xpack.canvas.workpadTableTools.exportButtonAriaLabel": "导出 {numberOfWorkpads} 个 Workpad",
|
||||
"xpack.canvas.workpadTableTools.exportButtonLabel": "导出 ({numberOfWorkpads})",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToCreateToolTip": "您无权创建 Workpad",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToDeleteToolTip": "您无权删除 Workpad",
|
||||
"xpack.canvas.workpadTableTools.noPermissionToUploadToolTip": "您无权上传 Workpad",
|
||||
"xpack.canvas.workpadTemplates.cloneTemplateLinkAriaLabel": "克隆 Workpad 模板“{templateName}”",
|
||||
"xpack.canvas.workpadTemplates.creatingTemplateLabel": "正在从模板“{templateName}”创建",
|
||||
"xpack.canvas.workpadTemplates.searchPlaceholder": "查找模板",
|
||||
"xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "描述",
|
||||
"xpack.canvas.workpadTemplates.table.nameColumnTitle": "模板名称",
|
||||
"xpack.canvas.workpadTemplates.table.tagsColumnTitle": "标签",
|
||||
|
|
|
@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('loads workpads', async function () {
|
||||
await retry.waitFor(
|
||||
'canvas workpads visible',
|
||||
async () => await testSubjects.exists('canvasWorkpadLoaderTable')
|
||||
async () => await testSubjects.exists('canvasWorkpadTable')
|
||||
);
|
||||
await a11y.testAppSnapshot();
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) {
|
|||
|
||||
describe('smoke test', function () {
|
||||
this.tags('includeFirefox');
|
||||
const workpadListSelector = 'canvasWorkpadLoaderTable > canvasWorkpadLoaderWorkpad';
|
||||
const workpadListSelector = 'canvasWorkpadTable > canvasWorkpadTableWorkpad';
|
||||
const testWorkpadId = 'workpad-1705f884-6224-47de-ba49-ca224fe6ec31';
|
||||
|
||||
before(async () => {
|
||||
|
|
|
@ -39,7 +39,7 @@ export function CanvasPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
* to load the workpad. Resolves once the workpad is in the DOM
|
||||
*/
|
||||
async loadFirstWorkpad(workpadName: string) {
|
||||
const elem = await testSubjects.find('canvasWorkpadLoaderWorkpad');
|
||||
const elem = await testSubjects.find('canvasWorkpadTableWorkpad');
|
||||
const text = await elem.getVisibleText();
|
||||
expect(text).to.be(workpadName);
|
||||
await elem.click();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue