Merge branch '19069-workflow-launching' into 19143-project-list-workflows
authorPeter Amstutz <peter.amstutz@curii.com>
Mon, 23 May 2022 15:15:44 +0000 (11:15 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Mon, 23 May 2022 15:15:44 +0000 (11:15 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

src/common/labels.ts
src/index.tsx
src/models/details.ts
src/store/context-menu/context-menu-actions.ts
src/store/navigation/navigation-action.ts
src/store/resource-type-filters/resource-type-filters.ts
src/views-components/context-menu/action-sets/workflow-action-set.ts [new file with mode: 0644]
src/views-components/context-menu/context-menu.tsx
src/views-components/details-panel/details-panel.tsx
src/views-components/details-panel/workflow-details.tsx [new file with mode: 0644]
src/views/workflow-panel/workflow-description-card.tsx

index 682513fb165e31105363a71decf7c2b4a74fa8fe..e784cec0f5d95fd915a58b0d2ca78163178f8bdb 100644 (file)
@@ -23,6 +23,8 @@ export const resourceLabel = (type: string, subtype = '') => {
             return "Group";
         case ResourceKind.VIRTUAL_MACHINE:
             return "Virtual Machine";
+        case ResourceKind.WORKFLOW:
+            return "Workflow";
         default:
             return "Unknown";
     }
index f928ea8ae33447c8b166aebdcbe1c8827b30fb63..03840d49ad232fe22d22a14c3762757690523894 100644 (file)
@@ -62,6 +62,7 @@ import { linkActionSet } from 'views-components/context-menu/action-sets/link-ac
 import { loadFileViewersConfig } from 'store/file-viewers/file-viewers-actions';
 import { filterGroupAdminActionSet, projectAdminActionSet } from 'views-components/context-menu/action-sets/project-admin-action-set';
 import { permissionEditActionSet } from 'views-components/context-menu/action-sets/permission-edit-action-set';
+import { workflowActionSet } from 'views-components/context-menu/action-sets/workflow-action-set';
 import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action';
 import { storeRedirects } from './common/redirect-to';
@@ -102,6 +103,7 @@ addMenuActionSet(ContextMenuKind.PROCESS_ADMIN, processResourceAdminActionSet);
 addMenuActionSet(ContextMenuKind.PROJECT_ADMIN, projectAdminActionSet);
 addMenuActionSet(ContextMenuKind.FILTER_GROUP_ADMIN, filterGroupAdminActionSet);
 addMenuActionSet(ContextMenuKind.PERMISSION_EDIT, permissionEditActionSet);
+addMenuActionSet(ContextMenuKind.WORKFLOW, workflowActionSet);
 
 storeRedirects();
 
index 150b694083bc9378ca9a41087afdb9e310fec2fb..b6eabd7014efade5dea3c51775f876413573d0bb 100644 (file)
@@ -7,5 +7,6 @@ import { CollectionResource } from "./collection";
 import { ProcessResource } from "./process";
 import { EmptyResource } from "./empty";
 import { CollectionFile, CollectionDirectory } from 'models/collection-file';
+import { WorkflowResource } from 'models/workflow';
 
-export type DetailsResource = ProjectResource | CollectionResource | ProcessResource | EmptyResource | CollectionFile | CollectionDirectory;
+export type DetailsResource = ProjectResource | CollectionResource | ProcessResource | EmptyResource | CollectionFile | CollectionDirectory | WorkflowResource;
index bc7f94b0a8bfc06c24b75d86ad4643f5cdb64103..3e239feeaa8bb9cb27867db910b6222398e48495 100644 (file)
@@ -261,6 +261,8 @@ export const resourceUuidToContextMenuKind = (uuid: string, readonly = false) =>
                 return ContextMenuKind.ROOT_PROJECT;
             case ResourceKind.LINK:
                 return ContextMenuKind.LINK;
+            case ResourceKind.WORKFLOW:
+                return ContextMenuKind.WORKFLOW;
             default:
                 return;
         }
index 9a4f31fd2fb67161ceb1df602f89413fc076dfbc..c8811bf43955a92ad493ddc6e2acc28e8846b1b3 100644 (file)
@@ -8,6 +8,7 @@ import { ResourceKind, extractUuidKind } from 'models/resource';
 import { SidePanelTreeCategory } from '../side-panel-tree/side-panel-tree-actions';
 import { Routes, getGroupUrl, getNavUrl, getUserProfileUrl } from 'routes/routes';
 import { RootState } from 'store/store';
+import { openDetailsPanel } from 'store/details-panel/details-panel-action';
 import { ServiceRepository } from 'services/services';
 import { pluginConfig } from 'plugins';
 import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
@@ -40,6 +41,9 @@ export const navigateTo = (uuid: string) =>
             case ResourceKind.VIRTUAL_MACHINE:
                 dispatch<any>(navigateToAdminVirtualMachines);
                 return;
+            case ResourceKind.WORKFLOW:
+                dispatch<any>(openDetailsPanel(uuid));
+                return;
         }
 
         switch (uuid) {
index 0539cefecc93dc74df0912b8450d81613c9e566b..64a391ca9e5419ac7c3e9c79e4e4a04606304c93 100644 (file)
@@ -27,6 +27,7 @@ export enum ObjectTypeFilter {
     PROJECT = 'Project',
     PROCESS = 'Process',
     COLLECTION = 'Data collection',
+    WORKFLOW = 'Workflow',
 }
 
 export enum GroupTypeFilter {
@@ -63,6 +64,7 @@ export const getSimpleObjectTypeFilters = pipe(
     initFilter(ObjectTypeFilter.PROJECT),
     initFilter(ObjectTypeFilter.PROCESS),
     initFilter(ObjectTypeFilter.COLLECTION),
+    initFilter(ObjectTypeFilter.WORKFLOW),
 );
 
 // Using pipe() with more than 7 arguments makes the return type be 'any',
@@ -86,6 +88,8 @@ export const getInitialResourceTypeFilters = pipe(
         initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
         initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
     ),
+    initFilter(ObjectTypeFilter.WORKFLOW)
+
 );
 
 export const getInitialProcessTypeFilters = pipe(
@@ -133,6 +137,8 @@ const objectTypeToResourceKind = (type: ObjectTypeFilter) => {
             return ResourceKind.PROCESS;
         case ObjectTypeFilter.COLLECTION:
             return ResourceKind.COLLECTION;
+        case ObjectTypeFilter.WORKFLOW:
+            return ResourceKind.WORKFLOW;
     }
 };
 
diff --git a/src/views-components/context-menu/action-sets/workflow-action-set.ts b/src/views-components/context-menu/action-sets/workflow-action-set.ts
new file mode 100644 (file)
index 0000000..2aa7890
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+import { openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+
+export const workflowActionSet: ContextMenuActionSet = [[
+    {
+        name: "Run",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name));
+        }
+    },
+]];
index 6f3a4389211363e9294bbfe3c53fdf530d32195a..4766259a921dfc0728040dbe111dca3f7032a7b9 100644 (file)
@@ -70,7 +70,7 @@ export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) =>
 
 const emptyActionSet: ContextMenuActionSet = [];
 const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet => (
-   resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet
+    resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet
 );
 
 export enum ContextMenuKind {
@@ -110,4 +110,5 @@ export enum ContextMenuKind {
     GROUP_MEMBER = "GroupMember",
     PERMISSION_EDIT = "PermissionEdit",
     LINK = "Link",
+    WORKFLOW = "Workflow",
 }
index 399f4ef4ef273569dda2c30d440229b58f4c8708..adbbab79333b385eec7b028c98c75aa7c4a041cb 100644 (file)
@@ -18,6 +18,7 @@ import { ProjectDetails } from "./project-details";
 import { CollectionDetails } from "./collection-details";
 import { ProcessDetails } from "./process-details";
 import { EmptyDetails } from "./empty-details";
+import { WorkflowDetails } from "./workflow-details";
 import { DetailsData } from "./details-data";
 import { DetailsResource } from "models/details";
 import { Config } from 'common/config';
@@ -71,6 +72,8 @@ const getItem = (res: DetailsResource): DetailsData => {
                 return new CollectionDetails(res);
             case ResourceKind.PROCESS:
                 return new ProcessDetails(res);
+            case ResourceKind.WORKFLOW:
+                return new WorkflowDetails(res);
             default:
                 return new EmptyDetails(res);
         }
@@ -152,9 +155,9 @@ export const DetailsPanel = withStyles(styles)(
                 let shouldShowInlinePreview = false;
                 if (!('kind' in res)) {
                     shouldShowInlinePreview = isInlineFileUrlSafe(
-                      res ? res.url : "",
-                      authConfig.keepWebServiceUrl,
-                      authConfig.keepWebInlineServiceUrl
+                        res ? res.url : "",
+                        authConfig.keepWebServiceUrl,
+                        authConfig.keepWebInlineServiceUrl
                     ) || authConfig.clusterConfig.Collections.TrustAllContent;
                 }
 
@@ -191,14 +194,14 @@ export const DetailsPanel = withStyles(styles)(
                     </Grid>
                     <Grid item>
                         <Tabs onChange={this.handleChange}
-                            value={(item.getTabLabels().length >= tabNr+1) ? tabNr : 0}>
-                            { item.getTabLabels().map((tabLabel, idx) =>
+                            value={(item.getTabLabels().length >= tabNr + 1) ? tabNr : 0}>
+                            {item.getTabLabels().map((tabLabel, idx) =>
                                 <Tab key={`tab-label-${idx}`} disableRipple label={tabLabel} />)
                             }
                         </Tabs>
                     </Grid>
                     <Grid item xs className={this.props.classes.tabContainer} >
-                        {item.getDetails({tabNr, showPreview: shouldShowInlinePreview})}
+                        {item.getDetails({ tabNr, showPreview: shouldShowInlinePreview })}
                     </Grid>
                 </Grid >;
             }
diff --git a/src/views-components/details-panel/workflow-details.tsx b/src/views-components/details-panel/workflow-details.tsx
new file mode 100644 (file)
index 0000000..7076823
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { DefaultIcon, WorkflowIcon } from 'components/icon/icon';
+import { WorkflowResource } from 'models/workflow';
+import { DetailsData } from "./details-data";
+import { DefaultView } from 'components/default-view/default-view';
+import { DetailsAttribute } from 'components/details-attribute/details-attribute';
+import { ResourceOwnerWithName } from 'views-components/data-explorer/renderers';
+import { formatDate } from "common/formatters";
+import { Grid } from '@material-ui/core';
+import { withStyles, StyleRulesCallback, WithStyles, Button } from '@material-ui/core';
+import { openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { ArvadosTheme } from 'common/custom-theme';
+
+export interface WorkflowDetailsCardDataProps {
+    workflow?: WorkflowResource;
+}
+
+export interface WorkflowDetailsCardActionProps {
+    onClick: (wf: WorkflowResource) => () => void;
+}
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+    onClick: (wf: WorkflowResource) =>
+        () => wf && dispatch<any>(openRunProcess(wf.uuid, wf.ownerUuid, wf.name)),
+});
+
+type CssRules = 'runButton';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    runButton: {
+        boxShadow: 'none',
+        padding: '2px 10px 2px 5px',
+        fontSize: '0.75rem'
+    },
+});
+
+export const WorkflowDetailsAttributes = connect(null, mapDispatchToProps)(
+    withStyles(styles)(
+        ({ workflow, onClick, classes }: WorkflowDetailsCardDataProps & WorkflowDetailsCardActionProps & WithStyles<CssRules>) => {
+            return <Grid container>
+                <Button onClick={workflow && onClick(workflow)} className={classes.runButton} variant='contained'
+                    data-cy='details-panel-run-btn' color='primary' size='small'>
+                    Run
+                </Button>
+                {workflow && workflow.description !== "" && <Grid item xs={12} >
+                    <DetailsAttribute
+                        label={"Description"}
+                        value={workflow?.description} />
+                </Grid>}
+                <Grid item xs={12} >
+                    <DetailsAttribute
+                        label={"Workflow UUID"}
+                        linkToUuid={workflow?.uuid} />
+                </Grid>
+                <Grid item xs={12} >
+                    <DetailsAttribute
+                        label='Owner' linkToUuid={workflow?.ownerUuid}
+                        uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+                </Grid>
+                <Grid item xs={12}>
+                    <DetailsAttribute label='Created at' value={formatDate(workflow?.createdAt)} />
+                </Grid>
+                <Grid item xs={12}>
+                    <DetailsAttribute label='Last modified' value={formatDate(workflow?.modifiedAt)} />
+                </Grid>
+                <Grid item xs={12} >
+                    <DetailsAttribute
+                        label='Last modified by user' linkToUuid={workflow?.modifiedByUserUuid}
+                        uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+                </Grid>
+            </Grid >;
+        }));
+
+export class WorkflowDetails extends DetailsData<WorkflowResource> {
+    getIcon(className?: string) {
+        return <WorkflowIcon className={className} />;
+    }
+
+    getDetails() {
+        return <WorkflowDetailsAttributes workflow={this.item} />;
+    }
+}
index d03f46e4565941a094eac0c6a8dfa86c02a0a3d7..f25a8e648c6f0e1e221ef120445d22c6dc7078b4 100644 (file)
@@ -15,11 +15,12 @@ import {
     TableCell,
     TableBody,
     TableRow,
-    Grid,
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import { WorkflowIcon } from 'components/icon/icon';
 import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
+import { parseWorkflowDefinition, getWorkflowInputs, getInputLabel, stringifyInputType } from 'models/workflow';
+import { WorkflowDetailsCardDataProps, WorkflowDetailsAttributes } from 'views-components/details-panel/workflow-details';
 import { WorkflowResource, parseWorkflowDefinition, getWorkflowInputs, getInputLabel, stringifyInputType } from 'models/workflow';
 import { DetailsAttribute } from 'components/details-attribute/details-attribute';
 import { ResourceOwnerWithName } from 'views-components/data-explorer/renderers';
@@ -57,10 +58,6 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
 });
 
