diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d85704c7bb8..780750925c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Rename wizard to visBuilder in class name, type name and function name ([#2639](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2639)) - [Vis Builder] Rename wizard on save modal and visualization table ([#2645](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2645)) - Change save object type, wizard id and name to visBuilder #2673 ([#2673](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2673)) +- Add extension point in saved object management to register namespaces and show filter ([#2656](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2656)) ### 🐛 Bug Fixes diff --git a/src/plugins/saved_objects_management/public/index.ts b/src/plugins/saved_objects_management/public/index.ts index 147247c56d77..2377afe175c4 100644 --- a/src/plugins/saved_objects_management/public/index.ts +++ b/src/plugins/saved_objects_management/public/index.ts @@ -39,6 +39,9 @@ export { SavedObjectsManagementColumnServiceSetup, SavedObjectsManagementColumnServiceStart, SavedObjectsManagementColumn, + SavedObjectsManagementNamespaceServiceSetup, + SavedObjectsManagementNamespaceServiceStart, + SavedObjectsManagementNamespace, SavedObjectsManagementRecord, ISavedObjectsManagementServiceRegistry, SavedObjectsManagementServiceRegistryEntry, diff --git a/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts b/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts index 0dbf71f8e8c0..bdfab075db80 100644 --- a/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts +++ b/src/plugins/saved_objects_management/public/lib/get_saved_object_counts.ts @@ -33,10 +33,11 @@ import { HttpStart } from 'src/core/public'; export async function getSavedObjectCounts( http: HttpStart, typesToInclude: string[], + namespacesToInclude: string[], searchString?: string ): Promise> { return await http.post>( `/api/opensearch-dashboards/management/saved_objects/scroll/counts`, - { body: JSON.stringify({ typesToInclude, searchString }) } + { body: JSON.stringify({ typesToInclude, namespacesToInclude, searchString }) } ); } diff --git a/src/plugins/saved_objects_management/public/lib/parse_query.test.ts b/src/plugins/saved_objects_management/public/lib/parse_query.test.ts index 854c1e4ea3d6..a940cf3ebbca 100644 --- a/src/plugins/saved_objects_management/public/lib/parse_query.test.ts +++ b/src/plugins/saved_objects_management/public/lib/parse_query.test.ts @@ -34,8 +34,19 @@ describe('getQueryText', () => { it('should know how to get the text out of the AST', () => { const ast = { getTermClauses: () => [{ value: 'foo' }, { value: 'bar' }], - getFieldClauses: () => [{ value: 'lala' }, { value: 'lolo' }], + getFieldClauses: (field) => { + if (field === 'type') { + return [{ value: 'lala' }, { value: 'lolo' }]; + } else if (field === 'namespaces') { + return [{ value: 'default' }]; + } + return []; + }, }; - expect(parseQuery({ ast } as any)).toEqual({ queryText: 'foo bar', visibleTypes: 'lala' }); + expect(parseQuery({ ast } as any, ['type'])).toEqual({ + queryText: 'foo bar', + visibleTypes: 'lala', + visibleNamespaces: 'default', + }); }); }); diff --git a/src/plugins/saved_objects_management/public/lib/parse_query.ts b/src/plugins/saved_objects_management/public/lib/parse_query.ts index dca6384efbe1..24c35d500aaa 100644 --- a/src/plugins/saved_objects_management/public/lib/parse_query.ts +++ b/src/plugins/saved_objects_management/public/lib/parse_query.ts @@ -38,6 +38,7 @@ interface ParsedQuery { export function parseQuery(query: Query): ParsedQuery { let queryText: string | undefined; let visibleTypes: string[] | undefined; + let visibleNamespaces: string[] | undefined; if (query) { if (query.ast.getTermClauses().length) { @@ -49,10 +50,14 @@ export function parseQuery(query: Query): ParsedQuery { if (query.ast.getFieldClauses('type')) { visibleTypes = query.ast.getFieldClauses('type')[0].value as string[]; } + if (query.ast.getFieldClauses('namespaces')) { + visibleNamespaces = query.ast.getFieldClauses('namespaces')[0].value as string[]; + } } return { queryText, visibleTypes, + visibleNamespaces, }; } diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index 8013c05aad79..2c42df5c7824 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -104,6 +104,7 @@ export const mountManagementSection = async ({ serviceRegistry={serviceRegistry} actionRegistry={pluginStart.actions} columnRegistry={pluginStart.columns} + namespaceRegistry={pluginStart.namespaces} allowedTypes={allowedObjectTypes} setBreadcrumbs={setBreadcrumbs} /> diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index a86362994a6b..d18762f4912f 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -274,27 +274,35 @@ exports[`SavedObjectsTable should render normally 1`] = ` "has": [MockFunction], } } - filterOptions={ + filters={ Array [ Object { - "name": "index-pattern", - "value": "index-pattern", - "view": "index-pattern (0)", - }, - Object { - "name": "visualization", - "value": "visualization", - "view": "visualization (0)", - }, - Object { - "name": "dashboard", - "value": "dashboard", - "view": "dashboard (0)", - }, - Object { - "name": "search", - "value": "search", - "view": "search (0)", + "field": "type", + "multiSelect": "or", + "name": "Type", + "options": Array [ + Object { + "name": "index-pattern", + "value": "index-pattern", + "view": "index-pattern (0)", + }, + Object { + "name": "visualization", + "value": "visualization", + "view": "visualization (0)", + }, + Object { + "name": "dashboard", + "value": "dashboard", + "view": "dashboard (0)", + }, + Object { + "name": "search", + "value": "search", + "view": "search (0)", + }, + ], + "type": "field_value_selection", }, ] } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx index 8e473730615b..7e5bb318f4d0 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx @@ -62,7 +62,19 @@ const defaultProps: TableProps = { selectionConfig: { onSelectionChange: () => {}, }, - filterOptions: [{ value: 2 }], + filters: [ + { + type: 'field_value_selection', + field: 'type', + name: 'Type', + multiSelect: 'or', + options: [ + { + value: 2, + }, + ], + }, + ], onDelete: () => {}, onActionRefresh: () => {}, onExport: () => {}, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx index 50ec838b9860..636933d449df 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx @@ -61,11 +61,12 @@ export interface TableProps { basePath: IBasePath; actionRegistry: SavedObjectsManagementActionServiceStart; columnRegistry: SavedObjectsManagementColumnServiceStart; + namespaceRegistry: SavedObjectsManagementNamespaceServiceStart; selectedSavedObjects: SavedObjectWithMetadata[]; selectionConfig: { onSelectionChange: (selection: SavedObjectWithMetadata[]) => void; }; - filterOptions: any[]; + filters: any[]; canDelete: boolean; onDelete: () => void; onActionRefresh: (object: SavedObjectWithMetadata) => void; @@ -76,7 +77,7 @@ export interface TableProps { items: SavedObjectWithMetadata[]; itemId: string | (() => string); totalItemCount: number; - onQueryChange: (query: any) => void; + onQueryChange: (query: any, filterFields: string[]) => void; onTableChange: (table: any) => void; isSearching: boolean; onShowRelationships: (object: SavedObjectWithMetadata) => void; @@ -163,7 +164,7 @@ export class Table extends PureComponent { items, totalItemCount, isSearching, - filterOptions, + filters, selectionConfig: selection, onDelete, onActionRefresh, @@ -174,6 +175,7 @@ export class Table extends PureComponent { basePath, actionRegistry, columnRegistry, + namespaceRegistry, dateFormat, } = this.props; @@ -184,26 +186,6 @@ export class Table extends PureComponent { pageSizeOptions: [5, 10, 20, 50], }; - const filters = [ - { - type: 'field_value_selection', - field: 'type', - name: i18n.translate('savedObjectsManagement.objectsTable.table.typeFilterName', { - defaultMessage: 'Type', - }), - multiSelect: 'or', - options: filterOptions, - }, - // Add this back in once we have tag support - // { - // type: 'field_value_selection', - // field: 'tag', - // name: 'Tags', - // multiSelect: 'or', - // options: [], - // }, - ]; - const columns = [ { field: 'type', diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 2f8382c5946f..e3c511bb33de 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -53,6 +53,7 @@ import { dataPluginMock } from '../../../../data/public/mocks'; import { serviceRegistryMock } from '../../services/service_registry.mock'; import { actionServiceMock } from '../../services/action_service.mock'; import { columnServiceMock } from '../../services/column_service.mock'; +import { namespaceServiceMock } from '../../services/namespace_service.mock'; import { SavedObjectsTable, SavedObjectsTableProps, @@ -147,6 +148,7 @@ describe('SavedObjectsTable', () => { serviceRegistry: serviceRegistryMock.create(), actionRegistry: actionServiceMock.createStart(), columnRegistry: columnServiceMock.createStart(), + namespaceRegistry: namespaceServiceMock.createStart(), savedObjectsClient: savedObjects.client, indexPatterns: dataPluginMock.createStartContract().indexPatterns, http, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 8d0d13d3334a..da0f3d8abc40 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -85,6 +85,7 @@ import { ISavedObjectsManagementServiceRegistry, SavedObjectsManagementActionServiceStart, SavedObjectsManagementColumnServiceStart, + SavedObjectsManagementNamespaceServiceStart, } from '../../services'; import { Header, Table, Flyout, Relationships } from './components'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; @@ -99,6 +100,7 @@ export interface SavedObjectsTableProps { serviceRegistry: ISavedObjectsManagementServiceRegistry; actionRegistry: SavedObjectsManagementActionServiceStart; columnRegistry: SavedObjectsManagementColumnServiceStart; + namespaceRegistry: SavedObjectsManagementNamespaceServiceStart; savedObjectsClient: SavedObjectsClientContract; indexPatterns: IndexPatternsContract; http: HttpStart; @@ -177,7 +179,7 @@ export class SavedObjectsTable extends Component { const { allowedTypes } = this.props; - const { queryText, visibleTypes } = parseQuery(this.state.activeQuery); + const { queryText, visibleTypes, visibleNamespaces } = parseQuery(this.state.activeQuery); const filteredTypes = allowedTypes.filter( (type) => !visibleTypes || visibleTypes.includes(type) @@ -187,6 +189,7 @@ export class SavedObjectsTable extends Component ({ ...state, @@ -227,7 +235,10 @@ export class SavedObjectsTable extends Component { const { activeQuery: query, page, perPage } = this.state; const { notifications, http, allowedTypes } = this.props; - const { queryText, visibleTypes } = parseQuery(query); + const { queryText, visibleTypes, visibleNamespaces } = parseQuery(query); + const filteredTypes = allowedTypes.filter( + (type) => !visibleTypes || visibleTypes.includes(type) + ); // "searchFields" is missing from the "findOptions" but gets injected via the API. // The API extracts the fields from each uiExports.savedObjectsManagement "defaultSearchField" attribute const findOptions: SavedObjectsFindOptions = { @@ -235,7 +246,8 @@ export class SavedObjectsTable extends Component !visibleTypes || visibleTypes.includes(type)), + type: filteredTypes, + namespaces: visibleNamespaces, }; if (findOptions.type.length > 1) { findOptions.sortField = 'type'; @@ -390,6 +402,7 @@ export class SavedObjectsTable extends Component { const { exportAllSelectedOptions, isIncludeReferencesDeepChecked, activeQuery } = this.state; const { notifications, http } = this.props; + const { queryText } = parseQuery(activeQuery); const exportTypes = Object.entries(exportAllSelectedOptions).reduce((accum, [id, selected]) => { if (selected) { @@ -764,18 +777,55 @@ export class SavedObjectsTable extends Component ({ value: type, name: type, - view: `${type} (${savedObjectCounts[type] || 0})`, + view: `${type} (${typeCounts[type] || 0})`, })); + const filters = [ + { + type: 'field_value_selection', + field: 'type', + name: i18n.translate('savedObjectsManagement.objectsTable.table.typeFilterName', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + options: filterOptions, + }, + ]; + + const availableNamespaces = namespaceRegistry.getAll() || []; + if (availableNamespaces && availableNamespaces.length > 0) { + const nsCounts = savedObjectCounts.namespaces || {}; + const nsFilterOptions = availableNamespaces.map((ns) => { + return { + name: ns.name, + value: ns.id, + view: `${ns.name} (${nsCounts[ns.id] || 0})`, + }; + }); + + filters.push({ + type: 'field_value_selection', + field: 'namespaces', + name: + namespaceRegistry.getAlias() || + i18n.translate('savedObjectsManagement.objectsTable.table.namespaceFilterName', { + defaultMessage: 'Namespaces', + }), + multiSelect: 'or', + options: nsFilterOptions, + }); + } + return ( {this.renderFlyout()} @@ -799,7 +849,7 @@ export class SavedObjectsTable extends Component void; }) => { const capabilities = coreStart.application.capabilities; @@ -78,6 +80,7 @@ const SavedObjectsTablePage = ({ serviceRegistry={serviceRegistry} actionRegistry={actionRegistry} columnRegistry={columnRegistry} + namespaceRegistry={namespaceRegistry} savedObjectsClient={coreStart.savedObjects.client} indexPatterns={dataStart.indexPatterns} search={dataStart.search} diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index c15a96bddb7a..ec7d64ed700c 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -45,6 +45,9 @@ import { SavedObjectsManagementColumnService, SavedObjectsManagementColumnServiceSetup, SavedObjectsManagementColumnServiceStart, + SavedObjectsManagementNamespaceService, + SavedObjectsManagementNamespaceServiceSetup, + SavedObjectsManagementNamespaceServiceStart, SavedObjectsManagementServiceRegistry, ISavedObjectsManagementServiceRegistry, } from './services'; @@ -53,12 +56,14 @@ import { registerServices } from './register_services'; export interface SavedObjectsManagementPluginSetup { actions: SavedObjectsManagementActionServiceSetup; columns: SavedObjectsManagementColumnServiceSetup; + namespaces: SavedObjectsManagementNamespaceServiceSetup; serviceRegistry: ISavedObjectsManagementServiceRegistry; } export interface SavedObjectsManagementPluginStart { actions: SavedObjectsManagementActionServiceStart; columns: SavedObjectsManagementColumnServiceStart; + namespaces: SavedObjectsManagementNamespaceServiceStart; } export interface SetupDependencies { @@ -84,6 +89,7 @@ export class SavedObjectsManagementPlugin > { private actionService = new SavedObjectsManagementActionService(); private columnService = new SavedObjectsManagementColumnService(); + private namespaceService = new SavedObjectsManagementNamespaceService(); private serviceRegistry = new SavedObjectsManagementServiceRegistry(); public setup( @@ -92,6 +98,7 @@ export class SavedObjectsManagementPlugin ): SavedObjectsManagementPluginSetup { const actionSetup = this.actionService.setup(); const columnSetup = this.columnService.setup(); + const namespaceSetup = this.namespaceService.setup(); if (home) { home.featureCatalogue.register({ @@ -133,6 +140,7 @@ export class SavedObjectsManagementPlugin return { actions: actionSetup, columns: columnSetup, + namespaces: namespaceSetup, serviceRegistry: this.serviceRegistry, }; } @@ -140,10 +148,12 @@ export class SavedObjectsManagementPlugin public start(core: CoreStart, { data }: StartDependencies) { const actionStart = this.actionService.start(); const columnStart = this.columnService.start(); + const namespaceStart = this.namespaceService.start(); return { actions: actionStart, columns: columnStart, + namespaces: namespaceStart, }; } } diff --git a/src/plugins/saved_objects_management/public/services/index.ts b/src/plugins/saved_objects_management/public/services/index.ts index 7d099852776d..498ea683384d 100644 --- a/src/plugins/saved_objects_management/public/services/index.ts +++ b/src/plugins/saved_objects_management/public/services/index.ts @@ -38,6 +38,11 @@ export { SavedObjectsManagementColumnServiceStart, SavedObjectsManagementColumnServiceSetup, } from './column_service'; +export { + SavedObjectsManagementNamespaceService, + SavedObjectsManagementNamespaceServiceStart, + SavedObjectsManagementNamespaceServiceSetup, +} from './namespace_service'; export { SavedObjectsManagementServiceRegistry, ISavedObjectsManagementServiceRegistry, @@ -46,5 +51,6 @@ export { export { SavedObjectsManagementAction, SavedObjectsManagementColumn, + SavedObjectsManagementNamespace, SavedObjectsManagementRecord, } from './types'; diff --git a/src/plugins/saved_objects_management/public/services/namespace_service.mock.ts b/src/plugins/saved_objects_management/public/services/namespace_service.mock.ts new file mode 100644 index 000000000000..d1599920e3ce --- /dev/null +++ b/src/plugins/saved_objects_management/public/services/namespace_service.mock.ts @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { + SavedObjectsManagementNamespaceService, + SavedObjectsManagementNamespaceServiceSetup, + SavedObjectsManagementNamespaceServiceStart, +} from './namespace_service'; + +const createSetupMock = (): jest.Mocked => { + const mock = { + register: jest.fn(), + registerAlias: jest.fn(), + }; + return mock; +}; + +const createStartMock = (): jest.Mocked => { + const mock = { + getAll: jest.fn(), + getAlias: jest.fn(), + }; + + mock.getAll.mockReturnValue([]); + mock.getAlias.mockReturnValue('Namespace'); + + return mock; +}; + +const createServiceMock = (): jest.Mocked< + PublicMethodsOf +> => { + const mock = { + setup: jest.fn().mockReturnValue(createSetupMock()), + start: jest.fn().mockReturnValue(createStartMock()), + }; + return mock; +}; + +export const namespaceServiceMock = { + create: createServiceMock, + createSetup: createSetupMock, + createStart: createStartMock, +}; diff --git a/src/plugins/saved_objects_management/public/services/namespace_service.ts b/src/plugins/saved_objects_management/public/services/namespace_service.ts new file mode 100644 index 000000000000..5b1a797ae2da --- /dev/null +++ b/src/plugins/saved_objects_management/public/services/namespace_service.ts @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { SavedObjectsManagementNamespace } from './types'; + +export interface SavedObjectsManagementNamespaceServiceSetup { + /** + * register given namespace in the registry. + */ + register: (namespace: SavedObjectsManagementNamespace) => void; + registerAlias: (alias: string) => void; +} + +export interface SavedObjectsManagementNamespaceServiceStart { + /** + * return all {@link SavedObjectsManagementNamespace | namespaces} currently registered. + */ + getAll: () => Array>; + getAlias: () => string; +} + +export class SavedObjectsManagementNamespaceService { + private readonly namespaces = new Map>(); + private readonly alias; + + setup(): SavedObjectsManagementNamespaceServiceSetup { + return { + register: (ns) => { + if (this.namespaces.has(ns.id)) { + throw new Error(`Saved Objects Management Namespace with id '${ns.id}' already exists`); + } + this.namespaces.set(ns.id, ns); + }, + registerAlias: (alias) => { + if (!!this.alias) { + throw new Error( + `An alias has already been registered. Cannot register more than one alias.` + ); + } + this.alias = alias; + }, + }; + } + + start(): SavedObjectsManagementNamespaceServiceStart { + return { + getAll: () => [...this.namespaces.values()], + getAlias: () => this.alias, + }; + } +} diff --git a/src/plugins/saved_objects_management/public/services/types/namespace.ts b/src/plugins/saved_objects_management/public/services/types/namespace.ts new file mode 100644 index 000000000000..3a47f273c150 --- /dev/null +++ b/src/plugins/saved_objects_management/public/services/types/namespace.ts @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +export interface SavedObjectsManagementNamespace { + id: string; + name: string; +} diff --git a/src/plugins/saved_objects_management/server/routes/find.ts b/src/plugins/saved_objects_management/server/routes/find.ts index 2987425321ca..4324db3eedcc 100644 --- a/src/plugins/saved_objects_management/server/routes/find.ts +++ b/src/plugins/saved_objects_management/server/routes/find.ts @@ -45,6 +45,9 @@ export const registerFindRoute = ( perPage: schema.number({ min: 0, defaultValue: 20 }), page: schema.number({ min: 0, defaultValue: 1 }), type: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + namespaces: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), search: schema.maybe(schema.string()), defaultSearchOperator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { defaultValue: 'OR', @@ -66,6 +69,20 @@ export const registerFindRoute = ( const managementService = await managementServicePromise; const { client } = context.core.savedObjects; const searchTypes = Array.isArray(req.query.type) ? req.query.type : [req.query.type]; + if ('namespaces' in req.query) { + const namespacesToInclude = Array.isArray(req.query.namespaces) + ? req.query.namespaces + : [req.query.namespaces]; + const searchNamespaces = []; + namespacesToInclude.forEach((ns) => { + if (ns == null) { + searchNamespaces.push('default'); + } else { + searchNamespaces.push(ns); + } + }); + req.query.namespaces = searchNamespaces; + } const includedFields = Array.isArray(req.query.fields) ? req.query.fields : [req.query.fields]; diff --git a/src/plugins/saved_objects_management/server/routes/scroll_count.ts b/src/plugins/saved_objects_management/server/routes/scroll_count.ts index 0c69b376ea5b..4069eac17de1 100644 --- a/src/plugins/saved_objects_management/server/routes/scroll_count.ts +++ b/src/plugins/saved_objects_management/server/routes/scroll_count.ts @@ -39,17 +39,33 @@ export const registerScrollForCountRoute = (router: IRouter) => { validate: { body: schema.object({ typesToInclude: schema.arrayOf(schema.string()), + namespacesToInclude: schema.maybe(schema.arrayOf(schema.string())), searchString: schema.maybe(schema.string()), }), }, }, router.handleLegacyErrors(async (context, req, res) => { const { client } = context.core.savedObjects; + const namespaces = []; + if (req.body.namespacesToInclude && req.body.namespacesToInclude.length > 0) { + req.body.namespacesToInclude.forEach((ns) => { + if (ns === null) { + namespaces.push('default'); + } else { + namespaces.push(ns); + } + }); + } const findOptions: SavedObjectsFindOptions = { type: req.body.typesToInclude, perPage: 1000, }; + + if (namespaces.length > 0) { + findOptions.namespaces = namespaces; + } + if (req.body.searchString) { findOptions.search = `${req.body.searchString}*`; findOptions.searchFields = ['title']; @@ -57,16 +73,37 @@ export const registerScrollForCountRoute = (router: IRouter) => { const objects = await findAll(client, findOptions); - const counts = objects.reduce((accum, result) => { + const counts = { + type: {}, + namespaces: {}, + }; + + objects.forEach((result) => { const type = result.type; - accum[type] = accum[type] || 0; - accum[type]++; - return accum; - }, {} as Record); + if (req.body.namespacesToInclude) { + const resultNamespaces = (result.namespaces || []).flat(); + resultNamespaces.forEach((ns) => { + if (ns === null) { + ns = 'default'; + } + counts.namespaces[ns] = counts.namespaces[ns] || 0; + counts.namespaces[ns]++; + }); + } + counts.type[type] = counts.type[type] || 0; + counts.type[type]++; + }); for (const type of req.body.typesToInclude) { - if (!counts[type]) { - counts[type] = 0; + if (!counts.type[type]) { + counts.type[type] = 0; + } + } + + const namespacesToInclude = req.body.namespacesToInclude || []; + for (const ns of namespacesToInclude) { + if (!counts.namespaces[ns]) { + counts.namespaces[ns] = 0; } }