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);
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);
store.dispatch(loadRunProcess);
} else if (workflowMatch) {
store.dispatch(loadWorkflow);
+ } else if (searchResultsMatch) {
+ store.dispatch(loadSearchResults);
}
};
PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
SHARED_WITH_ME: '/shared-with-me',
RUN_PROCESS: '/run-process',
- WORKFLOWS: '/workflows'
+ WORKFLOWS: '/workflows',
+ SEARCH_RESULTS: '/search'
};
export const getResourceUrl = (uuid: string) => {
export const matchWorkflowRoute = (route: string) =>
matchPath<ResourceRouteParams>(route, { path: Routes.WORKFLOWS });
+
+export const matchSearchResultsRoute = (route: string) =>
+ matchPath<ResourceRouteParams>(route, { path: Routes.SEARCH_RESULTS });
export const navigateToSharedWithMe = push(Routes.SHARED_WITH_ME);
-export const navigateToRunProcess = push(Routes.RUN_PROCESS);
\ No newline at end of file
+export const navigateToRunProcess = push(Routes.RUN_PROCESS);
+
+export const navigateToSearchResults = push(Routes.SEARCH_RESULTS);
\ No newline at end of file
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';
+import { navigateToSearchResults, navigateTo } from '~/store/navigation/navigation-action';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
import { SearchBarAdvanceFormData } from '~/models/search-bar';
export const searchBarActions = unionize({
if (data.saveQuery && data.searchQuery) {
services.searchService.saveQuery(data.searchQuery);
dispatch(searchBarActions.SET_SAVED_QUERIES(services.searchService.getSavedQueries()));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Query has been sucessfully saved', kind: SnackbarKind.SUCCESS }));
}
};
const isOpen = getState().searchBar.open;
if(isOpen) {
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
+ dispatch(searchBarActions.SET_CURRENT_VIEW(SearchView.BASIC));
}
};
+export const navigateToItem = (uuid: string) =>
+ (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
+ dispatch(navigateTo(uuid));
+ };
+
export const searchData = (searchValue: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const currentView = getState().searchBar.currentView;
+ if (currentView !== SearchView.AUTOCOMPLETE) {
+ dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
+ }
dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
if (searchValue) {
});
dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
}
+ dispatch(navigateToSearchResults);
};
-const getFilters = (filterName: string, searchValue: string): string => {
+export const getFilters = (filterName: string, searchValue: string): string => {
return new FilterBuilder()
.addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION, ResourceKind.PROCESS])
.addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
open: false,
searchResults: [],
searchValue: '',
- savedQueries: ['']
+ savedQueries: []
};
export const searchBarReducer = (state = initialState, action: SearchBarActions): SearchBar =>
--- /dev/null
+// 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';
+import { getFilters } from '~/store/search-bar/search-bar-actions';
+
+export class SearchResultsMiddlewareService extends DataExplorerMiddlewareService {
+ constructor(private services: ServiceRepository, id: string) {
+ super(id);
+ }
+
+ async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ const state = api.getState();
+ const userUuid = state.auth.user!.uuid;
+ const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
+ const searchValue = state.searchBar.searchValue;
+ try {
+ const response = await this.services.groupsService.contents(userUuid, getParams(dataExplorer, searchValue));
+ api.dispatch(updateResources(response.items));
+ api.dispatch(setItems(response));
+ } catch {
+ api.dispatch(couldNotFetchWorkflows());
+ }
+ }
+}
+
+export const getParams = (dataExplorer: DataExplorer, searchValue: string) => ({
+ ...dataExplorerToListParams(dataExplorer),
+ filters: getFilters('name', searchValue)
+});
+
+
+export const getOrder = (dataExplorer: DataExplorer) => {
+ const sortColumn = dataExplorer.columns.find(c => c.sortDirection !== SortDirection.NONE);
+ const order = new OrderBuilder<GroupContentsResource>();
+ 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<GroupContentsResource>) =>
+ 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
--- /dev/null
+// 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
import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
import { appInfoReducer } from '~/store/app-info/app-info-reducer';
import { searchBarReducer } from './search-bar/search-bar-reducer';
+import { SEARCH_RESULTS_PANEL_ID } from '~/store/search-results-panel/search-results-panel-actions';
+import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
const trashPanelMiddleware = dataExplorerMiddleware(
new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
);
+ const searchResultsPanelMiddleware = dataExplorerMiddleware(
+ new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID)
+ );
const sharedWithMePanelMiddleware = dataExplorerMiddleware(
new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
);
projectPanelMiddleware,
favoritePanelMiddleware,
trashPanelMiddleware,
+ searchResultsPanelMiddleware,
sharedWithMePanelMiddleware,
workflowPanelMiddleware
];
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<any> => {
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';
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<any>(initSidePanelTree());
if (router.location) {
const match = matchRootRoute(router.location.pathname);
dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
});
+export const loadSearchResults = handleFirstTimeLoad(
+ async (dispatch: Dispatch<any>) => {
+ await dispatch(loadSearchResultsPanel());
+ });
+
const finishLoadingProject = (project: GroupContentsResource | string) =>
async (dispatch: Dispatch<any>) => {
const uuid = typeof project === 'string' ? project : project.uuid;
import * as React from 'react';
import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
-import { RecentQueriesItem } from '~/views-components/search-bar/search-bar-view';
+import { RenderAutocompleteItems } from '~/views-components/search-bar/search-bar-view';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
import Highlighter from "react-highlight-words";
export interface SearchBarAutocompleteViewDataProps {
searchResults?: GroupContentsResource[];
searchValue?: string;
+ navigateTo: (uuid: string) => void;
}
type SearchBarAutocompleteViewProps = SearchBarAutocompleteViewDataProps & WithStyles<CssRules>;
export const SearchBarAutocompleteView = withStyles(styles)(
- ({ classes, searchResults, searchValue }: SearchBarAutocompleteViewProps) =>
+ ({ classes, searchResults, searchValue, navigateTo }: SearchBarAutocompleteViewProps) =>
<Paper className={classes.searchView}>
{searchResults && <List component="nav" className={classes.list}>
{searchResults.map((item: GroupContentsResource) => {
- return <RecentQueriesItem key={item.uuid} text={getFormattedText(item.name, searchValue)} />;
+ return <RenderAutocompleteItems key={item.uuid} text={getFormattedText(item.name, searchValue)} navigateTo={navigateTo} uuid={item.uuid} />;
})}
</List>}
</Paper>
import * as React from 'react';
import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
import { SearchView } from '~/store/search-bar/search-bar-reducer';
-import { RecentQueriesItem, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
+import { RenderRecentQueries, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
type CssRules = 'advanced' | 'searchQueryList' | 'list' | 'searchView';
recentQueries: () => string[];
deleteSavedQuery: (id: number) => void;
savedQueries: string[];
+ onSearch: (searchValue: string) => void;
}
export const SearchBarBasicView = withStyles(styles)(
- ({ classes, setView, recentQueries, deleteSavedQuery, savedQueries }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
+ ({ classes, setView, recentQueries, deleteSavedQuery, savedQueries, onSearch }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
<Paper className={classes.searchView}>
<div className={classes.searchQueryList}>Recent search queries</div>
<List component="nav" className={classes.list}>
- {recentQueries().map((query, index) => <RecentQueriesItem key={index} text={query} />)}
+ {recentQueries().map((query, index) => <RenderRecentQueries key={index} text={query} onSearch={onSearch} />)}
</List>
<div className={classes.searchQueryList}>Saved search queries</div>
<List component="nav" className={classes.list}>
- {savedQueries.map((query, index) => <RenderSavedQueries key={index} text={query} id={index} deleteSavedQuery={deleteSavedQuery} />)}
+ {savedQueries.map((query, index) => <RenderSavedQueries key={index} text={query} id={index} deleteSavedQuery={deleteSavedQuery} onSearch={onSearch} />)}
</List>
<div className={classes.advanced} onClick={() => setView(SearchView.ADVANCED)}>Advanced search</div>
</Paper>
saveQuery: (data: SearchBarAdvanceFormData) => void;
deleteSavedQuery: (id: number) => void;
openSearchView: () => void;
+ navigateTo: (uuid: string) => void;
}
type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
value: string;
}
-interface RenderSavedQueriesProps {
+
+
+interface RenderRecentQueriesProps {
text: string | JSX.Element;
- id: number;
- deleteSavedQuery: (id: number) => void;
+ onSearch: (searchValue: string | JSX.Element) => void;
}
-interface RenderRecentQueriesProps {
+export const RenderRecentQueries = (props: RenderRecentQueriesProps) => {
+ return <ListItem button>
+ <ListItemText secondary={props.text} onClick={() => props.onSearch(props.text)} />
+ </ListItem>;
+};
+
+interface RenderAutocompleteItemsProps {
text: string | JSX.Element;
+ navigateTo: (uuid: string) => void;
+ uuid: string;
}
-export const RecentQueriesItem = (props: RenderRecentQueriesProps) => {
+export const RenderAutocompleteItems = (props: RenderAutocompleteItemsProps) => {
return <ListItem button>
- <ListItemText secondary={props.text} />
+ <ListItemText secondary={props.text} onClick={() => props.navigateTo(props.uuid)} />
</ListItem>;
};
+interface RenderSavedQueriesProps {
+ text: string | JSX.Element;
+ id: number;
+ deleteSavedQuery: (id: number) => void;
+ onSearch: (searchValue: string | JSX.Element) => void;
+}
export const RenderSavedQueries = (props: RenderSavedQueriesProps) => {
return <ListItem button>
- <ListItemText secondary={props.text} />
+ <ListItemText secondary={props.text} onClick={() => props.onSearch(props.text)} />
<ListItemSecondaryAction>
<Tooltip title="Remove">
<IconButton aria-label="Remove" onClick={() => props.deleteSavedQuery(props.id)}>
render() {
const { classes, currentView, openSearchView, closeView, isPopoverOpen } = this.props;
- return <ClickAwayListener onClickAway={() => closeView()}>
+ return <ClickAwayListener onClickAway={closeView}>
<Paper className={isPopoverOpen ? classes.containerSearchViewOpened : classes.container} >
<form onSubmit={this.handleSubmit}>
<Input
}
getView = (currentView: string) => {
- const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, searchResults, saveQuery } = this.props;
+ const { onSetView, loadRecentQueries, savedQueries, deleteSavedQuery, searchValue, searchResults, saveQuery, onSearch, navigateTo } = this.props;
switch (currentView) {
case SearchView.BASIC:
- return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} />;
+ return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} onSearch={onSearch} />;
case SearchView.ADVANCED:
- return <SearchBarAdvancedView setView={onSetView} saveQuery={saveQuery}/>;
+ return <SearchBarAdvancedView setView={onSetView} saveQuery={saveQuery} />;
case SearchView.AUTOCOMPLETE:
return <SearchBarAutocompleteView
+ navigateTo={navigateTo}
searchResults={searchResults}
searchValue={searchValue} />;
default:
- return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} />;
+ return <SearchBarBasicView setView={onSetView} recentQueries={loadRecentQueries} savedQueries={savedQueries} deleteSavedQuery={deleteSavedQuery} onSearch={onSearch} />;
}
}
loadRecentQueries,
saveQuery,
openSearchView,
- closeSearchView
+ closeSearchView,
+ navigateToItem
} from '~/store/search-bar/search-bar-actions';
import { SearchBarView } from '~/views-components/search-bar/search-bar-view';
import { SearchBarAdvanceFormData } from '~/models/search-bar';
loadRecentQueries: () => dispatch<any>(loadRecentQueries()),
saveQuery: (data: SearchBarAdvanceFormData) => dispatch<any>(saveQuery(data)),
deleteSavedQuery: (id: number) => dispatch<any>(deleteSavedQuery(id)),
- openSearchView: () => dispatch<any>(openSearchView())
+ openSearchView: () => dispatch<any>(openSearchView()),
+ navigateTo: (uuid: string) => dispatch<any>(navigateToItem(uuid))
});
export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);
\ No newline at end of file
import { ContainerRequestState } from "~/models/container-request";
import { FavoritesState } from '../../store/favorites/favorites-reducer';
import { RootState } from '~/store/store';
-import { PanelDefaultView } from '~/components/panel-default-view/panel-default-view';
import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
type CssRules = "toolbar" | "button";
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { SortDirection } from '~/components/data-table/data-column';
+import { DataColumns } from '~/components/data-table/data-table';
+import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
+import { ResourceKind } from '~/models/resource';
+import { ContainerRequestState } from '~/models/container-request';
+import { resourceLabel } from '~/common/labels';
+import { SearchBarAdvanceFormData } from '~/store/search-bar/search-bar-actions';
+import { SEARCH_RESULTS_PANEL_ID } from '~/store/search-results-panel/search-results-panel-actions';
+import { DataExplorer } from '~/views-components/data-explorer/data-explorer';
+import {
+ ProcessStatus,
+ ResourceFileSize,
+ ResourceLastModifiedDate,
+ ResourceName,
+ ResourceOwner,
+ ResourceType
+} from '~/views-components/data-explorer/renderers';
+
+export enum SearchResultsPanelColumnNames {
+ NAME = "Name",
+ PROJECT = "Project",
+ STATUS = "Status",
+ TYPE = 'Type',
+ OWNER = "Owner",
+ FILE_SIZE = "File size",
+ LAST_MODIFIED = "Last modified"
+}
+
+export interface SearchResultsPanelDataProps {
+ data: SearchBarAdvanceFormData;
+}
+
+export interface SearchResultsPanelActionProps {
+ onItemClick: (item: string) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, 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<string, WorkflowPanelFilter> = [
+ {
+ name: SearchResultsPanelColumnNames.NAME,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.ASC,
+ filters: [],
+ render: (uuid: string) => <ResourceName uuid={uuid} />
+ },
+ {
+ name: SearchResultsPanelColumnNames.PROJECT,
+ selected: true,
+ configurable: true,
+ filters: [],
+ render: uuid => <ResourceFileSize uuid={uuid} />
+ },
+ {
+ name: SearchResultsPanelColumnNames.STATUS,
+ selected: true,
+ configurable: true,
+ filters: [],
+ render: uuid => <ProcessStatus uuid={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) => <ResourceType uuid={uuid} />,
+ },
+ {
+ name: SearchResultsPanelColumnNames.OWNER,
+ selected: true,
+ configurable: true,
+ filters: [],
+ render: uuid => <ResourceOwner uuid={uuid} />
+ },
+ {
+ name: SearchResultsPanelColumnNames.FILE_SIZE,
+ selected: true,
+ configurable: true,
+ filters: [],
+ render: uuid => <ResourceFileSize uuid={uuid} />
+ },
+ {
+ name: SearchResultsPanelColumnNames.LAST_MODIFIED,
+ selected: true,
+ configurable: true,
+ sortDirection: SortDirection.NONE,
+ filters: [],
+ render: uuid => <ResourceLastModifiedDate uuid={uuid} />
+ }
+];
+
+export const SearchResultsPanelView = (props: SearchResultsPanelProps) => {
+ return <DataExplorer
+ id={SEARCH_RESULTS_PANEL_ID}
+ onRowClick={props.onItemClick}
+ onRowDoubleClick={props.onItemDoubleClick}
+ onContextMenu={props.onContextMenu}
+ contextMenuColumn={true} />;
+};
\ No newline at end of file
--- /dev/null
+// 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 } from './search-results-panel-view';
+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 mapDispatchToProps = (dispatch: Dispatch): SearchResultsPanelActionProps => ({
+ onContextMenu: (event, resourceUuid) => {
+ const kind = resourceKindToContextMenuKind(resourceUuid);
+ if (kind) {
+ dispatch<any>(openContextMenu(event, {
+ name: '',
+ uuid: resourceUuid,
+ ownerUuid: '',
+ kind: ResourceKind.NONE,
+ menuKind: kind
+ }));
+ }
+ },
+ onDialogOpen: (ownerUuid: string) => { return; },
+ onItemClick: (resourceUuid: string) => {
+ dispatch<any>(loadDetailsPanel(resourceUuid));
+ },
+ onItemDoubleClick: uuid => {
+ dispatch<any>(navigateTo(uuid));
+ }
+});
+
+export const SearchResultsPanel = connect(null, mapDispatchToProps)(SearchResultsPanelView);
\ No newline at end of file
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 { SearchResultsPanel } from '~/views/search-results-panel/search-results-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';
<Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
<Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
<Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
+ <Route path={Routes.SEARCH_RESULTS} component={SearchResultsPanel} />
</Switch>
</Grid>
</Grid>
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,