merge conflict
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 3 Sep 2018 09:45:58 +0000 (11:45 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 3 Sep 2018 09:45:58 +0000 (11:45 +0200)
Feature #13859

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

src/common/custom-theme.ts
src/common/formatters.ts
src/store/processes/process.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 [new file with mode: 0644]
src/views/process-panel/process-subprocesses.tsx [new file with mode: 0644]

index 2b0c58918f11270ef786e7d5a99d06f8bd61e001..c4d3dbc35894537d2d66cda9926a2e56f74fb7cc 100644 (file)
@@ -11,7 +11,7 @@ import green from '@material-ui/core/colors/green';
 import yellow from '@material-ui/core/colors/yellow';
 import red from '@material-ui/core/colors/red';
 
-interface ArvadosThemeOptions extends ThemeOptions {
+export interface ArvadosThemeOptions extends ThemeOptions {
     customs: any;
 }
 
@@ -24,6 +24,9 @@ export interface ArvadosTheme extends Theme {
 interface Colors {
     green700: string;
     yellow700: string;
+    red900: string;
+    blue500: string;
+    grey500: string;
 }
 
 const red900 = red["900"];
@@ -36,11 +39,14 @@ const grey700 = grey["700"];
 const grey900 = grey["900"];
 const rocheBlue = '#06C';
 
-const themeOptions: ArvadosThemeOptions = {
+export const themeOptions: ArvadosThemeOptions = {
     customs: {
         colors: {
             green700: green["700"],
-            yellow700: yellow["700"]
+            yellow700: yellow["700"],
+            red900: red['900'],
+            blue500: blue['500'],
+            grey500,
         }
     },
     overrides: {
index 49e0690515e868a4b1145c79ccbd2ede9d813314..0402f3903763e75ac04ee16c555f05d787cbbc77 100644 (file)
@@ -19,6 +19,17 @@ export const formatFileSize = (size?: number) => {
     return "";
 };
 
+export const formatTime = (time: number) => {
+    const minutes = Math.floor(time / (1000 * 60) % 60).toFixed(0);
+    const hours = Math.floor(time / (1000 * 60 * 60)).toFixed(0);
+
+    return hours + "h " + minutes + "m";
+};
+
+export const getTimeDiff = (endTime: string, startTime: string) => {
+    return new Date(endTime).getTime() - new Date(startTime).getTime();
+};
+
 export const formatProgress = (loaded: number, total: number) => {
     const progress = loaded >= 0 && total > 0 ? loaded * 100 / total : 0;
     return `${progress.toFixed(2)}%`;
index 0f12b3fe2f30c3f9821292b946ee0fbe2c74509b..467fc8a92ef83a7c7dd3358ee3f9d860ff3e56fa 100644 (file)
@@ -7,6 +7,9 @@ import { ContainerResource } from '../../models/container';
 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';
 
 export interface Process {
     containerRequest: ContainerRequestResource;
@@ -41,6 +44,28 @@ export const getSubprocesses = (uuid: string) => (resources: ResourcesState) =>
     return [];
 };
 
+export const getProcessRuntime = ({ container }: Process) =>
+    container
+        ? getTimeDiff(container.finishedAt || '', container.startedAt || '')
+        : 0;
+
+export const getProcessStatusColor = (status: string, { customs }: ArvadosTheme) => {
+    switch (status) {
+        case SubprocessesStatus.COMPLETED:
+            return customs.colors.green700;
+        case SubprocessesStatus.CANCELED:
+            return customs.colors.red900;
+        case SubprocessesStatus.QUEUED:
+            return customs.colors.grey500;
+        case SubprocessesStatus.FAILED:
+            return customs.colors.red900;
+        case SubprocessesStatus.ACTIVE:
+            return customs.colors.blue500;
+        default:
+            return customs.colors.grey500;
+    }
+};
+
 export const getProcessStatus = (process: Process) =>
     process.container
         ? process.container.state
@@ -49,3 +74,4 @@ export const getProcessStatus = (process: Process) =>
 const isSubprocess = (containerUuid: string) => (resource: Resource) =>
     resource.kind === ResourceKind.CONTAINER_REQUEST
     && (resource as ContainerRequestResource).requestingContainerUuid === containerUuid;
+
index f3a804d70f45f7a27f4560d11d60ae0eea1cd11e..f2379ed8dd697af03cd227f84d55c1a80e28e3f4 100644 (file)
@@ -10,10 +10,11 @@ import {
 import { ArvadosTheme } from '~/common/custom-theme';
 import { MoreOptionsIcon, ProcessIcon } from '~/components/icon/icon';
 import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
-import { Process } from '~/store/processes/process';
-import { getProcessStatus } from '../../store/processes/process';
+import { Process, getProcessStatusColor } from '~/store/processes/process';
+import { getProcessStatus } from '~/store/processes/process';
 
-type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'headerText' | 'link' | 'content' | 'title' | 'avatar';
+
+type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
@@ -24,7 +25,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         color: theme.customs.colors.green700,
     },
     avatar: {
-        alignSelf: 'flex-start'
+        alignSelf: 'flex-start',
+        paddingTop: theme.spacing.unit * 0.5
     },
     label: {
         display: 'flex',
@@ -47,15 +49,10 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     chip: {
         height: theme.spacing.unit * 3,
         width: theme.spacing.unit * 12,
-        backgroundColor: theme.customs.colors.green700,
         color: theme.palette.common.white,
         fontSize: '0.875rem',
         borderRadius: theme.spacing.unit * 0.625,
     },
-    headerText: {
-        fontSize: '0.875rem',
-        marginLeft: theme.spacing.unit * 3,
-    },
     content: {
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 2,
@@ -63,7 +60,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         }
     },
     title: {
-        overflow: 'hidden'
+        overflow: 'hidden',
+        paddingTop: theme.spacing.unit * 0.5
     }
 });
 
@@ -74,8 +72,8 @@ export interface ProcessInformationCardDataProps {
 
 type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules>;
 
-export const ProcessInformationCard = withStyles(styles)(
-    ({ classes, process, onContextMenu }: ProcessInformationCardProps) =>
+export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
+    ({ classes, process, onContextMenu, theme }: ProcessInformationCardProps) =>
         <Card className={classes.card}>
             <CardHeader
                 classes={{
@@ -85,7 +83,9 @@ export const ProcessInformationCard = withStyles(styles)(
                 avatar={<ProcessIcon className={classes.iconHeader} />}
                 action={
                     <div>
-                        <Chip label={getProcessStatus(process)} className={classes.chip} />
+                        <Chip label={getProcessStatus(process)}
+                            className={classes.chip} 
+                            style={{ backgroundColor: getProcessStatusColor(getProcessStatus(process), theme as ArvadosTheme) }}/>
                         <IconButton
                             aria-label="More options"
                             onClick={event => onContextMenu(event)}>
@@ -94,7 +94,7 @@ export const ProcessInformationCard = withStyles(styles)(
                     </div>
                 }
                 title={
-                    <Tooltip title={process.containerRequest.name} placement="bottom-start">
+                    <Tooltip title={process.containerRequest.name} placement="bottom-start" color='inherit'>
                         <Typography noWrap variant="title">
                            {process.containerRequest.name}
                         </Typography>
index feada3acb3c8231266365de6641a982c498f4b43..8e78f564f288de0220f84ed82753cc0f3cb5bc33 100644 (file)
@@ -9,9 +9,14 @@ import { DefaultView } from '~/components/default-view/default-view';
 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';
 
 export interface ProcessPanelRootDataProps {
     process?: Process;
+    subprocesses: Array<Process>;
 }
 
 export interface ProcessPanelRootActionProps {
@@ -59,10 +64,16 @@ export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
                     onToggle={() => { return; }}
                 />
             </Grid>
+            <Grid item xs={12}>
+                <ProcessSubprocesses
+                    subprocesses={props.subprocesses}
+                    onContextMenu={props.onContextMenu} />
+            </Grid>
         </Grid>
         : <Grid container
             alignItems='center'
-            justify='center'>
+            justify='center'
+            style={{ minHeight: '100%' }}>
             <DefaultView
                 icon={ProcessIcon}
                 messages={['Process not found']} />
index 421945fed7f1756c1a6f8794b6e5e018d2a27b42..9f1d0adb7a2fae5314e2b2b55136624060519613 100644 (file)
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { RootState } from '~/store/store';
 import { connect } from 'react-redux';
-import { getProcess } from '~/store/processes/process';
+import { getProcess, getSubprocesses } from '~/store/processes/process';
 import { Dispatch } from 'redux';
 import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions';
 import { matchProcessRoute } from '~/routes/routes';
@@ -16,7 +16,8 @@ const mapStateToProps = ({ router, resources }: RootState): ProcessPanelRootData
     const match = matchProcessRoute(pathname);
     const uuid = match ? match.params.id : '';
     return {
-        process: getProcess(uuid)(resources)
+        process: getProcess(uuid)(resources),
+        subprocesses: getSubprocesses(uuid)(resources)
     };
 };
 
diff --git a/src/views/process-panel/process-subprocesses-card.tsx b/src/views/process-panel/process-subprocesses-card.tsx
new file mode 100644 (file)
index 0000000..57c127a
--- /dev/null
@@ -0,0 +1,114 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import {
+    StyleRulesCallback, WithStyles, withStyles, Card,
+    CardHeader, IconButton, CardContent, Typography, Tooltip
+} from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { MoreOptionsIcon } from '~/components/icon/icon';
+import { DetailsAttribute } from '~/components/details-attribute/details-attribute';
+import { Process, getProcessStatus, getProcessRuntime } from '~/store/processes/process';
+import { formatTime } from '~/common/formatters';
+import { getProcessStatusColor } from '~/store/processes/process';
+
+export type CssRules = 'label' | 'value' | 'title' | 'content' | 'action' | 'options' | 'status' | 'rightSideHeader' | 'titleHeader'| 'header';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    label: {
+        fontSize: '0.875rem',
+    },
+    value: {
+        textTransform: 'none',
+        fontSize: '0.875rem',
+    },
+    title: {
+        overflow: 'hidden'
+    },
+    content: {
+        paddingTop: theme.spacing.unit * 0.5,
+        '&:last-child': {
+            paddingBottom: 0
+        }
+    },
+    action: {
+        marginTop: 0
+    },
+    options: {
+        width: theme.spacing.unit * 4,
+        height: theme.spacing.unit * 4,
+        color: theme.palette.common.white,
+    },
+    status: {
+        paddingTop: theme.spacing.unit * 0.5,
+        color: theme.palette.common.white,
+    },
+    rightSideHeader: {
+        display: 'flex'
+    },
+    titleHeader: {
+        color: theme.palette.common.white,
+        fontWeight: 600
+    },
+    header: {
+        paddingTop: 0,
+        paddingBottom: 0,
+    },
+});
+
+export enum SubprocessesStatus {
+    ACTIVE = 'Active',
+    COMPLETED = 'Completed',
+    QUEUED = 'Queued',
+    FAILED = 'Failed',
+    CANCELED = 'Canceled'
+}
+
+export interface SubprocessItemProps {
+    title: string;
+    status: string;
+    runtime?: string;
+}
+
+export interface ProcessSubprocessesCardDataProps {
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+    subprocess: Process;
+}
+
+type ProcessSubprocessesCardProps = ProcessSubprocessesCardDataProps & WithStyles<CssRules>;
+
+export const ProcessSubprocessesCard = withStyles(styles, { withTheme: true })(
+    ({ classes, onContextMenu, subprocess, theme }: ProcessSubprocessesCardProps) => {
+        return <Card>
+            <CardHeader
+                className={classes.header}
+                style={{ backgroundColor: getProcessStatusColor(getProcessStatus(subprocess), theme as ArvadosTheme) }}
+                classes={{ content: classes.title, action: classes.action }}
+                action={
+                    <div className={classes.rightSideHeader}>
+                        <Typography noWrap variant="body2" className={classes.status}>
+                            {getProcessStatus(subprocess)}
+                        </Typography>
+                        <IconButton
+                            className={classes.options}
+                            aria-label="More options"
+                            onClick={onContextMenu}>
+                            <MoreOptionsIcon />
+                        </IconButton>
+                    </div>
+                }
+                title={
+                    <Tooltip title={subprocess.containerRequest.name}>
+                        <Typography noWrap variant="body2" className={classes.titleHeader}>
+                            {subprocess.containerRequest.name}
+                        </Typography>
+                    </Tooltip>
+                } />
+            <CardContent className={classes.content}>
+                <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+                    label="Runtime" value={formatTime(getProcessRuntime(subprocess))} />
+            </CardContent>
+        </Card>;
+    });
\ No newline at end of file
diff --git a/src/views/process-panel/process-subprocesses.tsx b/src/views/process-panel/process-subprocesses.tsx
new file mode 100644 (file)
index 0000000..cfd517c
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Grid } from '@material-ui/core';
+import { ProcessSubprocessesCard } from '~/views/process-panel/process-subprocesses-card';
+import { Process } from '~/store/processes/process';
+
+export interface ProcessSubprocessesDataProps {
+    subprocesses: Array<Process>;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+}
+
+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>
+        )}
+    </Grid>;
+};