Skip to content

Commit

Permalink
[Discover] Enable tags for saved searches (elastic#136162)
Browse files Browse the repository at this point in the history
* [Discover] Add initial support for tags to saved search modal

* [Discover] Add tags to savedSearch types

* [Discover] Finish initial support for adding tags to saved searches

* [Discover] Start to convert saved object finder to a table in order to support tags

* [Discover] Add support for displaying saved search tags in open search flyout

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* [Discover] Continue support for tags in saved object finder

* [Discover] Clean up saved object finder

* [Discover] Finish initial support for tags in saved object finder

* [Discover] Update SimpleSavedObject constructor to SimpleSavedObjectImpl

* [Discover] Remove orig files

* [Discover] Saved search tag type registration and telemetry

* [Discover] Create new saved_objects_finder plugin

* [Discover] Continue work creating saved_objects_finder plugin

* [Discover] Revert some changes in saved_objects

* [Discover] Revert some changes in saved_objects again

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* [Discover] Update saved_objects_finder i18n keys

* [Discover] Update docs

* [Discover] Add plugins to saved_object_finder and fix broken types

* [Discover] Finish creating saved_objects_finder plugin and use it in Discover

* [Discover] Update SavedObjectFinderProps type export, and update x-pack telemetry

* [Discover] Fix broken jest tests

* [Discover] Update saved_objects_finder API

* [Discover] Remove unused translations

* [Discover] Fix issue with initial saved object finder fetch

* [Discover] Fix some of the saved object finder jest tests

* [Discover] Clean up finder after merge

* [Discover] Fixing saved_object_finder.tsx

* [Discover] Add savedObjectsTaggingOss reference to saved_search plugin

* [Discover] Fix broken open_search_panel test

* [Discover] Removed allowed types from saved object finder

* [Discover] Removing type column when there's only one saved object type, and adjusting column widths

* [Discover] Fix issue where visible types were entirely removed, fixed pageSizeOptions

* [Discover] Add showFilter to open_search_panel's saved_objects_finder, fallback to all available types when no type filter is applied to saved_objects_finder, hide type column and filter button when there is only one type in metadata list

* [Discover] Fix remaining saved_object_finder Jest tests

* [Discover] Update snapshot

* [Discover] Fix failing functional tests

* [Discover] Add tagging Jest tests for saved_objects_finder

* [Discover] Fix small bugs in saved_object_finder, update Jest tests, add functional tests for Discover saved search tagging

* [Discover] Removed unused variable in functional test

* [Discover] Update Discover Jest tests with tagging tests

* [Discover] Remove translations

* [Discover] Updating saved_objects_finder to use static export vs preconfigured component, adding lazy load support

* [Discover] Move saved_object_finder service deps to a 'services' prop, fix broken open_search_panel Jest test

* [Discover] Fix broken Jest test

* [Discover] Fix broken Jest test from merge

* [Discover] Fix discover tags integration test description

* - Updated tags prop to be `string | undefined`
- Type imports cleanup
- Added loading indicator for saved object finder
- Changed `savedObjectsPlugin.settings.getListingLimit()` to `uiSettings.get(LISTING_LIMIT_SETTING)`
- Removed old saved object finder comment
- Removed tag changes from transform plugin
- Change code owners of saved_objects_finder to Data Discovery

* [Discover] Fixed LISTING_LIMIT_SETTING import

* [Discover] Revert tags saving change that introduced a bug

* [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs'

* [Discover] Try again to fix LISTING_LIMIT_SETTINGS import

* [Discover] Fix failing snapshot

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
2 people authored and Mpdreamz committed Sep 6, 2022
1 parent 9558100 commit 612061d
Show file tree
Hide file tree
Showing 55 changed files with 2,504 additions and 82 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/x-pack/plugins/graph/ @elastic/kibana-data-discovery
/x-pack/test/functional/apps/graph @elastic/kibana-data-discovery
/src/plugins/unified_field_list/ @elastic/kibana-data-discovery
/src/plugins/saved_objects_finder/ @elastic/kibana-data-discovery

# Vis Editors
/x-pack/plugins/lens/ @elastic/kibana-vis-editors
Expand Down
1 change: 1 addition & 0 deletions .i18nrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"newsfeed": "src/plugins/newsfeed",
"presentationUtil": "src/plugins/presentation_util",
"savedObjects": "src/plugins/saved_objects",
"savedObjectsFinder": "src/plugins/saved_objects_finder",
"savedObjectsManagement": "src/plugins/saved_objects_management",
"server": "src/legacy/server",
"share": "src/plugins/share",
Expand Down
4 changes: 4 additions & 0 deletions docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ Content is fetched from the remote (https://feeds.elastic.co) once a day, with p
|NOTE: This plugin is deprecated and will be removed in 8.0. See https://github.com/elastic/kibana/issues/46435 for more information.
|{kib-repo}blob/{branch}/src/plugins/saved_objects_finder/README.md[savedObjectsFinder]
|The savedObjectsFinder plugin exposes a UI for finding saved objects on the client side.
|{kib-repo}blob/{branch}/src/plugins/saved_objects_management/README.md[savedObjectsManagement]
|The savedObjectsManagement plugin manages the Saved Objects management section.
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,4 @@ pageLoadAssetSize:
kubernetesSecurity: 77234
threatIntelligence: 29195
files: 22673
savedObjectsFinder: 21691
11 changes: 10 additions & 1 deletion src/plugins/discover/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@
"navigation",
"uiActions",
"savedObjects",
"savedObjectsFinder",
"savedObjectsManagement",
"dataViewFieldEditor",
"dataViewEditor",
"expressions"
],
"optionalPlugins": ["home", "share", "usageCollection", "spaces", "triggersActionsUi"],
"optionalPlugins": [
"home",
"share",
"usageCollection",
"spaces",
"triggersActionsUi",
"savedObjectsTaggingOss"
],
"requiredBundles": ["kibanaUtils", "kibanaReact", "dataViews", "unifiedSearch", "savedSearch"],
"extraPublicDirs": ["common"],
"owner": {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,5 @@ export const discoverServiceMock = {
addWarning: jest.fn(),
},
expressions: expressionsPlugin,
savedObjectsTagging: {},
} as unknown as DiscoverServices;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,160 @@
* Side Public License, v 1.
*/

import { showSaveModal } from '@kbn/saved-objects-plugin/public';
import * as savedObjectsPlugin from '@kbn/saved-objects-plugin/public';
jest.mock('@kbn/saved-objects-plugin/public');

jest.mock('../../utils/persist_saved_search', () => ({
persistSavedSearch: jest.fn(() => ({ id: 'the-saved-search-id' })),
}));
import { onSaveSearch } from './on_save_search';
import { dataViewMock } from '../../../../__mocks__/data_view';
import { savedSearchMock } from '../../../../__mocks__/saved_search';
import { DiscoverServices } from '../../../../build_services';
import { GetStateReturn } from '../../services/discover_state';
import { i18nServiceMock } from '@kbn/core/public/mocks';
import { ReactElement } from 'react';
import { discoverServiceMock } from '../../../../__mocks__/services';
import * as persistSavedSearchUtils from '../../utils/persist_saved_search';
import { SavedSearch } from '@kbn/saved-search-plugin/public';

describe('onSaveSearch', () => {
it('should call showSaveModal', async () => {
const serviceMock = {
core: {
i18n: i18nServiceMock.create(),
},
} as unknown as DiscoverServices;
const stateMock = {
appStateContainer: {
getState: () => ({
rowsPerPage: 250,
}),
},
} as unknown as GetStateReturn;

test('onSaveSearch', async () => {
const serviceMock = {
core: {
i18n: i18nServiceMock.create(),
},
} as unknown as DiscoverServices;
const stateMock = {
appStateContainer: {
getState: () => ({
rowsPerPage: 250,
}),
},
} as unknown as GetStateReturn;
await onSaveSearch({
dataView: dataViewMock,
navigateTo: jest.fn(),
savedSearch: savedSearchMock,
services: serviceMock,
state: stateMock,
});

expect(savedObjectsPlugin.showSaveModal).toHaveBeenCalled();
});

await onSaveSearch({
dataView: dataViewMock,
navigateTo: jest.fn(),
savedSearch: savedSearchMock,
services: serviceMock,
state: stateMock,
it('should pass tags to the save modal', async () => {
const serviceMock = discoverServiceMock;
const stateMock = {
appStateContainer: {
getState: () => ({
rowsPerPage: 250,
}),
},
} as unknown as GetStateReturn;
let saveModal: ReactElement | undefined;
jest.spyOn(savedObjectsPlugin, 'showSaveModal').mockImplementationOnce((modal) => {
saveModal = modal;
});
await onSaveSearch({
dataView: dataViewMock,
navigateTo: jest.fn(),
savedSearch: {
...savedSearchMock,
tags: ['tag1', 'tag2'],
},
services: serviceMock,
state: stateMock,
});
expect(saveModal?.props.tags).toEqual(['tag1', 'tag2']);
});

expect(showSaveModal).toHaveBeenCalled();
it('should update the saved search tags', async () => {
const serviceMock = discoverServiceMock;
const stateMock = {
appStateContainer: {
getState: () => ({
rowsPerPage: 250,
}),
},
resetInitialAppState: jest.fn(),
} as unknown as GetStateReturn;
let saveModal: ReactElement | undefined;
jest.spyOn(savedObjectsPlugin, 'showSaveModal').mockImplementationOnce((modal) => {
saveModal = modal;
});
let savedSearch: SavedSearch = {
...savedSearchMock,
tags: ['tag1', 'tag2'],
};
await onSaveSearch({
dataView: dataViewMock,
navigateTo: jest.fn(),
savedSearch,
services: serviceMock,
state: stateMock,
});
expect(savedSearch.tags).toEqual(['tag1', 'tag2']);
jest
.spyOn(persistSavedSearchUtils, 'persistSavedSearch')
.mockImplementationOnce((newSavedSearch, _) => {
savedSearch = newSavedSearch;
return Promise.resolve({ id: newSavedSearch.id });
});
saveModal?.props.onSave({
newTitle: savedSearch.title,
newCopyOnSave: false,
newDescription: savedSearch.description,
newTags: ['tag3', 'tag4'],
isTitleDuplicateConfirmed: false,
onTitleDuplicate: jest.fn(),
});
expect(savedSearch.tags).toEqual(['tag3', 'tag4']);
});

it('should not update tags if savedObjectsTagging is undefined', async () => {
const serviceMock = discoverServiceMock;
const stateMock = {
appStateContainer: {
getState: () => ({
rowsPerPage: 250,
}),
},
resetInitialAppState: jest.fn(),
} as unknown as GetStateReturn;
let saveModal: ReactElement | undefined;
jest.spyOn(savedObjectsPlugin, 'showSaveModal').mockImplementationOnce((modal) => {
saveModal = modal;
});
let savedSearch: SavedSearch = {
...savedSearchMock,
tags: ['tag1', 'tag2'],
};
await onSaveSearch({
dataView: dataViewMock,
navigateTo: jest.fn(),
savedSearch,
services: {
...serviceMock,
savedObjectsTagging: undefined,
},
state: stateMock,
});
expect(savedSearch.tags).toEqual(['tag1', 'tag2']);
jest
.spyOn(persistSavedSearchUtils, 'persistSavedSearch')
.mockImplementationOnce((newSavedSearch, _) => {
savedSearch = newSavedSearch;
return Promise.resolve({ id: newSavedSearch.id });
});
saveModal?.props.onSave({
newTitle: savedSearch.title,
newCopyOnSave: false,
newDescription: savedSearch.description,
newTags: ['tag3', 'tag4'],
isTitleDuplicateConfirmed: false,
onTitleDuplicate: jest.fn(),
});
expect(savedSearch.tags).toEqual(['tag1', 'tag2']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -106,31 +106,38 @@ export async function onSaveSearch({
onClose?: () => void;
onSaveCb?: () => void;
}) {
const { uiSettings } = services;
const { uiSettings, savedObjectsTagging } = services;
const onSave = async ({
newTitle,
newCopyOnSave,
newTimeRestore,
newDescription,
newTags,
isTitleDuplicateConfirmed,
onTitleDuplicate,
}: {
newTitle: string;
newTimeRestore: boolean;
newCopyOnSave: boolean;
newDescription: string;
newTags: string[];
isTitleDuplicateConfirmed: boolean;
onTitleDuplicate: () => void;
}) => {
const currentTitle = savedSearch.title;
const currentTimeRestore = savedSearch.timeRestore;
const currentRowsPerPage = savedSearch.rowsPerPage;
const currentDescription = savedSearch.description;
const currentTags = savedSearch.tags;
savedSearch.title = newTitle;
savedSearch.description = newDescription;
savedSearch.timeRestore = newTimeRestore;
savedSearch.rowsPerPage = uiSettings.get(DOC_TABLE_LEGACY)
? currentRowsPerPage
: state.appStateContainer.getState().rowsPerPage;
if (savedObjectsTagging) {
savedSearch.tags = newTags;
}
const saveOptions: SaveSavedSearchOptions = {
onTitleDuplicate,
copyOnSave: newCopyOnSave,
Expand All @@ -151,6 +158,10 @@ export async function onSaveSearch({
savedSearch.title = currentTitle;
savedSearch.timeRestore = currentTimeRestore;
savedSearch.rowsPerPage = currentRowsPerPage;
savedSearch.description = currentDescription;
if (savedObjectsTagging) {
savedSearch.tags = currentTags;
}
} else {
state.resetInitialAppState();
}
Expand All @@ -160,10 +171,12 @@ export async function onSaveSearch({

const saveModal = (
<SaveSearchObjectModal
services={services}
title={savedSearch.title ?? ''}
showCopyOnSave={!!savedSearch.id}
description={savedSearch.description}
timeRestore={savedSearch.timeRestore}
tags={savedSearch.tags ?? []}
onSave={onSave}
onClose={onClose ?? (() => {})}
/>
Expand All @@ -172,23 +185,46 @@ export async function onSaveSearch({
}

const SaveSearchObjectModal: React.FC<{
services: DiscoverServices;
title: string;
showCopyOnSave: boolean;
description?: string;
timeRestore?: boolean;
onSave: (props: OnSaveProps & { newTimeRestore: boolean }) => void;
tags: string[];
onSave: (props: OnSaveProps & { newTimeRestore: boolean; newTags: string[] }) => void;
onClose: () => void;
}> = ({ title, description, showCopyOnSave, timeRestore: savedTimeRestore, onSave, onClose }) => {
}> = ({
services,
title,
description,
tags,
showCopyOnSave,
timeRestore: savedTimeRestore,
onSave,
onClose,
}) => {
const { savedObjectsTagging } = services;
const [timeRestore, setTimeRestore] = useState<boolean>(savedTimeRestore || false);
const [currentTags, setCurrentTags] = useState(tags);

const onModalSave = (params: OnSaveProps) => {
onSave({
...params,
newTimeRestore: timeRestore,
newTags: currentTags,
});
};

const options = (
const tagSelector = savedObjectsTagging ? (
<savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector
initialSelection={currentTags}
onTagsSelected={(newTags) => {
setCurrentTags(newTags);
}}
/>
) : undefined;

const timeSwitch = (
<EuiFormRow
helpText={
<FormattedMessage
Expand All @@ -211,6 +247,15 @@ const SaveSearchObjectModal: React.FC<{
</EuiFormRow>
);

const options = tagSelector ? (
<>
{tagSelector}
{timeSwitch}
</>
) : (
timeSwitch
);

return (
<SavedObjectSaveModal
title={title}
Expand Down
Loading

0 comments on commit 612061d

Please sign in to comment.