From 52dfd2aeb83d675a2010b550a3af42c43b388af1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 10 Nov 2020 14:45:14 -0700 Subject: [PATCH 01/14] add tag selector to save modal --- x-pack/plugins/maps/kibana.json | 2 +- x-pack/plugins/maps/public/kibana_services.ts | 1 + x-pack/plugins/maps/public/plugin.ts | 2 + .../public/routes/map_page/save_modal.tsx | 99 +++++++++++++++++++ .../routes/map_page/saved_map/saved_map.ts | 5 + .../public/routes/map_page/top_nav_config.tsx | 16 +++ 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/maps/public/routes/map_page/save_modal.tsx diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 8983d4ab1a4da..e47968b027cc3 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -19,7 +19,7 @@ "savedObjects", "share" ], - "optionalPlugins": ["home"], + "optionalPlugins": ["home", "savedObjectsTagging"], "ui": true, "server": true, "extraPublicDirs": ["common/constants"], diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 4dcc9193420c0..02b875257a5ac 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -48,6 +48,7 @@ export const getCoreI18n = () => coreStart.i18n; export const getSearchService = () => pluginsStart.data.search; export const getEmbeddableService = () => pluginsStart.embeddable; export const getNavigateToApp = () => coreStart.application.navigateToApp; +export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig: MapsConfigType; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 3da346aaf4443..ecb647cbb61b2 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -63,6 +63,7 @@ import { setLicensingPluginStart, } from './licensed_features'; import { EMSSettings } from '../common/ems_settings'; +import { SavedObjectTaggingPluginStart } from '../../saved_objects_tagging/public'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -86,6 +87,7 @@ export interface MapsPluginStartDependencies { visualizations: VisualizationsStart; savedObjects: SavedObjectsStart; dashboard: DashboardStart; + savedObjectsTagging?: SavedObjectTaggingPluginStart; } /** diff --git a/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx b/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx new file mode 100644 index 0000000000000..c36823d271600 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; +import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { + getSavedObjectsClient, + getCoreOverlays, + getSavedObjectsTagging, +} from '../../kibana_services'; +import { SavedMap } from './saved_map'; +import { + checkForDuplicateTitle, + SavedObjectSaveModalOrigin, + OnSaveProps, +} from '../../../../../../src/plugins/saved_objects/public'; + +interface Props { + savedMap: SavedMap; + description: string; +} + +interface State { + selectedTags: string[]; +} + +export class SaveModal extends Component { + constructor(props: Props) { + super(props); + this.state = { + selectedTags: props.savedMap.getTags(), + }; + } + + onTagsSelected(selectedTags: string[]) { + this.setState({ selectedTags }); + } + + render() { + const { savedMap } = this.props; + const savedObjectsTagging = getSavedObjectsTagging(); + const tagSelector = savedObjectsTagging ? ( + + ) : undefined; + + return ( + { + try { + await checkForDuplicateTitle( + { + id: props.newCopyOnSave ? undefined : savedMap.getSavedObjectId(), + title: props.newTitle, + copyOnSave: props.newCopyOnSave, + lastSavedTitle: savedMap.getSavedObjectId() ? savedMap.getTitle() : '', + getEsType: () => MAP_SAVED_OBJECT_TYPE, + getDisplayName: getMapEmbeddableDisplayName, + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient: getSavedObjectsClient(), + overlays: getCoreOverlays(), + } + ); + } catch (e) { + // ignore duplicate title failure, user notified in save modal + return {}; + } + + await savedMap.save({ + ...props, + saveByReference: true, + }); + // showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save + return { id: 'id' }; + }} + onClose={() => {}} + documentInfo={{ + description: this.props.mapDescription, + id: savedMap.getSavedObjectId(), + title: savedMap.getTitle(), + }} + objectType={i18n.translate('xpack.maps.topNav.saveModalType', { + defaultMessage: 'map', + })} + /> + ); + } +} diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index 036f8cf11d374..d0bf4bc1ccad2 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -216,6 +216,11 @@ export class SavedMap { return this._getStateTransfer().getAppNameFromId(appId); }; + public getTags(): string[] { + // TODO call something like savedObjectsTagging.ui.getTagIdsFromReferences(state.persistedDoc.references) + return []; + } + public hasSaveAndReturnConfig() { const hasOriginatingApp = !!this._originatingApp; const isNewMap = !this.getSavedObjectId(); diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx index 2d0a7d967a6cf..d357bf63b09e2 100644 --- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -14,6 +14,7 @@ import { getCoreI18n, getSavedObjectsClient, getCoreOverlays, + getSavedObjectsTagging, } from '../../kibana_services'; import { checkForDuplicateTitle, @@ -125,6 +126,19 @@ export function getTopNavConfig({ } }, run: () => { + let selectedTags = savedMap.getTags(); + function onTagsSelected(newTags: string[]) { + selectedTags = newTags; + } + + const savedObjectsTagging = getSavedObjectsTagging(); + const tagSelector = savedObjectsTagging ? ( + + ) : undefined; + const saveModal = ( ); showSaveModal(saveModal, getCoreI18n().Context); From 34f63856b46fbb7a38df09e35ad2470bcb7efe72 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 10 Nov 2020 16:08:31 -0700 Subject: [PATCH 02/14] save tag references onSave --- .../maps/public/map_attribute_service.ts | 15 ++++++++++---- .../routes/map_page/saved_map/saved_map.ts | 20 +++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index 0e3ef1b9ea518..984a8a9d142c2 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -33,20 +33,27 @@ export function getMapAttributeService(): MapAttributeService { MapByReferenceInput >(MAP_SAVED_OBJECT_TYPE, { saveMethod: async (attributes: MapSavedObjectAttributes, savedObjectId?: string) => { - const { attributes: attributesWithExtractedReferences, references } = extractReferences({ - attributes, + // AttributeService "attributes" contains "references" as a child. + // SavedObjectClient "attributes" uses "references" as a sibling. + // https://github.com/elastic/kibana/issues/83133 + const savedObjectClientReferences = attributes.references; + const savedObjectClientAttributes = { ...attributes }; + delete savedObjectClientAttributes.references; + const { attributes: updatedAttributes, references } = extractReferences({ + attributes: savedObjectClientAttributes, + references: savedObjectClientReferences, }); const savedObject = await (savedObjectId ? getSavedObjectsClient().update( MAP_SAVED_OBJECT_TYPE, savedObjectId, - attributesWithExtractedReferences, + updatedAttributes, { references } ) : getSavedObjectsClient().create( MAP_SAVED_OBJECT_TYPE, - attributesWithExtractedReferences, + updatedAttributes, { references } )); return { id: savedObject.id }; diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index d0bf4bc1ccad2..69e7decd2fe25 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -33,7 +33,12 @@ import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_sele import { getMapAttributeService } from '../../../map_attribute_service'; import { OnSaveProps } from '../../../../../../../src/plugins/saved_objects/public'; import { MapByReferenceInput, MapEmbeddableInput } from '../../../embeddable/types'; -import { getCoreChrome, getToasts, getIsAllowByValueEmbeddables } from '../../../kibana_services'; +import { + getCoreChrome, + getToasts, + getIsAllowByValueEmbeddables, + getSavedObjectsTagging, +} from '../../../kibana_services'; import { goToSpecifiedPath } from '../../../render_app'; import { LayerDescriptor } from '../../../../common/descriptor_types'; import { getInitialLayers } from './get_initial_layers'; @@ -252,9 +257,11 @@ export class SavedMap { newTitle, newCopyOnSave, returnToOrigin, + newTags, saveByReference, }: OnSaveProps & { returnToOrigin: boolean; + newTags?: string[]; saveByReference: boolean; }) { if (!this._attributes) { @@ -269,8 +276,17 @@ export class SavedMap { let updatedMapEmbeddableInput: MapEmbeddableInput; try { + const savedObjectsTagging = getSavedObjectsTagging(); + // Attribute service deviates from Saved Object client by including references as a child to attributes in stead of a sibling + const attributes = + savedObjectsTagging && newTags + ? { + ...this._attributes, + references: savedObjectsTagging.ui.updateTagsReferences([], newTags), + } + : this._attributes; updatedMapEmbeddableInput = (await getMapAttributeService().wrapAttributes( - this._attributes, + attributes, saveByReference, newCopyOnSave ? undefined : this._mapEmbeddableInput )) as MapEmbeddableInput; From f506746a075f7f07b586070e1162ee347b32c558 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 08:41:38 -0700 Subject: [PATCH 03/14] populate tags when unwrapping attributes --- x-pack/plugins/maps/public/map_attribute_service.ts | 2 +- .../public/routes/map_page/saved_map/saved_map.ts | 13 ++++++++++--- .../maps/public/routes/map_page/top_nav_config.tsx | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index 984a8a9d142c2..bc3ff3c3cc161 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -69,7 +69,7 @@ export function getMapAttributeService(): MapAttributeService { } const { attributes } = injectReferences(savedObject); - return attributes; + return { ...attributes, references: savedObject.references }; }, checkForDuplicateTitle: (props: OnSaveProps) => { return checkForDuplicateTitle( diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index 69e7decd2fe25..98f428f9a2999 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -56,6 +56,7 @@ export class SavedMap { private _originatingApp?: string; private readonly _stateTransfer?: EmbeddableStateTransfer; private readonly _store: MapStore; + private _tags: string[] = []; constructor({ defaultLayers = [], @@ -92,7 +93,14 @@ export class SavedMap { description: '', }; } else { - this._attributes = await getMapAttributeService().unwrapAttributes(this._mapEmbeddableInput); + const doc = await getMapAttributeService().unwrapAttributes(this._mapEmbeddableInput); + const references = doc.references; + delete doc.references; + this._attributes = doc; + const savedObjectsTagging = getSavedObjectsTagging(); + if (savedObjectsTagging && references && references.length) { + this._tags = savedObjectsTagging.ui.getTagIdsFromReferences(references); + } } if (this._attributes?.mapStateJSON) { @@ -222,8 +230,7 @@ export class SavedMap { }; public getTags(): string[] { - // TODO call something like savedObjectsTagging.ui.getTagIdsFromReferences(state.persistedDoc.references) - return []; + return this._tags; } public hasSaveAndReturnConfig() { diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx index d357bf63b09e2..43a74a9c73012 100644 --- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -134,7 +134,7 @@ export function getTopNavConfig({ const savedObjectsTagging = getSavedObjectsTagging(); const tagSelector = savedObjectsTagging ? ( ) : undefined; From 40384183f38a4ccf5f464f521cc207e7dc0f49b7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 09:15:11 -0700 Subject: [PATCH 04/14] tslint --- .../maps/public/map_attribute_service.ts | 15 ++- .../public/routes/map_page/save_modal.tsx | 99 ------------------- 2 files changed, 7 insertions(+), 107 deletions(-) delete mode 100644 x-pack/plugins/maps/public/routes/map_page/save_modal.tsx diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index bc3ff3c3cc161..9b2f3105f6870 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SavedObjectReference } from 'src/core/types'; import { AttributeService } from '../../../../src/plugins/embeddable/public'; import { MapSavedObjectAttributes } from '../common/map_saved_object_type'; import { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; @@ -14,11 +15,9 @@ import { getCoreOverlays, getEmbeddableService, getSavedObjectsClient } from './ import { extractReferences, injectReferences } from '../common/migrations/references'; import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; -export type MapAttributeService = AttributeService< - MapSavedObjectAttributes, - MapByValueInput, - MapByReferenceInput ->; +type MapDoc = MapSavedObjectAttributes & { references?: SavedObjectReference[] }; + +export type MapAttributeService = AttributeService; let mapAttributeService: MapAttributeService | null = null; @@ -28,11 +27,11 @@ export function getMapAttributeService(): MapAttributeService { } mapAttributeService = getEmbeddableService().getAttributeService< - MapSavedObjectAttributes, + MapDoc, MapByValueInput, MapByReferenceInput >(MAP_SAVED_OBJECT_TYPE, { - saveMethod: async (attributes: MapSavedObjectAttributes, savedObjectId?: string) => { + saveMethod: async (attributes: MapDoc, savedObjectId?: string) => { // AttributeService "attributes" contains "references" as a child. // SavedObjectClient "attributes" uses "references" as a sibling. // https://github.com/elastic/kibana/issues/83133 @@ -58,7 +57,7 @@ export function getMapAttributeService(): MapAttributeService { )); return { id: savedObject.id }; }, - unwrapMethod: async (savedObjectId: string): Promise => { + unwrapMethod: async (savedObjectId: string): Promise => { const savedObject = await getSavedObjectsClient().get( MAP_SAVED_OBJECT_TYPE, savedObjectId diff --git a/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx b/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx deleted file mode 100644 index c36823d271600..0000000000000 --- a/x-pack/plugins/maps/public/routes/map_page/save_modal.tsx +++ /dev/null @@ -1,99 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import { i18n } from '@kbn/i18n'; -import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; -import { - getSavedObjectsClient, - getCoreOverlays, - getSavedObjectsTagging, -} from '../../kibana_services'; -import { SavedMap } from './saved_map'; -import { - checkForDuplicateTitle, - SavedObjectSaveModalOrigin, - OnSaveProps, -} from '../../../../../../src/plugins/saved_objects/public'; - -interface Props { - savedMap: SavedMap; - description: string; -} - -interface State { - selectedTags: string[]; -} - -export class SaveModal extends Component { - constructor(props: Props) { - super(props); - this.state = { - selectedTags: props.savedMap.getTags(), - }; - } - - onTagsSelected(selectedTags: string[]) { - this.setState({ selectedTags }); - } - - render() { - const { savedMap } = this.props; - const savedObjectsTagging = getSavedObjectsTagging(); - const tagSelector = savedObjectsTagging ? ( - - ) : undefined; - - return ( - { - try { - await checkForDuplicateTitle( - { - id: props.newCopyOnSave ? undefined : savedMap.getSavedObjectId(), - title: props.newTitle, - copyOnSave: props.newCopyOnSave, - lastSavedTitle: savedMap.getSavedObjectId() ? savedMap.getTitle() : '', - getEsType: () => MAP_SAVED_OBJECT_TYPE, - getDisplayName: getMapEmbeddableDisplayName, - }, - props.isTitleDuplicateConfirmed, - props.onTitleDuplicate, - { - savedObjectsClient: getSavedObjectsClient(), - overlays: getCoreOverlays(), - } - ); - } catch (e) { - // ignore duplicate title failure, user notified in save modal - return {}; - } - - await savedMap.save({ - ...props, - saveByReference: true, - }); - // showSaveModal wrapper requires onSave to return an object with an id to close the modal after successful save - return { id: 'id' }; - }} - onClose={() => {}} - documentInfo={{ - description: this.props.mapDescription, - id: savedMap.getSavedObjectId(), - title: savedMap.getTitle(), - }} - objectType={i18n.translate('xpack.maps.topNav.saveModalType', { - defaultMessage: 'map', - })} - /> - ); - } -} From ebc47e7c9bf93066b9b4c9529e79f6ab2c6fd604 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 11:12:56 -0700 Subject: [PATCH 05/14] update listing page to show tags --- .../table_list_view/table_list_view.tsx | 4 +- .../routes/list_page/maps_list_view.tsx | 558 ++++-------------- 2 files changed, 103 insertions(+), 459 deletions(-) diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 4f509876a75f4..3d5483eb19f4a 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -57,7 +57,7 @@ export interface TableListViewProps { listingLimit: number; initialFilter: string; initialPageSize: number; - noItemsFragment: JSX.Element; + noItemsFragment?: JSX.Element; tableColumns: Array>; tableListTitle: string; toastNotifications: ToastsStart; @@ -73,7 +73,7 @@ export interface TableListViewProps { /** * Describes the content of the table. If not specified, the caption will be "This table contains {itemCount} rows." */ - tableCaption: string; + tableCaption?: string; searchFilters?: SearchFilterConfig[]; } diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index 97ed3d428d341..f5ee6b7962411 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -5,31 +5,9 @@ */ import React, { MouseEvent } from 'react'; -import _ from 'lodash'; -import { - EuiTitle, - EuiFieldSearch, - EuiBasicTable, - EuiPage, - EuiPageBody, - EuiPageContent, - EuiLink, - EuiFlexGroup, - EuiFlexItem, - EuiButton, - EuiSpacer, - EuiOverlayMask, - EuiConfirmModal, - EuiCallOut, -} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { Direction } from '@elastic/eui'; -import { - CriteriaWithPagination, - EuiBasicTableColumn, -} from '@elastic/eui/src/components/basic_table/basic_table'; -import { EuiTableSortingType } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; +import { TableListView } from '../../../../../../src/plugins/kibana_react/public'; import { goToSpecifiedPath } from '../../render_app'; import { APP_ID, MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { @@ -39,459 +17,125 @@ import { getCoreChrome, getNavigateToApp, getSavedObjectsClient, + getSavedObjectsTagging, + getSavedObjects, } from '../../kibana_services'; import { getAppTitle } from '../../../common/i18n_getters'; import { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; -export const EMPTY_FILTER = ''; - -function navigateToNewMap() { - const navigateToApp = getNavigateToApp(); - navigateToApp(APP_ID, { - path: MAP_PATH, - }); -} -interface State { - sortField?: string | number | symbol; - sortDirection?: Direction; - hasInitialFetchReturned: boolean; - isFetchingItems: boolean; - showDeleteModal: boolean; - showLimitError: boolean; - filter: string; - items: TableRow[]; - selectedIds: string[]; - page: number; - perPage: number; - readOnly: boolean; - listingLimit: number; - totalItems?: number; -} - -interface TableRow { - id: string; - title: string; - description?: string; +const savedObjectsTagging = getSavedObjectsTagging(); +const searchFilters = savedObjectsTagging + ? [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })] + : []; + +const tableColumns = [ + { + field: 'title', + name: i18n.translate('xpack.maps.mapListing.titleFieldTitle', { + defaultMessage: 'Title', + }), + sortable: true, + render: (field, record) => ( + { + e.preventDefault(); + goToSpecifiedPath(`/${MAP_PATH}/${record.id}`); + }} + data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`} + > + {field} + + ), + }, + { + field: 'description', + name: i18n.translate('xpack.maps.mapListing.descriptionFieldTitle', { + defaultMessage: 'Description', + }), + dataType: 'string', + sortable: true, + }, +]; +if (savedObjectsTagging) { + tableColumns.push(savedObjectsTagging.ui.getTableColumnDefinition()); } -export class MapsListView extends React.Component { - _isMounted: boolean = false; - state: State = { - hasInitialFetchReturned: false, - isFetchingItems: false, - showDeleteModal: false, - showLimitError: false, - filter: EMPTY_FILTER, - items: [], - selectedIds: [], - page: 0, - perPage: 20, - readOnly: !getMapsCapabilities().save, - listingLimit: getUiSettings().get('savedObjects:listingLimit'), - }; - - componentWillUnmount() { - this._isMounted = false; - this.debouncedFetch.cancel(); +export function MapsListView() { + function navigateToNewMap() { + const navigateToApp = getNavigateToApp(); + navigateToApp(APP_ID, { + path: MAP_PATH, + }); } - componentDidMount() { - this._isMounted = true; - this.initMapList(); - } + async function findMaps(searchQuery: string) { + let searchTerm = searchQuery; + let tagReferences; - async initMapList() { - this.fetchItems(); - getCoreChrome().docTitle.change(getAppTitle()); - getCoreChrome().setBreadcrumbs([{ text: getAppTitle() }]); - } + if (savedObjectsTagging) { + const parsed = savedObjectsTagging.ui.parseSearchQuery(searchQuery, { + useName: true, + }); + searchTerm = parsed.searchTerm; + tagReferences = parsed.tagReferences; + } - debouncedFetch = _.debounce(async (filter) => { - const response = await getSavedObjectsClient().find({ + const resp = await getSavedObjectsClient().find({ type: MAP_SAVED_OBJECT_TYPE, - search: filter ? `${filter}*` : undefined, - perPage: this.state.listingLimit, + search: searchTerm ? `${searchTerm}*` : undefined, + perPage: getSavedObjects().settings.getListingLimit(), page: 1, searchFields: ['title^3', 'description'], defaultSearchOperator: 'AND', fields: ['description', 'title'], + hasReference: tagReferences, }); - if (!this._isMounted) { - return; - } - - // We need this check to handle the case where search results come back in a different - // order than they were sent out. Only load results for the most recent search. - if (filter === this.state.filter) { - this.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, - items: response.savedObjects.map((savedObject) => { - return { - id: savedObject.id, - title: savedObject.attributes.title, - description: savedObject.attributes.description, - }; - }), - totalItems: response.total, - showLimitError: response.total > this.state.listingLimit, - }); - } - }, 300); - - fetchItems = () => { - this.setState( - { - isFetchingItems: true, - }, - this.debouncedFetch.bind(null, this.state.filter) - ); - }; - - deleteSelectedItems = async () => { - try { - const deletions = this.state.selectedIds.map((id) => { - return getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, id); - }); - await Promise.all(deletions); - } catch (error) { - getToasts().addDanger({ - title: i18n.translate('xpack.maps.mapListing.unableToDeleteToastTitle', { - defaultMessage: `Unable to delete map(s)`, - }), - text: `${error}`, - }); - } - this.fetchItems(); - this.setState({ - selectedIds: [], - }); - this.closeDeleteModal(); - }; - - closeDeleteModal = () => { - this.setState({ showDeleteModal: false }); - }; - - openDeleteModal = () => { - this.setState({ showDeleteModal: true }); - }; - - onTableChange = ({ page, sort }: CriteriaWithPagination) => { - const { index: pageIndex, size: pageSize } = page; - - let { field: sortField, direction: sortDirection } = sort || {}; - - // 3rd sorting state that is not captured by sort - native order (no sort) - // when switching from desc to asc for the same field - use native order - if ( - this.state.sortField === sortField && - this.state.sortDirection === 'desc' && - sortDirection === 'asc' - ) { - sortField = undefined; - sortDirection = undefined; - } - - this.setState({ - page: pageIndex, - perPage: pageSize, - sortField, - sortDirection, - }); - }; - - getPageOfItems = () => { - // do not sort original list to preserve elasticsearch ranking order - const itemsCopy = this.state.items.slice(); - - if (this.state.sortField) { - itemsCopy.sort((a, b) => { - const fieldA = _.get(a, this.state.sortField!, ''); - const fieldB = _.get(b, this.state.sortField!, ''); - let order = 1; - if (this.state.sortDirection === 'desc') { - order = -1; - } - return order * fieldA.toLowerCase().localeCompare(fieldB.toLowerCase()); - }); - } - - // If begin is greater than the length of the sequence, an empty array is returned. - const startIndex = this.state.page * this.state.perPage; - // If end is greater than the length of the sequence, slice extracts through to the end of the sequence (arr.length). - const lastIndex = startIndex + this.state.perPage; - return itemsCopy.slice(startIndex, lastIndex); - }; - - hasNoItems() { - if (!this.state.isFetchingItems && this.state.items.length === 0 && !this.state.filter) { - return true; - } - - return false; - } - - renderConfirmDeleteModal() { - return ( - - -

- -

-
-
- ); - } - - renderListingLimitWarning() { - if (this.state.showLimitError) { - return ( - - -

- - - - - . -

-
- -
- ); - } - } - - renderNoResultsMessage() { - if (this.state.isFetchingItems) { - return ''; - } - - if (this.hasNoItems()) { - return i18n.translate('xpack.maps.mapListing.noItemsDescription', { - defaultMessage: `Looks like you don't have any maps. Click the create button to create one.`, - }); - } - - return i18n.translate('xpack.maps.mapListing.noMatchDescription', { - defaultMessage: 'No items matched your search.', - }); - } - - renderSearchBar() { - let deleteBtn; - if (this.state.selectedIds.length > 0) { - deleteBtn = ( - - - - - - ); - } - - return ( - - {deleteBtn} - - { - this.setState( - { - filter: e.target.value, - }, - this.fetchItems - ); - }} - data-test-subj="searchFilter" - /> - - - ); - } - - renderTable() { - const tableColumns: Array> = [ - { - field: 'title', - name: i18n.translate('xpack.maps.mapListing.titleFieldTitle', { - defaultMessage: 'Title', - }), - sortable: true, - render: (field, record) => ( - { - e.preventDefault(); - goToSpecifiedPath(`/${MAP_PATH}/${record.id}`); - }} - data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`} - > - {field} - - ), - }, - { - field: 'description', - name: i18n.translate('xpack.maps.mapListing.descriptionFieldTitle', { - defaultMessage: 'Description', - }), - dataType: 'string', - sortable: true, - }, - ]; - const pagination = { - pageIndex: this.state.page, - pageSize: this.state.perPage, - totalItemCount: this.state.items.length, - pageSizeOptions: [10, 20, 50], + return { + total: resp.total, + hits: resp.savedObjects.map((savedObject) => { + return { + id: savedObject.id, + title: savedObject.attributes.title, + description: savedObject.attributes.description, + references: savedObject.references, + }; + }), }; - - let selection; - if (!this.state.readOnly) { - selection = { - onSelectionChange: (s: TableRow[]) => { - this.setState({ - selectedIds: s.map((item) => { - return item.id; - }), - }); - }, - }; - } - - const sorting: EuiTableSortingType = {}; - if (this.state.sortField) { - sorting.sort = { - field: this.state.sortField, - direction: this.state.sortDirection!, - }; - } - const items = this.state.items.length === 0 ? [] : this.getPageOfItems(); - - return ( - - ); } - renderListing() { - let createButton; - if (!this.state.readOnly) { - createButton = ( - - - - ); - } - return ( - - {this.state.showDeleteModal && this.renderConfirmDeleteModal()} - - - - -

- -

-
-
- - {createButton} -
- - - - {this.renderListingLimitWarning()} - - {this.renderSearchBar()} - - - - {this.renderTable()} -
- ); - } - - renderPageContent() { - if (!this.state.hasInitialFetchReturned) { - return; - } - - return {this.renderListing()}; + function deleteMaps(items) { + items.forEach((item) => { + getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, item.id); + }); } - render() { - return ( - - {this.renderPageContent()} - - ); - } + const isReadOnly = !getMapsCapabilities().save; + + getCoreChrome().docTitle.change(getAppTitle()); + getCoreChrome().setBreadcrumbs([{ text: getAppTitle() }]); + + return ( + + ); } From acbf2555f253704c4692526014008c9940daea96 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 11:19:57 -0700 Subject: [PATCH 06/14] fix data-test-subj id in functional tests --- x-pack/test/functional/page_objects/gis_page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index c4f1bd7dc2a6b..abb0c4b0f0c1b 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -174,7 +174,7 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte } async expectMissingCreateNewButton() { - await testSubjects.missingOrFail('newMapLink'); + await testSubjects.missingOrFail('newItemButton'); } async expectMissingAddLayerButton() { From 447876e641859038a2d055a03b289c17c1b4af6d Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 12:21:28 -0700 Subject: [PATCH 07/14] i18n cleanup --- .../translations/translations/ja-JP.json | 27 +++++-------------- .../translations/translations/zh-CN.json | 27 +++++-------------- 2 files changed, 12 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3f0db7fe5a99c..7217684f47d39 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1361,6 +1361,12 @@ "data.search.unableToGetSavedQueryToastTitle": "保存したクエリ {savedQueryId} を読み込めません", "data.search.upgradeLicense": "クエリがタイムアウトしました。無料のベーシックティアではクエリがタイムアウトすることはありません。", "data.search.upgradeLicenseActionText": "今すぐアップグレード", + "data.search.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", + "data.search.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", + "data.search.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", + "data.search.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", + "data.search.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", + "data.search.functions.kibana.help": "Kibana グローバルコンテキストを取得します", "devTools.badge.readOnly.text": "読み込み専用", "devTools.badge.readOnly.tooltip": "を保存できませんでした", "devTools.devToolsTitle": "開発ツール", @@ -1662,12 +1668,6 @@ "expressions.functions.font.invalidFontWeightErrorMessage": "無効なフォント太さ:'{weight}'", "expressions.functions.font.invalidTextAlignmentErrorMessage": "無効なテキストアラインメント:'{align}'", "expressions.functions.fontHelpText": "フォントスタイルを作成します。", - "data.search.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", - "data.search.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", - "data.search.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", - "data.search.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", - "data.search.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", - "data.search.functions.kibana.help": "Kibana グローバルコンテキストを取得します", "expressions.functions.theme.args.defaultHelpText": "テーマ情報がない場合のデフォルト値。", "expressions.functions.theme.args.variableHelpText": "読み取るテーマ変数名。", "expressions.functions.themeHelpText": "テーマ設定を読み取ります。", @@ -11293,24 +11293,9 @@ "xpack.maps.layerWizardSelect.solutionsCategoryLabel": "ソリューション", "xpack.maps.loadMap.errorAttemptingToLoadSavedMap": "マップを読み込めません", "xpack.maps.map.initializeErrorTitle": "マップを初期化できません", - "xpack.maps.mapListing.advancedSettingsLinkText": "高度な設定", - "xpack.maps.mapListing.cancelTitle": "キャンセル", - "xpack.maps.mapListing.createMapButtonLabel": "マップを作成", - "xpack.maps.mapListing.deleteSelectedButtonLabel": "選択項目を削除", - "xpack.maps.mapListing.deleteSelectedItemsTitle": "選択項目を削除しますか?", - "xpack.maps.mapListing.deleteTitle": "削除", - "xpack.maps.mapListing.deleteWarning": "削除されたアイテムは復元できません。", "xpack.maps.mapListing.descriptionFieldTitle": "説明", "xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "マップを読み込めません", - "xpack.maps.mapListing.limitExceededTitle": "リスティング制限超過", - "xpack.maps.mapListing.limitHelpDescription": "{totalItems} 個のアイテムがありますが、listingLimit の設定により {listingLimit} 個までしか下の表に表示できません。この設定は次の場所で変更できます ", - "xpack.maps.mapListing.listingTableTitle": "マップ", - "xpack.maps.mapListing.noItemsDescription": "マップがないようです。作成ボタンをクリックして作成してください。", - "xpack.maps.mapListing.noMatchDescription": "検索に一致するアイテムがありません。", - "xpack.maps.mapListing.searchAriaLabel": "フィルターアイテム", - "xpack.maps.mapListing.searchPlaceholder": "検索…", "xpack.maps.mapListing.titleFieldTitle": "タイトル", - "xpack.maps.mapListing.unableToDeleteToastTitle": "マップを削除できません", "xpack.maps.maps.choropleth.rightSourcePlaceholder": "インデックスパターンを選択", "xpack.maps.mapSavedObjectLabel": "マップ", "xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "自動的にマップをデータ境界に合わせる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e46cfb6658ea6..8fc5005c69ad2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1362,6 +1362,12 @@ "data.search.unableToGetSavedQueryToastTitle": "无法加载已保存查询 {savedQueryId}", "data.search.upgradeLicense": "您的查询已超时。使用我们免费的基础级许可,您的查询永不会超时。", "data.search.upgradeLicenseActionText": "立即升级", + "data.search.functions.kibana_context.filters.help": "指定 Kibana 常规筛选", + "data.search.functions.kibana_context.help": "更新 kibana 全局上下文", + "data.search.functions.kibana_context.q.help": "指定 Kibana 自由格式文本查询", + "data.search.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", + "data.search.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", + "data.search.functions.kibana.help": "获取 kibana 全局上下文", "devTools.badge.readOnly.text": "只读", "devTools.badge.readOnly.tooltip": "无法保存", "devTools.devToolsTitle": "开发工具", @@ -1663,12 +1669,6 @@ "expressions.functions.font.invalidFontWeightErrorMessage": "无效的字体粗细:“{weight}”", "expressions.functions.font.invalidTextAlignmentErrorMessage": "无效的文本对齐方式:“{align}”", "expressions.functions.fontHelpText": "创建字体样式。", - "data.search.functions.kibana_context.filters.help": "指定 Kibana 常规筛选", - "data.search.functions.kibana_context.help": "更新 kibana 全局上下文", - "data.search.functions.kibana_context.q.help": "指定 Kibana 自由格式文本查询", - "data.search.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", - "data.search.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", - "data.search.functions.kibana.help": "获取 kibana 全局上下文", "expressions.functions.theme.args.defaultHelpText": "主题信息不可用时的默认值。", "expressions.functions.theme.args.variableHelpText": "要读取的主题变量的名称。", "expressions.functions.themeHelpText": "读取主题设置。", @@ -11306,24 +11306,9 @@ "xpack.maps.layerWizardSelect.solutionsCategoryLabel": "解决方案", "xpack.maps.loadMap.errorAttemptingToLoadSavedMap": "无法加载地图", "xpack.maps.map.initializeErrorTitle": "无法初始化地图", - "xpack.maps.mapListing.advancedSettingsLinkText": "高级设置", - "xpack.maps.mapListing.cancelTitle": "取消", - "xpack.maps.mapListing.createMapButtonLabel": "创建地图", - "xpack.maps.mapListing.deleteSelectedButtonLabel": "删除选定", - "xpack.maps.mapListing.deleteSelectedItemsTitle": "删除选定项?", - "xpack.maps.mapListing.deleteTitle": "删除", - "xpack.maps.mapListing.deleteWarning": "您无法恢复已删除项。", "xpack.maps.mapListing.descriptionFieldTitle": "描述", "xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "无法加载地图", - "xpack.maps.mapListing.limitExceededTitle": "已超过列表限制", - "xpack.maps.mapListing.limitHelpDescription": "您有 {totalItems} 项,但您的 listingLimit 设置阻止下表显示 {listingLimit} 项以上。此设置可在以下选项下更改: ", - "xpack.maps.mapListing.listingTableTitle": "Maps", - "xpack.maps.mapListing.noItemsDescription": "似乎您没有任何地图。单击创建按钮来创建。", - "xpack.maps.mapListing.noMatchDescription": "没有任何项匹配您的搜索。", - "xpack.maps.mapListing.searchAriaLabel": "筛选项", - "xpack.maps.mapListing.searchPlaceholder": "搜索......", "xpack.maps.mapListing.titleFieldTitle": "标题", - "xpack.maps.mapListing.unableToDeleteToastTitle": "无法删除地图", "xpack.maps.maps.choropleth.rightSourcePlaceholder": "选择索引模式", "xpack.maps.mapSavedObjectLabel": "地图", "xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "使地图自适应数据边界", From b8fc06811e3579736bfa2aa522010bcd4ea99988 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 13:34:14 -0700 Subject: [PATCH 08/14] tslint --- .../routes/list_page/maps_list_view.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index f5ee6b7962411..efebe4fa31d3c 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -5,8 +5,10 @@ */ import React, { MouseEvent } from 'react'; +import { SavedObjectReference } from 'src/core/types'; import { i18n } from '@kbn/i18n'; import { EuiLink } from '@elastic/eui'; +import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; import { TableListView } from '../../../../../../src/plugins/kibana_react/public'; import { goToSpecifiedPath } from '../../render_app'; import { APP_ID, MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; @@ -23,19 +25,26 @@ import { import { getAppTitle } from '../../../common/i18n_getters'; import { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; +interface MapItem { + id: string; + title: string; + description?: string; + references?: SavedObjectReference[]; +} + const savedObjectsTagging = getSavedObjectsTagging(); const searchFilters = savedObjectsTagging ? [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })] : []; -const tableColumns = [ +const tableColumns: Array> = [ { field: 'title', name: i18n.translate('xpack.maps.mapListing.titleFieldTitle', { defaultMessage: 'Title', }), sortable: true, - render: (field, record) => ( + render: (field: string, record: MapItem) => ( { e.preventDefault(); @@ -104,10 +113,11 @@ export function MapsListView() { }; } - function deleteMaps(items) { - items.forEach((item) => { - getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, item.id); + async function deleteMaps(items: object[]) { + const deletions = items.map((item) => { + return getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, (item as MapItem).id); }); + await Promise.all(deletions); } const isReadOnly = !getMapsCapabilities().save; @@ -119,9 +129,9 @@ export function MapsListView() { ); From 2cb1ea54cc71d8b89a024fdf956628c24e5195bf Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 13:46:37 -0700 Subject: [PATCH 09/14] remove unused import --- x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index efebe4fa31d3c..aabbeb748a801 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -14,7 +14,6 @@ import { goToSpecifiedPath } from '../../render_app'; import { APP_ID, MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { getMapsCapabilities, - getUiSettings, getToasts, getCoreChrome, getNavigateToApp, From a189307c82a77c0ace07f9d57361cadc038e8c40 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 16:17:39 -0700 Subject: [PATCH 10/14] use listingTable service for functional tests --- .../table_list_view/table_list_view.tsx | 1 + .../functional/page_objects/dashboard_page.ts | 4 +- test/functional/services/listing_table.ts | 31 +++++++--------- .../apps/maps/saved_object_management.js | 14 +++---- .../test/functional/page_objects/gis_page.ts | 37 ++++++------------- 5 files changed, 32 insertions(+), 55 deletions(-) diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 3d5483eb19f4a..75fd2c30a995a 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -445,6 +445,7 @@ class TableListView extends React.Component { const elements = await find.allByCssSelector( `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` @@ -126,14 +123,8 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider /** * Searches for item on Landing page and retruns items count that match `ListingTitleLink-${name}` pattern - * @param appName 'visualize' | 'dashboard' - * @param name item name */ - public async searchAndExpectItemsCount( - appName: 'visualize' | 'dashboard', - name: string, - count: number - ) { + public async searchAndExpectItemsCount(appName: AppName, name: string, count: number) { await this.searchForItemWithName(name); await retry.try(async () => { const links = await testSubjects.findAll( @@ -165,10 +156,8 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider /** * Clicks item on Landing page by link name if it is present - * @param appName 'dashboard' | 'visualize' - * @param name item name */ - public async clickItemLink(appName: 'dashboard' | 'visualize', name: string) { + public async clickItemLink(appName: AppName, name: string) { await testSubjects.click( `${prefixMap[appName]}ListingTitleLink-${name.split(' ').join('-')}` ); @@ -204,6 +193,12 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider } }); } + + public async onListingPage(appName: AppName) { + return await testSubjects.exists(`${appName}LandingPage`, { + timeout: 5000, + }); + } } return new ListingTable(); diff --git a/x-pack/test/functional/apps/maps/saved_object_management.js b/x-pack/test/functional/apps/maps/saved_object_management.js index 277a8a5651453..8c62136472921 100644 --- a/x-pack/test/functional/apps/maps/saved_object_management.js +++ b/x-pack/test/functional/apps/maps/saved_object_management.js @@ -139,8 +139,8 @@ export default function ({ getPageObjects, getService }) { await PageObjects.maps.openNewMap(); await PageObjects.maps.saveMap(MAP1_NAME); - const count = await PageObjects.maps.getMapCountWithName(MAP1_NAME); - expect(count).to.equal(1); + + await PageObjects.maps.searchAndExpectItemsCount(MAP1_NAME, 1); }); it('should allow saving map that crosses dateline', async () => { @@ -148,8 +148,8 @@ export default function ({ getPageObjects, getService }) { await PageObjects.maps.setView('64', '179', '5'); await PageObjects.maps.saveMap(MAP2_NAME); - const count = await PageObjects.maps.getMapCountWithName(MAP2_NAME); - expect(count).to.equal(1); + + await PageObjects.maps.searchAndExpectItemsCount(MAP2_NAME, 1); }); }); @@ -157,11 +157,9 @@ export default function ({ getPageObjects, getService }) { it('should delete selected saved objects', async () => { await PageObjects.maps.deleteSavedMaps(MAP_NAME_PREFIX); - const map1Count = await PageObjects.maps.getMapCountWithName(MAP1_NAME); - expect(map1Count).to.equal(0); + await PageObjects.maps.searchAndExpectItemsCount(MAP1_NAME, 0); - const map2Count = await PageObjects.maps.getMapCountWithName(MAP2_NAME); - expect(map2Count).to.equal(0); + await PageObjects.maps.searchAndExpectItemsCount(MAP2_NAME, 0); }); }); }); diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index abb0c4b0f0c1b..7f9290c838e60 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -21,6 +21,7 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte const renderable = getService('renderable'); const browser = getService('browser'); const MenuToggle = getService('MenuToggle'); + const listingTable = getService('listingTable'); const setViewPopoverToggle = new MenuToggle({ name: 'SetView Popover', @@ -120,13 +121,10 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte await retry.try(async () => { await this.searchForMapWithName(name); - await this.selectMap(name); + await listingTable.clickItemLink('map', name); await PageObjects.header.waitUntilLoadingHasFinished(); - - const onMapListingPage = await this.onMapListingPage(); - if (onMapListingPage) { - throw new Error(`Failed to open map ${name}`); - } + // check Map landing page is not present + await testSubjects.missingOrFail('mapLandingPage', { timeout: 10000 }); }); await this.waitForLayersToLoad(); @@ -134,8 +132,8 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte async deleteSavedMaps(search: string) { await this.searchForMapWithName(search); - await testSubjects.click('checkboxSelectAll'); - await testSubjects.click('deleteSelectedItems'); + await listingTable.checkListingSelectAllCheckbox(); + await listingTable.clickDeleteSelected(); await PageObjects.common.clickConfirmOnModal(); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -187,8 +185,7 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte async onMapListingPage() { log.debug(`onMapListingPage`); - const exists = await testSubjects.exists('mapsListingPage', { timeout: 3500 }); - return exists; + return await listingTable.onListingPage('map'); } async searchForMapWithName(name: string) { @@ -196,21 +193,11 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte await this.gotoMapListingPage(); - await retry.try(async () => { - const searchFilter = await testSubjects.find('searchFilter'); - await searchFilter.clearValue(); - await searchFilter.click(); - await searchFilter.type(name); - await PageObjects.common.pressEnterKey(); - }); + await listingTable.searchForItemWithName(name); await PageObjects.header.waitUntilLoadingHasFinished(); } - async selectMap(name: string) { - await testSubjects.click(`mapListingTitleLink-${name.split(' ').join('-')}`); - } - async getHits() { await inspector.open(); await inspector.openInspectorRequestsView(); @@ -232,13 +219,11 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte } } - async getMapCountWithName(name: string) { + async searchAndExpectItemsCount(name, count) { await this.gotoMapListingPage(); - log.debug(`getMapCountWithName: ${name}`); - await this.searchForMapWithName(name); - const buttons = await find.allByButtonText(name); - return buttons.length; + log.debug(`searchAndExpectItemsCount: ${name}`); + await listingTable.searchAndExpectItemsCount('map', name, count); } async setView(lat: number, lon: number, zoom: number) { From a6e6ae596cca419fc90343830c1916679ef030c2 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 11 Nov 2020 18:39:43 -0700 Subject: [PATCH 11/14] tslint and fix mvt grid layer functional test --- x-pack/test/functional/apps/maps/mvt_super_fine.js | 2 +- x-pack/test/functional/es_archives/maps/kibana/data.json | 2 +- x-pack/test/functional/page_objects/gis_page.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/maps/mvt_super_fine.js b/x-pack/test/functional/apps/maps/mvt_super_fine.js index b5a7935a81eb5..6d86b93c3ec44 100644 --- a/x-pack/test/functional/apps/maps/mvt_super_fine.js +++ b/x-pack/test/functional/apps/maps/mvt_super_fine.js @@ -19,7 +19,7 @@ export default function ({ getPageObjects, getService }) { ['global_maps_all', 'test_logstash_reader', 'geoshape_data_reader'], false ); - await PageObjects.maps.loadSavedMap('geo grid vector grid example (SUPER_FINE resolution)'); + await PageObjects.maps.loadSavedMap('geo grid vector grid example SUPER_FINE resolution'); }); after(async () => { diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index e3a8743e60897..79e8c14cc3982 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -638,7 +638,7 @@ "description": "", "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"TILED_VECTOR\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", - "title": "geo grid vector grid example (SUPER_FINE resolution)", + "title": "geo grid vector grid example SUPER_FINE resolution", "uiStateJSON": "{\"isDarkMode\":false}" }, "type": "map" diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 7f9290c838e60..5d457665074be 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -219,7 +219,7 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte } } - async searchAndExpectItemsCount(name, count) { + async searchAndExpectItemsCount(name: string, count: number) { await this.gotoMapListingPage(); log.debug(`searchAndExpectItemsCount: ${name}`); From 22c5e8f01cbb8cf740f160d78024532199c7c6e1 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 12 Nov 2020 12:20:32 -0700 Subject: [PATCH 12/14] review feedback --- x-pack/plugins/maps/server/plugin.ts | 2 +- .../test/functional/page_objects/gis_page.ts | 9 +- .../security_and_spaces/apis/create.ts | 1 + .../fixtures/es_archiver/maps/data.json | 210 ++++++++++++++++++ .../common/lib/authentication.ts | 18 ++ .../functional/tests/index.ts | 1 + .../functional/tests/maps_integration.ts | 139 ++++++++++++ 7 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 x-pack/test/saved_object_tagging/common/fixtures/es_archiver/maps/data.json create mode 100644 x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 65d79272494f0..5925db1af691f 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -194,7 +194,7 @@ export class MapsPlugin implements Plugin { catalogue: [APP_ID], savedObject: { all: [], - read: [MAP_SAVED_OBJECT_TYPE, 'index-pattern', 'query'], + read: [MAP_SAVED_OBJECT_TYPE, 'index-pattern', 'query', 'tag'], }, ui: ['show'], }, diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 5d457665074be..7e22acf785d36 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -148,7 +148,7 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte await renderable.waitForRender(); } - async saveMap(name: string, uncheckReturnToOriginModeSwitch = false) { + async saveMap(name: string, uncheckReturnToOriginModeSwitch = false, tags?: string[]) { await testSubjects.click('mapSaveButton'); await testSubjects.setValue('savedObjectTitle', name); if (uncheckReturnToOriginModeSwitch) { @@ -160,6 +160,13 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte } await testSubjects.setEuiSwitch('returnToOriginModeSwitch', 'uncheck'); } + if (tags) { + await testSubjects.click('savedObjectTagSelector'); + for (const tagName of tags) { + await testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); + } + await testSubjects.click('savedObjectTitle'); + } await testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton'); } diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/create.ts index 70884ba6c968b..8ca92ac472c6e 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/create.ts @@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, USERS.NOT_A_KIBANA_USER, ], }; diff --git a/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/maps/data.json b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/maps/data.json new file mode 100644 index 0000000000000..cdaf4fe171ec0 --- /dev/null +++ b/x-pack/test/saved_object_tagging/common/fixtures/es_archiver/maps/data.json @@ -0,0 +1,210 @@ +{ + "type": "doc", + "value": { + "id": "space:default", + "index": ".kibana", + "source": { + "space": { + "_reserved": true, + "description": "This is the default space", + "name": "Default Space" + }, + "type": "space", + "updated_at": "2017-09-21T18:49:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "tag:tag-1", + "index": ".kibana", + "source": { + "tag": { + "name": "tag-1", + "description": "My first tag!", + "color": "#FF00FF" + }, + "type": "tag", + "updated_at": "2017-09-21T18:49:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "tag:tag-2", + "index": ".kibana", + "source": { + "tag": { + "name": "tag-2", + "description": "Another awesome tag", + "color": "#11FF22" + }, + "type": "tag", + "updated_at": "2017-09-21T18:49:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "tag:tag-3", + "index": ".kibana", + "source": { + "tag": { + "name": "tag-3", + "description": "Last but not least", + "color": "#AA0077" + }, + "type": "tag", + "updated_at": "2017-09-21T18:49:16.270Z" + }, + "type": "doc" + } +} + +{ + "type": "doc", + "value": { + "id": "config:6.3.0", + "index": ".kibana", + "source": { + "config": { + "buildNum": 8467, + "defaultIndex": "0bf35f60-3dc9-11e8-8660-4d65aa086b3c" + }, + "references": [ + ], + "type": "config", + "updated_at": "2018-04-11T20:43:55.434Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "map:63af0ed0-2515-11eb-8f44-eda8d4b698b3", + "index": ".kibana", + "source": { + "map": { + "title" : "map 3 (tag-1 and tag-3)", + "description" : "", + "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"d897b506-e719-42b8-9927-351eedd7d357\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"}]", + "mapStateJSON" : "{\"zoom\":0.97,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "type" : "map", + "references" : [ + { + "type" : "tag", + "id" : "tag-1", + "name" : "tag-ref-tag-1" + }, + { + "type" : "tag", + "id" : "tag-3", + "name" : "tag-ref-tag-3" + } + ], + "migrationVersion" : { + "map" : "7.10.0" + }, + "updated_at" : "2020-11-12T18:32:16.189Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "map:4afc6d10-2515-11eb-8f44-eda8d4b698b3", + "index": ".kibana", + "source": { + "map" : { + "title" : "map 1 (tag-2)", + "description" : "", + "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"6a91fb66-465c-4193-8c59-9b3f5f262756\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"}]", + "mapStateJSON" : "{\"zoom\":0.97,\"center\":{\"lon\":0,\"lat\":16.22097},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "type" : "map", + "references" : [ + { + "type" : "tag", + "id" : "tag-2", + "name" : "tag-ref-tag-2" + } + ], + "migrationVersion" : { + "map" : "7.10.0" + }, + "updated_at" : "2020-11-12T18:31:34.753Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "map:562cce50-2515-11eb-8f44-eda8d4b698b3", + "index": ".kibana", + "source": { + "map" : { + "title" : "map 2 (tag-3)", + "description" : "", + "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"285d5190-aaf1-4dfc-912b-9c7d9e0104a8\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"}]", + "mapStateJSON" : "{\"zoom\":0.97,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "type" : "map", + "references" : [ + { + "type" : "tag", + "id" : "tag-3", + "name" : "tag-ref-tag-3" + } + ], + "migrationVersion" : { + "map" : "7.10.0" + }, + "updated_at" : "2020-11-12T18:31:53.525Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "map:6f021340-2515-11eb-8f44-eda8d4b698b3", + "index": ".kibana", + "source": { + "map" : { + "title" : "map 4 (tag-1)", + "description" : "", + "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true},\"id\":\"3deeb666-33cf-4e9a-ab78-e453ed9d721d\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\"},\"type\":\"VECTOR_TILE\"}]", + "mapStateJSON" : "{\"zoom\":0.97,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}", + "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}" + }, + "type" : "map", + "references" : [ + { + "type" : "tag", + "id" : "tag-1", + "name" : "tag-ref-tag-1" + } + ], + "migrationVersion" : { + "map" : "7.10.0" + }, + "updated_at" : "2020-11-12T18:32:35.188Z" + } + } +} diff --git a/x-pack/test/saved_object_tagging/common/lib/authentication.ts b/x-pack/test/saved_object_tagging/common/lib/authentication.ts index c318755bedcdd..8917057ad685e 100644 --- a/x-pack/test/saved_object_tagging/common/lib/authentication.ts +++ b/x-pack/test/saved_object_tagging/common/lib/authentication.ts @@ -118,6 +118,19 @@ export const ROLES = { ], }, }, + KIBANA_RBAC_DEFAULT_SPACE_MAPS_READ_USER: { + name: 'kibana_rbac_default_space_maps_read_user', + privileges: { + kibana: [ + { + feature: { + maps: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, }; export const USERS = { @@ -185,4 +198,9 @@ export const USERS = { password: 'password', roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER.name], }, + DEFAULT_SPACE_MAPS_READ_USER: { + username: 'a_kibana_rbac_default_space_maps_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_MAPS_READ_USER.name], + }, }; diff --git a/x-pack/test/saved_object_tagging/functional/tests/index.ts b/x-pack/test/saved_object_tagging/functional/tests/index.ts index 43673487ba74f..0ddfa64d682a8 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/index.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/index.ts @@ -23,5 +23,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./visualize_integration')); loadTestFile(require.resolve('./dashboard_integration')); loadTestFile(require.resolve('./feature_control')); + loadTestFile(require.resolve('./maps_integration')); }); } diff --git a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts new file mode 100644 index 0000000000000..4e44659b4fc67 --- /dev/null +++ b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const listingTable = getService('listingTable'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const PageObjects = getPageObjects(['maps', 'tagManagement', 'common']); + + /** + * Select tags in the searchbar's tag filter. + */ + const selectFilterTags = async (...tagNames: string[]) => { + // open the filter dropdown + const filterButton = await find.byCssSelector('.euiFilterGroup .euiFilterButton'); + await filterButton.click(); + // select the tags + for (const tagName of tagNames) { + await testSubjects.click( + `tag-searchbar-option-${PageObjects.tagManagement.testSubjFriendly(tagName)}` + ); + } + // click elsewhere to close the filter dropdown + const searchFilter = await find.byCssSelector('main .euiFieldSearch'); + await searchFilter.click(); + }; + + describe('maps integration', () => { + before(async () => { + await esArchiver.load('maps'); + }); + after(async () => { + await esArchiver.unload('maps'); + }); + + describe('listing', () => { + beforeEach(async () => { + await PageObjects.common.navigateToUrlWithBrowserHistory('maps', '/'); + await PageObjects.maps.gotoMapListingPage(); + }); + + it('allows to manually type tag filter query', async () => { + await listingTable.searchForItemWithName('tag:(tag-1)', { escape: false }); + + await listingTable.expectItemsCount('map', 2); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.eql(['map 3 (tag-1 and tag-3)', 'map 4 (tag-1)']); + }); + + it('allows to filter by selecting a tag in the filter menu', async () => { + await selectFilterTags('tag-3'); + + await listingTable.expectItemsCount('map', 2); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.eql(['map 3 (tag-1 and tag-3)', 'map 2 (tag-3)']); + }); + + it('allows to filter by multiple tags', async () => { + await selectFilterTags('tag-2', 'tag-3'); + + await listingTable.expectItemsCount('map', 3); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.eql(['map 3 (tag-1 and tag-3)', 'map 1 (tag-2)', 'map 2 (tag-3)']); + }); + }); + + describe('creating', () => { + beforeEach(async () => { + await PageObjects.maps.openNewMap(); + }); + + it('allows to select tags for a new map', async () => { + await PageObjects.maps.saveMap('my-new-map', false, ['tag-1', 'tag-3']); + + await PageObjects.maps.gotoMapListingPage(); + await selectFilterTags('tag-1'); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.contain('my-new-map'); + }); + + it('allows to create a tag from the tag selector', async () => { + const { tagModal } = PageObjects.tagManagement; + + await testSubjects.click('mapSaveButton'); + await testSubjects.setValue('savedObjectTitle', 'map-with-new-tag'); + + await testSubjects.click('savedObjectTagSelector'); + await testSubjects.click(`tagSelectorOption-action__create`); + + expect(await tagModal.isOpened()).to.be(true); + + await tagModal.fillForm( + { + name: 'my-new-tag', + color: '#FFCC33', + description: '', + }, + { + submit: true, + } + ); + + expect(await tagModal.isOpened()).to.be(false); + + await testSubjects.click('confirmSaveSavedObjectButton'); + + await PageObjects.maps.gotoMapListingPage(); + await selectFilterTags('my-new-tag'); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.contain('map-with-new-tag'); + }); + }); + + describe('editing', () => { + beforeEach(async () => { + await PageObjects.common.navigateToUrlWithBrowserHistory('maps', '/'); + }); + + it('allows to select tags for an existing map', async () => { + await listingTable.clickItemLink('map', 'map 4 (tag-1)'); + + await PageObjects.maps.saveMap('map 4 (tag-1)', false, ['tag-3']); + + await PageObjects.maps.gotoMapListingPage(); + await selectFilterTags('tag-3'); + const itemNames = await listingTable.getAllItemsNames(); + expect(itemNames).to.contain('map 4 (tag-1)'); + }); + }); + }); +} From 636433d441d6c2986958206a3a98972d3ba82fea Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 12 Nov 2020 13:06:18 -0700 Subject: [PATCH 13/14] add tags to all privileges and add test user to find, delete, get, get_all, and update tests --- x-pack/plugins/maps/server/plugin.ts | 2 +- .../api_integration/security_and_spaces/apis/_find.ts | 1 + .../api_integration/security_and_spaces/apis/delete.ts | 1 + .../api_integration/security_and_spaces/apis/get.ts | 1 + .../api_integration/security_and_spaces/apis/get_all.ts | 1 + .../api_integration/security_and_spaces/apis/update.ts | 1 + 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index 5925db1af691f..a79e5353048c8 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -185,7 +185,7 @@ export class MapsPlugin implements Plugin { catalogue: [APP_ID], savedObject: { all: [MAP_SAVED_OBJECT_TYPE, 'query'], - read: ['index-pattern'], + read: ['index-pattern', 'tag'], }, ui: ['save', 'show', 'saveQuery'], }, diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/_find.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/_find.ts index 4f08134365e95..8734b7cf5bb68 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/_find.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/_find.ts @@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, ], unauthorized: [USERS.NOT_A_KIBANA_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER], }; diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/delete.ts index 64f120fd75629..a2e3630622d67 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/delete.ts @@ -53,6 +53,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, USERS.NOT_A_KIBANA_USER, ], }; diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get.ts index 1a354bbbcb660..9cde766b4f514 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get.ts @@ -57,6 +57,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, ], unauthorized: [USERS.NOT_A_KIBANA_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER], }; diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts index 61b859cf81992..677bdee56ed8b 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/get_all.ts @@ -63,6 +63,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, ], unauthorized: [USERS.NOT_A_KIBANA_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER], }; diff --git a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/update.ts b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/update.ts index 77bf9d7ca3287..3347eca9920d6 100644 --- a/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/update.ts +++ b/x-pack/test/saved_object_tagging/api_integration/security_and_spaces/apis/update.ts @@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, USERS.NOT_A_KIBANA_USER, ], }; From 37daf1a3d4ca09d3171586411f736b840753a41b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 16 Nov 2020 16:34:37 -0700 Subject: [PATCH 14/14] move functions to module scope --- .../routes/list_page/maps_list_view.tsx | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index aabbeb748a801..a579e3f122cc6 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -68,57 +68,57 @@ if (savedObjectsTagging) { tableColumns.push(savedObjectsTagging.ui.getTableColumnDefinition()); } -export function MapsListView() { - function navigateToNewMap() { - const navigateToApp = getNavigateToApp(); - navigateToApp(APP_ID, { - path: MAP_PATH, - }); - } - - async function findMaps(searchQuery: string) { - let searchTerm = searchQuery; - let tagReferences; +function navigateToNewMap() { + const navigateToApp = getNavigateToApp(); + navigateToApp(APP_ID, { + path: MAP_PATH, + }); +} - if (savedObjectsTagging) { - const parsed = savedObjectsTagging.ui.parseSearchQuery(searchQuery, { - useName: true, - }); - searchTerm = parsed.searchTerm; - tagReferences = parsed.tagReferences; - } +async function findMaps(searchQuery: string) { + let searchTerm = searchQuery; + let tagReferences; - const resp = await getSavedObjectsClient().find({ - type: MAP_SAVED_OBJECT_TYPE, - search: searchTerm ? `${searchTerm}*` : undefined, - perPage: getSavedObjects().settings.getListingLimit(), - page: 1, - searchFields: ['title^3', 'description'], - defaultSearchOperator: 'AND', - fields: ['description', 'title'], - hasReference: tagReferences, + if (savedObjectsTagging) { + const parsed = savedObjectsTagging.ui.parseSearchQuery(searchQuery, { + useName: true, }); - - return { - total: resp.total, - hits: resp.savedObjects.map((savedObject) => { - return { - id: savedObject.id, - title: savedObject.attributes.title, - description: savedObject.attributes.description, - references: savedObject.references, - }; - }), - }; + searchTerm = parsed.searchTerm; + tagReferences = parsed.tagReferences; } - async function deleteMaps(items: object[]) { - const deletions = items.map((item) => { - return getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, (item as MapItem).id); - }); - await Promise.all(deletions); - } + const resp = await getSavedObjectsClient().find({ + type: MAP_SAVED_OBJECT_TYPE, + search: searchTerm ? `${searchTerm}*` : undefined, + perPage: getSavedObjects().settings.getListingLimit(), + page: 1, + searchFields: ['title^3', 'description'], + defaultSearchOperator: 'AND', + fields: ['description', 'title'], + hasReference: tagReferences, + }); + + return { + total: resp.total, + hits: resp.savedObjects.map((savedObject) => { + return { + id: savedObject.id, + title: savedObject.attributes.title, + description: savedObject.attributes.description, + references: savedObject.references, + }; + }), + }; +} +async function deleteMaps(items: object[]) { + const deletions = items.map((item) => { + return getSavedObjectsClient().delete(MAP_SAVED_OBJECT_TYPE, (item as MapItem).id); + }); + await Promise.all(deletions); +} + +export function MapsListView() { const isReadOnly = !getMapsCapabilities().save; getCoreChrome().docTitle.change(getAppTitle());