16068: Merge branch 'main' of git.arvados.org:arvados-workbench2 into 16068-merge...
authorStephen Smith <stephen@curii.com>
Tue, 19 Apr 2022 17:50:34 +0000 (13:50 -0400)
committerStephen Smith <stephen@curii.com>
Tue, 19 Apr 2022 18:18:37 +0000 (14:18 -0400)
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

cypress/integration/collection.spec.js
cypress/integration/process.spec.js
cypress/support/commands.js
cypress/support/index.d.ts [new file with mode: 0644]
src/views-components/details-panel/process-details.tsx
src/views/process-panel/process-details-attributes.tsx
src/views/process-panel/process-details-card.tsx
src/views/process-panel/process-information-card.tsx [deleted file]
src/views/process-panel/process-panel-root.tsx
src/views/process-panel/process-panel.tsx

index 39a2af4209db4d3b4b5b15f5d64630ee88c40334..568121f119f8c3b5aa500cab491aa4c2f55930d3 100644 (file)
@@ -334,8 +334,8 @@ describe('Collection panel tests', function () {
                     'bar' // make sure we can go back to the original name as a last step
                 ];
                 eachPair(names, (from, to) => {
-                    cy.get('[data-cy=collection-files-panel]')
-                        .contains(`${from}`).rightclick({force: true});
+                    cy.waitForDom().get('[data-cy=collection-files-panel]')
+                        .contains(`${from}`).rightclick();
                     cy.get('[data-cy=context-menu]')
                         .contains('Rename')
                         .click();
index 3234f7c4bd5bcd04a89df3274642b467a2e3a2f6..48b936cf69c8e50e225b32e8d316826cec67c141 100644 (file)
@@ -95,7 +95,7 @@ describe('Process tests', function() {
         .then(function(containerRequest) {
             cy.loginAs(activeUser);
             cy.goToPath(`/processes/${containerRequest.uuid}`);
-            cy.get('[data-cy=process-info]').should('contain', crName);
+            cy.get('[data-cy=process-details]').should('contain', crName);
             cy.get('[data-cy=process-logs]')
                 .should('contain', 'No logs yet')
                 .and('not.contain', 'hello world');
@@ -245,4 +245,4 @@ describe('Process tests', function() {
                 .and('contain', 'No additional warning details available');
         });
     });
-});
\ No newline at end of file
+});
index a28308e3cd6f2db7e9af30ba1bd5068e15cceb00..c2d78b54b2e71f3b295bbee47d63a6352193f926 100644 (file)
@@ -401,3 +401,73 @@ function b64toBlob(b64Data, contentType = '', sliceSize = 512) {
     const blob = new Blob(byteArrays, { type: contentType });
     return blob
 }
+
+// From https://github.com/cypress-io/cypress/issues/7306#issuecomment-1076451070=
+// This command requires the async package (https://www.npmjs.com/package/async)
+Cypress.Commands.add('waitForDom', () => {
+    cy.window().then(win => {
+      let timeElapsed = 0;
+
+      cy.log("Waiting for DOM mutations to complete");
+
+      return new Cypress.Promise((resolve) => {
+        // set the required variables
+        let async = require("async");
+        let observerConfig = { attributes: true, childList: true, subtree: true };
+        let items = Array.apply(null, { length: 50 }).map(Number.call, Number);
+        win.mutationCount = 0;
+        win.previousMutationCount = null;
+
+        // create an observer instance
+        let observer = new win.MutationObserver((mutations) => {
+          mutations.forEach((mutation) => {
+            // Only record "attributes" type mutations that are not a "class" mutation.
+            // If the mutation is not an "attributes" type, then we always record it.
+            if (mutation.type === 'attributes' && mutation.attributeName !== 'class') {
+              win.mutationCount += 1;
+            } else if (mutation.type !== 'attributes') {
+              win.mutationCount += 1;
+            }
+          });
+
+          // initialize the previousMutationCount
+          if (win.previousMutationCount == null) win.previousMutationCount = 0;
+        });
+
+        // watch the document body for the specified mutations
+        observer.observe(win.document.body, observerConfig);
+
+        // check the DOM for mutations up to 50 times for a maximum time of 5 seconds
+        async.eachSeries(items, function iteratee(item, callback) {
+          // keep track of the elapsed time so we can log it at the end of the command
+          timeElapsed = timeElapsed + 100;
+
+          // make each iteration of the loop 100ms apart
+          setTimeout(() => {
+            if (win.mutationCount === win.previousMutationCount) {
+              // pass an argument to the async callback to exit the loop
+              return callback('Resolved - DOM changes complete.');
+            } else if (win.previousMutationCount != null) {
+              // only set the previous count if the observer has checked the DOM at least once
+              win.previousMutationCount = win.mutationCount;
+              return callback();
+            } else if (win.mutationCount === 0 && win.previousMutationCount == null && item === 4) {
+              // this is an early exit in case nothing is changing in the DOM. That way we only
+              // wait 500ms instead of the full 5 seconds when no DOM changes are occurring.
+              return callback('Resolved - Exiting early since no DOM changes were detected.');
+            } else {
+              // proceed to the next iteration
+              return callback();
+            }
+          }, 100);
+        }, function done() {
+          // Log the total wait time so users can see it
+          cy.log(`DOM mutations ${timeElapsed >= 5000 ? "did not complete" : "completed"} in ${timeElapsed} ms`);
+
+          // disconnect the observer and resolve the promise
+          observer.disconnect();
+          resolve();
+        });
+      });
+    });
+  });
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644 (file)
index 0000000..d74d5b3
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+  * This command tries to ensure that the elements in the DOM are actually visible
+  * and done (re)rendering. This is due to how React re-renders components.
+  *
+  * IMPORTANT NOTES:
+  *    => You should only use this command in instances where a test is failing due
+  *       to detached elements. Cypress will probably give you a warning along the lines
+  *       of, "Element has an effective width/height of 0". This warning is not very useful
+  *       in pointing out it is due to the element being detached from the DOM AFTER the
+  *       cy.get command had already retrieved it. This command can save you from that
+  *       by explicitly waiting for the DOM to stop changing.
+  *    => This command can take anywhere from 100ms to 5 seconds to complete
+  *    => This command will exit early (500ms) when no changes are occurring in the DOM.
+  *       We wait a minimum of 500ms because sometimes it can take up to around that time
+  *       for mutations to start occurring.
+  *
+  * GitHub Issues:
+  *    * https://github.com/cypress-io/cypress/issues/695 (Closed - no activity)
+  *    * https://github.com/cypress-io/cypress/issues/7306 (Open - re-get detached elements)
+  *
+  * @example Wait for the DOM to stop changing before retrieving an element
+  * cy.waitForDom().get('#an-elements-id')
+  */
+ waitForDom(): Chainable<any>
index d9c991f5858ea4c1ca3a50f1903716d044dfd350..bb0e8a4043ea7b2a4beac8f0885b7c2866783dc5 100644 (file)
@@ -15,6 +15,6 @@ export class ProcessDetails extends DetailsData<ProcessResource> {
     }
 
     getDetails() {
-        return <ProcessDetailsAttributes item={this.item} />;
+        return <ProcessDetailsAttributes request={this.item} />;
     }
 }
