From 2787be4c060410a37dc3cf4b512ccb0561d5c394 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Thu, 8 Dec 2022 22:03:23 -0500 Subject: [PATCH] 19783: Project search almost works Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- src/store/auth/auth-action.ts | 7 ++- src/store/tree-picker/tree-picker-actions.ts | 49 +++++++++++----- .../projects-tree-picker/home-tree-picker.tsx | 6 +- .../projects-tree-picker.tsx | 56 +++++++++++-------- .../shared-tree-picker.tsx | 5 +- 5 files changed, 79 insertions(+), 44 deletions(-) diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index 7fc9df77..6eb8356f 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -92,6 +92,9 @@ export const saveApiToken = (token: string) => async (dispatch: Dispatch, getSta // If the token is from a LoginCluster federation, get user & token data // from the token issuing cluster. + if (!config) { + return; + } const lc = (config as Config).loginCluster const tokenCluster = tokenParts.length === 3 ? tokenParts[1].substring(0, 5) @@ -127,7 +130,7 @@ export const getNewExtraToken = (reuseStored: boolean = false) => const client = await svc.apiClientAuthorizationService.get('current'); dispatch(authActions.SET_EXTRA_TOKEN({ extraApiToken: extraToken, - extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt): undefined, + extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt) : undefined, })); return extraToken; } catch (e) { @@ -145,7 +148,7 @@ export const getNewExtraToken = (reuseStored: boolean = false) => const newExtraToken = getTokenV2(client); dispatch(authActions.SET_EXTRA_TOKEN({ extraApiToken: newExtraToken, - extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt): undefined, + extraApiTokenExpiration: client.expiresAt ? new Date(client.expiresAt) : undefined, })); return newExtraToken; } catch { diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index b7710494..e6c4e09b 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -44,7 +44,6 @@ export interface LoadProjectParams { includeCollections?: boolean; includeFiles?: boolean; includeFilterGroups?: boolean; - loadShared?: boolean; options?: { showOnlyOwned: boolean; showOnlyWritable: boolean; }; } @@ -60,7 +59,8 @@ export const getProjectsTreePickerIds = (pickerId: string) => ({ home: `${pickerId}_home`, shared: `${pickerId}_shared`, favorites: `${pickerId}_favorites`, - publicFavorites: `${pickerId}_publicFavorites` + publicFavorites: `${pickerId}_publicFavorites`, + search: `${pickerId}_search`, }); export const getAllNodes = (pickerId: string, filter = (node: TreeNode) => true) => (state: TreePicker) => @@ -88,11 +88,12 @@ export const getSelectedNodes = (pickerId: string) => (state: TreePicker) export const initProjectsTreePicker = (pickerId: string) => async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => { - const { home, shared, favorites, publicFavorites } = getProjectsTreePickerIds(pickerId); + const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(pickerId); dispatch(initUserProject(home)); dispatch(initSharedProject(shared)); dispatch(initFavoritesProject(favorites)); dispatch(initPublicFavoritesProject(publicFavorites)); + dispatch(initSearchProject(search)); }; interface ReceiveTreePickerDataParams { @@ -116,35 +117,37 @@ export const receiveTreePickerData = (params: ReceiveTreePickerDataParams) interface LoadProjectParamsWithId extends LoadProjectParams { id: string; pickerId: string; - includeCollections?: boolean; - includeFiles?: boolean; - includeFilterGroups?: boolean; loadShared?: boolean; - options?: { showOnlyOwned: boolean; showOnlyWritable: boolean; }; + searchProjects?: boolean; } export const loadProject = (params: LoadProjectParamsWithId) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const { id, pickerId, includeCollections = false, includeFiles = false, includeFilterGroups = false, loadShared = false, options } = params; + const { id, pickerId, includeCollections = false, includeFiles = false, includeFilterGroups = false, loadShared = false, options, searchProjects = false } = params; dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId })); - let filterB = pipe( - (fb: FilterBuilder) => includeCollections - ? fb.addIsA('uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION]) - : fb.addIsA('uuid', [ResourceKind.PROJECT]), - fb => fb.addNotIn("collections.properties.type", ["intermediate", "log"]), - )(new FilterBuilder()); + let filterB = new FilterBuilder(); + + filterB = (includeCollections && !searchProjects) + ? filterB.addIsA('uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION]) + : filterB.addIsA('uuid', [ResourceKind.PROJECT]); const state = getState(); if (state.treePickerSearch.collectionFilterValues[pickerId]) { filterB = filterB.addILike('collections.name', state.treePickerSearch.collectionFilterValues[pickerId]); + } else { + filterB = filterB.addNotIn("collections.properties.type", ["intermediate", "log"]); + } + + if (searchProjects && state.treePickerSearch.projectSearchValues[pickerId]) { + filterB = filterB.addILike('groups.name', state.treePickerSearch.projectSearchValues[pickerId]); } const filters = filterB.getFilters(); - const { items, itemsAvailable } = await services.groupsService.contents(loadShared ? '' : id, { filters, excludeHomeProject: loadShared || undefined, limit: 1000 }); + const { items, itemsAvailable } = await services.groupsService.contents((loadShared || searchProjects) ? '' : id, { filters, excludeHomeProject: loadShared || undefined, limit: 1000 }); if (itemsAvailable > 1000) { items.push({ @@ -292,6 +295,22 @@ export const initPublicFavoritesProject = (pickerId: string) => })); }; +export const SEARCH_PROJECT_ID = 'Search all Projects'; +export const initSearchProject = (pickerId: string) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(receiveTreePickerData({ + id: '', + pickerId, + data: [{ uuid: SEARCH_PROJECT_ID, name: SEARCH_PROJECT_ID }], + extractNodeData: value => ({ + id: value.uuid, + status: TreeNodeStatus.INITIAL, + value, + }), + })); + }; + + interface LoadFavoritesProjectParams { pickerId: string; includeCollections?: boolean; diff --git a/src/views-components/projects-tree-picker/home-tree-picker.tsx b/src/views-components/projects-tree-picker/home-tree-picker.tsx index 4e8eeda9..3133c5db 100644 --- a/src/views-components/projects-tree-picker/home-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/home-tree-picker.tsx @@ -6,12 +6,12 @@ import { connect } from 'react-redux'; import { ProjectsTreePicker, ProjectsTreePickerProps } from 'views-components/projects-tree-picker/generic-projects-tree-picker'; import { Dispatch } from 'redux'; import { loadUserProject } from 'store/tree-picker/tree-picker-actions'; -import { ProjectIcon } from 'components/icon/icon'; +import { ProjectsIcon } from 'components/icon/icon'; export const HomeTreePicker = connect(() => ({ - rootItemIcon: ProjectIcon, + rootItemIcon: ProjectsIcon, }), (dispatch: Dispatch): Pick => ({ loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => { dispatch(loadUserProject(pickerId, includeCollections, includeFiles, options)); }, -}))(ProjectsTreePicker); \ No newline at end of file +}))(ProjectsTreePicker); diff --git a/src/views-components/projects-tree-picker/projects-tree-picker.tsx b/src/views-components/projects-tree-picker/projects-tree-picker.tsx index 8f21784b..13f43e81 100644 --- a/src/views-components/projects-tree-picker/projects-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/projects-tree-picker.tsx @@ -10,6 +10,7 @@ import { values, pipe } from 'lodash/fp'; import { HomeTreePicker } from 'views-components/projects-tree-picker/home-tree-picker'; import { SharedTreePicker } from 'views-components/projects-tree-picker/shared-tree-picker'; import { FavoritesTreePicker } from 'views-components/projects-tree-picker/favorites-tree-picker'; +import { SearchProjectsPicker } from 'views-components/projects-tree-picker/search-projects-picker'; import { getProjectsTreePickerIds, treePickerActions, treePickerSearchActions, initProjectsTreePicker, SHARED_PROJECT_ID, FAVORITES_PROJECT_ID @@ -42,13 +43,13 @@ interface ProjectsTreePickerActionProps { } const mapStateToProps = (state: RootState, props: ToplevelPickerProps): ProjectsTreePickerSearchProps => ({ - projectSearch: "", - collectionFilter: "", + projectSearch: state.treePickerSearch.projectSearchValues[props.pickerId], + collectionFilter: state.treePickerSearch.collectionFilterValues[props.pickerId], ...props }); const mapDispatchToProps = (dispatch: Dispatch, props: ToplevelPickerProps): (ProjectsTreePickerActionProps & DispatchProp) => { - const { home, shared, favorites, publicFavorites } = getProjectsTreePickerIds(props.pickerId); + const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(props.pickerId); const params = { includeCollections: props.includeCollections, includeFiles: props.includeFiles, @@ -58,20 +59,22 @@ const mapDispatchToProps = (dispatch: Dispatch, props: ToplevelPickerProps): (Pr dispatch(treePickerSearchActions.SET_TREE_PICKER_LOAD_PARAMS({ pickerId: shared, params })); dispatch(treePickerSearchActions.SET_TREE_PICKER_LOAD_PARAMS({ pickerId: favorites, params })); dispatch(treePickerSearchActions.SET_TREE_PICKER_LOAD_PARAMS({ pickerId: publicFavorites, params })); + dispatch(treePickerSearchActions.SET_TREE_PICKER_LOAD_PARAMS({ pickerId: search, params })); return { - onProjectSearch: (projectSearchValue: string) => dispatch(treePickerSearchActions.SET_TREE_PICKER_PROJECT_SEARCH({ pickerId: props.pickerId, projectSearchValue })), + onProjectSearch: (projectSearchValue: string) => dispatch(treePickerSearchActions.SET_TREE_PICKER_PROJECT_SEARCH({ pickerId: search, projectSearchValue })), onCollectionFilter: (collectionFilterValue: string) => { dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: home, collectionFilterValue })); dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: shared, collectionFilterValue })); dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: favorites, collectionFilterValue })); dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: publicFavorites, collectionFilterValue })); + dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: search, collectionFilterValue })); }, dispatch } }; -type CssRules = 'pickerHeight' | 'searchFlex' | 'searchPadding'; +type CssRules = 'pickerHeight' | 'searchFlex'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ pickerHeight: { @@ -91,11 +94,12 @@ export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)( class FileInputComponent extends React.Component { componentDidMount() { - const { home, shared, favorites, publicFavorites } = getProjectsTreePickerIds(this.props.pickerId); + const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(this.props.pickerId); this.props.dispatch(initProjectsTreePicker(this.props.pickerId)); - this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_PROJECT_SEARCH({ pickerId: this.props.pickerId, projectSearchValue: "" })); + this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_PROJECT_SEARCH({ pickerId: search, projectSearchValue: "" })); + this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: search, collectionFilterValue: "" })); this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: home, collectionFilterValue: "" })); this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: shared, collectionFilterValue: "" })); this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: favorites, collectionFilterValue: "" })); @@ -103,9 +107,9 @@ export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)( } componentWillUnmount() { - const { home, shared, favorites, publicFavorites } = getProjectsTreePickerIds(this.props.pickerId); + const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(this.props.pickerId); // Release all the state, we don't need it to hang around forever. - this.props.dispatch(treePickerActions.RESET_TREE_PICKER({ pickerId: this.props.pickerId })); + this.props.dispatch(treePickerActions.RESET_TREE_PICKER({ pickerId: search })); this.props.dispatch(treePickerActions.RESET_TREE_PICKER({ pickerId: home })); this.props.dispatch(treePickerActions.RESET_TREE_PICKER({ pickerId: shared })); this.props.dispatch(treePickerActions.RESET_TREE_PICKER({ pickerId: favorites })); @@ -117,7 +121,7 @@ export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)( const onProjectSearch = this.props.onProjectSearch; const onCollectionFilter = this.props.onCollectionFilter; - const { home, shared, favorites, publicFavorites } = getProjectsTreePickerIds(pickerId); + const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(pickerId); const relatedTreePickers = getRelatedTreePickers(pickerId); const p = { includeCollections: this.props.includeCollections, @@ -132,18 +136,26 @@ export const ProjectsTreePicker = connect(mapStateToProps, mapDispatchToProps)( -
- -
-
- -
-
- -
-
- -
+ + {this.props.projectSearch !== "" ? +
+ +
+ : + <> +
+ +
+
+ +
+
+ +
+
+ +
+ } ; } })); diff --git a/src/views-components/projects-tree-picker/shared-tree-picker.tsx b/src/views-components/projects-tree-picker/shared-tree-picker.tsx index 201bd118..c15df6ba 100644 --- a/src/views-components/projects-tree-picker/shared-tree-picker.tsx +++ b/src/views-components/projects-tree-picker/shared-tree-picker.tsx @@ -7,11 +7,12 @@ import { ProjectsTreePicker, ProjectsTreePickerProps } from 'views-components/pr import { Dispatch } from 'redux'; import { ShareMeIcon } from 'components/icon/icon'; import { loadProject } from 'store/tree-picker/tree-picker-actions'; +import { SHARED_PROJECT_ID } from 'store/tree-picker/tree-picker-actions'; export const SharedTreePicker = connect(() => ({ rootItemIcon: ShareMeIcon, }), (dispatch: Dispatch): Pick => ({ loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => { - dispatch(loadProject({ id: 'Shared with me', pickerId, includeCollections, includeFiles, loadShared: true, options })); + dispatch(loadProject({ id: SHARED_PROJECT_ID, pickerId, includeCollections, includeFiles, loadShared: true, options })); }, -}))(ProjectsTreePicker); \ No newline at end of file +}))(ProjectsTreePicker); -- 2.30.2