[data view mgmt] Delete data views from list (#127557)

* Delete data views from list
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Matthew Kime 2022-03-21 10:56:43 -05:00 committed by GitHub
parent 2adb417a9b
commit a6ec64f104
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 545 additions and 56 deletions

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { filter } from 'lodash';
import React, { useEffect, useState, useCallback } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import {
@ -22,11 +21,12 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataView, DataViewField } from '../../../../../plugins/data_views/public';
import { useKibana, toMountPoint } from '../../../../../plugins/kibana_react/public';
import { useKibana } from '../../../../../plugins/kibana_react/public';
import { IndexPatternManagmentContext } from '../../types';
import { Tabs } from './tabs';
import { IndexHeader } from './index_header';
import { getTags } from '../utils';
import { removeDataView, RemoveDataViewProps } from './remove_data_view';
export interface EditIndexPatternProps extends RouteComponentProps {
indexPattern: DataView;
@ -46,15 +46,6 @@ const mappingConflictHeader = i18n.translate(
}
);
const confirmModalOptionsDelete = {
confirmButtonText: i18n.translate('indexPatternManagement.editIndexPattern.deleteButton', {
defaultMessage: 'Delete',
}),
title: i18n.translate('indexPatternManagement.editDataView.deleteHeader', {
defaultMessage: 'Delete data view',
}),
};
const securityDataView = i18n.translate(
'indexPatternManagement.editIndexPattern.badge.securityDataViewTitle',
{
@ -91,47 +82,14 @@ export const EditIndexPattern = withRouter(
setDefaultIndex(indexPattern.id || '');
}, [uiSettings, indexPattern.id]);
const removePattern = () => {
async function doRemove() {
if (indexPattern.id === defaultIndex) {
const indexPatterns = await dataViews.getIdsWithTitle();
uiSettings.remove('defaultIndex');
const otherPatterns = filter(indexPatterns, (pattern) => {
return pattern.id !== indexPattern.id;
});
if (otherPatterns.length) {
uiSettings.set('defaultIndex', otherPatterns[0].id);
}
}
if (indexPattern.id) {
Promise.resolve(dataViews.delete(indexPattern.id)).then(function () {
history.push('');
});
}
}
const warning =
indexPattern.namespaces.length > 1 || indexPattern.namespaces.includes('*') ? (
<FormattedMessage
id="indexPatternManagement.editDataView.deleteWarning"
defaultMessage="When you delete {dataViewName}, you remove it from every space it is shared in. You can't undo this action."
values={{
dataViewName: <EuiCode>{indexPattern.title}</EuiCode>,
}}
/>
) : (
''
);
overlays
.openConfirm(toMountPoint(<div>{warning}</div>), confirmModalOptionsDelete)
.then((isConfirmed) => {
if (isConfirmed) {
doRemove();
}
});
};
const removeHandler = removeDataView({
dataViews,
uiSettings,
overlays,
onDelete: () => {
history.push('');
},
});
const timeFilterHeader = i18n.translate(
'indexPatternManagement.editIndexPattern.timeFilterHeader',
@ -161,12 +119,34 @@ export const EditIndexPattern = withRouter(
const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping;
const userEditPermission = dataViews.getCanSaveSync();
const warning =
(indexPattern.namespaces && indexPattern.namespaces.length > 1) ||
indexPattern.namespaces.includes('*') ? (
<FormattedMessage
id="indexPatternManagement.editDataView.deleteWarningWithNamespaces"
defaultMessage="Delete the data view {dataViewName} from every space it is shared in. You can't undo this action."
values={{
dataViewName: <EuiCode>{indexPattern.title}</EuiCode>,
}}
/>
) : (
<FormattedMessage
id="indexPatternManagement.editDataView.deleteWarning"
defaultMessage="The data view {dataViewName} will be deleted. You can't undo this action."
values={{
dataViewName: <EuiCode>{indexPattern.title}</EuiCode>,
}}
/>
);
return (
<div data-test-subj="editIndexPattern" role="region" aria-label={headingAriaLabel}>
<IndexHeader
indexPattern={indexPattern}
setDefault={setDefaultPattern}
deleteIndexPatternClick={removePattern}
deleteIndexPatternClick={() =>
removeHandler([indexPattern as RemoveDataViewProps], <div>{warning}</div>)
}
defaultIndex={defaultIndex}
canSave={userEditPermission}
>

View file

@ -10,3 +10,5 @@ export { EditIndexPattern } from './edit_index_pattern';
export { EditIndexPatternContainer } from './edit_index_pattern_container';
export { CreateEditField } from './create_edit_field';
export { CreateEditFieldContainer } from './create_edit_field/create_edit_field_container';
export type { RemoveDataViewProps } from './remove_data_view';
export { removeDataView } from './remove_data_view';

View file

@ -0,0 +1,48 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { IUiSettingsClient, OverlayStart } from 'src/core/public';
import { asyncForEach } from '@kbn/std';
import { EuiConfirmModalProps } from '@elastic/eui';
import { toMountPoint } from '../../../../../plugins/kibana_react/public';
import { DataViewsPublicPluginStart } from '../../../../../plugins/data_views/public';
const confirmModalOptionsDelete = {
confirmButtonText: i18n.translate('indexPatternManagement.editIndexPattern.deleteButton', {
defaultMessage: 'Delete',
}),
title: i18n.translate('indexPatternManagement.editDataView.deleteHeader', {
defaultMessage: 'Delete data view',
}),
buttonColor: 'danger' as EuiConfirmModalProps['buttonColor'],
};
export interface RemoveDataViewProps {
id: string;
title: string;
namespaces?: string[] | undefined;
}
interface RemoveDataViewDeps {
dataViews: DataViewsPublicPluginStart;
uiSettings: IUiSettingsClient;
overlays: OverlayStart;
onDelete: () => void;
}
export const removeDataView =
({ dataViews, overlays, onDelete }: RemoveDataViewDeps) =>
(dataViewArray: RemoveDataViewProps[], msg: JSX.Element) => {
overlays.openConfirm(toMountPoint(msg), confirmModalOptionsDelete).then(async (isConfirmed) => {
if (isConfirmed) {
await asyncForEach(dataViewArray, async ({ id }) => dataViews.delete(id));
onDelete();
}
});
};

View file

@ -0,0 +1,274 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`delete modal content render 1`] = `
<div>
<EuiCallOut
color="warning"
iconType="alert"
title="Data views are deleted from every space they are shared in."
/>
<EuiSpacer
size="m"
/>
<div>
<FormattedMessage
defaultMessage="You'll permanently delete {count, number} {count, plural, one {data view} other {data views}
}."
id="indexPatternManagement.dataViewTable.deleteConfirmSummary"
values={
Object {
"count": 1,
}
}
/>
</div>
<EuiSpacer
size="m"
/>
<EuiBasicTable
columns={
Array [
Object {
"field": "title",
"name": "Data view",
"sortable": true,
},
Object {
"align": "right",
"field": "namespaces",
"name": "Spaces",
"render": [Function],
"sortable": true,
"width": "100px",
},
]
}
items={
Array [
Object {
"id": "1",
"namespaces": Array [
"a",
"b",
"c",
],
"title": "logstash-*",
},
]
}
noItemsMessage={
<EuiI18n
default="No items found"
token="euiBasicTable.noItemsMessage"
/>
}
responsive={true}
tableCaption="Data views selected for deletion"
tableLayout="fixed"
/>
</div>
`;
exports[`delete modal content render 2`] = `
<div>
<EuiCallOut
color="warning"
iconType="alert"
title="Data views are deleted from every space they are shared in."
/>
<EuiSpacer
size="m"
/>
<div>
<FormattedMessage
defaultMessage="You'll permanently delete {count, number} {count, plural, one {data view} other {data views}
}."
id="indexPatternManagement.dataViewTable.deleteConfirmSummary"
values={
Object {
"count": 1,
}
}
/>
</div>
<EuiSpacer
size="m"
/>
<EuiBasicTable
columns={
Array [
Object {
"field": "title",
"name": "Data view",
"sortable": true,
},
]
}
items={
Array [
Object {
"id": "1",
"namespaces": Array [
"a",
"b",
"c",
],
"title": "logstash-*",
},
]
}
noItemsMessage={
<EuiI18n
default="No items found"
token="euiBasicTable.noItemsMessage"
/>
}
responsive={true}
tableCaption="Data views selected for deletion"
tableLayout="fixed"
/>
</div>
`;
exports[`delete modal content render 3`] = `
<div>
<EuiCallOut
color="warning"
iconType="alert"
title="Data views are deleted from every space they are shared in."
/>
<EuiSpacer
size="m"
/>
<div>
<FormattedMessage
defaultMessage="You'll permanently delete {count, number} {count, plural, one {data view} other {data views}
}."
id="indexPatternManagement.dataViewTable.deleteConfirmSummary"
values={
Object {
"count": 1,
}
}
/>
</div>
<EuiSpacer
size="m"
/>
<EuiBasicTable
columns={
Array [
Object {
"field": "title",
"name": "Data view",
"sortable": true,
},
Object {
"align": "right",
"field": "namespaces",
"name": "Spaces",
"render": [Function],
"sortable": true,
"width": "100px",
},
]
}
items={
Array [
Object {
"id": "1",
"namespaces": Array [
"*",
],
"title": "logstash-*",
},
]
}
noItemsMessage={
<EuiI18n
default="No items found"
token="euiBasicTable.noItemsMessage"
/>
}
responsive={true}
tableCaption="Data views selected for deletion"
tableLayout="fixed"
/>
</div>
`;
exports[`delete modal content render 4`] = `
<div>
<EuiCallOut
color="warning"
iconType="alert"
title="Data views are deleted from every space they are shared in."
/>
<EuiSpacer
size="m"
/>
<div>
<FormattedMessage
defaultMessage="You'll permanently delete {count, number} {count, plural, one {data view} other {data views}
}."
id="indexPatternManagement.dataViewTable.deleteConfirmSummary"
values={
Object {
"count": 2,
}
}
/>
</div>
<EuiSpacer
size="m"
/>
<EuiBasicTable
columns={
Array [
Object {
"field": "title",
"name": "Data view",
"sortable": true,
},
Object {
"align": "right",
"field": "namespaces",
"name": "Spaces",
"render": [Function],
"sortable": true,
"width": "100px",
},
]
}
items={
Array [
Object {
"id": "1",
"namespaces": Array [
"*",
],
"title": "logstash-*",
},
Object {
"id": "1",
"namespaces": Array [
"a",
"b",
"c",
],
"title": "log*",
},
]
}
noItemsMessage={
<EuiI18n
default="No items found"
token="euiBasicTable.noItemsMessage"
/>
}
responsive={true}
tableCaption="Data views selected for deletion"
tableLayout="fixed"
/>
</div>
`;

View file

@ -0,0 +1,28 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { deleteModalMsg } from './delete_modal_msg';
describe('delete modal content', () => {
const toDVProps = (title: string, namespaces: string[]) => {
return {
id: '1',
title,
namespaces,
};
};
test('render', () => {
expect(deleteModalMsg([toDVProps('logstash-*', ['a', 'b', 'c'])], true)).toMatchSnapshot();
expect(deleteModalMsg([toDVProps('logstash-*', ['a', 'b', 'c'])], false)).toMatchSnapshot();
expect(deleteModalMsg([toDVProps('logstash-*', ['*'])], true)).toMatchSnapshot();
expect(
deleteModalMsg([toDVProps('logstash-*', ['*']), toDVProps('log*', ['a', 'b', 'c'])], true)
).toMatchSnapshot();
});
});

View file

@ -0,0 +1,75 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiCallOut, EuiTableFieldDataColumnType, EuiBasicTable, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { RemoveDataViewProps } from '../edit_index_pattern';
const all = i18n.translate('indexPatternManagement.dataViewTable.spaceCountAll', {
defaultMessage: 'all',
});
const dataViewColumnName = i18n.translate(
'indexPatternManagement.dataViewTable.dataViewColumnName',
{
defaultMessage: 'Data view',
}
);
const spacesColumnName = i18n.translate('indexPatternManagement.dataViewTable.spacesColumnName', {
defaultMessage: 'Spaces',
});
const tableTitle = i18n.translate('indexPatternManagement.dataViewTable.tableTitle', {
defaultMessage: 'Data views selected for deletion',
});
export const deleteModalMsg = (views: RemoveDataViewProps[], hasSpaces: boolean) => {
const columns: Array<EuiTableFieldDataColumnType<any>> = [
{
field: 'title',
name: dataViewColumnName,
sortable: true,
},
];
if (hasSpaces) {
columns.push({
field: 'namespaces',
name: spacesColumnName,
sortable: true,
width: '100px',
align: 'right',
render: (namespaces: string[]) => (namespaces.indexOf('*') !== -1 ? all : namespaces.length),
});
}
return (
<div>
<EuiCallOut
color="warning"
iconType="alert"
title="Data views are deleted from every space they are shared in."
/>
<EuiSpacer size="m" />
<div>
<FormattedMessage
id="indexPatternManagement.dataViewTable.deleteConfirmSummary"
defaultMessage="You'll permanently delete {count, number} {count, plural,
one {data view}
other {data views}
}."
values={{ count: views.length }}
/>
</div>
<EuiSpacer size="m" />
<EuiBasicTable tableCaption={tableTitle} items={views} columns={columns} />
</div>
);
};

View file

@ -13,6 +13,7 @@ import {
EuiInMemoryTable,
EuiPageHeader,
EuiSpacer,
EuiBasicTableColumn,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { RouteComponentProps, withRouter, useLocation } from 'react-router-dom';
@ -25,6 +26,8 @@ import { getIndexPatterns } from '../utils';
import { getListBreadcrumbs } from '../breadcrumbs';
import { SpacesList } from './spaces_list';
import type { SpacesContextProps } from '../../../../../../x-pack/plugins/spaces/public';
import { removeDataView, RemoveDataViewProps } from '../edit_index_pattern';
import { deleteModalMsg } from './delete_modal_msg';
const pagination = {
initialPageSize: 10,
@ -71,11 +74,13 @@ export const IndexPatternTable = ({
dataViews,
IndexPatternEditor,
spaces,
overlays,
} = useKibana<IndexPatternManagmentContext>().services;
const [query, setQuery] = useState('');
const [indexPatterns, setIndexPatterns] = useState<IndexPatternTableItem[]>([]);
const [isLoadingIndexPatterns, setIsLoadingIndexPatterns] = useState<boolean>(true);
const [showCreateDialog, setShowCreateDialog] = useState<boolean>(showCreateDialogProp);
const [selectedItems, setSelectedItems] = useState<IndexPatternTableItem[]>([]);
const handleOnChange = ({ queryText, error }: { queryText: string; error: unknown }) => {
if (!error) {
@ -83,7 +88,38 @@ export const IndexPatternTable = ({
}
};
const renderDeleteButton = () => {
const clickHandler = removeDataView({
dataViews,
overlays,
uiSettings,
onDelete: () => loadDataViews(),
});
if (selectedItems.length === 0) {
return;
}
return (
<EuiButton
color="danger"
iconType="trash"
onClick={() => clickHandler(selectedItems, deleteModalMsg(selectedItems, !!spaces))}
>
<FormattedMessage
id="indexPatternManagement.dataViewTable.deleteButtonLabel"
defaultMessage="Delete {selectedItems, number} {selectedItems, plural,
one {Data View}
other {Data Views}
}"
values={{ selectedItems: selectedItems.length }}
/>
</EuiButton>
);
};
const deleteButton = renderDeleteButton();
const search = {
toolsLeft: deleteButton && [deleteButton],
query,
onChange: handleOnChange,
box: {
@ -127,12 +163,46 @@ export const IndexPatternTable = ({
[spaces]
);
const columns = [
const removeHandler = removeDataView({
dataViews,
uiSettings,
overlays,
onDelete: () => loadDataViews(),
});
const alertColumn = {
name: 'Actions',
field: 'id',
width: '10%',
actions: [
{
name: i18n.translate('indexPatternManagement.dataViewTable.columnDelete', {
defaultMessage: 'Delete',
}),
description: i18n.translate(
'indexPatternManagement.dataViewTable.columnDeleteDescription',
{
defaultMessage: 'Delete this data view',
}
),
icon: 'trash',
color: 'danger',
type: 'icon',
onClick: (dataView: RemoveDataViewProps) =>
removeHandler([dataView], deleteModalMsg([dataView], !!spaces)),
isPrimary: true,
'data-test-subj': 'action-delete',
},
],
};
const columns: Array<EuiBasicTableColumn<IndexPatternTableItem>> = [
{
field: 'title',
name: i18n.translate('indexPatternManagement.dataViewTable.nameColumn', {
defaultMessage: 'Name',
}),
width: '70%',
render: (name: string, dataView: IndexPatternTableItem) => (
<div>
<EuiLink {...reactRouterNavigate(history, `patterns/${dataView.id}`)}>{name}</EuiLink>
@ -153,7 +223,10 @@ export const IndexPatternTable = ({
},
{
field: 'namespaces',
name: 'spaces',
name: i18n.translate('indexPatternManagement.dataViewTable.spacesColumn', {
defaultMessage: 'Spaces',
}),
width: '20%',
render: (name: string, dataView: IndexPatternTableItem) => {
return spaces ? (
<SpacesList
@ -173,6 +246,10 @@ export const IndexPatternTable = ({
},
];
if (dataViews.getCanSaveSync()) {
columns.push(alertColumn);
}
const createButton = canSave ? (
<EuiButton
fill={true}
@ -205,6 +282,10 @@ export const IndexPatternTable = ({
<></>
);
const selection = {
onSelectionChange: setSelectedItems,
};
return (
<div data-test-subj="indexPatternTable" role="region" aria-label={title}>
<EuiPageHeader
@ -224,12 +305,13 @@ export const IndexPatternTable = ({
<EuiInMemoryTable
allowNeutralSort={false}
itemId="id"
isSelectable={false}
isSelectable={dataViews.getCanSaveSync()}
items={indexPatterns}
columns={columns}
pagination={pagination}
sorting={sorting}
search={search}
selection={selection}
/>
</ContextWrapper>
{displayIndexPatternEditor}