parent
7c9077fdc5
commit
34a7dae336
|
@ -253,6 +253,70 @@
|
|||
},
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"id": "def-public.OpenFieldDeleteModalOptions",
|
||||
"type": "Interface",
|
||||
"label": "OpenFieldDeleteModalOptions",
|
||||
"description": [],
|
||||
"tags": [],
|
||||
"children": [
|
||||
{
|
||||
"tags": [],
|
||||
"id": "def-public.OpenFieldDeleteModalOptions.ctx",
|
||||
"type": "Object",
|
||||
"label": "ctx",
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/open_delete_modal.tsx",
|
||||
"lineNumber": 25
|
||||
},
|
||||
"signature": [
|
||||
"{ indexPattern: ",
|
||||
{
|
||||
"pluginId": "data",
|
||||
"scope": "common",
|
||||
"docId": "kibDataIndexPatternsPluginApi",
|
||||
"section": "def-common.IndexPattern",
|
||||
"text": "IndexPattern"
|
||||
},
|
||||
"; }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tags": [],
|
||||
"id": "def-public.OpenFieldDeleteModalOptions.onDelete",
|
||||
"type": "Function",
|
||||
"label": "onDelete",
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/open_delete_modal.tsx",
|
||||
"lineNumber": 28
|
||||
},
|
||||
"signature": [
|
||||
"((fieldNames: string[]) => void) | undefined"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tags": [],
|
||||
"id": "def-public.OpenFieldDeleteModalOptions.fieldName",
|
||||
"type": "CompoundType",
|
||||
"label": "fieldName",
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/open_delete_modal.tsx",
|
||||
"lineNumber": 29
|
||||
},
|
||||
"signature": [
|
||||
"string | string[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/open_delete_modal.tsx",
|
||||
"lineNumber": 24
|
||||
},
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"id": "def-public.OpenFieldEditorOptions",
|
||||
"type": "Interface",
|
||||
|
@ -369,7 +433,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 26
|
||||
"lineNumber": 27
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -377,7 +441,51 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 26
|
||||
"lineNumber": 27
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "def-public.PluginStart.openDeleteModal",
|
||||
"type": "Function",
|
||||
"label": "openDeleteModal",
|
||||
"signature": [
|
||||
"(options: ",
|
||||
{
|
||||
"pluginId": "indexPatternFieldEditor",
|
||||
"scope": "public",
|
||||
"docId": "kibIndexPatternFieldEditorPluginApi",
|
||||
"section": "def-public.OpenFieldDeleteModalOptions",
|
||||
"text": "OpenFieldDeleteModalOptions"
|
||||
},
|
||||
") => () => void"
|
||||
],
|
||||
"description": [],
|
||||
"children": [
|
||||
{
|
||||
"type": "Object",
|
||||
"label": "options",
|
||||
"isRequired": true,
|
||||
"signature": [
|
||||
{
|
||||
"pluginId": "indexPatternFieldEditor",
|
||||
"scope": "public",
|
||||
"docId": "kibIndexPatternFieldEditorPluginApi",
|
||||
"section": "def-public.OpenFieldDeleteModalOptions",
|
||||
"text": "OpenFieldDeleteModalOptions"
|
||||
}
|
||||
],
|
||||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 28
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [],
|
||||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 28
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -388,7 +496,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 27
|
||||
"lineNumber": 29
|
||||
},
|
||||
"signature": [
|
||||
"{ getAll: () => typeof ",
|
||||
|
@ -418,7 +526,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 28
|
||||
"lineNumber": 30
|
||||
},
|
||||
"signature": [
|
||||
"{ editIndexPattern: () => boolean; }"
|
||||
|
@ -432,7 +540,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 31
|
||||
"lineNumber": 33
|
||||
},
|
||||
"signature": [
|
||||
"React.FunctionComponent<",
|
||||
|
@ -449,7 +557,7 @@
|
|||
],
|
||||
"source": {
|
||||
"path": "src/plugins/index_pattern_field_editor/public/types.ts",
|
||||
"lineNumber": 25
|
||||
"lineNumber": 26
|
||||
},
|
||||
"lifecycle": "start",
|
||||
"initialIsOpen": true
|
||||
|
|
|
@ -13,6 +13,10 @@ You first need to add in your kibana.json the "`indexPatternFieldEditor`" plugin
|
|||
|
||||
You will then receive in the start contract of the indexPatternFieldEditor plugin the following API:
|
||||
|
||||
### `userPermissions.editIndexPattern(): boolean`
|
||||
|
||||
Convenience method that uses the `core.application.capabilities` api to determine whether the user can edit the index pattern.
|
||||
|
||||
### `openEditor(options: OpenFieldEditorOptions): CloseEditor`
|
||||
|
||||
Use this method to open the index pattern field editor to either create (runtime) or edit (concrete | runtime) a field.
|
||||
|
@ -33,13 +37,29 @@ You can provide an optional `onSave` handler to be notified when the field has b
|
|||
|
||||
You can optionally pass the name of a field to edit. Leave empty to create a new runtime field based field.
|
||||
|
||||
### `userPermissions.editIndexPattern(): boolean`
|
||||
### `openDeleteModal(options: OpenFieldDeleteModalOptions): CloseEditor`
|
||||
|
||||
Convenience method that uses the `core.application.capabilities` api to determine whether the user can edit the index pattern.
|
||||
Use this method to open a confirmation modal to delete runtime fields from an index pattern.
|
||||
|
||||
#### `options`
|
||||
|
||||
`ctx: FieldEditorContext` (**required**)
|
||||
|
||||
You need to provide the context in which the deletion modal is being consumed. This object has the following properties:
|
||||
|
||||
- `indexPattern: IndexPattern`: the index pattern you want to delete fields from.
|
||||
|
||||
`onDelete(fieldNames: string[]): void` (optional)
|
||||
|
||||
You can provide an optional `onDelete` handler to be notified when the fields have been deleted. This handler is called after the deletion has been persisted to the saved object.
|
||||
|
||||
`fieldName: string | string[]` (**required**)
|
||||
|
||||
You have to pass the field or fields to delete.
|
||||
|
||||
### `<DeleteRuntimeFieldProvider />`
|
||||
|
||||
This children func React component provides a handler to delete one or multiple runtime fields.
|
||||
This children func React component provides a handler to delete one or multiple runtime fields. It can be used as an alternative to `openDeleteModal` in a react context.
|
||||
|
||||
#### Props
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
|
||||
const geti18nTexts = (fieldsToDelete?: string[]) => {
|
||||
let modalTitle = '';
|
||||
if (fieldsToDelete) {
|
||||
const isSingle = fieldsToDelete.length === 1;
|
||||
|
||||
modalTitle = isSingle
|
||||
? i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteSingleTitle',
|
||||
{
|
||||
defaultMessage: `Remove field '{name}'?`,
|
||||
values: { name: fieldsToDelete[0] },
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteMultipleTitle',
|
||||
{
|
||||
defaultMessage: `Remove {count} fields?`,
|
||||
values: { count: fieldsToDelete.length },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
modalTitle,
|
||||
confirmButtonText: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Remove',
|
||||
}
|
||||
),
|
||||
cancelButtonText: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.cancelButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
),
|
||||
warningMultipleFields: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.multipleDeletionDescription',
|
||||
{
|
||||
defaultMessage: 'You are about to remove these runtime fields:',
|
||||
}
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export interface Props {
|
||||
fieldsToDelete: string[];
|
||||
closeModal: () => void;
|
||||
confirmDelete: () => void;
|
||||
}
|
||||
|
||||
export function DeleteFieldModal({ fieldsToDelete, closeModal, confirmDelete }: Props) {
|
||||
const i18nTexts = geti18nTexts(fieldsToDelete);
|
||||
const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts;
|
||||
const isMultiple = Boolean(fieldsToDelete.length > 1);
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
title={modalTitle}
|
||||
data-test-subj="runtimeFieldDeleteConfirmModal"
|
||||
onCancel={closeModal}
|
||||
onConfirm={confirmDelete}
|
||||
cancelButtonText={cancelButtonText}
|
||||
buttonColor="danger"
|
||||
confirmButtonText={confirmButtonText}
|
||||
>
|
||||
{isMultiple && (
|
||||
<>
|
||||
<p>{warningMultipleFields}</p>
|
||||
<ul>
|
||||
{fieldsToDelete.map((fieldName) => (
|
||||
<li key={fieldName}>{fieldName}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiConfirmModal>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useRef, useEffect } from 'react';
|
||||
|
||||
import { IndexPattern } from '../shared_imports';
|
||||
import { OpenFieldDeleteModalOptions } from '../open_delete_modal';
|
||||
import { CloseEditor } from '../types';
|
||||
|
||||
type DeleteFieldFunc = (fieldName: string | string[]) => void;
|
||||
export interface Props {
|
||||
children: (deleteFieldHandler: DeleteFieldFunc) => React.ReactNode;
|
||||
indexPattern: IndexPattern;
|
||||
onDelete?: (fieldNames: string[]) => void;
|
||||
}
|
||||
|
||||
export const getDeleteFieldProvider = (
|
||||
modalOpener: (options: OpenFieldDeleteModalOptions) => CloseEditor
|
||||
): React.FunctionComponent<Props> => {
|
||||
return React.memo(({ indexPattern, children, onDelete }: Props) => {
|
||||
const closeModal = useRef<CloseEditor | null>(null);
|
||||
const deleteFields = useCallback(
|
||||
async (fieldName: string | string[]) => {
|
||||
if (closeModal.current) {
|
||||
closeModal.current();
|
||||
}
|
||||
closeModal.current = modalOpener({
|
||||
ctx: {
|
||||
indexPattern,
|
||||
},
|
||||
fieldName,
|
||||
onDelete,
|
||||
});
|
||||
},
|
||||
[onDelete, indexPattern]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (closeModal.current) {
|
||||
closeModal.current();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <>{children(deleteFields)}</>;
|
||||
});
|
||||
};
|
|
@ -1,129 +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 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 React, { useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
|
||||
|
||||
type DeleteFieldFunc = (fieldName: string | string[]) => void;
|
||||
|
||||
export interface Props {
|
||||
children: (deleteFieldHandler: DeleteFieldFunc) => React.ReactNode;
|
||||
onConfirmDelete: (fieldsToDelete: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isModalOpen: boolean;
|
||||
fieldsToDelete: string[];
|
||||
}
|
||||
|
||||
const geti18nTexts = (fieldsToDelete?: string[]) => {
|
||||
let modalTitle = '';
|
||||
if (fieldsToDelete) {
|
||||
const isSingle = fieldsToDelete.length === 1;
|
||||
|
||||
modalTitle = isSingle
|
||||
? i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteSingleTitle',
|
||||
{
|
||||
defaultMessage: `Remove field '{name}'?`,
|
||||
values: { name: fieldsToDelete[0] },
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteMultipleTitle',
|
||||
{
|
||||
defaultMessage: `Remove {count} fields?`,
|
||||
values: { count: fieldsToDelete.length },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
modalTitle,
|
||||
confirmButtonText: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Remove',
|
||||
}
|
||||
),
|
||||
cancelButtonText: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.cancelButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
),
|
||||
warningMultipleFields: i18n.translate(
|
||||
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.multipleDeletionDescription',
|
||||
{
|
||||
defaultMessage: 'You are about to remove these runtime fields:',
|
||||
}
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const DeleteRuntimeFieldProvider = ({ children, onConfirmDelete }: Props) => {
|
||||
const [state, setState] = useState<State>({ isModalOpen: false, fieldsToDelete: [] });
|
||||
|
||||
const { isModalOpen, fieldsToDelete } = state;
|
||||
const i18nTexts = geti18nTexts(fieldsToDelete);
|
||||
const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts;
|
||||
const isMultiple = Boolean(fieldsToDelete.length > 1);
|
||||
|
||||
const deleteField: DeleteFieldFunc = useCallback((fieldNames) => {
|
||||
setState({
|
||||
isModalOpen: true,
|
||||
fieldsToDelete: Array.isArray(fieldNames) ? fieldNames : [fieldNames],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
setState({ isModalOpen: false, fieldsToDelete: [] });
|
||||
}, []);
|
||||
|
||||
const confirmDelete = useCallback(async () => {
|
||||
try {
|
||||
await onConfirmDelete(fieldsToDelete);
|
||||
closeModal();
|
||||
} catch (e) {
|
||||
// silently fail as "onConfirmDelete" is responsible
|
||||
// to show a toast message if there is an error
|
||||
}
|
||||
}, [closeModal, onConfirmDelete, fieldsToDelete]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(deleteField)}
|
||||
|
||||
{isModalOpen && (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={modalTitle}
|
||||
data-test-subj="runtimeFieldDeleteConfirmModal"
|
||||
onCancel={closeModal}
|
||||
onConfirm={confirmDelete}
|
||||
cancelButtonText={cancelButtonText}
|
||||
buttonColor="danger"
|
||||
confirmButtonText={confirmButtonText}
|
||||
>
|
||||
{isMultiple && (
|
||||
<>
|
||||
<p>{warningMultipleFields}</p>
|
||||
<ul>
|
||||
{fieldsToDelete.map((fieldName) => (
|
||||
<li key={fieldName}>{fieldName}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</EuiConfirmModal>
|
||||
</EuiOverlayMask>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,59 +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 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 React, { useCallback } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { NotificationsStart } from 'src/core/public';
|
||||
import { IndexPattern, UsageCollectionStart } from '../../shared_imports';
|
||||
import { pluginName } from '../../constants';
|
||||
import { DeleteRuntimeFieldProvider, Props as DeleteProviderProps } from './delete_field_provider';
|
||||
import { DataPublicPluginStart } from '../../../../data/public';
|
||||
|
||||
export interface Props extends Omit<DeleteProviderProps, 'onConfirmDelete'> {
|
||||
indexPattern: IndexPattern;
|
||||
onDelete?: (fieldNames: string[]) => void;
|
||||
}
|
||||
|
||||
export const getDeleteProvider = (
|
||||
indexPatternService: DataPublicPluginStart['indexPatterns'],
|
||||
usageCollection: UsageCollectionStart,
|
||||
notifications: NotificationsStart
|
||||
): React.FunctionComponent<Props> => {
|
||||
return React.memo(({ indexPattern, children, onDelete }: Props) => {
|
||||
const deleteFields = useCallback(
|
||||
async (fieldNames: string[]) => {
|
||||
fieldNames.forEach((fieldName) => {
|
||||
indexPattern.removeRuntimeField(fieldName);
|
||||
});
|
||||
|
||||
try {
|
||||
usageCollection.reportUiCounter(pluginName, METRIC_TYPE.COUNT, 'delete_runtime');
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
await indexPatternService.updateSavedObject(indexPattern);
|
||||
} catch (e) {
|
||||
const title = i18n.translate('indexPatternFieldEditor.save.deleteErrorTitle', {
|
||||
defaultMessage: 'Failed to save field removal',
|
||||
});
|
||||
notifications.toasts.addError(e, { title });
|
||||
}
|
||||
|
||||
if (onDelete) {
|
||||
onDelete(fieldNames);
|
||||
}
|
||||
},
|
||||
[onDelete, indexPattern]
|
||||
);
|
||||
|
||||
return <DeleteRuntimeFieldProvider children={children} onConfirmDelete={deleteFields} />;
|
||||
});
|
||||
};
|
|
@ -1,9 +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 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.
|
||||
*/
|
||||
|
||||
export { getDeleteProvider, Props as DeleteProviderProps } from './get_delete_provider';
|
|
@ -17,4 +17,6 @@ export {
|
|||
FieldEditorContext,
|
||||
} from './field_editor_flyout_content_container';
|
||||
|
||||
export { getDeleteFieldProvider, Props as DeleteFieldProviderProps } from './delete_field_provider';
|
||||
|
||||
export * from './field_format_editor';
|
||||
|
|
|
@ -29,4 +29,5 @@ export function plugin() {
|
|||
|
||||
// Expose types
|
||||
export type { OpenFieldEditorOptions } from './open_editor';
|
||||
export type { OpenFieldDeleteModalOptions } from './open_delete_modal';
|
||||
export type { FieldEditorContext } from './components/field_editor_flyout_content_container';
|
||||
|
|
|
@ -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 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 { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { NotificationsStart } from 'src/core/public';
|
||||
import { IndexPattern, UsageCollectionStart } from '../shared_imports';
|
||||
import { pluginName } from '../constants';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
|
||||
export async function removeFields(
|
||||
fieldNames: string[],
|
||||
indexPattern: IndexPattern,
|
||||
services: {
|
||||
indexPatternService: DataPublicPluginStart['indexPatterns'];
|
||||
usageCollection: UsageCollectionStart;
|
||||
notifications: NotificationsStart;
|
||||
}
|
||||
) {
|
||||
fieldNames.forEach((fieldName) => {
|
||||
indexPattern.removeRuntimeField(fieldName);
|
||||
});
|
||||
|
||||
try {
|
||||
services.usageCollection.reportUiCounter(pluginName, METRIC_TYPE.COUNT, 'delete_runtime');
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
await services.indexPatternService.updateSavedObject(indexPattern);
|
||||
} catch (e) {
|
||||
const title = i18n.translate('indexPatternFieldEditor.save.deleteErrorTitle', {
|
||||
defaultMessage: 'Failed to save field removal',
|
||||
});
|
||||
services.notifications.toasts.addError(e, { title });
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ const createSetupContract = (): Setup => {
|
|||
const createStartContract = (): Start => {
|
||||
return {
|
||||
openEditor: jest.fn(),
|
||||
openDeleteModal: jest.fn(),
|
||||
fieldFormatEditors: {
|
||||
getAll: jest.fn(),
|
||||
getById: jest.fn(),
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { CoreStart, OverlayRef } from 'src/core/public';
|
||||
|
||||
import {
|
||||
toMountPoint,
|
||||
DataPublicPluginStart,
|
||||
IndexPattern,
|
||||
UsageCollectionStart,
|
||||
} from './shared_imports';
|
||||
|
||||
import { CloseEditor } from './types';
|
||||
|
||||
import { DeleteFieldModal } from './components/delete_field_modal';
|
||||
import { removeFields } from './lib/remove_fields';
|
||||
|
||||
export interface OpenFieldDeleteModalOptions {
|
||||
ctx: {
|
||||
indexPattern: IndexPattern;
|
||||
};
|
||||
onDelete?: (fieldNames: string[]) => void;
|
||||
fieldName: string | string[];
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
core: CoreStart;
|
||||
indexPatternService: DataPublicPluginStart['indexPatterns'];
|
||||
usageCollection: UsageCollectionStart;
|
||||
}
|
||||
|
||||
export const getFieldDeleteModalOpener = ({
|
||||
core,
|
||||
indexPatternService,
|
||||
usageCollection,
|
||||
}: Dependencies) => (options: OpenFieldDeleteModalOptions): CloseEditor => {
|
||||
const { overlays, notifications } = core;
|
||||
|
||||
let overlayRef: OverlayRef | null = null;
|
||||
|
||||
const openDeleteModal = ({
|
||||
onDelete,
|
||||
fieldName,
|
||||
ctx: { indexPattern },
|
||||
}: OpenFieldDeleteModalOptions): CloseEditor => {
|
||||
const fieldsToDelete = Array.isArray(fieldName) ? fieldName : [fieldName];
|
||||
const closeModal = () => {
|
||||
if (overlayRef) {
|
||||
overlayRef.close();
|
||||
overlayRef = null;
|
||||
}
|
||||
};
|
||||
|
||||
const onConfirmDelete = async () => {
|
||||
closeModal();
|
||||
|
||||
await removeFields(fieldsToDelete, indexPattern, {
|
||||
indexPatternService,
|
||||
usageCollection,
|
||||
notifications,
|
||||
});
|
||||
|
||||
if (onDelete) {
|
||||
onDelete(fieldsToDelete);
|
||||
}
|
||||
};
|
||||
|
||||
overlayRef = overlays.openModal(
|
||||
toMountPoint(
|
||||
<DeleteFieldModal
|
||||
fieldsToDelete={fieldsToDelete}
|
||||
closeModal={closeModal}
|
||||
confirmDelete={onConfirmDelete}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
return closeModal;
|
||||
};
|
||||
|
||||
return openDeleteModal(options);
|
||||
};
|
|
@ -19,7 +19,7 @@ import {
|
|||
UsageCollectionStart,
|
||||
} from './shared_imports';
|
||||
|
||||
import { InternalFieldType } from './types';
|
||||
import { InternalFieldType, CloseEditor } from './types';
|
||||
import { FieldEditorFlyoutContentContainer } from './components/field_editor_flyout_content_container';
|
||||
|
||||
import { PluginStart } from './types';
|
||||
|
@ -32,7 +32,6 @@ export interface OpenFieldEditorOptions {
|
|||
fieldName?: string;
|
||||
}
|
||||
|
||||
type CloseEditor = () => void;
|
||||
interface Dependencies {
|
||||
core: CoreStart;
|
||||
/** The search service from the data plugin */
|
||||
|
|
|
@ -25,6 +25,8 @@ import { registerTestBed } from './test_utils';
|
|||
|
||||
import { FieldEditorFlyoutContentContainer } from './components/field_editor_flyout_content_container';
|
||||
import { IndexPatternFieldEditorPlugin } from './plugin';
|
||||
import { DeleteFieldModal } from './components/delete_field_modal';
|
||||
import { IndexPattern } from './shared_imports';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
|
@ -82,6 +84,70 @@ describe('IndexPatternFieldEditorPlugin', () => {
|
|||
expect(typeof closeEditorHandler).toBe('function');
|
||||
});
|
||||
|
||||
test('should expose a handler to open field deletion modal', async () => {
|
||||
const startApi = await plugin.start(coreStart, pluginStart);
|
||||
expect(startApi.openDeleteModal).toBeDefined();
|
||||
});
|
||||
|
||||
test('should call correct services when opening the deletion modal', async () => {
|
||||
const openModal = jest.fn();
|
||||
const onDeleteSpy = jest.fn();
|
||||
const removeFieldSpy = jest.fn();
|
||||
|
||||
const coreStartMocked = {
|
||||
...coreStart,
|
||||
overlays: {
|
||||
...coreStart.overlays,
|
||||
openModal,
|
||||
},
|
||||
};
|
||||
const pluginStartMocked = {
|
||||
...pluginStart,
|
||||
data: {
|
||||
...pluginStart.data,
|
||||
indexPatterns: {
|
||||
...pluginStart.data.indexPatterns,
|
||||
updateSavedObject: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
const { openDeleteModal } = await plugin.start(coreStartMocked, pluginStartMocked);
|
||||
|
||||
const indexPatternMock = ({ removeRuntimeField: removeFieldSpy } as unknown) as IndexPattern;
|
||||
|
||||
openDeleteModal({
|
||||
onDelete: onDeleteSpy,
|
||||
ctx: { indexPattern: indexPatternMock },
|
||||
fieldName: ['a', 'b', 'c'],
|
||||
});
|
||||
|
||||
expect(openModal).toHaveBeenCalled();
|
||||
|
||||
const [[arg]] = openModal.mock.calls;
|
||||
expect(arg.type).toBe(DeleteFieldModal);
|
||||
|
||||
// simulate user confirming deletion
|
||||
await arg.props.confirmDelete();
|
||||
|
||||
// consumer should be notified
|
||||
expect(onDeleteSpy).toHaveBeenCalled();
|
||||
|
||||
// fields should be removed on index pattern and changes persisted
|
||||
expect(removeFieldSpy).toHaveBeenCalledWith('a');
|
||||
expect(removeFieldSpy).toHaveBeenCalledWith('b');
|
||||
expect(removeFieldSpy).toHaveBeenCalledWith('c');
|
||||
expect(pluginStartMocked.data.indexPatterns.updateSavedObject).toHaveBeenLastCalledWith(
|
||||
indexPatternMock
|
||||
);
|
||||
});
|
||||
|
||||
test('should return a handler to close the modal', async () => {
|
||||
const { openDeleteModal } = await plugin.start(coreStart, pluginStart);
|
||||
|
||||
const closeModal = openDeleteModal({ fieldName: ['a'], ctx: { indexPattern: {} as any } });
|
||||
expect(typeof closeModal).toBe('function');
|
||||
});
|
||||
|
||||
test('should expose a render props component to delete runtime fields', async () => {
|
||||
const { DeleteRuntimeFieldProvider } = await plugin.start(coreStart, pluginStart);
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ import { Plugin, CoreSetup, CoreStart } from 'src/core/public';
|
|||
import { PluginSetup, PluginStart, SetupPlugins, StartPlugins } from './types';
|
||||
import { getFieldEditorOpener } from './open_editor';
|
||||
import { FormatEditorService } from './service';
|
||||
import { getDeleteProvider } from './components/delete_field_provider';
|
||||
import { getDeleteFieldProvider } from './components/delete_field_provider';
|
||||
import { getFieldDeleteModalOpener } from './open_delete_modal';
|
||||
|
||||
export class IndexPatternFieldEditorPlugin
|
||||
implements Plugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
||||
|
@ -31,6 +32,11 @@ export class IndexPatternFieldEditorPlugin
|
|||
application: { capabilities },
|
||||
} = core;
|
||||
const { data, usageCollection } = plugins;
|
||||
const openDeleteModal = getFieldDeleteModalOpener({
|
||||
core,
|
||||
indexPatternService: data.indexPatterns,
|
||||
usageCollection,
|
||||
});
|
||||
return {
|
||||
fieldFormatEditors,
|
||||
openEditor: getFieldEditorOpener({
|
||||
|
@ -41,16 +47,13 @@ export class IndexPatternFieldEditorPlugin
|
|||
search: data.search,
|
||||
usageCollection,
|
||||
}),
|
||||
openDeleteModal,
|
||||
userPermissions: {
|
||||
editIndexPattern: () => {
|
||||
return capabilities.management.kibana.indexPatterns;
|
||||
},
|
||||
},
|
||||
DeleteRuntimeFieldProvider: getDeleteProvider(
|
||||
data.indexPatterns,
|
||||
usageCollection,
|
||||
core.notifications
|
||||
),
|
||||
DeleteRuntimeFieldProvider: getDeleteFieldProvider(openDeleteModal),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,9 @@ import {
|
|||
UsageCollectionStart,
|
||||
} from './shared_imports';
|
||||
import { OpenFieldEditorOptions } from './open_editor';
|
||||
import { OpenFieldDeleteModalOptions } from './open_delete_modal';
|
||||
import { FormatEditorServiceSetup, FormatEditorServiceStart } from './service';
|
||||
import { DeleteProviderProps } from './components/delete_field_provider';
|
||||
import { DeleteFieldProviderProps } from './components';
|
||||
|
||||
export interface PluginSetup {
|
||||
fieldFormatEditors: FormatEditorServiceSetup['fieldFormatEditors'];
|
||||
|
@ -24,11 +25,12 @@ export interface PluginSetup {
|
|||
|
||||
export interface PluginStart {
|
||||
openEditor(options: OpenFieldEditorOptions): () => void;
|
||||
openDeleteModal(options: OpenFieldDeleteModalOptions): () => void;
|
||||
fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors'];
|
||||
userPermissions: {
|
||||
editIndexPattern: () => boolean;
|
||||
};
|
||||
DeleteRuntimeFieldProvider: FunctionComponent<DeleteProviderProps>;
|
||||
DeleteRuntimeFieldProvider: FunctionComponent<DeleteFieldProviderProps>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -61,3 +63,5 @@ export interface EsRuntimeField {
|
|||
source: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type CloseEditor = () => void;
|
||||
|
|
Loading…
Reference in a new issue