diff --git a/CHANGELOG.md b/CHANGELOG.md index 524377e99d1a..8158883699d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ 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)) +- [Save Object Aggregation View] Add extension point in saved object management to register namespaces and show filter ([#2656](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2656)) +- [Save Object Aggregation View] Fix for export all after scroll count response changed in PR#2656 ([#2696](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2696)) ### 🐛 Bug Fixes * [Vis Builder] Fixes auto bounds for timeseries bar chart visualization ([2401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2401)) diff --git a/src/plugins/saved_objects_management/public/lib/filter_query.test.ts b/src/plugins/saved_objects_management/public/lib/filter_query.test.ts new file mode 100644 index 000000000000..ed0a133ed2b0 --- /dev/null +++ b/src/plugins/saved_objects_management/public/lib/filter_query.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { filterQuery } from './filter_query'; + +describe('filterQuery', () => { + it('should return full list of allowed vals, requested values unspecified', () => { + const allowedVals = ['config', 'index-pattern', 'url', 'query']; + const requestedVals = undefined; + + const expected = ['config', 'index-pattern', 'url', 'query']; + expect(filterQuery(allowedVals, requestedVals)).toEqual(expected); + }); + + it('should return list of all requested values, all values within allowed values', () => { + const allowedVals = ['config', 'index-pattern', 'url', 'query']; + const requestedVals = ['config', 'index-pattern']; + + const expected = ['config', 'index-pattern']; + expect(filterQuery(allowedVals, requestedVals)).toEqual(expected); + }); + + it('should return only allowed values within requested values', () => { + const allowedVals = ['config', 'index-pattern', 'url', 'query']; + const requestedVals = ['config', 'index-pattern', 'forbidden']; + + const expected = ['config', 'index-pattern']; + expect(filterQuery(allowedVals, requestedVals)).toEqual(expected); + }); +}); diff --git a/src/plugins/saved_objects_management/public/lib/filter_query.ts b/src/plugins/saved_objects_management/public/lib/filter_query.ts new file mode 100644 index 000000000000..4194aba5f9e4 --- /dev/null +++ b/src/plugins/saved_objects_management/public/lib/filter_query.ts @@ -0,0 +1,11 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export function filterQuery(allowedVals: string[], requestedVals?: string[]): string[] { + const filteredVals = requestedVals + ? allowedVals.filter((val) => requestedVals.includes(val)) + : allowedVals; + return filteredVals; +} 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 bdfab075db80..6eaaac7d35f2 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 @@ -30,14 +30,18 @@ import { HttpStart } from 'src/core/public'; +export interface SavedObjectCountOptions { + typesToInclude: string[]; + namespacesToInclude?: string[]; + searchString?: string; +} + export async function getSavedObjectCounts( http: HttpStart, - typesToInclude: string[], - namespacesToInclude: string[], - searchString?: string + options: SavedObjectCountOptions ): Promise> { return await http.post>( `/api/opensearch-dashboards/management/saved_objects/scroll/counts`, - { body: JSON.stringify({ typesToInclude, namespacesToInclude, searchString }) } + { body: JSON.stringify(options) } ); } diff --git a/src/plugins/saved_objects_management/public/lib/index.ts b/src/plugins/saved_objects_management/public/lib/index.ts index e01e58f4bdde..fae58cad3eb2 100644 --- a/src/plugins/saved_objects_management/public/lib/index.ts +++ b/src/plugins/saved_objects_management/public/lib/index.ts @@ -32,7 +32,7 @@ export { fetchExportByTypeAndSearch } from './fetch_export_by_type_and_search'; export { fetchExportObjects } from './fetch_export_objects'; export { canViewInApp } from './in_app_url'; export { getRelationships } from './get_relationships'; -export { getSavedObjectCounts } from './get_saved_object_counts'; +export { getSavedObjectCounts, SavedObjectCountOptions } from './get_saved_object_counts'; export { getSavedObjectLabel } from './get_saved_object_label'; export { importFile } from './import_file'; export { importLegacyFile } from './import_legacy_file'; @@ -56,3 +56,4 @@ export { findObjects, findObject } from './find_objects'; export { extractExportDetails, SavedObjectsExportResultDetails } from './extract_export_details'; export { createFieldList } from './create_field_list'; export { getAllowedTypes } from './get_allowed_types'; +export { filterQuery } from './filter_query'; 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 e3c511bb33de..5a6bf0713d95 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 @@ -137,10 +137,12 @@ describe('SavedObjectsTable', () => { http.post.mockResolvedValue([]); getSavedObjectCountsMock.mockReturnValue({ - 'index-pattern': 0, - visualization: 0, - dashboard: 0, - search: 0, + type: { + 'index-pattern': 0, + visualization: 0, + dashboard: 0, + search: 0, + }, }); defaultProps = { 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 da0f3d8abc40..2f78f307d165 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 @@ -71,10 +71,12 @@ import { IndexPatternsContract } from '../../../../data/public'; import { parseQuery, getSavedObjectCounts, + SavedObjectCountOptions, getRelationships, getSavedObjectLabel, fetchExportObjects, fetchExportByTypeAndSearch, + filterQuery, findObjects, findObject, extractExportDetails, @@ -178,43 +180,57 @@ export class SavedObjectsTable extends Component { - const { allowedTypes } = this.props; + const { allowedTypes, namespaceRegistry } = this.props; const { queryText, visibleTypes, visibleNamespaces } = parseQuery(this.state.activeQuery); - const filteredTypes = allowedTypes.filter( - (type) => !visibleTypes || visibleTypes.includes(type) - ); + const filteredTypes = filterQuery(allowedTypes, visibleTypes); + + const availableNamespaces = namespaceRegistry.getAll()?.map((ns) => ns.id) || []; + + const filteredCountOptions: SavedObjectCountOptions = { + typesToInclude: filteredTypes, + searchString: queryText, + }; + + if (availableNamespaces.length) { + const filteredNamespaces = filterQuery(availableNamespaces, visibleNamespaces); + filteredCountOptions.namespacesToInclude = filteredNamespaces; + } // These are the saved objects visible in the table. const filteredSavedObjectCounts = await getSavedObjectCounts( this.props.http, - filteredTypes, - visibleNamespaces, - queryText + filteredCountOptions ); const exportAllOptions: ExportAllOption[] = []; const exportAllSelectedOptions: Record = {}; - Object.keys(filteredSavedObjectCounts).forEach((id) => { + const filteredTypeCounts = filteredSavedObjectCounts.type || {}; + + Object.keys(filteredTypeCounts).forEach((id) => { // Add this type as a bulk-export option. exportAllOptions.push({ id, - label: `${id} (${filteredSavedObjectCounts[id] || 0})`, + label: `${id} (${filteredTypeCounts[id] || 0})`, }); // Select it by default. exportAllSelectedOptions[id] = true; }); + const countOptions: SavedObjectCountOptions = { + typesToInclude: allowedTypes, + searchString: queryText, + }; + + if (availableNamespaces.length) { + countOptions.namespacesToInclude = availableNamespaces; + } + // Fetch all the saved objects that exist so we can accurately populate the counts within // the table filter dropdown. - const savedObjectCounts = await getSavedObjectCounts( - this.props.http, - allowedTypes, - [], - queryText - ); + const savedObjectCounts = await getSavedObjectCounts(this.props.http, countOptions); this.setState((state) => ({ ...state, @@ -234,11 +250,9 @@ export class SavedObjectsTable extends Component { const { activeQuery: query, page, perPage } = this.state; - const { notifications, http, allowedTypes } = this.props; + const { notifications, http, allowedTypes, namespaceRegistry } = this.props; const { queryText, visibleTypes, visibleNamespaces } = parseQuery(query); - const filteredTypes = allowedTypes.filter( - (type) => !visibleTypes || visibleTypes.includes(type) - ); + const filteredTypes = filterQuery(allowedTypes, visibleTypes); // "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 = { @@ -247,8 +261,14 @@ export class SavedObjectsTable extends Component ns.id) || []; + if (availableNamespaces.length) { + const filteredNamespaces = filterQuery(availableNamespaces, visibleNamespaces); + findOptions.namespaces = filteredNamespaces; + } + if (findOptions.type.length > 1) { findOptions.sortField = 'type'; } @@ -803,7 +823,7 @@ export class SavedObjectsTable extends Component 0) { + if (availableNamespaces.length) { const nsCounts = savedObjectCounts.namespaces || {}; const nsFilterOptions = availableNamespaces.map((ns) => { return { diff --git a/src/plugins/saved_objects_management/server/routes/find.ts b/src/plugins/saved_objects_management/server/routes/find.ts index 4324db3eedcc..c2002fc97a15 100644 --- a/src/plugins/saved_objects_management/server/routes/find.ts +++ b/src/plugins/saved_objects_management/server/routes/find.ts @@ -69,20 +69,6 @@ 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 4069eac17de1..63233748a896 100644 --- a/src/plugins/saved_objects_management/server/routes/scroll_count.ts +++ b/src/plugins/saved_objects_management/server/routes/scroll_count.ts @@ -46,24 +46,21 @@ export const registerScrollForCountRoute = (router: IRouter) => { }, 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 counts = { + type: {}, + }; const findOptions: SavedObjectsFindOptions = { type: req.body.typesToInclude, perPage: 1000, }; - if (namespaces.length > 0) { - findOptions.namespaces = namespaces; + const requestHasNamespaces = + Array.isArray(req.body.namespacesToInclude) && req.body.namespacesToInclude.length; + + if (requestHasNamespaces) { + counts.namespaces = {}; + findOptions.namespaces = req.body.namespacesToInclude; } if (req.body.searchString) { @@ -73,14 +70,9 @@ export const registerScrollForCountRoute = (router: IRouter) => { const objects = await findAll(client, findOptions); - const counts = { - type: {}, - namespaces: {}, - }; - objects.forEach((result) => { const type = result.type; - if (req.body.namespacesToInclude) { + if (requestHasNamespaces) { const resultNamespaces = (result.namespaces || []).flat(); resultNamespaces.forEach((ns) => { if (ns === null) {