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

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

15 files changed:
src/common/custom-theme.ts
src/components/subprocess-filter/subprocess-filter.tsx
src/routes/routes.ts
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/processes/process.ts
src/store/store.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..f13668582bec95b1a8125efa35736e5849b31c9d 100644 (file)
@@ -11,18 +11,23 @@ type CssRules = 'grid' | 'label' | 'value' | 'switch';
 
 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'
         }
index 05a8ab099ce1db395fb42f67561f330f3adae289..ac02dbcfca175cd2b1750913b56b52114ea67518 100644 (file)
@@ -9,7 +9,7 @@ import { ResourceKind, RESOURCE_UUID_PATTERN, extractUuidKind } from '~/models/r
 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: '/',
@@ -86,7 +86,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     } 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));
     }
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..902a5c9
--- /dev/null
@@ -0,0 +1,29 @@
+// 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'
+]);
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 467fc8a92ef83a7c7dd3358ee3f9d860ff3e56fa..7253d9fcad285fb08d52cdf07e76838eae8451ed 100644 (file)
@@ -8,14 +8,22 @@ import { ResourcesState, getResource } from '~/store/resources/resources';
 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) {
@@ -51,15 +59,15 @@ export const getProcessRuntime = ({ container }: Process) =>
 
 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;
@@ -74,4 +82,3 @@ export const getProcessStatus = (process: Process) =>
 const isSubprocess = (containerUuid: string) => (resource: Resource) =>
     resource.kind === ResourceKind.CONTAINER_REQUEST
     && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;
-
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 f2379ed8dd697af03cd227f84d55c1a80e28e3f4..01bb3ae79e0713f7014cb988269c04e63117a7c7 100644 (file)
@@ -10,8 +10,9 @@ 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';
@@ -84,8 +85,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 +95,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..9db16bc1f6210df3110976e41f0fa2423e3c1abc 100644 (file)
@@ -10,17 +10,17 @@ 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>;
 }
 
 export interface ProcessPanelRootActionProps {
     onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+    onToggle: (status: string) => void;
 }
 
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
@@ -35,33 +35,9 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
             </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}>
@@ -78,3 +54,4 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
                 icon={ProcessIcon}
                 messages={['Process not found']} />
         </Grid>;
+
index 9f1d0adb7a2fae5314e2b2b55136624060519613..e9bba3ad52d710c5195e2cd982892b2e35a29de3 100644 (file)
@@ -2,29 +2,48 @@
 //
 // 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
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..5ee5938ec9c9f0509d1249fb80d505edc655a674 100644 (file)
@@ -16,7 +16,7 @@ export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubp
     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>;
index ac60c9f66ef56a4e3842bf1465ce5473e10f15f0..8033222c9b5e3ccd27a37a860a917c22625e6926 100644 (file)
@@ -4,39 +4,51 @@
 
 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>