import * as React from 'react';
import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton, Tooltip, Button } from '@material-ui/core';
import { ColumnSelector } from "~/components/column-selector/column-selector";
-import { DataTable, DataColumns } from "~/components/data-table/data-table";
+import { DataTable, DataColumns, DataTableFetchMode } from "~/components/data-table/data-table";
import { DataColumn, SortDirection } from "~/components/data-table/data-column";
import { SearchInput } from '~/components/search-input/search-input';
import { ArvadosTheme } from "~/common/custom-theme";
});
interface DataExplorerDataProps<T> {
+ fetchMode: DataTableFetchMode;
items: T[];
itemsAvailable: number;
columns: DataColumns<T>;
onFiltersChange: (filters: DataTableFilters, column: DataColumn<T>) => void;
onChangePage: (page: number) => void;
onChangeRowsPerPage: (rowsPerPage: number) => void;
+ onLoadMore: (page: number) => void;
extractKey?: (item: T) => React.Key;
}
rowsPerPage, rowsPerPageOptions, onColumnToggle, searchValue, onSearch,
items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
- paperKey
+ paperKey, fetchMode
} = this.props;
return <Paper className={classes.root} {...paperProps} key={paperKey}>
{(!hideColumnSelector || !hideSearchInput) && <Toolbar className={classes.toolbar}>
defaultView={dataTableDefaultView} />
<Toolbar className={classes.footer}>
<Grid container justify="flex-end">
- <TablePagination
+ {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
count={itemsAvailable}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={rowsPerPageOptions}
page={this.props.page}
onChangePage={this.changePage}
onChangeRowsPerPage={this.changeRowsPerPage}
- component="div" />
+ component="div" /> : <Button
+ variant="text"
+ size="medium"
+ onClick={this.loadMore}
+ >Load more</Button>}
</Grid>
</Toolbar>
</Paper>;
this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
}
+ loadMore = () => {
+ this.props.onLoadMore(this.props.page + 1);
+ }
+
renderContextMenuTrigger = (item: T) =>
<Grid container justify="center">
<Tooltip title="More options" disableFocusListener>
export type DataColumns<T> = Array<DataColumn<T>>;
+export enum DataTableFetchMode {
+ PAGINATED,
+ INFINITE
+}
+
export interface DataTableDataProps<T> {
items: T[];
columns: DataColumns<T>;
export type SearchBarAdvanceFormData = {
type?: ResourceKind;
- cluster?: ClusterObjectType;
+ cluster?: string;
projectUuid?: string;
inTrash: boolean;
dateFrom: string;
key: string;
value: string;
}
-
-export enum ClusterObjectType {
- INDIANAPOLIS = "indianapolis",
- KAISERAUGST = "kaiseraugst",
- PENZBERG = "penzberg"
-}
//
// SPDX-License-Identifier: AGPL-3.0
-import * as _ from "lodash";
import { AxiosInstance } from "axios";
import { Resource } from "src/models/resource";
import { ApiActions } from "~/services/api/api-actions";
}
export class CommonResourceService<T extends Resource> extends CommonService<T> {
-
- constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
+ constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
super(serverApi, resourceType, actions);
}
-
}
export const getCommonResourceServiceError = (errorResponse: any) => {
}
export interface ListResults<T> {
+ clusterId?: string;
kind: string;
offset: number;
limit: number;
this.actions
);
}
-}
\ No newline at end of file
+}
import * as _ from "lodash";
import { CommonResourceService } from '~/services/common-service/common-resource-service';
import { ListResults, ListArguments } from '~/services/common-service/common-service';
-import { AxiosInstance } from "axios";
+import { AxiosInstance, AxiosRequestConfig } from "axios";
import { CollectionResource } from "~/models/collection";
import { ProjectResource } from "~/models/project";
import { ProcessResource } from "~/models/process";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
import { ApiActions } from "~/services/api/api-actions";
import { GroupResource } from "~/models/group";
+import { Session } from "~/models/session";
export interface ContentsArguments {
limit?: number;
super(serverApi, "groups", actions);
}
- async contents(uuid: string, args: ContentsArguments = {}): Promise<ListResults<GroupContentsResource>> {
+ async contents(uuid: string, args: ContentsArguments = {}, session?: Session): Promise<ListResults<GroupContentsResource>> {
const { filters, order, ...other } = args;
const params = {
...other,
};
const pathUrl = uuid ? `${uuid}/contents` : 'contents';
+
+ const cfg: AxiosRequestConfig = { params: CommonResourceService.mapKeys(_.snakeCase)(params) };
+ if (session) {
+ cfg.baseURL = session.baseUrl;
+ }
+
const response = await CommonResourceService.defaultResponse(
- this.serverApi
- .get(this.resourceType + pathUrl, {
- params: CommonResourceService.mapKeys(_.snakeCase)(params)
- }),
- this.actions,
- false
- );
+ this.serverApi.get(this.resourceType + pathUrl, cfg), this.actions, false
+ );
const { items, ...res } = response;
- const mappedItems = items.map((item: GroupContentsResource) => {
+ const mappedItems = (items || []).map((item: GroupContentsResource) => {
const mappedItem = TrashableResourceService.mapKeys(_.camelCase)(item);
if (item.kind === ResourceKind.COLLECTION || item.kind === ResourceKind.PROJECT) {
const { properties } = item;
}
});
const mappedResponse = { ...TrashableResourceService.mapKeys(_.camelCase)(res) };
- return { ...mappedResponse, items: mappedItems };
+ return { ...mappedResponse, items: mappedItems, clusterId: session && session.clusterId };
}
shared(params: SharedArguments = {}): Promise<ListResults<GroupContentsResource>> {
return Promise.resolve(uuid);
}
- const resp = await Axios.get(`${baseUrl}/api_client_authorizations`, {
+ const resp = await Axios.get(`${baseUrl}api_client_authorizations`, {
headers: {
Authorization: `OAuth2 ${token}`
},
};
};
-const getActiveSession = (sessions: Session[]): Session | undefined => sessions.find(s => s.active);
+export const getActiveSession = (sessions: Session[]): Session | undefined => sessions.find(s => s.active);
export const validateCluster = async (remoteHost: string, clusterId: string, activeSession: Session): Promise<{ user: User; token: string, baseUrl: string }> => {
const baseUrl = await getRemoteHostBaseUrl(remoteHost);
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "~/common/unionize";
-import { DataColumns } from "~/components/data-table/data-table";
+import { DataColumns, DataTableFetchMode } from "~/components/data-table/data-table";
import { DataTableFilters } from '~/components/data-table-filters/data-table-filters-tree';
export const dataExplorerActions = unionize({
+ CLEAR: ofType<{ id: string }>(),
RESET_PAGINATION: ofType<{ id: string }>(),
- REQUEST_ITEMS: ofType<{ id: string }>(),
+ REQUEST_ITEMS: ofType<{ id: string, criteriaChanged?: boolean }>(),
+ SET_FETCH_MODE: ofType<({ id: string, fetchMode: DataTableFetchMode })>(),
SET_COLUMNS: ofType<{ id: string, columns: DataColumns<any> }>(),
SET_FILTERS: ofType<{ id: string, columnName: string, filters: DataTableFilters }>(),
SET_ITEMS: ofType<{ id: string, items: any[], page: number, rowsPerPage: number, itemsAvailable: number }>(),
+ APPEND_ITEMS: ofType<{ id: string, items: any[], page: number, rowsPerPage: number, itemsAvailable: number }>(),
SET_PAGE: ofType<{ id: string, page: number }>(),
SET_ROWS_PER_PAGE: ofType<{ id: string, rowsPerPage: number }>(),
TOGGLE_COLUMN: ofType<{ id: string, columnName: string }>(),
export type DataExplorerAction = UnionOf<typeof dataExplorerActions>;
export const bindDataExplorerActions = (id: string) => ({
+ CLEAR: () =>
+ dataExplorerActions.CLEAR({ id }),
RESET_PAGINATION: () =>
dataExplorerActions.RESET_PAGINATION({ id }),
- REQUEST_ITEMS: () =>
- dataExplorerActions.REQUEST_ITEMS({ id }),
+ REQUEST_ITEMS: (criteriaChanged?: boolean) =>
+ dataExplorerActions.REQUEST_ITEMS({ id, criteriaChanged }),
+ SET_FETCH_MODE: (payload: { fetchMode: DataTableFetchMode }) =>
+ dataExplorerActions.SET_FETCH_MODE({ ...payload, id }),
SET_COLUMNS: (payload: { columns: DataColumns<any> }) =>
dataExplorerActions.SET_COLUMNS({ ...payload, id }),
SET_FILTERS: (payload: { columnName: string, filters: DataTableFilters }) =>
dataExplorerActions.SET_FILTERS({ ...payload, id }),
SET_ITEMS: (payload: { items: any[], page: number, rowsPerPage: number, itemsAvailable: number }) =>
dataExplorerActions.SET_ITEMS({ ...payload, id }),
+ APPEND_ITEMS: (payload: { items: any[], page: number, rowsPerPage: number, itemsAvailable: number }) =>
+ dataExplorerActions.APPEND_ITEMS({ ...payload, id }),
SET_PAGE: (payload: { page: number }) =>
dataExplorerActions.SET_PAGE({ ...payload, id }),
SET_ROWS_PER_PAGE: (payload: { rowsPerPage: number }) =>
return getDataExplorerColumnFilters(columns, columnName);
}
- abstract requestItems(api: MiddlewareAPI<Dispatch, RootState>): void;
+ abstract requestItems(api: MiddlewareAPI<Dispatch, RootState>, criteriaChanged?: boolean): void;
}
export const getDataExplorerColumnFilters = <T>(columns: DataColumns<T>, columnName: string): DataTableFilters => {
};
dataExplorerActions.match(action, {
SET_PAGE: handleAction(() => {
- api.dispatch(actions.REQUEST_ITEMS());
+ api.dispatch(actions.REQUEST_ITEMS(false));
}),
SET_ROWS_PER_PAGE: handleAction(() => {
- api.dispatch(actions.REQUEST_ITEMS());
+ api.dispatch(actions.REQUEST_ITEMS(true));
}),
SET_FILTERS: handleAction(() => {
api.dispatch(actions.RESET_PAGINATION());
- api.dispatch(actions.REQUEST_ITEMS());
+ api.dispatch(actions.REQUEST_ITEMS(true));
}),
TOGGLE_SORT: handleAction(() => {
- api.dispatch(actions.REQUEST_ITEMS());
+ api.dispatch(actions.REQUEST_ITEMS(true));
}),
SET_EXPLORER_SEARCH_VALUE: handleAction(() => {
api.dispatch(actions.RESET_PAGINATION());
- api.dispatch(actions.REQUEST_ITEMS());
+ api.dispatch(actions.REQUEST_ITEMS(true));
}),
- REQUEST_ITEMS: handleAction(() => {
- service.requestItems(api);
+ REQUEST_ITEMS: handleAction(({ criteriaChanged }) => {
+ service.requestItems(api, criteriaChanged);
}),
default: () => next(action)
});
//
// SPDX-License-Identifier: AGPL-3.0
-import { DataColumn, toggleSortDirection, resetSortDirection, SortDirection } from "~/components/data-table/data-column";
-import { dataExplorerActions, DataExplorerAction } from "./data-explorer-action";
-import { DataColumns } from "~/components/data-table/data-table";
+import {
+ DataColumn,
+ resetSortDirection,
+ SortDirection,
+ toggleSortDirection
+} from "~/components/data-table/data-column";
+import { DataExplorerAction, dataExplorerActions } from "./data-explorer-action";
+import { DataColumns, DataTableFetchMode } from "~/components/data-table/data-table";
import { DataTableFilters } from "~/components/data-table-filters/data-table-filters-tree";
export interface DataExplorer {
+ fetchMode: DataTableFetchMode;
columns: DataColumns<any>;
items: any[];
itemsAvailable: number;
}
export const initialDataExplorer: DataExplorer = {
+ fetchMode: DataTableFetchMode.PAGINATED,
columns: [],
items: [],
itemsAvailable: 0,
export const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
dataExplorerActions.match(action, {
+ CLEAR: ({ id }) =>
+ update(state, id, explorer => ({ ...explorer, page: 0, itemsAvailable: 0, items: [] })),
+
RESET_PAGINATION: ({ id }) =>
update(state, id, explorer => ({ ...explorer, page: 0 })),
+ SET_FETCH_MODE: ({ id, fetchMode }) =>
+ update(state, id, explorer => ({ ...explorer, fetchMode })),
+
SET_COLUMNS: ({ id, columns }) =>
update(state, id, setColumns(columns)),
SET_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
update(state, id, explorer => ({ ...explorer, items, itemsAvailable, page, rowsPerPage })),
+ APPEND_ITEMS: ({ id, items, itemsAvailable, page, rowsPerPage }) =>
+ update(state, id, explorer => ({
+ ...explorer,
+ items: state[id].items.concat(items),
+ itemsAvailable: state[id].itemsAvailable + itemsAvailable,
+ page,
+ rowsPerPage
+ })),
+
SET_PAGE: ({ id, page }) =>
update(state, id, explorer => ({ ...explorer, page })),
initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
);
+export const getTrashPanelTypeFilters = pipe(
+ (): DataTableFilters => createTree<DataTableFilterItem>(),
+ initFilter(ObjectTypeFilter.PROJECT),
+ initFilter(ObjectTypeFilter.COLLECTION),
+ initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
+ initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
+ initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
+);
const createFiltersBuilder = (filters: DataTableFilters) =>
({ fb: new FilterBuilder(), selectedFilters: getSelectedNodes(filters) });
import { getAdvancedDataFromQuery, getQueryFromAdvancedData, parseSearchQuery } from "~/store/search-bar/search-bar-actions";
import { ResourceKind } from "~/models/resource";
-import { ClusterObjectType } from "~/models/search-bar";
describe('search-bar-actions', () => {
describe('parseSearchQuery', () => {
});
it('should correctly build advanced data record from query #2', () => {
- const r = getAdvancedDataFromQuery('document from:2017-08-01 pdf has:filesize:101mb is:trashed type:arvados#collection cluster:indianapolis');
+ const r = getAdvancedDataFromQuery('document from:2017-08-01 pdf has:filesize:101mb is:trashed type:arvados#collection cluster:c97qx');
expect(r).toEqual({
searchValue: 'document pdf',
type: ResourceKind.COLLECTION,
- cluster: ClusterObjectType.INDIANAPOLIS,
+ cluster: 'c97qx',
projectUuid: undefined,
inTrash: true,
dateFrom: '2017-08-01',
const q = getQueryFromAdvancedData({
searchValue: 'document pdf',
type: ResourceKind.COLLECTION,
- cluster: ClusterObjectType.INDIANAPOLIS,
+ cluster: 'c97qx',
projectUuid: undefined,
inTrash: true,
dateFrom: '2017-08-01',
saveQuery: false,
queryName: ''
});
- expect(q).toBe('document pdf type:arvados#collection cluster:indianapolis is:trashed from:2017-08-01 has:filesize:101mb');
+ expect(q).toBe('document pdf type:arvados#collection cluster:c97qx is:trashed from:2017-08-01 has:filesize:101mb');
});
});
});
import { SearchView } from '~/store/search-bar/search-bar-reducer';
import { navigateTo, navigateToSearchResults } from '~/store/navigation/navigation-action';
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
-import { ClusterObjectType, PropertyValue, SearchBarAdvanceFormData } from '~/models/search-bar';
+import { PropertyValue, SearchBarAdvanceFormData } from '~/models/search-bar';
import { debounce } from 'debounce';
import * as _ from "lodash";
import { getModifiedKeysValues } from "~/common/objects";
import { activateSearchBarProject } from "~/store/search-bar/search-bar-tree-actions";
+import { Session } from "~/models/session";
+import { searchResultsPanelActions } from "~/store/search-results-panel/search-results-panel-actions";
+import { ListResults } from "~/services/common-service/common-service";
export const searchBarActions = unionize({
SET_CURRENT_VIEW: ofType<string>(),
dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
+ dispatch(searchResultsPanelActions.CLEAR());
dispatch(navigateToSearchResults);
};
const currentView = getState().searchBar.currentView;
if (searchValue || currentView === SearchView.ADVANCED) {
- const filters = getFilters('name', searchValue);
- const { items } = await services.groupsService.contents('', {
- filters,
- limit,
- recursive: true
- });
+ const sq = parseSearchQuery(searchValue);
+ const clusterId = getSearchQueryFirstProp(sq, 'cluster');
+ const sessions = getSearchSessions(clusterId, getState().auth.sessions);
+ const lists: ListResults<GroupContentsResource>[] = await Promise.all(sessions.map(session => {
+ const filters = getFilters('name', searchValue, sq);
+ return services.groupsService.contents('', {
+ filters,
+ limit,
+ recursive: true
+ }, session);
+ }));
+
+ const items = lists.reduce((items, list) => items.concat(list.items), [] as GroupContentsResource[]);
dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
}
};
return value;
};
-export interface ParseSearchQuery {
+export class ParseSearchQuery {
hasKeywords: boolean;
values: string[];
properties: {
return { hasKeywords: keywordsCnt > 0, values, properties };
};
-const getFirstProp = (sq: ParseSearchQuery, name: string) => sq.properties[name] && sq.properties[name][0];
-const getPropValue = (sq: ParseSearchQuery, name: string, value: string) => sq.properties[name] && sq.properties[name].find((v: string) => v === value);
-const getProperties = (sq: ParseSearchQuery): PropertyValue[] => {
+export const getSearchQueryFirstProp = (sq: ParseSearchQuery, name: string) => sq.properties[name] && sq.properties[name][0];
+export const getSearchQueryPropValue = (sq: ParseSearchQuery, name: string, value: string) => sq.properties[name] && sq.properties[name].find((v: string) => v === value);
+export const getSearchQueryProperties = (sq: ParseSearchQuery): PropertyValue[] => {
if (sq.properties.has) {
return sq.properties.has.map((value: string) => {
const v = value.split(':');
return {
searchValue: sq.values.join(' '),
- type: getFirstProp(sq, 'type') as ResourceKind,
- cluster: getFirstProp(sq, 'cluster') as ClusterObjectType,
- projectUuid: getFirstProp(sq, 'project'),
- inTrash: getPropValue(sq, 'is', 'trashed') !== undefined,
- dateFrom: getFirstProp(sq, 'from'),
- dateTo: getFirstProp(sq, 'to'),
- properties: getProperties(sq),
+ type: getSearchQueryFirstProp(sq, 'type') as ResourceKind,
+ cluster: getSearchQueryFirstProp(sq, 'cluster'),
+ projectUuid: getSearchQueryFirstProp(sq, 'project'),
+ inTrash: getSearchQueryPropValue(sq, 'is', 'trashed') !== undefined,
+ dateFrom: getSearchQueryFirstProp(sq, 'from'),
+ dateTo: getSearchQueryFirstProp(sq, 'to'),
+ properties: getSearchQueryProperties(sq),
saveQuery: false,
queryName: ''
};
};
-export const getFilters = (filterName: string, searchValue: string): string => {
+export const getSearchSessions = (clusterId: string | undefined, sessions: Session[]): Session[] => {
+ return sessions.filter(s => s.loggedIn && (!clusterId || s.clusterId === clusterId));
+};
+
+export const getFilters = (filterName: string, searchValue: string, sq: ParseSearchQuery): string => {
const filter = new FilterBuilder();
- const sq = parseSearchQuery(searchValue);
- const resourceKind = getFirstProp(sq, 'type') as ResourceKind;
+ const resourceKind = getSearchQueryFirstProp(sq, 'type') as ResourceKind;
let prefix = '';
switch (resourceKind) {
break;
}
+ const isTrashed = getSearchQueryPropValue(sq, 'is', 'trashed');
+
if (!sq.hasKeywords) {
filter
.addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
- .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
.addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT);
+
+ if (isTrashed) {
+ filter.addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS);
+ }
} else {
if (prefix) {
sq.values.forEach(v =>
sq.values.forEach(v => {
filter
.addILike(filterName, v, GroupContentsResourcePrefix.COLLECTION)
- .addILike(filterName, v, GroupContentsResourcePrefix.PROCESS)
.addILike(filterName, v, GroupContentsResourcePrefix.PROJECT);
+
+ if (isTrashed) {
+ filter.addILike(filterName, v, GroupContentsResourcePrefix.PROCESS);
+ }
});
}
- if (getPropValue(sq, 'is', 'trashed')) {
+ if (isTrashed) {
filter.addEqual("is_trashed", true);
}
- const projectUuid = getFirstProp(sq, 'project');
+ const projectUuid = getSearchQueryFirstProp(sq, 'project');
if (projectUuid) {
filter.addEqual('uuid', projectUuid, prefix);
}
- const dateFrom = getFirstProp(sq, 'from');
+ const dateFrom = getSearchQueryFirstProp(sq, 'from');
if (dateFrom) {
filter.addGte('modified_at', buildDateFilter(dateFrom));
}
- const dateTo = getFirstProp(sq, 'to');
+ const dateTo = getSearchQueryFirstProp(sq, 'to');
if (dateTo) {
filter.addLte('modified_at', buildDateFilter(dateTo));
}
- const props = getProperties(sq);
+ const props = getSearchQueryProperties(sq);
props.forEach(p => {
- // filter.addILike(`properties.${p.key}`, p.value);
+ if (p.value) {
+ filter.addILike(`properties.${p.key}`, p.value);
+ }
filter.addExists(p.key);
});
}
import { GroupContentsResource, GroupContentsResourcePrefix } from "~/services/groups-service/groups-service";
import { ListResults } from '~/services/common-service/common-service';
import { searchResultsPanelActions } from '~/store/search-results-panel/search-results-panel-actions';
-import { getFilters } from '~/store/search-bar/search-bar-actions';
+import {
+ getFilters,
+ getSearchQueryFirstProp,
+ getSearchSessions, ParseSearchQuery,
+ parseSearchQuery
+} from '~/store/search-bar/search-bar-actions';
import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
export class SearchResultsMiddlewareService extends DataExplorerMiddlewareService {
super(id);
}
- async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ async requestItems(api: MiddlewareAPI<Dispatch, RootState>, criteriaChanged?: boolean) {
const state = api.getState();
const userUuid = state.auth.user!.uuid;
const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
const searchValue = state.searchBar.searchValue;
+ const sq = parseSearchQuery(searchValue);
+ const clusterId = getSearchQueryFirstProp(sq, 'cluster');
+ const sessions = getSearchSessions(clusterId, state.auth.sessions);
+
+ if (searchValue.trim() === '') {
+ return;
+ }
+
try {
- const response = await this.services.groupsService.contents('', getParams(dataExplorer, searchValue));
- api.dispatch(updateResources(response.items));
- api.dispatch(setItems(response));
+ const params = getParams(dataExplorer, searchValue, sq);
+ const lists: ListResults<GroupContentsResource>[] = await Promise.all(sessions.map(session =>
+ this.services.groupsService.contents('', params, session)
+ ));
+
+ const items = lists
+ .reduce((items, list) => items.concat(list.items), [] as GroupContentsResource[]);
+
+ const itemsAvailable = lists
+ .reduce((itemsAvailable, list) => itemsAvailable + list.itemsAvailable, 0);
+
+ const list: ListResults<GroupContentsResource> = {
+ ...params,
+ kind: '',
+ items,
+ itemsAvailable
+ };
+
+ api.dispatch(updateResources(list.items));
+ api.dispatch(criteriaChanged
+ ? setItems(list)
+ : appendItems(list)
+ );
+
} catch {
- api.dispatch(couldNotFetchWorkflows());
+ api.dispatch(couldNotFetchSearchResults());
}
}
}
-export const getParams = (dataExplorer: DataExplorer, searchValue: string) => ({
+export const getParams = (dataExplorer: DataExplorer, searchValue: string, sq: ParseSearchQuery) => ({
...dataExplorerToListParams(dataExplorer),
- filters: getFilters('name', searchValue),
+ filters: getFilters('name', searchValue, sq),
order: getOrder(dataExplorer)
});
items: listResults.items.map(resource => resource.uuid),
});
-const couldNotFetchWorkflows = () =>
+export const appendItems = (listResults: ListResults<GroupContentsResource>) =>
+ searchResultsPanelActions.APPEND_ITEMS({
+ ...listResultsToDataExplorerItemsMeta(listResults),
+ items: listResults.items.map(resource => resource.uuid),
+ });
+
+const couldNotFetchSearchResults = () =>
snackbarActions.OPEN_SNACKBAR({
- message: 'Could not fetch workflows.',
+ message: `Could not fetch search results for some sessions.`,
kind: SnackbarKind.ERROR
});
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
- window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
+ window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({trace: true, traceLimit: 25})) ||
compose;
export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;
const otherFilters = new FilterBuilder()
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.COLLECTION)
- .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
+ // .addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROCESS)
.addILike("name", dataExplorer.searchValue, GroupContentsResourcePrefix.PROJECT)
.addEqual("is_trashed", true)
.getFilters();
import { Dispatch } from 'redux';
import { RootState } from "~/store/store";
import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
-import { snackbarActions } from '~/store/snackbar/snackbar-actions';
-import { loadFavoritePanel } from '~/store/favorite-panel/favorite-panel-action';
-import { openProjectPanel, projectPanelActions, setIsProjectPanelTrashed } from '~/store/project-panel/project-panel-action';
-import { activateSidePanelTreeItem, initSidePanelTree, SidePanelTreeCategory, loadSidePanelTreeProjects } from '~/store/side-panel-tree/side-panel-tree-actions';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+import { favoritePanelActions, loadFavoritePanel } from '~/store/favorite-panel/favorite-panel-action';
+import {
+ getProjectPanelCurrentUuid,
+ openProjectPanel,
+ projectPanelActions,
+ setIsProjectPanelTrashed
+} from '~/store/project-panel/project-panel-action';
+import {
+ activateSidePanelTreeItem,
+ initSidePanelTree,
+ loadSidePanelTreeProjects,
+ SidePanelTreeCategory
+} from '~/store/side-panel-tree/side-panel-tree-actions';
import { loadResource, updateResources } from '~/store/resources/resources-actions';
-import { favoritePanelActions } from '~/store/favorite-panel/favorite-panel-action';
import { projectPanelColumns } from '~/views/project-panel/project-panel';
import { favoritePanelColumns } from '~/views/favorite-panel/favorite-panel';
import { matchRootRoute } from '~/routes/routes';
-import { setSidePanelBreadcrumbs, setProcessBreadcrumbs, setSharedWithMeBreadcrumbs, setTrashBreadcrumbs, setBreadcrumbs, setGroupDetailsBreadcrumbs, setGroupsBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions';
+import {
+ setBreadcrumbs,
+ setGroupDetailsBreadcrumbs,
+ setGroupsBreadcrumbs,
+ setProcessBreadcrumbs,
+ setSharedWithMeBreadcrumbs,
+ setSidePanelBreadcrumbs,
+ setTrashBreadcrumbs
+} from '~/store/breadcrumbs/breadcrumbs-actions';
import { navigateToProject } from '~/store/navigation/navigation-action';
import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
import { ServiceRepository } from '~/services/services';
import { getResource } from '~/store/resources/resources';
-import { getProjectPanelCurrentUuid } from '~/store/project-panel/project-panel-action';
import * as projectCreateActions from '~/store/projects/project-create-actions';
import * as projectMoveActions from '~/store/projects/project-move-actions';
import * as projectUpdateActions from '~/store/projects/project-update-actions';
import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action";
import { initProcessLogsPanel } from '~/store/process-logs-panel/process-logs-panel-actions';
import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
-import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
-import { loadSharedWithMePanel } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
+import {
+ loadSharedWithMePanel,
+ sharedWithMePanelActions
+} from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
import { loadWorkflowPanel, workflowPanelActions } from '~/store/workflow-panel/workflow-panel-actions';
import { loadSshKeysPanel } from '~/store/auth/auth-action-ssh';
import { workflowPanelColumns } from '~/views/workflow-panel/workflow-panel-view';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
import { getProgressIndicator } from '~/store/progress-indicator/progress-indicator-reducer';
-import { ResourceKind, extractUuidKind } from '~/models/resource';
+import { extractUuidKind, ResourceKind } from '~/models/resource';
import { FilterBuilder } from '~/services/api/filter-builder';
import { GroupContentsResource } from '~/services/groups-service/groups-service';
-import { unionize, ofType, UnionOf, MatchCases } from '~/common/unionize';
+import { MatchCases, ofType, unionize, UnionOf } from '~/common/unionize';
import { loadRunProcessPanel } from '~/store/run-process-panel/run-process-panel-actions';
import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
-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 {
+ loadSearchResultsPanel,
+ searchResultsPanelActions
+} from '~/store/search-results-panel/search-results-panel-actions';
import { searchResultsPanelColumns } from '~/views/search-results-panel/search-results-panel-view';
import { loadVirtualMachinesPanel } from '~/store/virtual-machines/virtual-machines-actions';
import { loadRepositoriesPanel } from '~/store/repositories/repositories-actions';
import { loadKeepServicesPanel } from '~/store/keep-services/keep-services-actions';
import { loadUsersPanel, userBindedActions } from '~/store/users/users-actions';
-import { loadLinkPanel, linkPanelActions } from '~/store/link-panel/link-panel-actions';
-import { loadComputeNodesPanel, computeNodesActions } from '~/store/compute-nodes/compute-nodes-actions';
+import { linkPanelActions, loadLinkPanel } from '~/store/link-panel/link-panel-actions';
+import { computeNodesActions, loadComputeNodesPanel } from '~/store/compute-nodes/compute-nodes-actions';
import { linkPanelColumns } from '~/views/link-panel/link-panel-root';
import { userPanelColumns } from '~/views/user-panel/user-panel';
import { computeNodePanelColumns } from '~/views/compute-node-panel/compute-node-panel-root';
import { groupsPanelColumns } from '~/views/groups-panel/groups-panel';
import * as groupDetailsPanelActions from '~/store/group-details-panel/group-details-panel-actions';
import { groupDetailsPanelColumns } from '~/views/group-details-panel/group-details-panel';
+import { DataTableFetchMode } from "~/components/data-table/data-table";
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_FETCH_MODE({ fetchMode: DataTableFetchMode.INFINITE }));
dispatch(searchResultsPanelActions.SET_COLUMNS({ columns: searchResultsPanelColumns }));
dispatch(userBindedActions.SET_COLUMNS({ columns: userPanelColumns }));
dispatch(groupPanelActions.GroupsPanelActions.SET_COLUMNS({ columns: groupsPanelColumns }));
dispatch(dataExplorerActions.SET_ROWS_PER_PAGE({ id, rowsPerPage }));
},
+ onLoadMore: (page: number) => {
+ dispatch(dataExplorerActions.SET_PAGE({ id, page }));
+ },
+
onRowClick,
onRowDoubleClick,
</Grid>
</Grid>;
-export const RosurceWorkflowName = connect(
+export const ResourceWorkflowName = connect(
(state: RootState, props: { uuid: string }) => {
const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
return resource || { name: '', uuid: '', kind: '', ownerUuid: '' };
return <Typography>{JSON.stringify(data, null, 4)}</Typography>;
};
+const clusterColors = [
+ ['#f44336', '#fff'],
+ ['#2196f3', '#fff'],
+ ['#009688', '#fff'],
+ ['#cddc39', '#fff'],
+ ['#ff9800', '#fff']
+];
+
+export const ResourceCluster = (props: { uuid: string }) => {
+ const CLUSTER_ID_LENGTH = 5;
+ const pos = props.uuid.indexOf('-');
+ const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substr(0, pos) : '';
+ const ci = pos >= CLUSTER_ID_LENGTH ? (props.uuid.charCodeAt(0) + props.uuid.charCodeAt(1)) % clusterColors.length : 0;
+ return <Typography>
+ <div style={{
+ backgroundColor: clusterColors[ci][0],
+ color: clusterColors[ci][1],
+ padding: "2px 7px",
+ borderRadius: 3
+ }}>{clusterId}</div>
+ </Typography>;
+};
+
export const ComputeNodeInfo = withResourceData('info', renderNodeInfo);
export const ComputeNodeDomain = withResourceData('domain', renderCommonData);
import { CheckboxField } from '~/components/checkbox-field/checkbox-field';
import { NativeSelectField } from '~/components/select-field/select-field';
import { ResourceKind } from '~/models/resource';
-import { ClusterObjectType } from '~/models/search-bar';
import { HomeTreePicker } from '~/views-components/projects-tree-picker/home-tree-picker';
import { SEARCH_BAR_ADVANCE_FORM_PICKER_ID } from '~/store/search-bar/search-bar-actions';
import { SearchBarAdvancedPropertiesView } from '~/views-components/search-bar/search-bar-advanced-properties-view';
import { PropertyValueInput, PropertyValueFieldProps } from '~/views-components/resource-properties-form/property-value-field';
import { VocabularyProp, connectVocabulary } from '~/views-components/resource-properties-form/property-field-common';
import { compose } from 'redux';
+import { connect } from "react-redux";
+import { RootState } from "~/store/store";
export const SearchBarTypeField = () =>
<Field
{ key: ResourceKind.PROCESS, value: 'Process' }
]} />;
-export const SearchBarClusterField = () =>
- <Field
+
+interface SearchBarClusterFieldProps {
+ clusters: { key: string, value: string }[];
+}
+
+export const SearchBarClusterField = connect(
+ (state: RootState) => ({
+ clusters: [{key: '', value: 'Any'}].concat(
+ state.auth.sessions
+ .filter(s => s.loggedIn)
+ .map(s => ({
+ key: s.clusterId,
+ value: s.clusterId
+ })))
+ }))((props: SearchBarClusterFieldProps) => <Field
name='cluster'
component={NativeSelectField}
- items={[
- { key: '', value: 'Any' },
- { key: ClusterObjectType.INDIANAPOLIS, value: 'Indianapolis' },
- { key: ClusterObjectType.KAISERAUGST, value: 'Kaiseraugst' },
- { key: ClusterObjectType.PENZBERG, value: 'Penzberg' }
- ]} />;
+ items={props.clusters}/>
+ );
export const SearchBarProjectField = () =>
<Field
import { DataColumns } from '~/components/data-table/data-table';
import { SortDirection } from '~/components/data-table/data-column';
import { createTree } from '~/models/tree';
-import {
- CommonUuid, ComputeNodeInfo, ComputeNodeDomain, ComputeNodeHostname, ComputeNodeJobUuid,
- ComputeNodeFirstPingAt, ComputeNodeLastPingAt, ComputeNodeIpAddress
+import {
+ ComputeNodeInfo, ComputeNodeDomain, ComputeNodeHostname, ComputeNodeJobUuid,
+ ComputeNodeFirstPingAt, ComputeNodeLastPingAt, ComputeNodeIpAddress, CommonUuid
} from '~/views-components/data-explorer/renderers';
import { ResourcesState } from '~/store/resources/resources';
icon={ShareMeIcon}
messages={[DEFAULT_MESSAGE]} />
} />;
-};
\ No newline at end of file
+};
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,
+ ProcessStatus, ResourceCluster,
ResourceFileSize,
ResourceLastModifiedDate,
ResourceName,
import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
export enum SearchResultsPanelColumnNames {
+ CLUSTER = "Cluster",
NAME = "Name",
PROJECT = "Project",
STATUS = "Status",
}
export const searchResultsPanelColumns: DataColumns<string> = [
+ {
+ name: SearchResultsPanelColumnNames.CLUSTER,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: (uuid: string) => <ResourceCluster uuid={uuid} />
+ },
{
name: SearchResultsPanelColumnNames.NAME,
selected: true,
onRowDoubleClick={props.onItemDoubleClick}
onContextMenu={props.onContextMenu}
contextMenuColumn={true} />;
-};
\ No newline at end of file
+};
import { Dispatch } from "redux";
import { DataTableDefaultView } from '~/components/data-table-default-view/data-table-default-view';
import { createTree } from '~/models/tree';
-import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
+import {
+ getInitialResourceTypeFilters,
+ getTrashPanelTypeFilters
+} from '~/store/resource-type-filters/resource-type-filters';
type CssRules = "toolbar" | "button";
selected: true,
configurable: true,
sortDirection: SortDirection.NONE,
- filters: getInitialResourceTypeFilters(),
+ filters: getTrashPanelTypeFilters(),
render: uuid => <ResourceType uuid={uuid} />,
},
{
import { WORKFLOW_PANEL_ID } from '~/store/workflow-panel/workflow-panel-actions';
import {
ResourceLastModifiedDate,
- RosurceWorkflowName,
+ ResourceWorkflowName,
ResourceWorkflowStatus,
ResourceShare,
ResourceRunProcess
configurable: true,
sortDirection: SortDirection.ASC,
filters: createTree(),
- render: (uuid: string) => <RosurceWorkflowName uuid={uuid} />
+ render: (uuid: string) => <ResourceWorkflowName uuid={uuid} />
},
{
name: WorkflowPanelColumnNames.AUTHORISATION,