19482: Fix context menu, breadcrumbs
authorPeter Amstutz <peter.amstutz@curii.com>
Thu, 23 Feb 2023 23:03:40 +0000 (18:03 -0500)
committerPeter Amstutz <peter.amstutz@curii.com>
Thu, 23 Feb 2023 23:03:40 +0000 (18:03 -0500)
Also improve styling of Run button

Also fixed #19932 (CLI, python, CURL examples show every example code
twice) this was a bug in the code view panel.

Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

src/store/advanced-tab/advanced-tab.tsx
src/store/breadcrumbs/breadcrumbs-actions.ts
src/store/workbench/workbench-actions.ts
src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx
src/views-components/context-menu/action-sets/workflow-action-set.ts
src/views-components/details-panel/workflow-details.tsx
src/views/process-panel/process-details-card.tsx
src/views/workflow-panel/registered-workflow-panel.tsx

index ac088f025b8cdd72374584748d3764bb5752df48..82b4dfb02e2a6790ecb1a54d5b92f19818be57f3 100644 (file)
@@ -20,6 +20,7 @@ import { SshKeyResource } from 'models/ssh-key';
 import { VirtualMachinesResource } from 'models/virtual-machines';
 import { UserResource } from 'models/user';
 import { LinkResource } from 'models/link';
+import { WorkflowResource } from 'models/workflow';
 import { KeepServiceResource } from 'models/keep-services';
 import { ApiClientAuthorization } from 'models/api-client-authorization';
 import React from 'react';
@@ -101,9 +102,14 @@ enum LinkData {
     PROPERTIES = 'properties'
 }
 
-type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | ApiClientAuthorizationsData | UserData | LinkData;
+enum WorkflowData {
+    WORKFLOW = 'workflow',
+    CREATED_AT = 'created_at'
+}
+
+type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | ApiClientAuthorizationsData | UserData | LinkData | WorkflowData;
 type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix;
-type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | ApiClientAuthorization | UserResource | LinkResource | undefined;
+type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | ApiClientAuthorization | UserResource | LinkResource | WorkflowResource | undefined;
 
 export const openAdvancedTabDialog = (uuid: string) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
@@ -267,6 +273,23 @@ export const openAdvancedTabDialog = (uuid: string) =>
                 });
                 dispatch<any>(initAdvancedTabDialog(advanceDataLink));
                 break;
+            case ResourceKind.WORKFLOW:
+                const wfResources = getState().resources;
+                const dataWf = getResource<WorkflowResource>(uuid)(wfResources);
+                const advanceDataWf = advancedTabData({
+                    uuid,
+                    metadata: '',
+                    user: '',
+                    apiResponseKind: wfApiResponse,
+                    data: dataWf,
+                    resourceKind: WorkflowData.WORKFLOW,
+                    resourcePrefix: GroupContentsResourcePrefix.WORKFLOW,
+                    resourceKindProperty: WorkflowData.CREATED_AT,
+                    property: dataWf!.createdAt
+                });
+                dispatch<any>(initAdvancedTabDialog(advanceDataWf));
+                break;
+
             default:
                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
@@ -600,3 +623,22 @@ const linkApiResponse = (apiResponse: LinkResource): JSX.Element => {
 
     return <span style={{ marginLeft: '-15px' }}>{'{'} {response} {'\n'} <span style={{ marginLeft: '-15px' }}>{'}'}</span></span>;
 };
+
+
+const wfApiResponse = (apiResponse: WorkflowResource): JSX.Element => {
+    const {
+        uuid, name,
+        ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, description, definition
+    } = apiResponse;
+    const response = `
+"uuid": "${uuid}",
+"name": "${name}",
+"owner_uuid": "${ownerUuid}",
+"created_at": "${stringify(createdAt)}",
+"modified_at": ${stringify(modifiedAt)},
+"modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+"modified_by_user_uuid": ${stringify(modifiedByUserUuid)}
+"description": ${stringify(description)}`;
+
+    return <span style={{ marginLeft: '-15px' }}>{'{'} {response} {'\n'} <span style={{ marginLeft: '-15px' }}>{'}'}</span></span>;
+};
index 74cfde00300d545e80db987aab4e7fe0e5d7696c..7d6f182da2fbc554e21509057ab38e14767d7eda 100644 (file)
@@ -22,20 +22,21 @@ import { ProcessResource } from 'models/process';
 import { OrderBuilder } from 'services/api/order-builder';
 import { Breadcrumb } from 'components/breadcrumbs/breadcrumbs';
 import { ContainerRequestResource, containerRequestFieldsNoMounts } from 'models/container-request';
