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';
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) => {
});
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 }));
}
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>;
+};
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;
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),
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));
};
});
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;
}
}
};
+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,
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 },
setGroupDetailsBreadcrumbs,
setGroupsBreadcrumbs,
setProcessBreadcrumbs,
+ setWorkflowBreadcrumbs,
setSharedWithMeBreadcrumbs,
setSidePanelBreadcrumbs,
setTrashBreadcrumbs,
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 =
className={classes.codeSnippet}
lines={stringData ? [stringData] : []}
>
- {example as JSX.Element || null}
+ {React.isValidElement(example) ? (example as JSX.Element) : undefined}
</DefaultCodeSnippet>;
}
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));
}
// 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';
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;
() => 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"}
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 >;
}));
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>}
<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}>
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'
| 'readOnlyIcon'
| 'header'
| 'title'
- | 'avatar'
- | 'propertyTag';
+ | 'avatar';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
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" },
{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>