Skip to content

Commit

Permalink
add workspace filter into saved objects page (#76)
Browse files Browse the repository at this point in the history
* add workspace filter into saved objects page

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* workspace filter

Signed-off-by: Hailong Cui <ihailong@amazon.com>

* managment workspace filter

Signed-off-by: Hailong Cui <ihailong@amazon.com>

---------

Signed-off-by: Hailong Cui <ihailong@amazon.com>
  • Loading branch information
Hailong-am authored and SuZhou-Joe committed Aug 31, 2023
1 parent 4c493a4 commit 7e49007
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/core/public/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ export { shareWeakReplay } from './share_weak_replay';
export { Sha256 } from './crypto';
export { MountWrapper, mountReactNode } from './mount';
export { getWorkspaceIdFromUrl, WORKSPACE_TYPE } from './workspace';
export { WORKSPACE_PATH_PREFIX, PUBLIC_WORKSPACE } from '../../utils';
export { WORKSPACE_PATH_PREFIX, PUBLIC_WORKSPACE, MANAGEMENT_WORKSPACE } from '../../utils';
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export interface SavedObjectCountOptions {
export async function getSavedObjectCounts(
http: HttpStart,
options: SavedObjectCountOptions
): Promise<Record<string, number>> {
return await http.post<Record<string, number>>(
): Promise<Record<string, Record<string, number>>> {
return await http.post<Record<string, Record<string, number>>>(
`/api/opensearch-dashboards/management/saved_objects/scroll/counts`,
{ body: JSON.stringify(options) }
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ import { Query } from '@elastic/eui';
interface ParsedQuery {
queryText?: string;
visibleTypes?: string[];
visibleNamespaces?: string[];
visibleWorkspaces?: string[];
}

export function parseQuery(query: Query): ParsedQuery {
let queryText: string | undefined;
let visibleTypes: string[] | undefined;
let visibleNamespaces: string[] | undefined;
let visibleWorkspaces: string[] | undefined;

if (query) {
if (query.ast.getTermClauses().length) {
Expand All @@ -53,11 +56,15 @@ export function parseQuery(query: Query): ParsedQuery {
if (query.ast.getFieldClauses('namespaces')) {
visibleNamespaces = query.ast.getFieldClauses('namespaces')[0].value as string[];
}
if (query.ast.getFieldClauses('workspaces')) {
visibleWorkspaces = query.ast.getFieldClauses('workspaces')[0].value as string[];
}
}

return {
queryText,
visibleTypes,
visibleNamespaces,
visibleWorkspaces,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ import {
OverlayStart,
NotificationsStart,
ApplicationStart,
PUBLIC_WORKSPACE,
} from '../../../../../core/public';
WorkspaceAttribute,
} from 'src/core/public';
import { Subscription } from 'rxjs';
import { RedirectAppLinks } from '../../../../opensearch_dashboards_react/public';
import { IndexPatternsContract } from '../../../../data/public';
import {
Expand Down Expand Up @@ -95,6 +96,7 @@ import {
import { Header, Table, Flyout, Relationships } from './components';
import { DataPublicPluginStart } from '../../../../../plugins/data/public';
import { SavedObjectsCopyModal } from './components/copy_modal';
import { PUBLIC_WORKSPACE, MANAGEMENT_WORKSPACE } from '../../../../../core/public';

interface ExportAllOption {
id: string;
Expand Down Expand Up @@ -128,7 +130,7 @@ export interface SavedObjectsTableState {
page: number;
perPage: number;
savedObjects: SavedObjectWithMetadata[];
savedObjectCounts: Record<string, number>;
savedObjectCounts: Record<string, Record<string, number>>;
activeQuery: Query;
selectedSavedObjects: SavedObjectWithMetadata[];
isShowingImportFlyout: boolean;
Expand All @@ -144,23 +146,28 @@ export interface SavedObjectsTableState {
exportAllSelectedOptions: Record<string, boolean>;
isIncludeReferencesDeepChecked: boolean;
workspaceId: string | null;
availableWorkspace?: WorkspaceAttribute[];
}

export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedObjectsTableState> {
private _isMounted = false;
private currentWorkspaceIdSubscription?: Subscription;
private workspacesSubscription?: Subscription;

constructor(props: SavedObjectsTableProps) {
super(props);

const typeCounts = props.allowedTypes.reduce((typeToCountMap, type) => {
typeToCountMap[type] = 0;
return typeToCountMap;
}, {} as Record<string, number>);

this.state = {
totalCount: 0,
page: 0,
perPage: props.perPageConfig || 50,
savedObjects: [],
savedObjectCounts: props.allowedTypes.reduce((typeToCountMap, type) => {
typeToCountMap[type] = 0;
return typeToCountMap;
}, {} as Record<string, number>),
savedObjectCounts: { type: typeCounts } as Record<string, Record<string, number>>,
activeQuery: Query.parse(''),
selectedSavedObjects: [],
isShowingImportFlyout: false,
Expand All @@ -176,34 +183,53 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
exportAllSelectedOptions: {},
isIncludeReferencesDeepChecked: true,
workspaceId: this.props.workspaces.currentWorkspaceId$.getValue(),
availableWorkspace: this.props.workspaces.workspaceList$.getValue(),
};
}

private get workspaceIdQuery() {
return this.state.workspaceId
? Array.from(new Set([PUBLIC_WORKSPACE, this.state.workspaceId]))
: undefined;
const { availableWorkspace, workspaceId } = this.state;
// workspace is turned off
if (!availableWorkspace?.length) {
return undefined;
}
if (!workspaceId || workspaceId === MANAGEMENT_WORKSPACE) {
return availableWorkspace.map((ws) => ws.id);
} else if (workspaceId === PUBLIC_WORKSPACE) {
return [PUBLIC_WORKSPACE];
} else {
return [workspaceId, PUBLIC_WORKSPACE];
}
}

private get wsNameIdLookup() {
const { availableWorkspace } = this.state;
// Assumption: workspace name is unique across the system
return availableWorkspace?.reduce((map, ws) => {
return map.set(ws.name, ws.id);
}, new Map<string, string>());
}

componentDidMount() {
this._isMounted = true;
this.props.workspaces.currentWorkspaceId$.subscribe((workspaceId) =>
this.setState({
workspaceId,
})
);

this.fetchWorkspace();
this.fetchSavedObjects();
this.fetchCounts();
}

componentWillUnmount() {
this._isMounted = false;
this.debouncedFetchObjects.cancel();
this.currentWorkspaceIdSubscription?.unsubscribe();
this.workspacesSubscription?.unsubscribe();
}

fetchCounts = async () => {
const { allowedTypes, namespaceRegistry } = this.props;
const { queryText, visibleTypes, visibleNamespaces } = parseQuery(this.state.activeQuery);
const { queryText, visibleTypes, visibleNamespaces, visibleWorkspaces } = parseQuery(
this.state.activeQuery
);

const filteredTypes = filterQuery(allowedTypes, visibleTypes);

Expand All @@ -219,6 +245,11 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
const filteredNamespaces = filterQuery(availableNamespaces, visibleNamespaces);
filteredCountOptions.namespacesToInclude = filteredNamespaces;
}
if (visibleWorkspaces?.length) {
filteredCountOptions.workspaces = visibleWorkspaces.map(
(wsName) => this.wsNameIdLookup?.get(wsName) || PUBLIC_WORKSPACE
);
}

// These are the saved objects visible in the table.
const filteredSavedObjectCounts = await getSavedObjectCounts(
Expand Down Expand Up @@ -268,14 +299,27 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
this.setState({ isSearching: true }, this.debouncedFetchObjects);
};

fetchWorkspace = () => {
const workspace = this.props.workspaces;
this.currentWorkspaceIdSubscription = workspace.currentWorkspaceId$.subscribe((workspaceId) =>
this.setState({
workspaceId,
})
);

this.workspacesSubscription = workspace.workspaceList$.subscribe((workspaceList) => {
this.setState({ availableWorkspace: workspaceList });
});
};

fetchSavedObject = (type: string, id: string) => {
this.setState({ isSearching: true }, () => this.debouncedFetchObject(type, id));
};

debouncedFetchObjects = debounce(async () => {
const { activeQuery: query, page, perPage } = this.state;
const { notifications, http, allowedTypes, namespaceRegistry } = this.props;
const { queryText, visibleTypes, visibleNamespaces } = parseQuery(query);
const { queryText, visibleTypes, visibleNamespaces, visibleWorkspaces } = parseQuery(query);
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
Expand All @@ -294,6 +338,13 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
findOptions.namespaces = filteredNamespaces;
}

if (visibleWorkspaces?.length) {
const workspaceIds: string[] = visibleWorkspaces.map(
(wsName) => this.wsNameIdLookup?.get(wsName) || PUBLIC_WORKSPACE
);
findOptions.workspaces = workspaceIds;
}

if (findOptions.type.length > 1) {
findOptions.sortField = 'type';
}
Expand Down Expand Up @@ -880,6 +931,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
filteredItemCount,
isSearching,
savedObjectCounts,
availableWorkspace,
} = this.state;
const { http, allowedTypes, applications, namespaceRegistry } = this.props;

Expand Down Expand Up @@ -930,6 +982,34 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
});
}

// Add workspace filter
if (availableWorkspace?.length) {
const wsCounts = savedObjectCounts.workspaces || {};
const wsFilterOptions = availableWorkspace
.filter((ws) => {
return this.workspaceIdQuery?.includes(ws.id);
})
.map((ws) => {
return {
name: ws.name,
value: ws.name,
view: `${ws.name} (${wsCounts[ws.id] || 0})`,
};
});

filters.push({
type: 'field_value_selection',
field: 'workspaces',
name:
namespaceRegistry.getAlias() ||
i18n.translate('savedObjectsManagement.objectsTable.table.workspaceFilterName', {
defaultMessage: 'Workspaces',
}),
multiSelect: 'or',
options: wsFilterOptions,
});
}

return (
<EuiPageContent
horizontalPosition="center"
Expand Down
24 changes: 22 additions & 2 deletions src/plugins/saved_objects_management/server/routes/scroll_count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,30 @@ export const registerScrollForCountRoute = (router: IRouter) => {
},
router.handleLegacyErrors(async (context, req, res) => {
const { client } = context.core.savedObjects;
const counts = {
const counts: Record<string, Record<string, number>> = {
type: {},
};

const findOptions: SavedObjectsFindOptions = {
type: req.body.typesToInclude,
perPage: 1000,
workspaces: req.body.workspaces,
};

const requestHasNamespaces =
Array.isArray(req.body.namespacesToInclude) && req.body.namespacesToInclude.length;

const requestHasWorkspaces = Array.isArray(req.body.workspaces) && req.body.workspaces.length;

if (requestHasNamespaces) {
counts.namespaces = {};
findOptions.namespaces = req.body.namespacesToInclude;
}

if (requestHasWorkspaces) {
counts.workspaces = {};
findOptions.workspaces = req.body.workspaces;
}

if (req.body.searchString) {
findOptions.search = `${req.body.searchString}*`;
findOptions.searchFields = ['title'];
Expand All @@ -84,6 +90,13 @@ export const registerScrollForCountRoute = (router: IRouter) => {
counts.namespaces[ns]++;
});
}
if (requestHasWorkspaces) {
const resultWorkspaces = result.workspaces || ['public'];
resultWorkspaces.forEach((ws) => {
counts.workspaces[ws] = counts.workspaces[ws] || 0;
counts.workspaces[ws]++;
});
}
counts.type[type] = counts.type[type] || 0;
counts.type[type]++;
});
Expand All @@ -101,6 +114,13 @@ export const registerScrollForCountRoute = (router: IRouter) => {
}
}

const workspacesToInclude = req.body.workspaces || [];
for (const ws of workspacesToInclude) {
if (!counts.workspaces[ws]) {
counts.workspaces[ws] = 0;
}
}

return res.ok({
body: counts,
});
Expand Down

0 comments on commit 7e49007

Please sign in to comment.