"@types/react-virtualized-auto-sizer": "1.0.0",
"@types/react-window": "1.8.2",
"@types/redux-form": "7.4.12",
- "@types/shell-quote": "1.6.0",
+ "@types/shell-escape": "^0.2.0",
"axios": "^0.21.1",
"babel-core": "6.26.3",
"babel-runtime": "6.26.0",
"lodash.template": "4.5.0",
"material-ui-pickers": "^2.2.4",
"mem": "4.0.0",
+ "mime": "^3.0.0",
"moment": "2.29.1",
"parse-duration": "0.4.4",
"prop-types": "15.7.2",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"set-value": "2.0.1",
- "shell-quote": "1.6.1",
+ "shell-escape": "^0.2.0",
"sinon": "7.3",
"tslint": "5.20.0",
"tslint-etc": "1.6.0",
import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "views/run-process-panel/run-process-basic-form";
import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from "views/run-process-panel/run-process-advanced-form";
import { MOUNT_PATH_CWL_WORKFLOW, MOUNT_PATH_CWL_INPUT } from 'models/process';
-import { getWorkflow, getWorkflowInputs } from "models/workflow";
+import { CommandInputParameter, getWorkflow, getWorkflowInputs, getWorkflowOutputs } from "models/workflow";
import { ProjectResource } from "models/project";
import { UserResource } from "models/user";
+import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
export const loadProcess = (containerRequestUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process> => {
const containerRequest = await services.containerRequestService.get(containerRequestUuid);
dispatch<any>(updateResources([containerRequest]));
+ if (containerRequest.outputUuid) {
+ const collection = await services.collectionService.get(containerRequest.outputUuid);
+ dispatch<any>(updateResources([collection]));
+ }
+
if (containerRequest.containerUuid) {
const container = await services.containerService.get(containerRequest.containerUuid);
dispatch<any>(updateResources([container]));
return { containerRequest };
};
- export const loadContainers = (filters: string) =>
+ export const loadContainers = (filters: string, loadMounts: boolean = true) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const { items } = await services.containerService.list({ filters });
+ let args: any = { filters };
+ if (!loadMounts) {
+ args.select = containerFieldsNoMounts;
+ }
+ const { items } = await services.containerService.list(args);
dispatch<any>(updateResources(items));
return items;
};
+ // Until the api supports unselecting fields, we need a list of all other fields to omit mounts
+ const containerFieldsNoMounts = [
+ "auth_uuid",
+ "command",
+ "container_image",
+ "created_at",
+ "cwd",
+ "environment",
+ "etag",
+ "exit_code",
+ "finished_at",
+ "gateway_address",
+ "href",
+ "interactive_session_started",
+ "kind",
+ "lock_count",
+ "locked_by_uuid",
+ "log",
+ "modified_at",
+ "modified_by_client_uuid",
+ "modified_by_user_uuid",
+ "output_path",
+ "output_properties",
+ "output_storage_classes",
+ "output",
+ "owner_uuid",
+ "priority",
+ "progress",
+ "runtime_auth_scopes",
+ "runtime_constraints",
+ "runtime_status",
+ "runtime_user_uuid",
+ "scheduling_parameters",
+ "started_at",
+ "state",
+ "uuid",
+ ]
+
export const cancelRunningWorkflow = (uuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
try {
}
};
-const getInputs = (data: any) => {
+export const getInputs = (data: any): CommandInputParameter[] => {
if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
const inputs = getWorkflowInputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
return inputs ? inputs.map(
id: it.id,
label: it.label,
default: data.mounts[MOUNT_PATH_CWL_INPUT].content[it.id],
+ value: data.mounts[MOUNT_PATH_CWL_INPUT].content[it.id.split('/').pop()] || [],
+ doc: it.doc
+ }
+ )
+ ) : [];
+};
+
+export type InputCollectionMount = {
+ path: string;
+ pdh: string;
+}
+
+export const getInputCollectionMounts = (data: any): InputCollectionMount[] => {
+ if (!data || !data.mounts) { return []; }
+ return Object.keys(data.mounts)
+ .map(key => ({
+ ...data.mounts[key],
+ path: key,
+ }))
+ .filter(mount => mount.kind === 'collection' &&
+ mount.portable_data_hash &&
+ mount.path)
+ .map(mount => ({
+ path: mount.path,
+ pdh: mount.portable_data_hash,
+ }));
+};
+
+export const getOutputParameters = (data: any): CommandOutputParameter[] => {
+ if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
+ const outputs = getWorkflowOutputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
+ return outputs ? outputs.map(
+ (it: any) => (
+ {
+ type: it.type,
+ id: it.id,
+ label: it.label,
doc: it.doc
}
)
//
// SPDX-License-Identifier: AGPL-3.0
-import React from 'react';
+import React, { useState } from 'react';
import { Grid, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
import { DefaultView } from 'components/default-view/default-view';
import { ProcessIcon } from 'components/icon/icon';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
import { ArvadosTheme } from 'common/custom-theme';
import { ProcessDetailsCard } from './process-details-card';
+import { getIOParamDisplayValue, ProcessIOCard, ProcessIOCardType, ProcessIOParameter } from './process-io-card';
+
import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
import { ProcessLogsCard } from './process-log-card';
import { FilterOption } from 'views/process-panel/process-log-form';
+import { getInputs, getInputCollectionMounts, getOutputParameters } from 'store/processes/processes-actions';
+import { CommandInputParameter, getIOParamId } from 'models/workflow';
+import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter';
+import { AuthState } from 'store/auth/auth-reducer';
+ import { ProcessCmdCard } from './process-cmd-card';
type CssRules = 'root';
subprocesses: Array<Process>;
filters: Array<SubprocessFilterDataProps>;
processLogsPanel: ProcessLogsPanel;
+ auth: AuthState;
}
export interface ProcessPanelRootActionProps {
cancelProcess: (uuid: string) => void;
onLogFilterChange: (filter: FilterOption) => void;
navigateToLog: (uuid: string) => void;
- onLogCopyToClipboard: (uuid: string) => void;
+ onCopyToClipboard: (uuid: string) => void;
+ fetchOutputs: (uuid: string, fetchOutputs) => void;
}
export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
+type OutputDetails = {
+ rawOutputs?: any;
+ pdh?: string;
+}
+
const panelsData: MPVPanelState[] = [
{name: "Details"},
+ {name: "Command"},
{name: "Logs", visible: true},
+ {name: "Inputs"},
+ {name: "Outputs"},
{name: "Subprocesses"},
];
export const ProcessPanelRoot = withStyles(styles)(
- ({ process, processLogsPanel, ...props }: ProcessPanelRootProps) =>
- process
+ ({ process, auth, processLogsPanel, fetchOutputs, ...props }: ProcessPanelRootProps) => {
+
+ const [outputDetails, setOutputs] = useState<OutputDetails>({});
+ const [rawInputs, setInputs] = useState<CommandInputParameter[]>([]);
+
+
+ const [processedOutputs, setProcessedOutputs] = useState<ProcessIOParameter[]>([]);
+ const [processedInputs, setProcessedInputs] = useState<ProcessIOParameter[]>([]);
+
+ const outputUuid = process?.containerRequest.outputUuid;
+ const requestUuid = process?.containerRequest.uuid;
+
+ const inputMounts = getInputCollectionMounts(process?.containerRequest);
+
+ React.useEffect(() => {
+ if (outputUuid) {
+ fetchOutputs(outputUuid, setOutputs);
+ }
+ }, [outputUuid, fetchOutputs]);
+
+ React.useEffect(() => {
+ if (outputDetails.rawOutputs && process) {
+ const outputDefinitions = getOutputParameters(process.containerRequest);
+ setProcessedOutputs(formatOutputData(outputDefinitions, outputDetails.rawOutputs, outputDetails.pdh, auth));
+ } else {
+ setProcessedOutputs([]);
+ }
+ }, [outputDetails, auth, process]);
+
+ React.useEffect(() => {
+ if (process) {
+ const rawInputs = getInputs(process.containerRequest);
+ setInputs(rawInputs);
+ setProcessedInputs(formatInputData(rawInputs, auth));
+ }
+ }, [requestUuid, auth, process]);
+
+ return 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-details">
<ProcessDetailsCard
cancelProcess={props.cancelProcess}
/>
</MPVPanelContent>
+ <MPVPanelContent forwardProps xs="auto" data-cy="process-cmd">
+ <ProcessCmdCard
+ onCopy={props.onCopyToClipboard}
+ process={process} />
+ </MPVPanelContent>
<MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
<ProcessLogsCard
- onCopy={props.onLogCopyToClipboard}
+ onCopy={props.onCopyToClipboard}
process={process}
lines={getProcessPanelLogs(processLogsPanel)}
selectedFilter={{
navigateToLog={props.navigateToLog}
/>
</MPVPanelContent>
+ <MPVPanelContent forwardProps xs="auto" data-cy="process-inputs">
+ <ProcessIOCard
+ label={ProcessIOCardType.INPUT}
+ params={processedInputs}
+ raw={rawInputs}
+ mounts={inputMounts}
+ />
+ </MPVPanelContent>
+ <MPVPanelContent forwardProps xs="auto" data-cy="process-outputs">
+ <ProcessIOCard
+ label={ProcessIOCardType.OUTPUT}
+ params={processedOutputs}
+ raw={outputDetails.rawOutputs}
+ outputUuid={outputUuid || ""}
+ />
+ </MPVPanelContent>
<MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-children">
<SubprocessPanel />
</MPVPanelContent>
<DefaultView
icon={ProcessIcon}
messages={['Process not found']} />
- </Grid>);
+ </Grid>;
+ }
+);
+
+const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
+ return inputs.map(input => {
+ const doc = Array.isArray(input.doc) ? input.doc.join(', ') : input.doc;
+ return {
+ id: getIOParamId(input),
+ doc: input.label || doc || "",
+ value: getIOParamDisplayValue(auth, input)
+ };
+ });
+};
+
+const formatOutputData = (definitions: CommandOutputParameter[], values: any, pdh: string | undefined, auth: AuthState): ProcessIOParameter[] => {
+ return definitions.map(output => {
+ const doc = Array.isArray(output.doc) ? output.doc.join(', ') : output.doc;
+ return {
+ id: getIOParamId(output),
+ doc: output.label || doc || "",
+ value: getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh)
+ };
+ });
+};
} from 'store/process-panel/process-panel';
import { groupBy } from 'lodash';
import {
+ loadOutputs,
toggleProcessPanelFilter,
} from 'store/process-panel/process-panel-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';
-const mapStateToProps = ({ router, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
+const mapStateToProps = ({ router, auth, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
const uuid = getProcessPanelCurrentUuid(router) || '';
const subprocesses = getSubprocesses(uuid)(resources);
return {
subprocesses: subprocesses.filter(subprocess => processPanel.filters[getProcessStatus(subprocess)]),
filters: getFilters(processPanel, subprocesses),
processLogsPanel: processLogsPanel,
+ auth: auth,
};
};
const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
- onLogCopyToClipboard: (message: string) => {
+ onCopyToClipboard: (message: string) => {
dispatch<any>(snackbarActions.OPEN_SNACKBAR({
message,
hideDuration: 2000,
cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid)),
onLogFilterChange: (filter) => dispatch(setProcessLogsPanelFilter(filter.value)),
navigateToLog: (uuid) => dispatch<any>(navigateToLogCollection(uuid)),
+ fetchOutputs: (uuid, setOutputs) => dispatch<any>(loadOutputs(uuid, setOutputs)),
});
const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
languageName: node
linkType: hard
- "@types/shell-quote@npm:1.6.0":
- version: 1.6.0
- resolution: "@types/shell-quote@npm:1.6.0"
- checksum: 5d9f4e35c8df32d9994f8ae2f1a1fe8a6b7ee96794f803e0904ceae7ad7255a214954e85cd75bd847fe77458d3746430522e87237438f223b7d72a23c4928c0e
+ "@types/shell-escape@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "@types/shell-escape@npm:0.2.0"
+ checksum: 020696ed313eeb02deb2abcc581e8b570be6f9ee662892339965b524bb4fbdc9a97b6520d914117740ec11147b0b1aa52358b8e03fa214c2da99743adb196853
languageName: node
linkType: hard
languageName: node
linkType: hard
- "array-filter@npm:~0.0.0":
- version: 0.0.1
- resolution: "array-filter@npm:0.0.1"
- checksum: 0e9afdf5e248c45821c6fe1232071a13a3811e1902c2c2a39d12e4495e8b0b25739fd95bffbbf9884b9693629621f6077b4ae16207b8f23d17710fc2465cebbb
- languageName: node
- linkType: hard
-
"array-find-index@npm:^1.0.1":
version: 1.0.2
resolution: "array-find-index@npm:1.0.2"
languageName: node
linkType: hard
- "array-map@npm:~0.0.0":
- version: 0.0.0
- resolution: "array-map@npm:0.0.0"
- checksum: 30d73fdc99956c8bd70daea40db5a7d78c5c2c75a03c64fc77904885e79adf7d5a0595076534f4e58962d89435f0687182ac929e65634e3d19931698cbac8149
- languageName: node
- linkType: hard
-
- "array-reduce@npm:~0.0.0":
- version: 0.0.0
- resolution: "array-reduce@npm:0.0.0"
- checksum: d6226325271f477e3dd65b4d40db8597735b8d08bebcca4972e52d3c173d6c697533664fa8865789ea2d076bdaf1989bab5bdfbb61598be92074a67f13057c3a
- languageName: node
- linkType: hard
-
"array-union@npm:^1.0.1":
version: 1.0.2
resolution: "array-union@npm:1.0.2"
"@types/redux-devtools": 3.0.44
"@types/redux-form": 7.4.12
"@types/redux-mock-store": 1.0.2
- "@types/shell-quote": 1.6.0
+ "@types/shell-escape": ^0.2.0
"@types/sinon": 7.5
"@types/uuid": 3.4.4
axios: ^0.21.1
lodash.template: 4.5.0
material-ui-pickers: ^2.2.4
mem: 4.0.0
+ mime: ^3.0.0
moment: 2.29.1
node-sass: ^4.9.4
node-sass-chokidar: 1.5.0
redux-thunk: 2.3.0
reselect: 4.0.0
set-value: 2.0.1
- shell-quote: 1.6.1
+ shell-escape: ^0.2.0
sinon: 7.3
ts-mock-imports: 1.3.7
tslint: 5.20.0
languageName: node
linkType: hard
+"mime@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "mime@npm:3.0.0"
+ bin:
+ mime: cli.js
+ checksum: f43f9b7bfa64534e6b05bd6062961681aeb406a5b53673b53b683f27fcc4e739989941836a355eef831f4478923651ecc739f4a5f6e20a76487b432bfd4db928
+ languageName: node
+ linkType: hard
+
"mimic-fn@npm:^1.0.0":
version: 1.2.0
resolution: "mimic-fn@npm:1.2.0"
languageName: node
linkType: hard
- "shell-quote@npm:1.6.1":
- version: 1.6.1
- resolution: "shell-quote@npm:1.6.1"
- dependencies:
- array-filter: ~0.0.0
- array-map: ~0.0.0
- array-reduce: ~0.0.0
- jsonify: ~0.0.0
- checksum: 982a4fdf2d474f0dc40885de4222f100ba457d7c75d46b532bf23b01774b8617bc62522c6825cb1fa7dd4c54c18e9dcbae7df2ca8983101841b6f2e6a7cacd2f
+ "shell-escape@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "shell-escape@npm:0.2.0"
+ checksum: 0d87f1ae22ad22a74e148348ceaf64721e3024f83c90afcfb527318ce10ece654dd62e103dd89a242f2f4e4ce3cecdef63e3d148c40e5fabca8ba6c508f97d9f
languageName: node
linkType: hard