Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension point in saved object management to register namespaces and show filter #2656

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
15878e3
Create filter registry for saved object management to make filters ex…
cwperks Oct 18, 2022
e61d803
WIP on making fetchCounts generic
cwperks Oct 19, 2022
ef6bd97
First step at making scroll_counts generic
cwperks Oct 19, 2022
bc6158c
Work on getting other filter counts with same object count endpoint
cwperks Oct 19, 2022
e3dc3b3
Get tenant count options to display
cwperks Oct 19, 2022
445ffc7
Extend find to work with namespaces for saved objects
cwperks Oct 20, 2022
f31e0ef
Add missing filterFields
cwperks Oct 20, 2022
f4d13eb
Update jest tests
cwperks Oct 20, 2022
ee15b77
Update saved_objects_table snapshot
cwperks Oct 20, 2022
2141b33
Append index to id to make unique
cwperks Oct 21, 2022
51b1325
Add semi-colon
cwperks Oct 21, 2022
c2ceffe
Fix saved objects table tests with new id scheme
cwperks Oct 21, 2022
5f01763
Only append idx on config type to ensure Advanced Settings have a uni…
cwperks Oct 21, 2022
2d97645
Remove itemsClone in favor of showing only Advanced Settings of curre…
cwperks Oct 21, 2022
0cd8012
Revert snapshots in table.test.tsx
cwperks Oct 24, 2022
b805531
Add additional parse_query test
cwperks Oct 24, 2022
9a153ea
Add comma
cwperks Oct 24, 2022
8211f5e
Create namespaceRegistry to decouple security dashboards plugin and o…
cwperks Oct 26, 2022
8bd743f
Merge branch 'add-namespace-filter-and-registry-to-saved-objects' int…
cwperks Oct 26, 2022
d2098e8
Add ability to register an alias
cwperks Oct 26, 2022
3331bf2
Update parse query and add to CHANGELOG
cwperks Oct 26, 2022
deb909f
Merge branch 'main' into saved-object-management-filter-registry
cwperks Oct 27, 2022
ad80403
Remove commented out code
cwperks Oct 27, 2022
05a3943
Address code review comments
cwperks Oct 27, 2022
24f9d38
Merge branch 'main' into saved-object-management-filter-registry
cwperks Oct 27, 2022
2630566
Override i18n if alias is regitered
cwperks Oct 27, 2022
9cd0900
Merge branch 'main' into saved-object-management-filter-registry
cwperks Oct 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/plugins/saved_objects_management/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export {
SavedObjectsManagementColumnServiceSetup,
SavedObjectsManagementColumnServiceStart,
SavedObjectsManagementColumn,
SavedObjectsManagementNamespaceServiceSetup,
SavedObjectsManagementNamespaceServiceStart,
SavedObjectsManagementNamespace,
SavedObjectsManagementRecord,
ISavedObjectsManagementServiceRegistry,
SavedObjectsManagementServiceRegistryEntry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import { HttpStart } from 'src/core/public';
export async function getSavedObjectCounts(
http: HttpStart,
typesToInclude: string[],
namespacesToInclude: string[],
searchString?: string
): Promise<Record<string, number>> {
return await http.post<Record<string, number>>(
`/api/opensearch-dashboards/management/saved_objects/scroll/counts`,
{ body: JSON.stringify({ typesToInclude, searchString }) }
{ body: JSON.stringify({ typesToInclude, namespacesToInclude, searchString }) }
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export const mountManagementSection = async ({
serviceRegistry={serviceRegistry}
actionRegistry={pluginStart.actions}
columnRegistry={pluginStart.columns}
namespaceRegistry={pluginStart.namespaces}
allowedTypes={allowedObjectTypes}
setBreadcrumbs={setBreadcrumbs}
/>
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -76,7 +77,7 @@ export interface TableProps {
items: SavedObjectWithMetadata[];
itemId: string | (() => string);
totalItemCount: number;
onQueryChange: (query: any) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be made optional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filterFields param? It can, but in practical use it will always at least contain type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI reverted this back to the original parseQuery, but modified the function to also extract namespaces.

onQueryChange: (query: any, filterFields: string[]) => void;
onTableChange: (table: any) => void;
isSearching: boolean;
onShowRelationships: (object: SavedObjectWithMetadata) => void;
Expand Down Expand Up @@ -163,7 +164,7 @@ export class Table extends PureComponent<TableProps, TableState> {
items,
totalItemCount,
isSearching,
filterOptions,
filters,
selectionConfig: selection,
onDelete,
onActionRefresh,
Expand All @@ -174,6 +175,7 @@ export class Table extends PureComponent<TableProps, TableState> {
basePath,
actionRegistry,
columnRegistry,
namespaceRegistry,
dateFormat,
} = this.props;

Expand All @@ -184,26 +186,6 @@ export class Table extends PureComponent<TableProps, TableState> {
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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import {
ISavedObjectsManagementServiceRegistry,
SavedObjectsManagementActionServiceStart,
SavedObjectsManagementColumnServiceStart,
SavedObjectsManagementNamespaceServiceStart,
} from '../../services';
import { Header, Table, Flyout, Relationships } from './components';
import { DataPublicPluginStart } from '../../../../../plugins/data/public';
Expand All @@ -99,6 +100,7 @@ export interface SavedObjectsTableProps {
serviceRegistry: ISavedObjectsManagementServiceRegistry;
actionRegistry: SavedObjectsManagementActionServiceStart;
columnRegistry: SavedObjectsManagementColumnServiceStart;
namespaceRegistry: SavedObjectsManagementNamespaceServiceStart;
savedObjectsClient: SavedObjectsClientContract;
indexPatterns: IndexPatternsContract;
http: HttpStart;
Expand Down Expand Up @@ -177,7 +179,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb

fetchCounts = async () => {
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)
Expand All @@ -187,6 +189,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
const filteredSavedObjectCounts = await getSavedObjectCounts(
this.props.http,
filteredTypes,
visibleNamespaces,
queryText
);

Expand All @@ -206,7 +209,12 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb

// 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,
allowedTypes,
[],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why passing empty array here, base on the context seems here should pass in the allowedNameSpaces?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Kristen, thank you for pointing this out. For this implementation I was thinking that since namespaces are registered by plugins and not a static amount like with types, that passing an empty array would indicate to the API to fetch objects from all namespaces and return a count of the namespaces associated with the fetched objects. That's different than how the types works though where it assumes empty array means fetch no objects.

FYI I am working on a follow-up to this PR with a fix for a regression introduced on exporting button because of the filteredSavedObjectCounts type above since objects counts will now be a data structure like

{ 
  'types': {
     'config': 6,
     ...,
  },
  'namespaces': {
    'user_tenant': 5,
   ...
  }
}

instead of:

{
  'config': 5,
  'index-pattern': 7,
  ...
}

I have some changes ready, but want to add better test coverage. You can see changes being prepared here: main...cwperks:OpenSearch-Dashboards:namespace-registry-fixes

queryText
);

this.setState((state) => ({
...state,
Expand All @@ -227,15 +235,19 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
debouncedFetchObjects = debounce(async () => {
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 = {
search: queryText ? `${queryText}*` : undefined,
perPage,
page: page + 1,
fields: ['id'],
type: allowedTypes.filter((type) => !visibleTypes || visibleTypes.includes(type)),
type: filteredTypes,
namespaces: visibleNamespaces,
};
if (findOptions.type.length > 1) {
findOptions.sortField = 'type';
Expand Down Expand Up @@ -390,6 +402,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
onExportAll = async () => {
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) {
Expand Down Expand Up @@ -764,18 +777,55 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
isSearching,
savedObjectCounts,
} = this.state;
const { http, allowedTypes, applications } = this.props;
const { http, allowedTypes, applications, namespaceRegistry } = this.props;

const selectionConfig = {
onSelectionChange: this.onSelectionChanged,
};
const typeCounts = savedObjectCounts.type || {};

const filterOptions = allowedTypes.map((type) => ({
value: type,
name: type,
view: `${type} (${savedObjectCounts[type] || 0})`,
view: `${type} (${typeCounts[type] || 0})`,
cwperks marked this conversation as resolved.
Show resolved Hide resolved
}));

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 (
<EuiPageContent horizontalPosition="center">
{this.renderFlyout()}
Expand All @@ -799,7 +849,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
selectedSavedObjects={selectedSavedObjects}
onQueryChange={this.onQueryChange}
onTableChange={this.onTableChange}
filterOptions={filterOptions}
filters={filters}
onExport={this.onExport}
canDelete={applications.capabilities.savedObjectsManagement.delete as boolean}
onDelete={this.onDelete}
Expand Down
Loading