13860-process-statuses-filters
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 4 Sep 2018 10:17:22 +0000 (12:17 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 4 Sep 2018 10:17:22 +0000 (12:17 +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/context-menu/context-menu-actions.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-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 6901d8755588acb9d5f1600f30cca934ca05566c..30040e6d84f7d189a56d323d205834e86055da37 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, 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: '/',
@@ -79,7 +79,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     } else if (favoriteMatch) {
         store.dispatch(loadFavorites());
     } 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 3440a3053dad930dd1db230e0b52e9100e690935..e6f09542c4fb4a4e2b0cfb9997e2744fd7ca1976 100644 (file)
@@ -12,7 +12,6 @@ import { ProjectResource } from '~/models/project';
 import { UserResource } from '../../models/user';
 import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
 import { extractUuidKind, ResourceKind } from '~/models/resource';
-import { matchProcessRoute } from '~/routes/routes';
 
 export const contextMenuActions = unionize({
     OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
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 d0c0dd67b10453d3a11191ecd7418e10dc7b00bc..63396fb9bbeab5b6ebe18e2d8b2a18fe2251bac5 100644 (file)
@@ -29,6 +29,7 @@ import { propertiesReducer } from './properties/properties-reducer';
 import { RootState } from './store';
 import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
 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' &&
@@ -76,4 +77,5 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     snackbar: snackbarReducer,
     treePicker: treePickerReducer,
     fileUploader: fileUploaderReducer,
+    processPanel: processPanelReducer
 });
index 4aa51d8e64238fff972a9b51d164d564a4ef57fc..9db16bc1f6210df3110976e41f0fa2423e3c1abc 100644 (file)
@@ -7,19 +7,20 @@ import { Grid } from '@material-ui/core';
 import { ProcessInformationCard } from './process-information-card';
 import { DefaultView } from '~/components/default-view/default-view';
 import { ProcessIcon } from '~/components/icon/icon';
-import { Process, getProcessStatus } from '~/store/processes/process';
+import { Process } from '~/store/processes/process';
 import { SubprocessesCard } from './subprocesses-card';
-import { SubprocessFilterDataProps } from '~/components/subprocess-filter/subprocess-filter';
-import { groupBy } from 'lodash';
 import { ProcessSubprocesses } from '~/views/process-panel/process-subprocesses';
+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;
@@ -31,13 +32,13 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
                 <ProcessInformationCard
                     process={props.process}
                     onContextMenu={props.onContextMenu} />
-                {console.log(props.subprocesses)}
             </Grid>
             <Grid item xs={5}>
                 <SubprocessesCard
                     subprocesses={props.subprocesses}
-                    filters={mapGroupedProcessesToFilters(groupSubprocessesByStatus(props.subprocesses))}
-                    onToggle={() => { return; }} />
+                    filters={props.filters}
+                    onToggle={props.onToggle}
+                />
             </Grid>
             <Grid item xs={12}>
                 <ProcessSubprocesses
@@ -54,14 +55,3 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
                 messages={['Process not found']} />
         </Grid>;
 
-const groupSubprocessesByStatus = (processes: Process[]) =>
-    groupBy(processes, getProcessStatus);
-
-const mapGroupedProcessesToFilters = (groupedProcesses: { [status: string]: Process[] }): SubprocessFilterDataProps[] =>
-    Object
-        .keys(groupedProcesses)
-        .map(status => ({
-            label: status,
-            key: status,
-            value: groupedProcesses[status].length
-        }));
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 0ff628512782e63d729cdecbb12e84a23ce96fa0..8033222c9b5e3ccd27a37a860a917c22625e6926 100644 (file)
@@ -4,40 +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: 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.length} />     
+                    <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>