merge master
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 4 Sep 2018 12:16:44 +0000 (14:16 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 4 Sep 2018 12:16:44 +0000 (14:16 +0200)
Feature #13860

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

14 files changed:
src/common/custom-theme.ts
src/components/subprocess-filter/subprocess-filter.tsx
src/store/process-logs-panel/process-logs-panel-reducer.ts
src/store/process-panel/process-panel-actions.ts [new file with mode: 0644]
src/store/process-panel/process-panel-reducer.ts [new file with mode: 0644]
src/store/process-panel/process-panel.ts [new file with mode: 0644]
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/views/process-panel/process-information-card.tsx
src/views/process-panel/process-panel-root.tsx
src/views/process-panel/process-panel.tsx
src/views/process-panel/process-subprocesses-card.tsx
src/views/process-panel/process-subprocesses.tsx
src/views/process-panel/subprocesses-card.tsx

index c4d3dbc35894537d2d66cda9926a2e56f74fb7cc..20790100dcf2012f7f12ec6912aa30d0f56ca875 100644 (file)
@@ -27,6 +27,7 @@ interface Colors {
     red900: string;
     blue500: string;
     grey500: string;
+    grey700: string;
 }
 
 const red900 = red["900"];
@@ -47,6 +48,7 @@ export const themeOptions: ArvadosThemeOptions = {
             red900: red['900'],
             blue500: blue['500'],
             grey500,
+            grey700
         }
     },
     overrides: {
index 58c33ee5f238939f6ce795abdaad596d6a4cadd6..36be50d96aff7bb7fa6ff718c05baaf503bf53ba 100644 (file)
@@ -5,27 +5,24 @@
 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,
     }
 });
 
@@ -41,14 +38,14 @@ type SubprocessFilterProps = SubprocessFilterDataProps & WithStyles<CssRules>;
 
 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
index 38a3136437c1a6e12cad791cef21d662eb8c1f4c..c7d694c0eeb5f9f080937ce3ec9f6ee73893432a 100644 (file)
@@ -3,7 +3,6 @@
 // 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 = {
diff --git a/src/store/process-panel/process-panel-actions.ts b/src/store/process-panel/process-panel-actions.ts
new file mode 100644 (file)
index 0000000..85a7314
--- /dev/null
@@ -0,0 +1,32 @@
+// 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
+]);
diff --git a/src/store/process-panel/process-panel-reducer.ts b/src/store/process-panel/process-panel-reducer.ts
new file mode 100644 (file)
index 0000000..487b092
--- /dev/null
@@ -0,0 +1,24 @@
+// 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
diff --git a/src/store/process-panel/process-panel.ts b/src/store/process-panel/process-panel.ts
new file mode 100644 (file)
index 0000000..b521a67
--- /dev/null
@@ -0,0 +1,7 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface ProcessPanel {
+    filters: { [status: string]: boolean };
+}
index 01aca598be44d384a052e4d4d5f85c6955e2d9b8..9b4f42b84fc81d8d64b2177c0187a6ee94fb60c9 100644 (file)
@@ -31,6 +31,7 @@ import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
 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' &&
@@ -82,4 +83,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     snackbar: snackbarReducer,
     treePicker: treePickerReducer,
     fileUploader: fileUploaderReducer,
+    processPanel: processPanelReducer
 });
index effac1d14f4c77048278aac70bbc7c7cd50c1612..7077f414e5e2c1703c63f81107cae7a34aab9c65 100644 (file)
@@ -32,6 +32,7 @@ import * as processesActions from '../processes/processes-actions';
 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 = () =>
@@ -185,10 +186,12 @@ export const moveCollection = (data: MoveToFormDialogData) =>
 
 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) =>
index f2379ed8dd697af03cd227f84d55c1a80e28e3f4..788f2539941ab7143024f22297ab9fb0d4ceb1e0 100644 (file)
@@ -10,15 +10,16 @@ import {
 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',
@@ -56,7 +57,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     content: {
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 2,
-            paddingTop: '0px'
         }
     },
     title: {
@@ -84,8 +84,8 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
                 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)}>
@@ -94,20 +94,25 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
                     </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>
index 8e78f564f288de0220f84ed82753cc0f3cb5bc33..d12704bcf4b69c87ec59283dc824feba3e1dd308 100644 (file)
@@ -10,58 +10,35 @@ import { ProcessIcon } from '~/components/icon/icon';
 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}>
@@ -78,3 +55,4 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
                 icon={ProcessIcon}
                 messages={['Process not found']} />
         </Grid>;
+
index 9f1d0adb7a2fae5314e2b2b55136624060519613..b3e36ab77c1742c646db51d2672b76d198a2c0b7 100644 (file)
@@ -2,29 +2,49 @@
 //
 // 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
index 57c127a049b23bdf9561fa528fb772a9f46b36a4..8f6ccbc2b6259a104610e7732426bfbec87a2c3e 100644 (file)
@@ -58,14 +58,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-export enum SubprocessesStatus {
-    ACTIVE = 'Active',
-    COMPLETED = 'Completed',
-    QUEUED = 'Queued',
-    FAILED = 'Failed',
-    CANCELED = 'Canceled'
-}
-
 export interface SubprocessItemProps {
     title: string;
     status: string;
index cfd517cf6cfd2ded3386f7b11b2089581e03e375..6e00deb0f1a0963ce8f69280ec84ed86c0c072dd 100644 (file)
@@ -15,8 +15,8 @@ export interface ProcessSubprocessesDataProps {
 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>;
index ac60c9f66ef56a4e3842bf1465ce5473e10f15f0..85de7033340f8bde984c41a4e0e751e24a20bf35 100644 (file)
@@ -4,39 +4,60 @@
 
 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>