[Lens] Add description property and check duplicate title on save (#68219)
This commit is contained in:
parent
8a5a7c3032
commit
497ecf8164
|
@ -31,10 +31,13 @@ import { displayDuplicateTitleConfirmModal } from './display_duplicate_title_con
|
|||
* @param services
|
||||
*/
|
||||
export async function checkForDuplicateTitle(
|
||||
savedObject: SavedObject,
|
||||
savedObject: Pick<
|
||||
SavedObject,
|
||||
'id' | 'title' | 'getDisplayName' | 'lastSavedTitle' | 'copyOnSave' | 'getEsType'
|
||||
>,
|
||||
isTitleDuplicateConfirmed: boolean,
|
||||
onTitleDuplicate: (() => void) | undefined,
|
||||
services: SavedObjectKibanaServices
|
||||
services: Pick<SavedObjectKibanaServices, 'savedObjectsClient' | 'overlays'>
|
||||
): Promise<true> {
|
||||
const { savedObjectsClient, overlays } = services;
|
||||
// Don't check for duplicates if user has already confirmed save with duplicate title
|
||||
|
|
|
@ -23,7 +23,7 @@ import { confirmModalPromise } from './confirm_modal_promise';
|
|||
import { SavedObject } from '../../types';
|
||||
|
||||
export function displayDuplicateTitleConfirmModal(
|
||||
savedObject: SavedObject,
|
||||
savedObject: Pick<SavedObject, 'title' | 'getDisplayName'>,
|
||||
overlays: OverlayStart
|
||||
): Promise<true> {
|
||||
const confirmMessage = i18n.translate(
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface VisualizationListItem {
|
|||
stage: 'experimental' | 'beta' | 'production';
|
||||
savedObjectType: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
typeTitle: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { App } from './app';
|
||||
|
@ -13,8 +14,11 @@ import { AppMountParameters } from 'kibana/public';
|
|||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { Document, SavedObjectStore } from '../persistence';
|
||||
import { mount } from 'enzyme';
|
||||
import {
|
||||
SavedObjectSaveModal,
|
||||
checkForDuplicateTitle,
|
||||
} from '../../../../../src/plugins/saved_objects/public';
|
||||
import { createMemoryHistory, History } from 'history';
|
||||
import { SavedObjectSaveModal } from '../../../../../src/plugins/saved_objects/public';
|
||||
import {
|
||||
esFilters,
|
||||
FilterManager,
|
||||
|
@ -28,10 +32,20 @@ const dataStartMock = dataPluginMock.createStartContract();
|
|||
import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks';
|
||||
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
jest.mock('../persistence');
|
||||
jest.mock('src/core/public');
|
||||
jest.mock('../../../../../src/plugins/saved_objects/public', () => {
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { SavedObjectSaveModal, SavedObjectSaveModalOrigin } = jest.requireActual(
|
||||
'../../../../../src/plugins/saved_objects/public'
|
||||
);
|
||||
return {
|
||||
SavedObjectSaveModal,
|
||||
SavedObjectSaveModalOrigin,
|
||||
checkForDuplicateTitle: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const navigationStartMock = navigationPluginMock.createStartContract();
|
||||
|
||||
|
@ -644,6 +658,46 @@ describe('Lens App', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('checks for duplicate title before saving', async () => {
|
||||
const args = defaultArgs;
|
||||
args.editorFrame = frame;
|
||||
(args.docStorage.save as jest.Mock).mockReturnValue(Promise.resolve({ id: '123' }));
|
||||
|
||||
instance = mount(<App {...args} />);
|
||||
|
||||
const onChange = frame.mount.mock.calls[0][1].onChange;
|
||||
await act(async () =>
|
||||
onChange({
|
||||
filterableIndexPatterns: [],
|
||||
doc: ({ id: '123', expression: 'valid expression' } as unknown) as Document,
|
||||
})
|
||||
);
|
||||
instance.update();
|
||||
await act(async () => {
|
||||
getButton(instance).run(instance.getDOMNode());
|
||||
});
|
||||
instance.update();
|
||||
|
||||
const onTitleDuplicate = jest.fn();
|
||||
|
||||
await act(async () => {
|
||||
instance.find(SavedObjectSaveModal).prop('onSave')({
|
||||
onTitleDuplicate,
|
||||
isTitleDuplicateConfirmed: false,
|
||||
newCopyOnSave: false,
|
||||
newDescription: '',
|
||||
newTitle: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
expect(checkForDuplicateTitle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: '123' }),
|
||||
false,
|
||||
onTitleDuplicate,
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('does not show the copy button on first save', async () => {
|
||||
const args = defaultArgs;
|
||||
args.editorFrame = frame;
|
||||
|
|
|
@ -24,6 +24,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p
|
|||
import {
|
||||
SavedObjectSaveModalOrigin,
|
||||
OnSaveProps,
|
||||
checkForDuplicateTitle,
|
||||
} from '../../../../../src/plugins/saved_objects/public';
|
||||
import { Document, SavedObjectStore } from '../persistence';
|
||||
import { EditorFrameInstance } from '../types';
|
||||
|
@ -252,9 +253,11 @@ export function App({
|
|||
// state.persistedDoc,
|
||||
]);
|
||||
|
||||
const runSave = (
|
||||
const runSave = async (
|
||||
saveProps: Omit<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & {
|
||||
returnToOrigin: boolean;
|
||||
onTitleDuplicate?: OnSaveProps['onTitleDuplicate'];
|
||||
newDescription?: string;
|
||||
}
|
||||
) => {
|
||||
if (!lastKnownDoc) {
|
||||
|
@ -276,10 +279,30 @@ export function App({
|
|||
|
||||
const doc = {
|
||||
...lastDocWithoutPinned,
|
||||
description: saveProps.newDescription,
|
||||
id: saveProps.newCopyOnSave ? undefined : lastKnownDoc.id,
|
||||
title: saveProps.newTitle,
|
||||
};
|
||||
|
||||
await checkForDuplicateTitle(
|
||||
{
|
||||
...doc,
|
||||
copyOnSave: saveProps.newCopyOnSave,
|
||||
lastSavedTitle: lastKnownDoc?.title,
|
||||
getEsType: () => 'lens',
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.lens.app.saveModalType', {
|
||||
defaultMessage: 'Lens visualization',
|
||||
}),
|
||||
},
|
||||
saveProps.isTitleDuplicateConfirmed,
|
||||
saveProps.onTitleDuplicate,
|
||||
{
|
||||
savedObjectsClient: core.savedObjects.client,
|
||||
overlays: core.overlays,
|
||||
}
|
||||
);
|
||||
|
||||
const newlyCreated: boolean = saveProps.newCopyOnSave || !lastKnownDoc?.id;
|
||||
docStorage
|
||||
.save(doc)
|
||||
|
@ -492,6 +515,7 @@ export function App({
|
|||
documentInfo={{
|
||||
id: lastKnownDoc.id,
|
||||
title: lastKnownDoc.title || '',
|
||||
description: lastKnownDoc.description || '',
|
||||
}}
|
||||
objectType={i18n.translate('xpack.lens.app.saveModalType', {
|
||||
defaultMessage: 'Lens visualization',
|
||||
|
|
|
@ -48,6 +48,7 @@ export function getSavedObjectFormat({
|
|||
return {
|
||||
id: state.persistedId,
|
||||
title: state.title,
|
||||
description: state.description,
|
||||
type: 'lens',
|
||||
visualizationType: state.visualization.activeId,
|
||||
expression: expression ? toExpression(expression) : '',
|
||||
|
|
|
@ -388,6 +388,7 @@ describe('editor_frame state management', () => {
|
|||
filters: [],
|
||||
},
|
||||
title: 'heyo!',
|
||||
description: 'My lens',
|
||||
type: 'lens',
|
||||
visualizationType: 'line',
|
||||
},
|
||||
|
@ -406,6 +407,7 @@ describe('editor_frame state management', () => {
|
|||
},
|
||||
persistedId: 'b',
|
||||
title: 'heyo!',
|
||||
description: 'My lens',
|
||||
visualization: {
|
||||
activeId: 'line',
|
||||
state: {
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface PreviewState {
|
|||
export interface EditorFrameState extends PreviewState {
|
||||
persistedId?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
stagedPreview?: PreviewState;
|
||||
activeDatasourceId: string | null;
|
||||
}
|
||||
|
@ -157,6 +158,7 @@ export const reducer = (state: EditorFrameState, action: Action): EditorFrameSta
|
|||
...state,
|
||||
persistedId: action.doc.id,
|
||||
title: action.doc.title,
|
||||
description: action.doc.description,
|
||||
datasourceStates: Object.entries(action.doc.state.datasourceStates).reduce(
|
||||
(stateMap, [datasourceId, datasourceState]) => ({
|
||||
...stateMap,
|
||||
|
|
|
@ -25,6 +25,7 @@ describe('LensStore', () => {
|
|||
const { client, store } = testStore('FOO');
|
||||
const doc = await store.save({
|
||||
title: 'Hello',
|
||||
description: 'My doc',
|
||||
visualizationType: 'bar',
|
||||
expression: '',
|
||||
state: {
|
||||
|
@ -43,6 +44,7 @@ describe('LensStore', () => {
|
|||
expect(doc).toEqual({
|
||||
id: 'FOO',
|
||||
title: 'Hello',
|
||||
description: 'My doc',
|
||||
visualizationType: 'bar',
|
||||
expression: '',
|
||||
state: {
|
||||
|
@ -61,6 +63,7 @@ describe('LensStore', () => {
|
|||
expect(client.create).toHaveBeenCalledTimes(1);
|
||||
expect(client.create).toHaveBeenCalledWith('lens', {
|
||||
title: 'Hello',
|
||||
description: 'My doc',
|
||||
visualizationType: 'bar',
|
||||
expression: '',
|
||||
state: {
|
||||
|
|
|
@ -13,6 +13,7 @@ export interface Document {
|
|||
type?: string;
|
||||
visualizationType: string | null;
|
||||
title: string;
|
||||
description?: string;
|
||||
expression: string | null;
|
||||
state: {
|
||||
datasourceMetaData: {
|
||||
|
|
|
@ -34,10 +34,11 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
|
|||
searchFields: ['title^3'],
|
||||
toListItem(savedObject) {
|
||||
const { id, type, attributes } = savedObject;
|
||||
const { title } = attributes as { title: string };
|
||||
const { title, description } = attributes as { title: string; description?: string };
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
editUrl: getEditPath(id),
|
||||
editApp: 'lens',
|
||||
icon: 'lensApp',
|
||||
|
|
|
@ -29,6 +29,9 @@ export function setupSavedObjects(core: CoreSetup) {
|
|||
title: {
|
||||
type: 'text',
|
||||
},
|
||||
description: {
|
||||
type: 'text',
|
||||
},
|
||||
visualizationType: {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue