red900: string;
blue500: string;
grey500: string;
+ grey700: string;
}
const red900 = red["900"];
red900: red['900'],
blue500: blue['500'],
grey500,
+ grey700
}
},
overrides: {
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
grid: {
- display: 'flex'
+ display: 'flex',
+ height: '20px',
+ paddingTop: '0px!important',
+ paddingBottom: '0px!important',
+ marginBottom: theme.spacing.unit
},
label: {
width: '86px',
color: theme.palette.grey["500"],
- textAlign: 'right'
+ textAlign: 'right',
},
value: {
width: '24px',
- paddingLeft: theme.spacing.unit
+ paddingLeft: theme.spacing.unit,
},
switch: {
+ height: '20px',
'& span:first-child': {
height: '18px'
}
import { getProjectUrl } from '~/models/project';
import { getCollectionUrl } from '~/models/collection';
import { loadProject, loadFavorites, loadCollection, loadTrash, loadProcessLog } from '~/store/workbench/workbench-actions';
-import { loadProcess } from '~/store/processes/processes-actions';
+import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
export const Routes = {
ROOT: '/',
} else if (trashMatch) {
store.dispatch(loadTrash());
} else if (processMatch) {
- store.dispatch(loadProcess(processMatch.params.id));
+ store.dispatch(loadProcessPanel(processMatch.params.id));
} else if (processLogMatch) {
store.dispatch(loadProcessLog(processLogMatch.params.id));
}
// SPDX-License-Identifier: AGPL-3.0
import { ProcessLogsPanel } from './process-logs-panel';
-import { RootState } from '~/store/store';
import { ProcessLogsPanelAction, processLogsPanelActions } from './process-logs-panel-actions';
const initialState: ProcessLogsPanel = {
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { unionize, ofType, UnionOf } from "~/common/unionize";
+import { loadProcess } from '~/store/processes/processes-actions';
+import { Dispatch } from 'redux';
+
+export const procesPanelActions = unionize({
+ INIT_PROCESS_PANEL_FILTERS: ofType<string[]>(),
+ TOGGLE_PROCESS_PANEL_FILTER: ofType<string>(),
+});
+
+export type ProcessPanelAction = UnionOf<typeof procesPanelActions>;
+
+export const toggleProcessPanelFilter = procesPanelActions.TOGGLE_PROCESS_PANEL_FILTER;
+
+export const loadProcessPanel = (uuid: string) =>
+ (dispatch: Dispatch) => {
+ dispatch<any>(loadProcess(uuid));
+ dispatch(initProcessPanelFilters);
+ };
+
+export const initProcessPanelFilters = procesPanelActions.INIT_PROCESS_PANEL_FILTERS([
+ 'Queued',
+ 'Complete',
+ 'Active',
+ 'Failed'
+]);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ProcessPanel } from '~/store/process-panel/process-panel';
+import { ProcessPanelAction, procesPanelActions } from '~/store/process-panel/process-panel-actions';
+
+const initialState: ProcessPanel = {
+ filters: {}
+};
+
+export const processPanelReducer = (state = initialState, action: ProcessPanelAction): ProcessPanel =>
+ procesPanelActions.match(action, {
+ INIT_PROCESS_PANEL_FILTERS: statuses => {
+ const filters = statuses.reduce((filters, status) => ({ ...filters, [status]: true }), {});
+ return { filters };
+ },
+ TOGGLE_PROCESS_PANEL_FILTER: status => {
+ const filters = { ...state.filters, [status]: !state.filters[status] };
+ return { filters };
+ },
+ default: () => state,
+ });
+
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface ProcessPanel {
+ filters: { [status: string]: boolean };
+}
import { filterResources } from '../resources/resources';
import { ResourceKind, Resource } from '~/models/resource';
import { getTimeDiff } from '~/common/formatters';
-import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses-card';
import { ArvadosTheme } from '~/common/custom-theme';
+import { groupBy } from 'lodash';
export interface Process {
containerRequest: ContainerRequestResource;
container?: ContainerResource;
}
+enum ProcessStatus {
+ ACTIVE = 'Active',
+ COMPLETED = 'Complete',
+ QUEUED = 'Queued',
+ FAILED = 'Failed',
+ CANCELED = 'Canceled'
+}
+
export const getProcess = (uuid: string) => (resources: ResourcesState): Process | undefined => {
const containerRequest = getResource<ContainerRequestResource>(uuid)(resources);
if (containerRequest) {
export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme) => {
switch (status) {
- case SubprocessesStatus.COMPLETED:
+ case ProcessStatus.COMPLETED:
return customs.colors.green700;
- case SubprocessesStatus.CANCELED:
+ case ProcessStatus.CANCELED:
return customs.colors.red900;
- case SubprocessesStatus.QUEUED:
+ case ProcessStatus.QUEUED:
return customs.colors.grey500;
- case SubprocessesStatus.FAILED:
+ case ProcessStatus.FAILED:
return customs.colors.red900;
- case SubprocessesStatus.ACTIVE:
+ case ProcessStatus.ACTIVE:
return customs.colors.blue500;
default:
return customs.colors.grey500;
const isSubprocess = (containerUuid: string) => (resource: Resource) =>
resource.kind === ResourceKind.CONTAINER_REQUEST
&& (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;
-
import { TrashPanelMiddlewareService } from "~/store/trash-panel/trash-panel-middleware-service";
import { TRASH_PANEL_ID } from "~/store/trash-panel/trash-panel-action";
import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
+import { processPanelReducer } from '~/store/process-panel/process-panel-reducer';
const composeEnhancers =
(process.env.NODE_ENV === 'development' &&
snackbar: snackbarReducer,
treePicker: treePickerReducer,
fileUploader: fileUploaderReducer,
+ processPanel: processPanelReducer
});
import { ArvadosTheme } from '~/common/custom-theme';
import { MoreOptionsIcon, ProcessIcon } from '~/components/icon/icon';
import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
-import { Process, getProcessStatusColor } from '~/store/processes/process';
-import { getProcessStatus } from '~/store/processes/process';
+import { Process } from '~/store/processes/process';
+import { getProcessStatus, getProcessStatusColor } from '../../store/processes/process';
+import { formatDate } from '~/common/formatters';
type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar';
action={
<div>
<Chip label={getProcessStatus(process)}
- className={classes.chip}
- style={{ backgroundColor: getProcessStatusColor(getProcessStatus(process), theme as ArvadosTheme) }}/>
+ className={classes.chip}
+ style={{ backgroundColor: getProcessStatusColor(getProcessStatus(process), theme as ArvadosTheme) }} />
<IconButton
aria-label="More options"
onClick={event => onContextMenu(event)}>
</div>
}
title={
- <Tooltip title={process.containerRequest.name} placement="bottom-start" color='inherit'>
- <Typography noWrap variant="title">
- {process.containerRequest.name}
+ <Tooltip title={process.containerRequest.name} placement="bottom-start">
+ <Typography noWrap variant="title" color='inherit'>
+ {process.containerRequest.name}
</Typography>
</Tooltip>
}
- subheader={process.containerRequest.description} />
+ subheader={
+ <Tooltip title={process.containerRequest.description || '(no-description)'} placement="bottom-start">
+ <Typography noWrap variant="body2" color='inherit'>
+ {process.containerRequest.description || '(no-description)'}
+ </Typography>
+ </Tooltip>} />
<CardContent className={classes.content}>
<Grid container>
<Grid item xs={6}>
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='From' value={process.container ? process.container.startedAt : 'N/A'} />
+ label='From' value={process.container ? formatDate(process.container.startedAt!) : 'N/A'} />
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='To' value={process.container ? process.container.finishedAt : 'N/A'} />
+ label='To' value={process.container ? formatDate(process.container.finishedAt!) : 'N/A'} />
<DetailsAttribute classLabel={classes.label} classValue={classes.link}
label='Workflow' value='???' />
</Grid>
import { Process } from '~/store/processes/process';
import { SubprocessesCard } from './subprocesses-card';
import { ProcessSubprocesses } from '~/views/process-panel/process-subprocesses';
-import { SubprocessesStatus } from '~/views/process-panel/process-subprocesses-card';
-
-type CssRules = 'headerActive' | 'headerCompleted' | 'headerQueued' | 'headerFailed' | 'headerCanceled';
+import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
export interface ProcessPanelRootDataProps {
process?: Process;
subprocesses: Array<Process>;
+ filters: Array<SubprocessFilterDataProps>;
}
export interface ProcessPanelRootActionProps {
onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+ onToggle: (status: string) => void;
}
export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
</Grid>
<Grid item xs={5}>
<SubprocessesCard
- subprocesses={4}
- filters={[
- {
- key: 'queued',
- value: 1,
- label: 'Queued',
- checked: true
- }, {
- key: 'active',
- value: 2,
- label: 'Active',
- checked: true
- },
- {
- key: 'completed',
- value: 2,
- label: 'Completed',
- checked: true
- },
- {
- key: 'failed',
- value: 2,
- label: 'Failed',
- checked: true
- }
- ]}
- onToggle={() => { return; }}
+ subprocesses={props.subprocesses}
+ filters={props.filters}
+ onToggle={props.onToggle}
/>
</Grid>
<Grid item xs={12}>
icon={ProcessIcon}
messages={['Process not found']} />
</Grid>;
+
//
// SPDX-License-Identifier: AGPL-3.0
-import * as React from 'react';
import { RootState } from '~/store/store';
import { connect } from 'react-redux';
-import { getProcess, getSubprocesses } from '~/store/processes/process';
+import { getProcess, getSubprocesses, Process, getProcessStatus } from '~/store/processes/process';
import { Dispatch } from 'redux';
import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions';
import { matchProcessRoute } from '~/routes/routes';
import { ProcessPanelRootDataProps, ProcessPanelRootActionProps, ProcessPanelRoot } from './process-panel-root';
+import { ProcessPanel as ProcessPanelState} from '~/store/process-panel/process-panel';
+import { groupBy } from 'lodash';
+import { toggleProcessPanelFilter } from '~/store/process-panel/process-panel-actions';
-const mapStateToProps = ({ router, resources }: RootState): ProcessPanelRootDataProps => {
+const mapStateToProps = ({ router, resources, processPanel }: RootState): ProcessPanelRootDataProps => {
const pathname = router.location ? router.location.pathname : '';
const match = matchProcessRoute(pathname);
const uuid = match ? match.params.id : '';
+ const subprocesses = getSubprocesses(uuid)(resources);
return {
process: getProcess(uuid)(resources),
- subprocesses: getSubprocesses(uuid)(resources)
+ subprocesses: subprocesses.filter(subprocess => processPanel.filters[getProcessStatus(subprocess)]),
+ filters: getFilters(processPanel, subprocesses)
};
};
const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
- onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
+ onContextMenu: event => {
dispatch<any>(openProcessContextMenu(event));
+ },
+ onToggle: status => {
+ dispatch<any>(toggleProcessPanelFilter(status));
}
});
export const ProcessPanel = connect(mapStateToProps, mapDispatchToProps)(ProcessPanelRoot);
+
+export const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
+ const grouppedProcesses = groupBy(processes, getProcessStatus);
+ return Object
+ .keys(processPanel.filters)
+ .map(filter => ({
+ label: filter,
+ value: (grouppedProcesses[filter] || []).length,
+ checked: processPanel.filters[filter],
+ key: filter,
+ }));
+ };
\ No newline at end of file
},
});
-export enum SubprocessesStatus {
- ACTIVE = 'Active',
- COMPLETED = 'Completed',
- QUEUED = 'Queued',
- FAILED = 'Failed',
- CANCELED = 'Canceled'
-}
-
export interface SubprocessItemProps {
title: string;
status: string;
return <Grid container spacing={16}>
{subprocesses.map(subprocess =>
<Grid item xs={2} key={subprocess.containerRequest.uuid}>
- <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess}/>
+ <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess} />
</Grid>
)}
</Grid>;
import * as React from 'react';
import { ArvadosTheme } from '~/common/custom-theme';
-import { StyleRulesCallback, withStyles, WithStyles, Card, CardHeader, CardContent, Grid, Switch } from '@material-ui/core';
+import { StyleRulesCallback, withStyles, WithStyles, Card, CardHeader, CardContent, Grid, Switch, Typography } from '@material-ui/core';
import { SubprocessFilter } from '~/components/subprocess-filter/subprocess-filter';
import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
+import { Process } from '~/store/processes/process';
-type CssRules = 'root';
+type CssRules = 'root' | 'subtitle' | 'title';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
fontSize: '0.875rem'
+ },
+ subtitle: {
+ paddingBottom: '28px!important'
+ },
+ title: {
+ color: theme.customs.colors.grey700
}
});
interface SubprocessesDataProps {
- subprocesses: number;
+ subprocesses: Array<Process>;
filters: SubprocessFilterDataProps[];
- onToggle: (filter: SubprocessFilterDataProps) => void;
+ onToggle: (status: string) => void;
}
type SubprocessesProps = SubprocessesDataProps & WithStyles<CssRules>;
export const SubprocessesCard = withStyles(styles)(
- ({ classes, filters, subprocesses, onToggle }: SubprocessesProps) =>
+ ({ classes, filters, subprocesses, onToggle }: SubprocessesProps) =>
<Card className={classes.root}>
- <CardHeader title="Subprocess and filters" />
+ <CardHeader
+ className={classes.title}
+ title={
+ <Typography noWrap variant="title" color='inherit'>
+ Subprocess and filters
+ </Typography>} />
<CardContent>
<Grid container direction="column" spacing={16}>
- <Grid item xs={12} container spacing={16}>
- <SubprocessFilter label='Subprocesses' value={subprocesses} />
+ <Grid item xs={12} container spacing={16} className={classes.subtitle}>
+ <SubprocessFilter label='Subprocesses' value={subprocesses.length} />
</Grid>
<Grid item xs={12} container spacing={16}>
{
- filters.map(filter =>
- <SubprocessFilter {...filter} key={filter.key} onToggle={() => onToggle(filter)} />
+ filters.map(filter =>
+ <SubprocessFilter {...filter} key={filter.key} onToggle={() => onToggle(filter.label)} />
)
}
</Grid>