index 4f26a71f78c5b9cc2f690511e832aa5cd47cfe8c..4eadb656c6cde99f41afc7583f643ee7409e835d 100644 (file)
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from "react";
-import { Grid } from "@material-ui/core";
+import { Grid, StyleRulesCallback, withStyles } from "@material-ui/core";
+import { Dispatch } from 'redux';
 import { formatDate } from "common/formatters";
 import { resourceLabel } from "common/labels";
 import { DetailsAttribute } from "components/details-attribute/details-attribute";
-import { ProcessResource } from "models/process";
 import { ResourceKind } from "models/resource";
-import { ResourceOwnerWithName } from "views-components/data-explorer/renderers";
+import { ContainerRunTime, ResourceOwnerWithName } from "views-components/data-explorer/renderers";
+import { getProcess } from "store/processes/process";
+import { RootState } from "store/store";
+import { connect } from "react-redux";
+import { ProcessResource } from "models/process";
+import { ContainerResource } from "models/container";
+import { openProcessInputDialog } from "store/processes/process-input-actions";
+import { navigateToOutput, openWorkflow } from "store/process-panel/process-panel-actions";
+import { ArvadosTheme } from "common/custom-theme";
+import { ProcessRuntimeStatus } from "views-components/process-runtime-status/process-runtime-status";
+import { getPropertyChip } from "views-components/resource-properties-form/property-chip";
+
+type CssRules = 'link' | 'propertyTag';
 