-import { CollectionIcon, IconType, ProcessIcon, ProjectIcon } from 'components/icon/icon';
+import { CollectionIcon, IconType, ProcessIcon, ProjectIcon, WorkflowIcon } from 'components/icon/icon';
 import { CollectionResource } from 'models/collection';
 import { getSidePanelIcon } from 'views-components/side-panel-tree/side-panel-tree';
+import { WorkflowResource } from 'models/workflow';
 
 export const BREADCRUMBS = 'breadcrumbs';
 
-export const setBreadcrumbs = (breadcrumbs: any, currentItem?: CollectionResource | ContainerRequestResource | GroupResource) => {
+export const setBreadcrumbs = (breadcrumbs: any, currentItem?: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource) => {
     if (currentItem) {
         breadcrumbs.push(resourceToBreadcrumb(currentItem));
     }
     return propertiesActions.SET_PROPERTY({ key: BREADCRUMBS, value: breadcrumbs });
 };
 
-const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerRequestResource | GroupResource): IconType | undefined => {
+const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource): IconType | undefined => {
     switch (resource.kind) {
         case ResourceKind.PROJECT:
             return ProjectIcon;
@@ -43,12 +44,14 @@ const resourceToBreadcrumbIcon = (resource: CollectionResource | ContainerReques
             return ProcessIcon;
         case ResourceKind.COLLECTION:
             return CollectionIcon;
+        case ResourceKind.WORKFLOW:
+            return WorkflowIcon;
         default:
             return undefined;
     }
 }
 
-const resourceToBreadcrumb = (resource: CollectionResource | ContainerRequestResource | GroupResource): Breadcrumb => ({
+const resourceToBreadcrumb = (resource: CollectionResource | ContainerRequestResource | GroupResource | WorkflowResource): Breadcrumb => ({
     label: resource.name,
     uuid: resource.uuid,
     icon: resourceToBreadcrumbIcon(resource),
@@ -90,6 +93,9 @@ export const setSidePanelBreadcrumbs = (uuid: string) =>
                 breadcrumbs.push(resourceToBreadcrumb(parentProcessItem));
             }
             dispatch(setBreadcrumbs(breadcrumbs, processItem));
+        } else if (uuidKind === ResourceKind.WORKFLOW) {
+            const workflowItem = await services.workflowService.get(currentUuid);
+            dispatch(setBreadcrumbs(breadcrumbs, workflowItem));
         }
         dispatch(setBreadcrumbs(breadcrumbs));
     };
@@ -172,10 +178,10 @@ const getCollectionParent = (collection: CollectionResource) =>
         });
         const [parentOutput, parentLog] = await Promise.all([parentOutputPromise, parentLogPromise]);
         return parentOutput.items.length > 0 ?
-                parentOutput.items[0] :
-                parentLog.items.length > 0 ?
-                    parentLog.items[0] :
-                    undefined;
+            parentOutput.items[0] :
+            parentLog.items.length > 0 ?
+                parentLog.items[0] :
+                undefined;
     }
 
 
@@ -200,6 +206,15 @@ export const setProcessBreadcrumbs = (processUuid: string) =>
         }
     };
 
