From 375bef1554bb6c85068608b7c8f6c69622c04323 Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Fri, 12 Oct 2018 16:06:54 +0200 Subject: [PATCH] search-results-view Feature #14277 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- src/routes/route-change-handlers.ts | 7 +- src/routes/routes.ts | 6 +- src/store/search-bar/search-bar-actions.ts | 2 + src/store/search-bar/search-bar-reducer.ts | 2 +- .../search-results-middleware-service.ts | 81 +++++++++++ .../search-results-panel-actions.ts | 16 +++ src/store/trash/trash-actions.ts | 4 +- src/store/workbench/workbench-actions.ts | 8 ++ .../search-bar/search-bar-view.tsx | 2 +- src/views/favorite-panel/favorite-panel.tsx | 1 - .../search-results-panel-view.tsx | 128 ++++++++++++++++++ .../search-results-panel.tsx | 47 +++++++ src/views/workbench/workbench.tsx | 6 +- .../workflow-panel/workflow-panel-view.tsx | 2 +- 14 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 src/store/search-results-panel/search-results-middleware-service.ts create mode 100644 src/store/search-results-panel/search-results-panel-actions.ts create mode 100644 src/views/search-results-panel/search-results-panel-view.tsx create mode 100644 src/views/search-results-panel/search-results-panel.tsx diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts index af3bdab4..ef9e9ebc 100644 --- a/src/routes/route-change-handlers.ts +++ b/src/routes/route-change-handlers.ts @@ -4,10 +4,10 @@ import { History, Location } from 'history'; import { RootStore } from '~/store/store'; -import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute } from './routes'; +import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute, matchSearchResultsRoute } from './routes'; import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions'; import { navigateToRootProject } from '~/store/navigation/navigation-action'; -import { loadSharedWithMe, loadRunProcess, loadWorkflow } from '../store/workbench/workbench-actions'; +import { loadSharedWithMe, loadRunProcess, loadWorkflow, loadSearchResults } from '~//store/workbench/workbench-actions'; export const addRouteChangeHandlers = (history: History, store: RootStore) => { const handler = handleLocationChange(store); @@ -23,6 +23,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { const trashMatch = matchTrashRoute(pathname); const processMatch = matchProcessRoute(pathname); const processLogMatch = matchProcessLogRoute(pathname); + const searchResultsMatch = matchSearchResultsRoute(pathname); const sharedWithMeMatch = matchSharedWithMeRoute(pathname); const runProcessMatch = matchRunProcessRoute(pathname); const workflowMatch = matchWorkflowRoute(pathname); @@ -47,5 +48,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { store.dispatch(loadRunProcess); } else if (workflowMatch) { store.dispatch(loadWorkflow); + } else if (searchResultsMatch) { + store.dispatch(loadSearchResults); } }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 432cf750..34eea4f1 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -18,7 +18,8 @@ export const Routes = { PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`, SHARED_WITH_ME: '/shared-with-me', RUN_PROCESS: '/run-process', - WORKFLOWS: '/workflows' + WORKFLOWS: '/workflows', + SEARCH_RESULTS: '/q' }; export const getResourceUrl = (uuid: string) => { @@ -72,3 +73,6 @@ export const matchRunProcessRoute = (route: string) => export const matchWorkflowRoute = (route: string) => matchPath(route, { path: Routes.WORKFLOWS }); + +export const matchSearchResultsRoute = (route: string) => + matchPath(route, { path: Routes.SEARCH_RESULTS }); diff --git a/src/store/search-bar/search-bar-actions.ts b/src/store/search-bar/search-bar-actions.ts index 59770cc6..2f30e02d 100644 --- a/src/store/search-bar/search-bar-actions.ts +++ b/src/store/search-bar/search-bar-actions.ts @@ -10,6 +10,7 @@ import { ServiceRepository } from '~/services/services'; import { FilterBuilder } from "~/services/api/filter-builder"; import { ResourceKind } from '~/models/resource'; import { GroupClass } from '~/models/group'; +import { SearchView } from '~/store/search-bar/search-bar-reducer'; export const searchBarActions = unionize({ SET_CURRENT_VIEW: ofType(), @@ -67,6 +68,7 @@ export const deleteSavedQuery = (id: number) => export const openSearchView = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { dispatch(searchBarActions.OPEN_SEARCH_VIEW()); + dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC)); const savedSearchQueries = services.searchService.getSavedQueries(); dispatch(searchBarActions.SET_SAVED_QUERIES(savedSearchQueries)); }; diff --git a/src/store/search-bar/search-bar-reducer.ts b/src/store/search-bar/search-bar-reducer.ts index ce2a77cc..7e119d21 100644 --- a/src/store/search-bar/search-bar-reducer.ts +++ b/src/store/search-bar/search-bar-reducer.ts @@ -24,7 +24,7 @@ const initialState: SearchBar = { open: false, searchResults: [], searchValue: '', - savedQueries: [''] + savedQueries: [] }; export const searchBarReducer = (state = initialState, action: SearchBarActions): SearchBar => diff --git a/src/store/search-results-panel/search-results-middleware-service.ts b/src/store/search-results-panel/search-results-middleware-service.ts new file mode 100644 index 00000000..2ae2dee3 --- /dev/null +++ b/src/store/search-results-panel/search-results-middleware-service.ts @@ -0,0 +1,81 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ServiceRepository } from '~/services/services'; +import { MiddlewareAPI, Dispatch } from 'redux'; +import { DataExplorerMiddlewareService, dataExplorerToListParams, listResultsToDataExplorerItemsMeta } from '~/store/data-explorer/data-explorer-middleware-service'; +import { RootState } from '~/store/store'; +import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; +import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explorer-reducer'; +import { updateResources } from '~/store/resources/resources-actions'; +import { FilterBuilder } from '~/services/api/filter-builder'; +import { SortDirection } from '~/components/data-table/data-column'; +import { SearchResultsPanelColumnNames } from '~/views/search-results-panel/search-results-panel-view'; +import { OrderDirection, OrderBuilder } from '~/services/api/order-builder'; +import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service"; +import { ListResults } from '~/services/common-service/common-resource-service'; +import { searchResultsPanelActions } from '~/store/search-results-panel/search-results-panel-actions'; + +export class SearchResultsMiddlewareService extends DataExplorerMiddlewareService { + constructor(private services: ServiceRepository, id: string) { + super(id); + } + + async requestItems(api: MiddlewareAPI) { + const state = api.getState(); + const userUuid = state.auth.user!.uuid; + const dataExplorer = getDataExplorer(state.dataExplorer, this.getId()); + try { + const response = await this.services.groupsService.contents(userUuid, getParams(dataExplorer)); + api.dispatch(updateResources(response.items)); + api.dispatch(setItems(response)); + } catch { + api.dispatch(couldNotFetchWorkflows()); + } + } +} + +export const getParams = (dataExplorer: DataExplorer) => ({ + ...dataExplorerToListParams(dataExplorer), + order: getOrder(dataExplorer), + filters: getFilters(dataExplorer) +}); + +export const getFilters = (dataExplorer: DataExplorer) => { + const filters = new FilterBuilder() + .addILike("name", dataExplorer.searchValue) + .getFilters(); + return filters; +}; + +export const getOrder = (dataExplorer: DataExplorer) => { + const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE); + const order = new OrderBuilder(); + if (sortColumn) { + const sortDirection = sortColumn && sortColumn.sortDirection === SortDirection.ASC + ? OrderDirection.ASC + : OrderDirection.DESC; + const columnName = sortColumn && sortColumn.name === SearchResultsPanelColumnNames.NAME ? "name" : "modifiedAt"; + return order + .addOrder(sortDirection, columnName) + .addOrder(sortDirection, "name", GroupContentsResourcePrefix.COLLECTION) + .addOrder(sortDirection, "name", GroupContentsResourcePrefix.PROCESS) + .addOrder(sortDirection, "name", GroupContentsResourcePrefix.PROJECT) + .getOrder(); + } else { + return order.getOrder(); +} +}; + +export const setItems = (listResults: ListResults) => + searchResultsPanelActions.SET_ITEMS({ + ...listResultsToDataExplorerItemsMeta(listResults), + items: listResults.items.map(resource => resource.uuid), + }); + +const couldNotFetchWorkflows = () => + snackbarActions.OPEN_SNACKBAR({ + message: 'Could not fetch workflows.', + kind: SnackbarKind.ERROR + }); \ No newline at end of file diff --git a/src/store/search-results-panel/search-results-panel-actions.ts b/src/store/search-results-panel/search-results-panel-actions.ts new file mode 100644 index 00000000..05da5b3e --- /dev/null +++ b/src/store/search-results-panel/search-results-panel-actions.ts @@ -0,0 +1,16 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from 'redux'; +import { RootState } from '~/store/store'; +import { ServiceRepository } from '~/services/services'; +import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action'; + +export const SEARCH_RESULTS_PANEL_ID = "searchResultsPanel"; +export const searchResultsPanelActions = bindDataExplorerActions(SEARCH_RESULTS_PANEL_ID); + +export const loadSearchResultsPanel = () => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(searchResultsPanelActions.REQUEST_ITEMS()); + }; \ No newline at end of file diff --git a/src/store/trash/trash-actions.ts b/src/store/trash/trash-actions.ts index b59276c1..92d01582 100644 --- a/src/store/trash/trash-actions.ts +++ b/src/store/trash/trash-actions.ts @@ -10,8 +10,8 @@ import { trashPanelActions } from "~/store/trash-panel/trash-panel-action"; import { activateSidePanelTreeItem, loadSidePanelTreeProjects } from "~/store/side-panel-tree/side-panel-tree-actions"; import { projectPanelActions } from "~/store/project-panel/project-panel-action"; import { ResourceKind } from "~/models/resource"; -import { navigateToTrash } from '../navigation/navigation-action'; -import { matchTrashRoute, matchCollectionRoute } from '../../routes/routes'; +import { navigateToTrash } from '~/store/navigation/navigation-action'; +import { matchCollectionRoute } from '~/routes/routes'; export const toggleProjectTrashed = (uuid: string, ownerUuid: string, isTrashed: boolean) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index bc2a9ed6..1f31656a 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -51,6 +51,8 @@ import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-f import { SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { collectionPanelActions } from "~/store/collection-panel/collection-panel-action"; import { CollectionResource } from "~/models/collection"; +import { searchResultsPanelActions, loadSearchResultsPanel } from '~/store/search-results-panel/search-results-panel-actions'; +import { searchResultsPanelColumns } from '~/views/search-results-panel/search-results-panel-view'; export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen'; @@ -84,6 +86,7 @@ export const loadWorkbench = () => dispatch(trashPanelActions.SET_COLUMNS({ columns: trashPanelColumns })); dispatch(sharedWithMePanelActions.SET_COLUMNS({ columns: projectPanelColumns })); dispatch(workflowPanelActions.SET_COLUMNS({ columns: workflowPanelColumns })); + dispatch(searchResultsPanelActions.SET_COLUMNS({ columns: searchResultsPanelColumns })); dispatch(initSidePanelTree()); if (router.location) { const match = matchRootRoute(router.location.pathname); @@ -373,6 +376,11 @@ export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch) dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS)); }); +export const loadSearchResults = handleFirstTimeLoad( + async (dispatch: Dispatch) => { + await dispatch(loadSearchResultsPanel()); + }); + const finishLoadingProject = (project: GroupContentsResource | string) => async (dispatch: Dispatch) => { const uuid = typeof project === 'string' ? project : project.uuid; diff --git a/src/views-components/search-bar/search-bar-view.tsx b/src/views-components/search-bar/search-bar-view.tsx index 59fe4104..f394084c 100644 --- a/src/views-components/search-bar/search-bar-view.tsx +++ b/src/views-components/search-bar/search-bar-view.tsx @@ -116,7 +116,7 @@ export const SearchBarView = withStyles(styles)( render() { const { classes, currentView, openSearchView, closeView, isPopoverOpen } = this.props; - return closeView()}> + return
void; + onContextMenu: (event: React.MouseEvent, item: string) => void; + onDialogOpen: (ownerUuid: string) => void; + onItemDoubleClick: (item: string) => void; +} + +export type SearchResultsPanelProps = SearchResultsPanelDataProps & SearchResultsPanelActionProps; + +export interface WorkflowPanelFilter extends DataTableFilterItem { + type: ResourceKind | ContainerRequestState; +} + +export const searchResultsPanelColumns: DataColumns = [ + { + name: SearchResultsPanelColumnNames.NAME, + selected: true, + configurable: true, + sortDirection: SortDirection.ASC, + filters: [], + render: (uuid: string) => + }, + { + name: SearchResultsPanelColumnNames.PROJECT, + selected: true, + configurable: true, + filters: [], + render: uuid => + }, + { + name: SearchResultsPanelColumnNames.STATUS, + selected: true, + configurable: true, + filters: [], + render: uuid => + }, + { + name: SearchResultsPanelColumnNames.TYPE, + selected: true, + configurable: true, + filters: [ + { + name: resourceLabel(ResourceKind.COLLECTION), + selected: true, + type: ResourceKind.COLLECTION + }, + { + name: resourceLabel(ResourceKind.PROCESS), + selected: true, + type: ResourceKind.PROCESS + }, + { + name: resourceLabel(ResourceKind.PROJECT), + selected: true, + type: ResourceKind.PROJECT + } + ], + render: (uuid: string) => , + }, + { + name: SearchResultsPanelColumnNames.OWNER, + selected: true, + configurable: true, + filters: [], + render: uuid => + }, + { + name: SearchResultsPanelColumnNames.FILE_SIZE, + selected: true, + configurable: true, + filters: [], + render: uuid => + }, + { + name: SearchResultsPanelColumnNames.LAST_MODIFIED, + selected: true, + configurable: true, + sortDirection: SortDirection.NONE, + filters: [], + render: uuid => + } +]; + +export const SearchResultsPanelView = (props: SearchResultsPanelProps) => { + return ; +}; \ No newline at end of file diff --git a/src/views/search-results-panel/search-results-panel.tsx b/src/views/search-results-panel/search-results-panel.tsx new file mode 100644 index 00000000..8b01ca3f --- /dev/null +++ b/src/views/search-results-panel/search-results-panel.tsx @@ -0,0 +1,47 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { connect } from "react-redux"; +import { navigateTo } from '~/store/navigation/navigation-action'; +import { SearchResultsPanelActionProps, SearchResultsPanelDataProps } from './search-results-panel-view'; +import { RootState } from '~/store/store'; +import { openContextMenu, resourceKindToContextMenuKind } from '~/store/context-menu/context-menu-actions'; +import { ResourceKind } from '~/models/resource'; +import { loadDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { SearchResultsPanelView } from '~/views/search-results-panel/search-results-panel-view'; + +const mapStateToProps = (state: RootState): SearchResultsPanelDataProps => ({ + data: { + inTrash: false, + dataFrom: '', + dataTo: '', + saveQuery: false, + searchQuery: '' + } +}); + +const mapDispatchToProps = (dispatch: Dispatch): SearchResultsPanelActionProps => ({ + onContextMenu: (event, resourceUuid) => { + const kind = resourceKindToContextMenuKind(resourceUuid); + if (kind) { + dispatch(openContextMenu(event, { + name: '', + uuid: resourceUuid, + ownerUuid: '', + kind: ResourceKind.NONE, + menuKind: kind + })); + } + }, + onDialogOpen: (ownerUuid: string) => { return; }, + onItemClick: (resourceUuid: string) => { + dispatch(loadDetailsPanel(resourceUuid)); + }, + onItemDoubleClick: uuid => { + dispatch(navigateTo(uuid)); + } +}); + +export const SearchResultsPanel = connect(mapStateToProps, mapDispatchToProps)(SearchResultsPanelView); \ No newline at end of file diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index f26d50fb..ade9a4e1 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -41,10 +41,7 @@ import { SharedWithMePanel } from '~/views/shared-with-me-panel/shared-with-me-p import { RunProcessPanel } from '~/views/run-process-panel/run-process-panel'; import SplitterLayout from 'react-splitter-layout'; import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel'; -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 { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker'; +import { SearchResultsPanel } from '~/views/search-results-panel/search-results-panel'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -102,6 +99,7 @@ export const WorkbenchPanel = + diff --git a/src/views/workflow-panel/workflow-panel-view.tsx b/src/views/workflow-panel/workflow-panel-view.tsx index 57654bc8..cccdce80 100644 --- a/src/views/workflow-panel/workflow-panel-view.tsx +++ b/src/views/workflow-panel/workflow-panel-view.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { DataExplorer } from "~/views-components/data-explorer/data-explorer"; import { WorkflowIcon } from '~/components/icon/icon'; import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view'; -import { WORKFLOW_PANEL_ID, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions'; +import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions'; import { ResourceLastModifiedDate, RosurceWorkflowName, -- 2.30.2