-interface WorkflowDetailsCardDataProps {
-    workflow?: WorkflowResource;
-}
-
 type WorkflowDetailsCardProps = WorkflowDetailsCardDataProps & WithStyles<CssRules>;
 
 export const WorkflowDetailsCard = withStyles(styles)(
@@ -140,29 +137,3 @@ export const WorkflowDetailsCard = withStyles(styles)(
             </Table>;
         }
     });
-
-export const WorkflowDetailsAttributes = ({ workflow }: WorkflowDetailsCardDataProps) => {
-    return <Grid container>
-        <Grid item xs={12} >
-            <DetailsAttribute
-                label={"Workflow UUID"}
-                linkToUuid={workflow?.uuid} />
-        </Grid>
-        <Grid item xs={12} >
-            <DetailsAttribute
-                label='Owner' linkToUuid={workflow?.ownerUuid}
-                uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
-        </Grid>
-        <Grid item xs={12}>
-            <DetailsAttribute label='Created at' value={formatDate(workflow?.createdAt)} />
-        </Grid>
-        <Grid item xs={12}>
-            <DetailsAttribute label='Last modified' value={formatDate(workflow?.modifiedAt)} />
-        </Grid>
-        <Grid item xs={12} >
-            <DetailsAttribute
-                label='Last modified by user' linkToUuid={workflow?.modifiedByUserUuid}
-                uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
-        </Grid>
-    </Grid >;
-};