-type CssRules = 'label' | 'value';
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    link: {
+        fontSize: '0.875rem',
+        color: theme.palette.primary.main,
+        '&:hover': {
+            cursor: 'pointer'
+        }
+    },
+    propertyTag: {
+        marginRight: theme.spacing.unit / 2,
+        marginBottom: theme.spacing.unit / 2
+    },
+});
 
-export const ProcessDetailsAttributes = (props: { item: ProcessResource, twoCol?: boolean, classes?: Record<CssRules, string> }) => {
-    const item = props.item;
-    const classes = props.classes || { label: '', value: '', button: '' };
-    const mdSize = props.twoCol ? 6 : 12;
-    return <Grid container>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                label='Owner' linkToUuid={item.ownerUuid}
-                uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
-        </Grid>
-        <Grid item xs={12} md={12}>
-            <DetailsAttribute label='Status' value={item.state} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Last modified' value={formatDate(item.modifiedAt)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Started at' value={formatDate(item.createdAt)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Created at' value={formatDate(item.createdAt)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Finished at' value={formatDate(item.expiresAt)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Outputs' value={item.outputPath} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='UUID' linkToUuid={item.uuid} value={item.uuid} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Container UUID' value={item.containerUuid} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Priority' value={item.priority} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Runtime Constraints'
-            value={JSON.stringify(item.runtimeConstraints)} />
-        </Grid>
-        <Grid item xs={12} md={mdSize}>
-            <DetailsAttribute label='Docker Image locator'
-            linkToUuid={item.containerImage} value={item.containerImage} />
-        </Grid>
-    </Grid>;
+const mapStateToProps = (state: RootState, props: { request: ProcessResource }) => {
+    return {
+        container: getProcess(props.request.uuid)(state.resources)?.container,
+    };
 };
+
+interface ProcessDetailsAttributesActionProps {
+    openProcessInputDialog: (uuid: string) => void;
+    navigateToOutput: (uuid: string) => void;
+    openWorkflow: (uuid: string) => void;
+}
+
+const mapDispatchToProps = (dispatch: Dispatch): ProcessDetailsAttributesActionProps => ({
+    openProcessInputDialog: (uuid) => dispatch<any>(openProcessInputDialog(uuid)),
+    navigateToOutput: (uuid) => dispatch<any>(navigateToOutput(uuid)),
+    openWorkflow: (uuid) => dispatch<any>(openWorkflow(uuid)),
+});
+
+export const ProcessDetailsAttributes = withStyles(styles, { withTheme: true })(
+    connect(mapStateToProps, mapDispatchToProps)(
+        (props: { request: ProcessResource, container?: ContainerResource, twoCol?: boolean, hideProcessPanelRedundantFields?: boolean, classes: Record<CssRules, string> } & ProcessDetailsAttributesActionProps) => {
+            const containerRequest = props.request;
+            const container = props.container;
+            const classes = props.classes;
+            const mdSize = props.twoCol ? 6 : 12;
+            return <Grid container>
+                <Grid item xs={12}>
+                    <ProcessRuntimeStatus runtimeStatus={container?.runtimeStatus} />
+                </Grid>
+                {!props.hideProcessPanelRedundantFields && <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
+                </Grid>}
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Container Request UUID' linkToUuid={containerRequest.uuid} value={containerRequest.uuid} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Docker Image locator'
+                    linkToUuid={containerRequest.containerImage} value={containerRequest.containerImage} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute
+                        label='Owner' linkToUuid={containerRequest.ownerUuid}
+                        uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Container UUID' value={containerRequest.containerUuid} />
+                </Grid>
+                {!props.hideProcessPanelRedundantFields && <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Status' value={containerRequest.state} />
+                </Grid>}
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Created at' value={formatDate(containerRequest.createdAt)} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Started at' value={container ? formatDate(container.startedAt) : "(none)"} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Finished at' value={container ? formatDate(container.finishedAt) : "(none)"} />
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Container run time'>
+                        <ContainerRunTime uuid={containerRequest.uuid} />
+                    </DetailsAttribute>
+                </Grid>
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Requesting Container UUID' value={containerRequest.requestingContainerUuid || "(none)"} />
+                </Grid>
+                <Grid item xs={6}>
+                    <span onClick={() => props.navigateToOutput(containerRequest.outputUuid!)}>
+                        <DetailsAttribute classLabel={classes.link} label='Outputs' />
+                    </span>
+                    <span onClick={() => props.openProcessInputDialog(containerRequest.uuid)}>
+                        <DetailsAttribute classLabel={classes.link} label='Inputs' />
+                    </span>
+                </Grid>
+                {containerRequest.properties.workflowUuid &&
+                <Grid item xs={12} md={mdSize}>
+                    <span onClick={() => props.openWorkflow(containerRequest.properties.workflowUuid)}>
+                        <DetailsAttribute classValue={classes.link}
+                            label='Workflow' value={containerRequest.properties.workflowName} />
+                    </span>
+                </Grid>}
+                <Grid item xs={12} md={mdSize}>
+                    <DetailsAttribute label='Priority' value={containerRequest.priority} />
+                </Grid>
+                {/*
+                    NOTE: The property list should be kept at the bottom, because it spans
+                    the entire available width, without regards of the twoCol prop.
+                */}
+                <Grid item xs={12} md={12}>
+                    <DetailsAttribute label='Properties' />
+                    { Object.keys(containerRequest.properties).length > 0
+                        ? Object.keys(containerRequest.properties).map(k =>
+                                Array.isArray(containerRequest.properties[k])
+                                ? containerRequest.properties[k].map((v: string) =>
+                                    getPropertyChip(k, v, undefined, classes.propertyTag))
+                                : getPropertyChip(k, containerRequest.properties[k], undefined, classes.propertyTag))
+                        : <div>No properties</div> }
+                </Grid>
+            </Grid>;
+        }
+    )
+);
index d3349c3ae8d79ea62d7dcb052ab6852bd8d43f4b..21563c16aa01ecdbb735d922b0a5f9feab46f99d 100644 (file)
@@ -12,14 +12,17 @@ import {
     IconButton,
     CardContent,
     Tooltip,
+    Typography,
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
-import { CloseIcon } from 'components/icon/icon';
+import { CloseIcon, MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 import { ProcessDetailsAttributes } from './process-details-attributes';
+import { ProcessStatus } from 'views-components/data-explorer/renderers';
+import { ContainerState } from 'models/container';
 
-type CssRules = 'card' | 'content' | 'title' | 'header';
+type CssRules = 'card' | 'content' | 'title' | 'header' | 'cancelButton' | 'avatar' | 'iconHeader';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
@@ -29,6 +32,14 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         paddingTop: theme.spacing.unit,
         paddingBottom: theme.spacing.unit,
     },
+    iconHeader: {
+        fontSize: '1.875rem',
+        color: theme.customs.colors.green700,
+    },
+    avatar: {
+        alignSelf: 'flex-start',
+        paddingTop: theme.spacing.unit * 0.5
+    },
     content: {
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 2,
@@ -38,31 +49,71 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         overflow: 'hidden',
         paddingTop: theme.spacing.unit * 0.5
     },
+    cancelButton: {
+        paddingRight: theme.spacing.unit * 2,
+        fontSize: '14px',
+        color: theme.customs.colors.red900,
+        "&:hover": {
+            cursor: 'pointer'
+        }
+    },
 });
 
 export interface ProcessDetailsCardDataProps {
     process: Process;
+    cancelProcess: (uuid: string) => void;
+    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
 }
 
 type ProcessDetailsCardProps = ProcessDetailsCardDataProps & WithStyles<CssRules> & MPVPanelProps;
 
 export const ProcessDetailsCard = withStyles(styles)(
-    ({ classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => {
+    ({ cancelProcess, onContextMenu, classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => {
         return <Card className={classes.card}>
             <CardHeader
                 className={classes.header}
                 classes={{
                     content: classes.title,
+                    avatar: classes.avatar,
                 }}
-                title='Details'
-                action={ doHidePanel &&
+                avatar={<ProcessIcon className={classes.iconHeader} />}
+                title={
+                    <Tooltip title={process.containerRequest.name} placement="bottom-start">
+                        <Typography noWrap variant='h6' color='inherit'>
+                            {process.containerRequest.name}
+                        </Typography>
+                    </Tooltip>
+                }
+                subheader={
+                    <Tooltip title={getDescription(process)} placement="bottom-start">
+                        <Typography noWrap variant='body1' color='inherit'>
+                            {getDescription(process)}
+                        </Typography>
+                    </Tooltip>}
+                action={
+                    <div>
+                        {process.container && process.container.state === ContainerState.RUNNING &&
+                            <span className={classes.cancelButton} onClick={() => cancelProcess(process.containerRequest.uuid)}>Cancel</span>}
+                        <ProcessStatus uuid={process.containerRequest.uuid} />
+                        <Tooltip title="More options" disableFocusListener>
+                            <IconButton
+                                aria-label="More options"
+                                onClick={event => onContextMenu(event)}>
+                                <MoreOptionsIcon />
+                            </IconButton>
+                        </Tooltip>
+                        { doHidePanel &&
                         <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
                             <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
-                        </Tooltip> } />
+                        </Tooltip> }
+                    </div>
+                } />
             <CardContent className={classes.content}>
-                <ProcessDetailsAttributes item={process.containerRequest} twoCol />
+                <ProcessDetailsAttributes request={process.containerRequest} twoCol hideProcessPanelRedundantFields />
             </CardContent>
         </Card>;
     }
 );
 
+const getDescription = (process: Process) =>
+    process.containerRequest.description || '(no-description)';
diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx
deleted file mode 100644 (file)
index 4fcbf25..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from 'react';
-import {
-    StyleRulesCallback,
-    WithStyles,
-    withStyles,
-    Card,
-    CardHeader,
-    IconButton,
-    CardContent,
-    Grid,
-    Typography,
-    Tooltip
-} from '@material-ui/core';
-import { ArvadosTheme } from 'common/custom-theme';
-import { CloseIcon, MoreOptionsIcon, ProcessIcon } from 'components/icon/icon';
-import { DetailsAttribute } from 'components/details-attribute/details-attribute';
-import { Process } from 'store/processes/process';
-import { formatDate } from 'common/formatters';
-import classNames from 'classnames';
-import { ContainerState } from 'models/container';
-import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
-import { ProcessRuntimeStatus } from 'views-components/process-runtime-status/process-runtime-status';
-import { ProcessStatus } from 'views-components/data-explorer/renderers';
-
-type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton' | 'header';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    card: {
-        height: '100%'
-    },
-    header: {
-        paddingTop: theme.spacing.unit,
-        paddingBottom: theme.spacing.unit,
-    },
-    iconHeader: {
-        fontSize: '1.875rem',
-        color: theme.customs.colors.green700,
-    },
-    avatar: {
-        alignSelf: 'flex-start',
-        paddingTop: theme.spacing.unit * 0.5
-    },
-    label: {
-        display: 'flex',
-        justifyContent: 'flex-start',
-        fontSize: '0.875rem',
-        marginRight: theme.spacing.unit * 3,
-        paddingRight: theme.spacing.unit
-    },
-    value: {
-        textTransform: 'none',
-        fontSize: '0.875rem',
-    },
-    link: {
-        fontSize: '0.875rem',
-        color: theme.palette.primary.main,
-        '&:hover': {
-            cursor: 'pointer'
-        }
-    },
-    content: {
-        paddingTop: '0px',
-        paddingLeft: theme.spacing.unit * 1,
-        paddingRight: theme.spacing.unit * 1,
-        '&:last-child': {
-            paddingBottom: theme.spacing.unit * 1,
-        }
-    },
-    title: {
-        overflow: 'hidden',
-        paddingTop: theme.spacing.unit * 0.5
-    },
-    cancelButton: {
-        paddingRight: theme.spacing.unit * 2,
-        fontSize: '14px',
-        color: theme.customs.colors.red900,
-        "&:hover": {
-            cursor: 'pointer'
-        }
-    }
-});
-
-export interface ProcessInformationCardDataProps {
-    process: Process;
-    onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
-    openProcessInputDialog: (uuid: string) => void;
-    navigateToOutput: (uuid: string) => void;
-    openWorkflow: (uuid: string) => void;
-    cancelProcess: (uuid: string) => void;
-}
-
-type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true> & MPVPanelProps;
-
-export const ProcessInformationCard = withStyles(styles, { withTheme: true })(
-    ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess, doHidePanel, panelName }: ProcessInformationCardProps) => {
-        const { container } = process;
-        const startedAt = container ? formatDate(container.startedAt) : 'N/A';
-        const finishedAt = container ? formatDate(container.finishedAt) : 'N/A';
-        return <Card className={classes.card}>
-            <CardHeader
-                className={classes.header}
-                classes={{
-                    content: classes.title,
-                    avatar: classes.avatar
-                }}
-                avatar={<ProcessIcon className={classes.iconHeader} />}
-                action={
-                    <div>
-                        {process.container && process.container.state === ContainerState.RUNNING &&
-                            <span className={classes.cancelButton} onClick={() => cancelProcess(process.containerRequest.uuid)}>Cancel</span>}
-                        <ProcessStatus uuid={process.containerRequest.uuid} />
-                        <Tooltip title="More options" disableFocusListener>
-                            <IconButton
-                                aria-label="More options"
-                                onClick={event => onContextMenu(event)}>
-                                <MoreOptionsIcon />
-                            </IconButton>
-                        </Tooltip>
-                        { doHidePanel &&
-                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
-                        </Tooltip> }
-                    </div>
-                }
-                title={ !!process.containerRequest.name &&
-                    <Typography noWrap variant='h6' color='inherit'>
-                        {process.containerRequest.name}
-                    </Typography>
-                }
-                subheader={
-                    <Typography noWrap variant='body1' color='inherit'>
-                        {process.containerRequest.description}
-                    </Typography>
-                }
-            />
-            <CardContent className={classes.content}>
-                <Grid container>
-                    <Grid item xs={12}>
-                        <ProcessRuntimeStatus runtimeStatus={process.container?.runtimeStatus} />
-                    </Grid>
-                    <Grid item xs={6}>
-                        <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='Started at'
-                            value={startedAt} />
-                        <DetailsAttribute classLabel={classes.label} classValue={classes.value}
-                            label='Finished at'
-                            value={finishedAt} />
-                        {process.containerRequest.properties.workflowUuid &&
-                            <span onClick={() => openWorkflow(process.containerRequest.properties.workflowUuid)}>
-                                <DetailsAttribute classLabel={classes.label} classValue={classNames(classes.value, classes.link)}
-                                    label='Workflow' value={process.containerRequest.properties.workflowName} />
-                            </span>}
-                    </Grid>
-                    <Grid item xs={6}>
-                        <span onClick={() => navigateToOutput(process.containerRequest.outputUuid!)}>
-                            <DetailsAttribute classLabel={classes.link} label='Outputs' />
-                        </span>
-                        <span onClick={() => openProcessInputDialog(process.containerRequest.uuid)}>
-                            <DetailsAttribute classLabel={classes.link} label='Inputs' />
-                        </span>
-                    </Grid>
-                </Grid>
-            </CardContent>
-        </Card>;
-    }
-);
index 156789a20dbe85c1427305ecbf4ab06d9a387ff5..4f95d0d88726f1b4354138946fb113ae749d786c 100644 (file)
@@ -4,7 +4,6 @@
 
 import React from 'react';
 import { Grid, StyleRulesCallback, WithStyles, withStyles } 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 } from 'store/processes/process';
@@ -35,9 +34,6 @@ export interface ProcessPanelRootDataProps {
 export interface ProcessPanelRootActionProps {
     onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
     onToggle: (status: string) => void;
-    openProcessInputDialog: (uuid: string) => void;
-    navigateToOutput: (uuid: string) => void;
-    navigateToWorkflow: (uuid: string) => void;
     cancelProcess: (uuid: string) => void;
     onLogFilterChange: (filter: FilterOption) => void;
     navigateToLog: (uuid: string) => void;
@@ -47,8 +43,7 @@ export interface ProcessPanelRootActionProps {
 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
 
 const panelsData: MPVPanelState[] = [
-    {name: "Info"},
-    {name: "Details", visible: false},
+    {name: "Details"},
     {name: "Logs", visible: true},
     {name: "Subprocesses"},
 ];
@@ -57,19 +52,13 @@ export const ProcessPanelRoot = withStyles(styles)(
     ({ process, processLogsPanel, ...props }: ProcessPanelRootProps) =>
     process
         ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
-            <MPVPanelContent forwardProps xs="auto" data-cy="process-info">
-                <ProcessInformationCard
+            <MPVPanelContent forwardProps xs="auto" data-cy="process-details">
+                <ProcessDetailsCard
                     process={process}
                     onContextMenu={event => props.onContextMenu(event, process)}
-                    openProcessInputDialog={props.openProcessInputDialog}
-                    navigateToOutput={props.navigateToOutput}
-                    openWorkflow={props.navigateToWorkflow}
                     cancelProcess={props.cancelProcess}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs="auto" data-cy="process-details">
-                <ProcessDetailsCard process={process} />
-            </MPVPanelContent>
             <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
                 <ProcessLogsCard
                     onCopy={props.onLogCopyToClipboard}
index e04602925798fb9c624bdb30e5d750d028fb6375..de6b13b3163c7f2ffaebb2181f8e235b73dbc416 100644 (file)
@@ -19,10 +19,7 @@ import {
 import { groupBy } from 'lodash';
 import {
     toggleProcessPanelFilter,
-    navigateToOutput,
-    openWorkflow
 } from 'store/process-panel/process-panel-actions';
-import { openProcessInputDialog } from 'store/processes/process-input-actions';
 import { cancelRunningWorkflow } from 'store/processes/processes-actions';
 import { navigateToLogCollection, setProcessLogsPanelFilter } from 'store/process-logs-panel/process-logs-panel-actions';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
@@ -52,9 +49,6 @@ const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps =>
     onToggle: status => {
         dispatch<any>(toggleProcessPanelFilter(status));
     },
-    openProcessInputDialog: (uuid) => dispatch<any>(openProcessInputDialog(uuid)),
-    navigateToOutput: (uuid) => dispatch<any>(navigateToOutput(uuid)),
-    navigateToWorkflow: (uuid) => dispatch<any>(openWorkflow(uuid)),
     cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid)),
     onLogFilterChange: (filter) => dispatch(setProcessLogsPanelFilter(filter.value)),
     navigateToLog: (uuid) => dispatch<any>(navigateToLogCollection(uuid)),