+export const setWorkflowBreadcrumbs = (workflowUuid: string) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const { resources } = getState();
+        const workflow = getResource<WorkflowResource>(workflowUuid)(resources);
+        if (workflow) {
+            dispatch<any>(setProjectBreadcrumbs(workflow.ownerUuid));
+        }
+    };
+
 export const setGroupsBreadcrumbs = () =>
     setBreadcrumbs([{
         label: SidePanelTreeCategory.GROUPS,
@@ -234,7 +249,7 @@ export const setUserProfileBreadcrumbs = (userUuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         try {
             const user = getResource<UserResource>(userUuid)(getState().resources)
-                        || await services.userService.get(userUuid, false);
+                || await services.userService.get(userUuid, false);
             const breadcrumbs: Breadcrumb[] = [
                 { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
                 { label: user ? user.username : userUuid, uuid: userUuid },
index 7cb0987b39fd6b8f62cd6efb9eada1929fd660c6..cd15111308238fa8187f67f8902c268fc4fb55f2 100644 (file)
@@ -32,6 +32,7 @@ import {
     setGroupDetailsBreadcrumbs,
     setGroupsBreadcrumbs,
     setProcessBreadcrumbs,
+    setWorkflowBreadcrumbs,
     setSharedWithMeBreadcrumbs,
     setSidePanelBreadcrumbs,
     setTrashBreadcrumbs,
@@ -591,7 +592,13 @@ export const loadProcess = (uuid: string) =>
 export const loadRegisteredWorkflow = (uuid: string) =>
     handleFirstTimeLoad(async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const workflow = await services.workflowService.get(uuid);
-        dispatch<any>(updateResources([workflow]));
+        if (workflow) {
+            dispatch<any>(updateResources([workflow]));
+            await dispatch<any>(
+                activateSidePanelTreeItem(workflow.ownerUuid)
+            );
+            dispatch<any>(setWorkflowBreadcrumbs(uuid));
+        }
     });
 
 export const updateProcess =
index bc84ed2cf3aeef617ff3cafcbacab432536a027a..3505faed4366b1518d6986176bad278d7a005fef 100644 (file)
@@ -120,6 +120,6 @@ const dialogContentExample = (example: JSX.Element | string, classes: any) => {
         className={classes.codeSnippet}
         lines={stringData ? [stringData] : []}
     >
-        {example as JSX.Element || null}
+        {React.isValidElement(example) ? (example as JSX.Element) : undefined}
     </DefaultCodeSnippet>;
 }
index 2aa78904e4cfa8938aa5f8e2de3e77554bea1013..cf28bcd3ff477ff27783059989f9d3a59354824d 100644 (file)
@@ -4,10 +4,55 @@
 
 import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
 import { openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+import {
+    RenameIcon,
+    ShareIcon,
+    MoveToIcon,
+    CopyIcon,
+    DetailsIcon,
+    AdvancedIcon,
+    OpenIcon,
+    Link,
+    RestoreVersionIcon,
+    FolderSharedIcon,
+    StartIcon
+} from "components/icon/icon";
+import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
+import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
+import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
 
 export const workflowActionSet: ContextMenuActionSet = [[
     {
-        name: "Run",
+        icon: OpenIcon,
+        name: "Open in new tab",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openInNewTabAction(resource));
+        }
+    },
+    {
+        icon: Link,
+        name: "Copy to clipboard",
+        execute: (dispatch, resource) => {
+            dispatch<any>(copyToClipboardAction(resource));
+        }
+    },
+    {
+        icon: DetailsIcon,
+        name: "View details",
+        execute: dispatch => {
+            dispatch<any>(toggleDetailsPanel());
+        }
+    },
+    {
+        icon: AdvancedIcon,
+        name: "API Details",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openAdvancedTabDialog(resource.uuid));
+        }
+    },
+    {
+        icon: StartIcon,
+        name: "Run Workflow",
         execute: (dispatch, resource) => {
             dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name));
         }
index 98978dd279671eaf23a8ca174440208f4ffa1773..cb5e6a66c7cf495036daa1beb869d8575f6a41e8 100644 (file)
@@ -3,8 +3,11 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { WorkflowIcon } from 'components/icon/icon';
-import { WorkflowResource } from 'models/workflow';
+import { WorkflowIcon, StartIcon } from 'components/icon/icon';
+import {
+    WorkflowResource, parseWorkflowDefinition, getWorkflowInputs,
+    getWorkflowOutputs, getWorkflow
+} from 'models/workflow';
 import { DetailsData } from "./details-data";
 import { DetailsAttribute } from 'components/details-attribute/details-attribute';
 import { ResourceWithName } from 'views-components/data-explorer/renderers';
@@ -15,6 +18,11 @@ import { openRunProcess } from "store/workflow-panel/workflow-panel-actions";
 import { Dispatch } from 'redux';
 import { connect } from 'react-redux';
 import { ArvadosTheme } from 'common/custom-theme';
+import { ProcessIOParameter } from 'views/process-panel/process-io-card';
+import { formatInputData, formatOutputData } from 'store/process-panel/process-panel-actions';
+import { AuthState } from 'store/auth/auth-reducer';
+import { RootState } from 'store/store';
+import { getPropertyChip } from "views-components/resource-properties-form/property-chip";
 
 export interface WorkflowDetailsCardDataProps {
     workflow?: WorkflowResource;
@@ -29,29 +37,96 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
         () => wf && dispatch<any>(openRunProcess(wf.uuid, wf.ownerUuid, wf.name)),
 });
 
-type CssRules = 'runButton';
+type CssRules = 'runButton' | 'propertyTag';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     runButton: {
+        backgroundColor: theme.customs.colors.green700,
+        '&:hover': {
+            backgroundColor: theme.customs.colors.green800,
+        },
+        marginRight: "5px",
         boxShadow: 'none',
         padding: '2px 10px 2px 5px',
-        fontSize: '0.75rem'
+        marginLeft: 'auto'
+    },
+    propertyTag: {
+        marginRight: theme.spacing.unit / 2,
+        marginBottom: theme.spacing.unit / 2
     },
 });
 
-export const WorkflowDetailsAttributes = connect(null, mapDispatchToProps)(
+interface AuthStateDataProps {
+    auth: AuthState;
+};
+
+export interface RegisteredWorkflowPanelDataProps {
+    item: WorkflowResource;
+    workflowCollection: string;
+    inputParams: ProcessIOParameter[];
+    outputParams: ProcessIOParameter[];
+    gitprops: { [key: string]: string; };
+};
+
+export const getRegisteredWorkflowPanelData = (item: WorkflowResource, auth: AuthState): RegisteredWorkflowPanelDataProps => {
+    let inputParams: ProcessIOParameter[] = [];
+    let outputParams: ProcessIOParameter[] = [];
+    let workflowCollection = "";
+    const gitprops: { [key: string]: string; } = {};
+
+    // parse definition
+    const wfdef = parseWorkflowDefinition(item);
+
+    const inputs = getWorkflowInputs(wfdef);
+    if (inputs) {
+        inputs.forEach(elm => {
+            if (elm.default !== undefined && elm.default !== null) {
+                elm.value = elm.default;
+            }
+        });
+        inputParams = formatInputData(inputs, auth);
+    }
+
+    const outputs = getWorkflowOutputs(wfdef);
+    if (outputs) {
+        outputParams = formatOutputData(outputs, {}, undefined, auth);
+    }
+
+    const wf = getWorkflow(wfdef);
+    if (wf) {
+        const REGEX = /keep:([0-9a-f]{32}\+\d+)\/.*/;
+        if (wf["steps"]) {
+            workflowCollection = wf["steps"][0].run.match(REGEX)[1];
+        }
+    }
+
+    for (const elm in wfdef) {
+        if (elm.startsWith("http://arvados.org/cwl#git")) {
+            gitprops[elm.substr(23)] = wfdef[elm]
+        }
+    }
+
+    return { item, workflowCollection, inputParams, outputParams, gitprops };
+};
+
+const mapStateToProps = (state: RootState): AuthStateDataProps => {
+    return { auth: state.auth };
+};
+
+export const WorkflowDetailsAttributes = connect(mapStateToProps, mapDispatchToProps)(
     withStyles(styles)(
-        ({ workflow, onClick, classes }: WorkflowDetailsCardDataProps & WorkflowDetailsCardActionProps & WithStyles<CssRules>) => {
+        ({ workflow, onClick, auth, classes }: WorkflowDetailsCardDataProps & AuthStateDataProps & WorkflowDetailsCardActionProps & WithStyles<CssRules>) => {
+            if (!workflow) {
+                return <Grid />
+            }
+
+            const data = getRegisteredWorkflowPanelData(workflow, auth);
             return <Grid container>
                 <Button onClick={workflow && onClick(workflow)} className={classes.runButton} variant='contained'
                     data-cy='details-panel-run-btn' color='primary' size='small'>
-                    Run
+                    <StartIcon />
+                    Run Process
                 </Button>
-                {workflow && workflow.description !== "" && <Grid item xs={12} >
-                    <DetailsAttribute
-                        label={"Description"}
-                        value={workflow?.description} />
-                </Grid>}
                 <Grid item xs={12} >
                     <DetailsAttribute
                         label={"Workflow UUID"}
@@ -73,6 +148,11 @@ export const WorkflowDetailsAttributes = connect(null, mapDispatchToProps)(
                         label='Last modified by user' linkToUuid={workflow?.modifiedByUserUuid}
                         uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
                 </Grid>
+                <Grid item xs={12} md={12}>
+                    <DetailsAttribute label='Properties' />
+                    {Object.keys(data.gitprops).map(k =>
+                        getPropertyChip(k, data.gitprops[k], undefined, classes.propertyTag))}
+                </Grid>
             </Grid >;
         }));
 
index 4fa4701a48cc5672fb0a1d895c5749fe775347d4..2e5f40b5803dcde530ce57ac3a14f437fa5cd1b7 100644 (file)
@@ -114,7 +114,7 @@ export const ProcessDetailsCard = withStyles(styles)(
                                 className={classes.runButton}
                                 onClick={() => startProcess(process.containerRequest.uuid)}>
                                 <StartIcon />
-                                Run Process
+                                Run Workflow
                             </Button>}
                         {process.container && process.container.state === ContainerState.RUNNING &&
                             <span className={classes.cancelButton} onClick={() => cancelProcess(process.containerRequest.uuid)}>Cancel</span>}
@@ -126,10 +126,10 @@ export const ProcessDetailsCard = withStyles(styles)(
                                 <MoreOptionsIcon />
                             </IconButton>
                         </Tooltip>
-                        { doHidePanel &&
-                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                            <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
-                        </Tooltip> }
+                        {doHidePanel &&
+                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                                <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+                            </Tooltip>}
                     </div>
                 } />
             <CardContent className={classes.content}>
index 554ae42631d5621ddf9f81646b89261e79cd615b..4b658452175ad96b8a323d3fca34686feaa5a78b 100644 (file)
@@ -11,26 +11,22 @@ import {
     Tooltip,
     Typography,
     Card,
-    CardHeader
+    CardHeader,
+    IconButton,
 } from '@material-ui/core';
+import { Dispatch } from "redux";
 import { connect, DispatchProp } from "react-redux";
 import { RouteComponentProps } from 'react-router';
 import { ArvadosTheme } from 'common/custom-theme';
 import { RootState } from 'store/store';
-import { WorkflowIcon } from 'components/icon/icon';
-import {
-    WorkflowResource, parseWorkflowDefinition, getWorkflowInputs,
-    getWorkflowOutputs, getWorkflow
-} from 'models/workflow';
+import { WorkflowIcon, MoreOptionsIcon } from 'components/icon/icon';
+import { WorkflowResource } from 'models/workflow';
 import { ProcessOutputCollectionFiles } from 'views/process-panel/process-output-collection-files';
-import { WorkflowDetailsAttributes } from 'views-components/details-panel/workflow-details';
+import { WorkflowDetailsAttributes, RegisteredWorkflowPanelDataProps, getRegisteredWorkflowPanelData } from 'views-components/details-panel/workflow-details';
 import { getResource } from 'store/resources/resources';
 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
-import { ProcessIOCard, ProcessIOCardType, ProcessIOParameter } from 'views/process-panel/process-io-card';
-import { formatInputData, formatOutputData } from 'store/process-panel/process-panel-actions';
-import { DetailsAttribute } from 'components/details-attribute/details-attribute';
-import { getPropertyChip } from "views-components/resource-properties-form/property-chip";
+import { ProcessIOCard, ProcessIOCardType } from 'views/process-panel/process-io-card';
 
 type CssRules = 'root'
     | 'button'
@@ -48,8 +44,7 @@ type CssRules = 'root'
     | 'readOnlyIcon'
     | 'header'
     | 'title'
-    | 'avatar'
-    | 'propertyTag';
+    | 'avatar';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
@@ -118,68 +113,21 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         alignSelf: 'flex-start',
         paddingTop: theme.spacing.unit * 0.5
     },
-    propertyTag: {
-        marginRight: theme.spacing.unit / 2,
-        marginBottom: theme.spacing.unit / 2
-    },
 });
 
-interface RegisteredWorkflowPanelDataProps {
-    item: WorkflowResource;
-    workflowCollection: string;
-    inputParams: ProcessIOParameter[];
-    outputParams: ProcessIOParameter[];
-    gitprops: { [key: string]: string; };
-}
-
 type RegisteredWorkflowPanelProps = RegisteredWorkflowPanelDataProps & DispatchProp & WithStyles<CssRules>
 
 export const RegisteredWorkflowPanel = withStyles(styles)(connect(
     (state: RootState, props: RouteComponentProps<{ id: string }>) => {
         const item = getResource<WorkflowResource>(props.match.params.id)(state.resources);
-        let inputParams: ProcessIOParameter[] = [];
-        let outputParams: ProcessIOParameter[] = [];
-        let workflowCollection = "";
-        const gitprops: { [key: string]: string; } = {};
         if (item) {
-            // parse definition
-            const wfdef = parseWorkflowDefinition(item);
-
-            const inputs = getWorkflowInputs(wfdef);
-            if (inputs) {
-                inputs.forEach(elm => {
-                    if (elm.default !== undefined && elm.default !== null) {
-                        elm.value = elm.default;
-                    }
-                });
-                inputParams = formatInputData(inputs, state.auth);
-            }
-
-            const outputs = getWorkflowOutputs(wfdef);
-            if (outputs) {
-                outputParams = formatOutputData(outputs, {}, undefined, state.auth);
-            }
-
-            const wf = getWorkflow(wfdef);
-            if (wf) {
-                const REGEX = /keep:([0-9a-f]{32}\+\d+)\/.*/;
-                if (wf["steps"]) {
-                    workflowCollection = wf["steps"][0].run.match(REGEX)[1];
-                }
-            }
-
-            for (const elm in wfdef) {
-                if (elm.startsWith("http://arvados.org/cwl#git")) {
-                    gitprops[elm.substr(23)] = wfdef[elm]
-                }
-            }
-
+            return getRegisteredWorkflowPanelData(item, state.auth);
         }
-        return { item, inputParams, outputParams, workflowCollection, gitprops };
+        return { item, inputParams: [], outputParams: [], workflowCollection: "", gitprops: {} };
     })(
         class extends React.Component<RegisteredWorkflowPanelProps> {
             render() {
-                const { classes, item, inputParams, outputParams, workflowCollection, gitprops, dispatch } = this.props;
+                const { classes, item, inputParams, outputParams, workflowCollection } = this.props;
                 const panelsData: MPVPanelState[] = [
                     { name: "Details" },
                     { name: "Inputs" },
@@ -210,18 +158,21 @@ export const RegisteredWorkflowPanel = withStyles(styles)(connect(
                                                 {item.description || '(no-description)'}
                                             </Typography>
                                         </Tooltip>}
+                                    action={
+                                        <Tooltip title="More options" disableFocusListener>
+                                            <IconButton
+                                                aria-label="More options"
+                                                onClick={event => this.handleContextMenu(event)}>
+                                                <MoreOptionsIcon />
+                                            </IconButton>
+                                        </Tooltip>}
+
                                 />
 
                                 <Grid container justify="space-between">
                                     <Grid item xs={12}>
                                         <WorkflowDetailsAttributes workflow={item} />
                                     </Grid>
-
-                                    <Grid item xs={12} md={12}>
-                                        <DetailsAttribute label='Properties' />
-                                        {Object.keys(gitprops).map(k =>
-                                            getPropertyChip(k, gitprops[k], undefined, classes.propertyTag))}
-                                    </Grid>
                                 </Grid>
                             </Card>
                         </MPVPanelContent>