red900: string;
blue500: string;
grey500: string;
+ grey700: string;
}
const red900 = red["900"];
red900: red['900'],
blue500: blue['500'],
grey500,
+ grey700
}
},
overrides: {
import * as React from 'react';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { ArvadosTheme } from '~/common/custom-theme';
-import { Grid, Typography, Switch } from '@material-ui/core';
+import { Typography, Switch } from '@material-ui/core';
-type CssRules = 'grid' | 'label' | 'value' | 'switch';
+type CssRules = 'container' | 'label' | 'value';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
- grid: {
- display: 'flex'
+ container: {
+ display: 'flex',
+ alignItems: 'center',
+ height: '20px'
},
label: {
width: '86px',
color: theme.palette.grey["500"],
- textAlign: 'right'
+ textAlign: 'right',
},
value: {
width: '24px',
- paddingLeft: theme.spacing.unit
- },
- switch: {
- '& span:first-child': {
- height: '18px'
- }
+ paddingLeft: theme.spacing.unit,
}
});
export const SubprocessFilter = withStyles(styles)(
({ classes, label, value, key, checked, onToggle }: SubprocessFilterProps) =>
- <Grid item className={classes.grid} md={12} lg={6} >
+ <div className={classes.container} >
<Typography component="span" className={classes.label}>{label}:</Typography>
<Typography component="span" className={classes.value}>{value}</Typography>
- {onToggle && <Switch classes={{ root: classes.switch }}
+ {onToggle && <Switch
checked={checked}
onChange={onToggle}
value={key}
color="primary" />
}
- </Grid>
+ </div>
);
\ No newline at end of file
// 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';
+import { ProcessStatus } from '~/store/processes/process';
+
+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([
+ ProcessStatus.QUEUED,
+ ProcessStatus.COMPLETED,
+ ProcessStatus.FAILED,
+ ProcessStatus.RUNNING,
+ ProcessStatus.LOCKED,
+ ProcessStatus.CANCELLED
+]);
--- /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 { 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 { trashPanelColumns } from "~/views/trash-panel/trash-panel";
import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action";
import { initProcessLogsPanel } from '../process-logs-panel/process-logs-panel-actions';
+import { loadProcessPanel } from '~/store/process-panel/process-panel-actions';
export const loadWorkbench = () =>
export const loadProcess = (uuid: string) =>
async (dispatch: Dispatch, getState: () => RootState) => {
+ dispatch<any>(loadProcessPanel(uuid));
const process = await dispatch<any>(processesActions.loadProcess(uuid));
await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
dispatch<any>(setProcessBreadcrumbs(uuid));
dispatch(loadDetailsPanel(uuid));
+
};
export const loadProcessLog = (uuid: string) =>
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';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
card: {
- marginBottom: theme.spacing.unit * 2
+ height: '100%'
},
iconHeader: {
fontSize: '1.875rem',
content: {
'&:last-child': {
paddingBottom: theme.spacing.unit * 2,
- paddingTop: '0px'
}
},
title: {
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>;
+ totalSubprocessesLength: number;
}
export interface ProcessPanelRootActionProps {
onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+ onToggle: (status: string) => void;
}
export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
props.process
- ? <Grid container spacing={16}>
- <Grid item xs={7}>
+ ? <Grid container spacing={16} alignItems="stretch">
+ <Grid item sm={12} md={7}>
<ProcessInformationCard
process={props.process}
onContextMenu={props.onContextMenu} />
</Grid>
- <Grid item xs={5}>
+ <Grid item sm={12} md={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; }}
+ subprocessesAmount={props.totalSubprocessesLength}
+ 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),
+ totalSubprocessesLength: subprocesses.length,
};
};
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;
export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubprocessesDataProps) => {
return <Grid container spacing={16}>
{subprocesses.map(subprocess =>
- <Grid item xs={2} key={subprocess.containerRequest.uuid}>
- <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess}/>
+ <Grid item xs={12} sm={6} md={4} lg={2} key={subprocess.containerRequest.uuid}>
+ <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, Typography } from '@material-ui/core';
import { SubprocessFilter } from '~/components/subprocess-filter/subprocess-filter';
import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
-type CssRules = 'root';
+type CssRules = 'root' | 'subtitle' | 'title' | 'gridFilter';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
- fontSize: '0.875rem'
+ fontSize: '0.875rem',
+ height: '100%'
+ },
+ subtitle: {
+ paddingBottom: '32px!important'
+ },
+ title: {
+ color: theme.customs.colors.grey700
+ },
+ gridFilter: {
+ height: '20px',
+ marginBottom: theme.spacing.unit,
+ paddingTop: '0px!important',
+ paddingBottom: '0px!important',
}
});
interface SubprocessesDataProps {
- subprocesses: number;
+ subprocessesAmount: number;
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, subprocessesAmount, 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>
- <Grid item xs={12} container spacing={16}>
+ <Grid item md={12} lg={6} className={classes.subtitle}>
+ <SubprocessFilter label='Subprocesses' value={subprocessesAmount} />
+ </Grid>
+ <Grid item md={12} lg={6}/>
{
- filters.map(filter =>
- <SubprocessFilter {...filter} key={filter.key} onToggle={() => onToggle(filter)} />
+ filters.map(filter =>
+ <Grid item md={12} lg={6} key={filter.key} className={classes.gridFilter}>
+ <SubprocessFilter {...filter} onToggle={() => onToggle(filter.label)} />
+ </Grid>
)
}
</Grid>