From: Peter Amstutz Date: Wed, 25 May 2022 13:45:54 +0000 (-0400) Subject: Merge branch '19143-project-list-workflows' X-Git-Tag: 2.4.1~1^2~1 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/bf6ffb898a38a806ea0dd98daca7b3801923b62f?hp=d1c2611350e47527730f6d3b226b998abf15a607 Merge branch '19143-project-list-workflows' refs #19143 refs #19069 Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- diff --git a/cypress/integration/create-workflow.spec.js b/cypress/integration/create-workflow.spec.js index b1ea5dbf..8df8389f 100644 --- a/cypress/integration/create-workflow.spec.js +++ b/cypress/integration/create-workflow.spec.js @@ -73,12 +73,12 @@ describe('Multi-file deletion tests', function () { cy.get('[data-cy=run-process-next-button]').click(); - cy.get('[data-cy=new-process-panel]').contains('Run Process').should('be.disabled'); + cy.get('[data-cy=new-process-panel]').contains('Run workflow').should('be.disabled'); cy.get('[data-cy=new-process-panel]') .within(() => { cy.get('[name=name]').type(`Workflow name (${Math.floor(Math.random() * 999999)})`); - cy.get('[readonly]').click(); + cy.contains('input').next().click(); }); cy.get('[data-cy=choose-a-file-dialog]').as('chooseFileDialog'); @@ -105,7 +105,7 @@ describe('Multi-file deletion tests', function () { cy.get('@chooseFileDialog').find('button').contains('Ok').click(); cy.get('[data-cy=new-process-panel]') - .find('button').contains('Run Process').should('not.be.disabled'); + .find('button').contains('Run workflow').should('not.be.disabled'); }); }); @@ -148,14 +148,14 @@ describe('Multi-file deletion tests', function () { cy.get('[data-cy=side-panel-button]').click(); - cy.get('#aside-menu-list').contains('Run a process').click(); + cy.get('#aside-menu-list').contains('Run a workflow').click(); cy.get('@testWorkflow') .then((testWorkflow) => { cy.get('main').contains(testWorkflow.name).click(); cy.get('[data-cy=run-process-next-button]').click(); - cy.get('label').contains('#main/foo').parent('div').find('input').click(); + cy.get('label').contains('foo').parent('div').find('input').click(); cy.get('div[role=dialog]') .within(() => { cy.get('p').contains('Projects').closest('div[role=button]') @@ -174,7 +174,7 @@ describe('Multi-file deletion tests', function () { cy.get('[data-cy=ok-button]').click(); }); - cy.get('label').contains('#main/bar').parent('div').find('input').click(); + cy.get('label').contains('bar').parent('div').find('input').click(); cy.get('div[role=dialog]') .within(() => { cy.get('p').contains('Projects').closest('div[role=button]') @@ -193,13 +193,13 @@ describe('Multi-file deletion tests', function () { }); }); - cy.get('label').contains('#main/foo').parent('div') + cy.get('label').contains('foo').parent('div') .within(() => { cy.contains('baz'); cy.contains('bar'); }); - cy.get('label').contains('#main/bar').parent('div') + cy.get('label').contains('bar').parent('div') .within(() => { cy.contains(testCollection.name); cy.contains(testCollection2.name); diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js index 9bc90ebd..105657ef 100644 --- a/cypress/integration/favorites.spec.js +++ b/cypress/integration/favorites.spec.js @@ -219,13 +219,16 @@ describe('Favorites tests', function () { cy.get('[data-cy=side-panel-button]').click(); - cy.get('#aside-menu-list').contains('Run a process').click(); + cy.get('#aside-menu-list').contains('Run a workflow').click(); cy.get('@testWorkflow') .then((testWorkflow) => { cy.get('main').contains(testWorkflow.name).click(); cy.get('[data-cy=run-process-next-button]').click(); - cy.get('[readonly]').click(); + cy.get('[data-cy=new-process-panel]') + .within(() => { + cy.contains('input').next().click(); + }); cy.get('[data-cy=choose-a-file-dialog]').as('chooseFileDialog'); cy.get('[data-cy=projects-tree-favourites-tree-picker]').contains('Favorites').closest('ul').find('i').click(); cy.get('@chooseFileDialog').find(`[data-id=${mySharedWritableProject.uuid}]`); @@ -240,7 +243,10 @@ describe('Favorites tests', function () { cy.get('main').contains(testWorkflow2.name).click(); cy.get('button').contains('Change Workflow').click(); cy.get('[data-cy=run-process-next-button]').click(); - cy.get('[readonly]').click(); + cy.get('[data-cy=new-process-panel]') + .within(() => { + cy.contains('image_collection').next().click(); + }); cy.get('[data-cy=choose-a-directory-dialog]').as('chooseDirectoryDialog'); cy.get('[data-cy=projects-tree-favourites-tree-picker]').contains('Favorites').closest('ul').find('i').click(); cy.get('@chooseDirectoryDialog').find(`[data-id=${mySharedWritableProject.uuid}]`); diff --git a/cypress/integration/side-panel.spec.js b/cypress/integration/side-panel.spec.js index 4c824d32..e187d533 100644 --- a/cypress/integration/side-panel.spec.js +++ b/cypress/integration/side-panel.spec.js @@ -62,7 +62,6 @@ describe('Side panel tests', function() { {url: '/shared-with-me', label: 'Shared with me'}, {url: '/public-favorites', label: 'Public Favorites'}, {url: '/favorites', label: 'My Favorites'}, - {url: '/workflows', label: 'Workflows'}, {url: '/all_processes', label: 'All Processes'}, {url: '/trash', label: 'Trash'}, ].map(function(section) { diff --git a/src/common/labels.ts b/src/common/labels.ts index 682513fb..e784cec0 100644 --- a/src/common/labels.ts +++ b/src/common/labels.ts @@ -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"; } diff --git a/src/index.tsx b/src/index.tsx index f928ea8a..03840d49 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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(); diff --git a/src/models/details.ts b/src/models/details.ts index 150b6940..b6eabd70 100644 --- a/src/models/details.ts +++ b/src/models/details.ts @@ -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; diff --git a/src/models/workflow.ts b/src/models/workflow.ts index ad84bd9e..6d21dbc7 100644 --- a/src/models/workflow.ts +++ b/src/models/workflow.ts @@ -139,7 +139,7 @@ export const parseWorkflowDefinition = (workflow: WorkflowResource): WorkflowRes export const getWorkflow = (workflowDefinition: WorkflowResourceDefinition) => { if (!workflowDefinition.$graph) { return undefined; } - const mainWorkflow = workflowDefinition.$graph.find(item => item.class === 'Workflow' && item.id === '#main'); + const mainWorkflow = workflowDefinition.$graph.find(item => item.id === '#main'); return mainWorkflow ? mainWorkflow : undefined; @@ -153,7 +153,7 @@ export const getWorkflowInputs = (workflowDefinition: WorkflowResourceDefinition }; export const getInputLabel = (input: CommandInputParameter) => { - return `${input.label || input.id}`; + return `${input.label || input.id.split('/').pop()}`; }; export const isRequiredInput = ({ type }: CommandInputParameter) => { diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts index 5b3ce668..237b6d96 100644 --- a/src/routes/route-change-handlers.ts +++ b/src/routes/route-change-handlers.ts @@ -33,7 +33,6 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { const runProcessMatch = Routes.matchRunProcessRoute(pathname); const virtualMachineUserMatch = Routes.matchUserVirtualMachineRoute(pathname); const virtualMachineAdminMatch = Routes.matchAdminVirtualMachineRoute(pathname); - const workflowMatch = Routes.matchWorkflowRoute(pathname); const sshKeysUserMatch = Routes.matchSshKeysUserRoute(pathname); const sshKeysAdminMatch = Routes.matchSshKeysAdminRoute(pathname); const siteManagerMatch = Routes.matchSiteManagerRoute(pathname); @@ -77,8 +76,6 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => { store.dispatch(WorkbenchActions.loadSharedWithMe); } else if (runProcessMatch) { store.dispatch(WorkbenchActions.loadRunProcess); - } else if (workflowMatch) { - store.dispatch(WorkbenchActions.loadWorkflow); } else if (searchResultsMatch) { store.dispatch(WorkbenchActions.loadSearchResults); } else if (virtualMachineUserMatch) { diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 1116949a..3e239fee 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -189,7 +189,7 @@ export const openProcessContextMenu = (event: React.MouseEvent, pro name: res.name, description: res.description, outputUuid: res.outputUuid || '', - workflowUuid: res.properties.workflowUuid || '', + workflowUuid: res.properties.template_uuid || '', menuKind: ContextMenuKind.PROCESS_RESOURCE })); } @@ -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; } diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts index 1cdb6784..c8811bf4 100644 --- a/src/store/navigation/navigation-action.ts +++ b/src/store/navigation/navigation-action.ts @@ -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(navigateToAdminVirtualMachines); return; + case ResourceKind.WORKFLOW: + dispatch(openDetailsPanel(uuid)); + return; } switch (uuid) { @@ -58,9 +62,6 @@ export const navigateTo = (uuid: string) => case SidePanelTreeCategory.SHARED_WITH_ME: dispatch(navigateToSharedWithMe); return; - case SidePanelTreeCategory.WORKFLOWS: - dispatch(navigateToWorkflows); - return; case SidePanelTreeCategory.TRASH: dispatch(navigateToTrash); return; diff --git a/src/store/processes/processes-actions.ts b/src/store/processes/processes-actions.ts index 6bd2976c..213e292b 100644 --- a/src/store/processes/processes-actions.ts +++ b/src/store/processes/processes-actions.ts @@ -18,6 +18,8 @@ import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "views/run-proce 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 { ProjectResource } from "models/project"; +import { UserResource } from "models/user"; export const loadProcess = (containerRequestUuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { @@ -60,7 +62,8 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) => const stringifiedDefinition = JSON.stringify(process.mounts[MOUNT_PATH_CWL_WORKFLOW].content); const newWorkflow = { ...workflow, definition: stringifiedDefinition }; - const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description }; + const owner = getResource(workflow.ownerUuid)(getState().resources); + const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description, owner }; dispatch(initialize(RUN_PROCESS_BASIC_FORM, basicInitialData)); const advancedInitialData: RunProcessAdvancedFormData = { diff --git a/src/store/resource-type-filters/resource-type-filters.test.ts b/src/store/resource-type-filters/resource-type-filters.test.ts index a3684507..f001770e 100644 --- a/src/store/resource-type-filters/resource-type-filters.test.ts +++ b/src/store/resource-type-filters/resource-type-filters.test.ts @@ -31,20 +31,21 @@ describe("serializeResourceTypeFilters", () => { const filters = getInitialResourceTypeFilters(); const serializedFilters = serializeResourceTypeFilters(filters); expect(serializedFilters) - .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.COLLECTION}"]]`); + .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.COLLECTION}","${ResourceKind.WORKFLOW}"]]`); }); it("should serialize all but collection filters", () => { const filters = deselectNode(ObjectTypeFilter.COLLECTION)(getInitialResourceTypeFilters()); const serializedFilters = serializeResourceTypeFilters(filters); expect(serializedFilters) - .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}"]]`); + .toEqual(`["uuid","is_a",["${ResourceKind.PROJECT}","${ResourceKind.PROCESS}","${ResourceKind.WORKFLOW}"]]`); }); it("should serialize output collections and projects", () => { const filters = pipe( () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROCESS), + deselectNode(ObjectTypeFilter.WORKFLOW), deselectNode(CollectionTypeFilter.GENERAL_COLLECTION), deselectNode(CollectionTypeFilter.LOG_COLLECTION), deselectNode(CollectionTypeFilter.INTERMEDIATE_COLLECTION), @@ -59,6 +60,7 @@ describe("serializeResourceTypeFilters", () => { const filters = pipe( () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROCESS), + deselectNode(ObjectTypeFilter.WORKFLOW), deselectNode(CollectionTypeFilter.GENERAL_COLLECTION), deselectNode(CollectionTypeFilter.LOG_COLLECTION), deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION), @@ -74,6 +76,7 @@ describe("serializeResourceTypeFilters", () => { () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROJECT), deselectNode(ObjectTypeFilter.PROCESS), + deselectNode(ObjectTypeFilter.WORKFLOW), deselectNode(CollectionTypeFilter.OUTPUT_COLLECTION) )(); @@ -87,7 +90,8 @@ describe("serializeResourceTypeFilters", () => { () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROJECT), deselectNode(ProcessTypeFilter.CHILD_PROCESS), - deselectNode(ObjectTypeFilter.COLLECTION) + deselectNode(ObjectTypeFilter.COLLECTION), + deselectNode(ObjectTypeFilter.WORKFLOW), )(); const serializedFilters = serializeResourceTypeFilters(filters); @@ -100,7 +104,8 @@ describe("serializeResourceTypeFilters", () => { () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROJECT), deselectNode(ProcessTypeFilter.MAIN_PROCESS), - deselectNode(ObjectTypeFilter.COLLECTION) + deselectNode(ObjectTypeFilter.COLLECTION), + deselectNode(ObjectTypeFilter.WORKFLOW), )(); const serializedFilters = serializeResourceTypeFilters(filters); @@ -113,6 +118,7 @@ describe("serializeResourceTypeFilters", () => { () => getInitialResourceTypeFilters(), deselectNode(ObjectTypeFilter.PROCESS), deselectNode(ObjectTypeFilter.COLLECTION), + deselectNode(ObjectTypeFilter.WORKFLOW), )(); const serializedFilters = serializeResourceTypeFilters(filters); @@ -126,6 +132,7 @@ describe("serializeResourceTypeFilters", () => { deselectNode(GroupTypeFilter.PROJECT), deselectNode(ObjectTypeFilter.PROCESS), deselectNode(ObjectTypeFilter.COLLECTION), + deselectNode(ObjectTypeFilter.WORKFLOW), )(); const serializedFilters = serializeResourceTypeFilters(filters); @@ -139,6 +146,7 @@ describe("serializeResourceTypeFilters", () => { deselectNode(GroupTypeFilter.FILTER_GROUP), deselectNode(ObjectTypeFilter.PROCESS), deselectNode(ObjectTypeFilter.COLLECTION), + deselectNode(ObjectTypeFilter.WORKFLOW), )(); const serializedFilters = serializeResourceTypeFilters(filters); diff --git a/src/store/resource-type-filters/resource-type-filters.ts b/src/store/resource-type-filters/resource-type-filters.ts index 0539cefe..64a391ca 100644 --- a/src/store/resource-type-filters/resource-type-filters.ts +++ b/src/store/resource-type-filters/resource-type-filters.ts @@ -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/store/run-process-panel/run-process-panel-actions.test.ts b/src/store/run-process-panel/run-process-panel-actions.test.ts index 745be88f..c615f216 100644 --- a/src/store/run-process-panel/run-process-panel-actions.test.ts +++ b/src/store/run-process-panel/run-process-panel-actions.test.ts @@ -66,8 +66,8 @@ describe("run-process-panel-actions", () => { email: "test@gmail.com", firstName: "TestFirstName", lastName: "TestLastName", - uuid: "ce8i5-tpzed-yid70bw31f51234", - ownerUuid: "ce8i5-tpzed-000000000000000", + uuid: "zzzzz-tpzed-yid70bw31f51234", + ownerUuid: "zzzzz-tpzed-000000000000000", isAdmin: false, isActive: true, username: "testfirstname", @@ -77,17 +77,17 @@ describe("run-process-panel-actions", () => { }, }, runProcessPanel: { - processPathname: "/projects/ce8i5-tpzed-yid70bw31f51234", - processOwnerUuid: "ce8i5-tpzed-yid70bw31f51234", + processPathname: "/projects/zzzzz-tpzed-yid70bw31f51234", + processOwnerUuid: "zzzzz-tpzed-yid70bw31f51234", selectedWorkflow: { - href: "/workflows/ce8i5-7fd4e-2tlnerdkxnl4fjt", + href: "/workflows/zzzzz-7fd4e-2tlnerdkxnl4fjt", kind: "arvados#workflow", etag: "8gh5xlhlgo61yqscyl1spw8tc", - uuid: "ce8i5-7fd4e-2tlnerdkxnl4fjt", - ownerUuid: "ce8i5-tpzed-o4njwilpp4ov321", + uuid: "zzzzz-7fd4e-2tlnerdkxnl4fjt", + ownerUuid: "zzzzz-tpzed-o4njwilpp4ov321", createdAt: "2020-07-15T19:40:50.296041000Z", - modifiedByClientUuid: "ce8i5-ozdt8-libnr89sc5nq111", - modifiedByUserUuid: "ce8i5-tpzed-o4njwilpp4ov321", + modifiedByClientUuid: "zzzzz-ozdt8-libnr89sc5nq111", + modifiedByUserUuid: "zzzzz-tpzed-o4njwilpp4ov321", modifiedAt: "2020-07-15T19:40:50.296376000Z", name: "revsort.cwl", description: @@ -107,7 +107,7 @@ describe("run-process-panel-actions", () => { "arvados-cwl-runner", "--api=containers", "--local", - "--project-uuid=ce8i5-tpzed-yid70bw31f51234", + "--project-uuid=zzzzz-tpzed-yid70bw31f51234", "/var/lib/cwl/workflow.json#main", "/var/lib/cwl/cwl.input.json", ], @@ -116,13 +116,13 @@ describe("run-process-panel-actions", () => { description: "basicFormTestDescription", mounts: undefined, name: "basicFormTestName", - outputName: undefined, + outputName: "Output from basicFormTestName", outputPath: "/var/spool/cwl", - ownerUuid: "ce8i5-tpzed-yid70bw31f51234", + ownerUuid: "zzzzz-tpzed-yid70bw31f51234", priority: 1, properties: { workflowName: "revsort.cwl", - workflowUuid: "ce8i5-7fd4e-2tlnerdkxnl4fjt", + template_uuid: "zzzzz-7fd4e-2tlnerdkxnl4fjt", }, runtimeConstraints: { API: true, diff --git a/src/store/run-process-panel/run-process-panel-actions.ts b/src/store/run-process-panel/run-process-panel-actions.ts index adb5ade7..e0dada5c 100644 --- a/src/store/run-process-panel/run-process-panel-actions.ts +++ b/src/store/run-process-panel/run-process-panel-actions.ts @@ -21,6 +21,9 @@ import { } from 'views/run-process-panel/run-process-advanced-form'; import { dialogActions } from 'store/dialog/dialog-actions'; import { setBreadcrumbs } from 'store/breadcrumbs/breadcrumbs-actions'; +import { getResource } from 'store/resources/resources'; +import { ProjectResource } from "models/project"; +import { UserResource } from "models/user"; export const runProcessPanelActions = unionize({ SET_PROCESS_PATHNAME: ofType(), @@ -99,15 +102,23 @@ export const setWorkflow = (workflow: WorkflowResource, isWorkflowChanged = true const advancedFormValues = getWorkflowRunnerSettings(workflow); + let owner = getResource(getState().runProcessPanel.processOwnerUuid)(getState().resources); + const userUuid = getUserUuid(getState()); + if (!owner || !userUuid || owner.writableBy.indexOf(userUuid) === -1) { + owner = undefined; + } + if (isStepChanged && isWorkflowChanged) { dispatch(runProcessPanelActions.SET_STEP_CHANGED(false)); dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow)); dispatch(loadPresets(workflow.uuid)); + dispatch(initialize(RUN_PROCESS_BASIC_FORM, { name: workflow.name, owner })); dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedFormValues)); } if (!isWorkflowChanged) { dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow)); dispatch(loadPresets(workflow.uuid)); + dispatch(initialize(RUN_PROCESS_BASIC_FORM, { name: workflow.name, owner })); dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedFormValues)); } }; @@ -144,7 +155,7 @@ export const runProcess = async (dispatch: Dispatch, getState: () => RootSt const userUuid = getUserUuid(getState()); if (!userUuid) { return; } const { processOwnerUuid, selectedWorkflow } = state.runProcessPanel; - const ownerUUid = processOwnerUuid ? processOwnerUuid : userUuid; + const ownerUUid = basicForm.owner ? basicForm.owner.uuid : (processOwnerUuid ? processOwnerUuid : userUuid); if (selectedWorkflow) { const advancedForm = getFormValues(RUN_PROCESS_ADVANCED_FORM)(state) as RunProcessAdvancedFormData || getWorkflowRunnerSettings(selectedWorkflow); const newProcessData = { @@ -173,9 +184,9 @@ export const runProcess = async (dispatch: Dispatch, getState: () => RootSt ], outputPath: '/var/spool/cwl', priority: 1, - outputName: advancedForm[OUTPUT_FIELD] ? advancedForm[OUTPUT_FIELD] : undefined, + outputName: advancedForm[OUTPUT_FIELD] ? advancedForm[OUTPUT_FIELD] : `Output from ${basicForm.name}`, properties: { - workflowUuid: selectedWorkflow.uuid, + template_uuid: selectedWorkflow.uuid, workflowName: selectedWorkflow.name }, useExisting: false diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts index 895fe79c..dd56b428 100644 --- a/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/src/store/side-panel-tree/side-panel-tree-actions.ts @@ -23,7 +23,6 @@ export enum SidePanelTreeCategory { PROJECTS = 'Projects', SHARED_WITH_ME = 'Shared with me', PUBLIC_FAVORITES = 'Public Favorites', - WORKFLOWS = 'Workflows', FAVORITES = 'My Favorites', TRASH = 'Trash', ALL_PROCESSES = 'All Processes', @@ -52,7 +51,6 @@ let SIDE_PANEL_CATEGORIES: string[] = [ SidePanelTreeCategory.SHARED_WITH_ME, SidePanelTreeCategory.PUBLIC_FAVORITES, SidePanelTreeCategory.FAVORITES, - SidePanelTreeCategory.WORKFLOWS, SidePanelTreeCategory.GROUPS, SidePanelTreeCategory.ALL_PROCESSES, SidePanelTreeCategory.TRASH diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index ba405cb8..d2ff84b3 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -58,7 +58,7 @@ import { sharedWithMePanelActions } from 'store/shared-with-me-panel/shared-with-me-panel-actions'; import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog'; -import { loadWorkflowPanel, workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions'; +import { workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions'; import { loadSshKeysPanel } from 'store/auth/auth-action-ssh'; import { loadLinkAccountPanel, linkAccountPanelActions } from 'store/link-account-panel/link-account-panel-actions'; import { loadSiteManagerPanel } from 'store/auth/auth-action-session'; @@ -457,12 +457,6 @@ export const loadRunProcess = handleFirstTimeLoad( } ); -export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch) => { - dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS)); - await dispatch(loadWorkflowPanel()); - dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS)); -}); - export const loadPublicFavorites = () => handleFirstTimeLoad( (dispatch: Dispatch) => { diff --git a/src/store/workflow-panel/workflow-panel-actions.test.ts b/src/store/workflow-panel/workflow-panel-actions.test.ts index 1e2b0488..1a7cad82 100644 --- a/src/store/workflow-panel/workflow-panel-actions.test.ts +++ b/src/store/workflow-panel/workflow-panel-actions.test.ts @@ -82,7 +82,6 @@ describe('workflow-panel-actions', () => { await openRunProcess("zzzzz-7fd4e-0123456789abcde", "zzzzz-tpzed-0123456789abcde", "testing", { inputparm: "value" })(dispatchWrapper, store.getState, services); expect(dispatchMock).toHaveBeenCalledWith(runProcessPanelActions.SET_WORKFLOWS(wflist)); expect(dispatchMock).toHaveBeenCalledWith(runProcessPanelActions.SET_SELECTED_WORKFLOW(wflist[0])); - expect(dispatchMock).toHaveBeenCalledWith(runProcessPanelActions.SET_PROCESS_OWNER_UUID("zzzzz-tpzed-0123456789abcde")); expect(dispatchMock).toHaveBeenCalledWith(initialize(RUN_PROCESS_BASIC_FORM, { name: "testing" })); expect(dispatchMock).toHaveBeenCalledWith(initialize(RUN_PROCESS_INPUTS_FORM, { inputparm: "value" })); }); diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts index b533717e..7c90fa6b 100644 --- a/src/store/workflow-panel/workflow-panel-actions.ts +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -15,6 +15,10 @@ import { initialize } from 'redux-form'; import { RUN_PROCESS_BASIC_FORM } from 'views/run-process-panel/run-process-basic-form'; import { RUN_PROCESS_INPUTS_FORM } from 'views/run-process-panel/run-process-inputs-form'; import { RUN_PROCESS_ADVANCED_FORM } from 'views/run-process-panel/run-process-advanced-form'; +import { getResource, ResourcesState } from 'store/resources/resources'; +import { ProjectResource } from 'models/project'; +import { UserResource } from 'models/user'; +import { getUserUuid } from "common/getuser"; export const WORKFLOW_PANEL_ID = "workflowPanel"; const UUID_PREFIX_PROPERTY_NAME = 'uuidPrefix'; @@ -50,12 +54,21 @@ export const openRunProcess = (workflowUuid: string, ownerUuid?: string, name?: dispatch(loadPresets(workflow.uuid)); dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, getWorkflowRunnerSettings(workflow))); + let owner; if (ownerUuid) { - dispatch(runProcessPanelActions.SET_PROCESS_OWNER_UUID(ownerUuid)); + // Must be writable. + const userUuid = getUserUuid(getState()); + owner = getResource(ownerUuid)(getState().resources); + if (!owner || !userUuid || owner.writableBy.indexOf(userUuid) === -1) { + owner = undefined; + } } - if (name) { - dispatch(initialize(RUN_PROCESS_BASIC_FORM, { name })); + if (owner) { + dispatch(runProcessPanelActions.SET_PROCESS_OWNER_UUID(owner.uuid)); } + + dispatch(initialize(RUN_PROCESS_BASIC_FORM, { name, owner })); + if (inputObj) { dispatch(initialize(RUN_PROCESS_INPUTS_FORM, inputObj)); } 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 index 00000000..2aa78904 --- /dev/null +++ b/src/views-components/context-menu/action-sets/workflow-action-set.ts @@ -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(openRunProcess(resource.uuid, resource.ownerUuid, resource.name)); + } + }, +]]; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index 6f3a4389..4766259a 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -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", } diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx index 399f4ef4..adbbab79 100644 --- a/src/views-components/details-panel/details-panel.tsx +++ b/src/views-components/details-panel/details-panel.tsx @@ -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)( = tabNr+1) ? tabNr : 0}> - { item.getTabLabels().map((tabLabel, idx) => + value={(item.getTabLabels().length >= tabNr + 1) ? tabNr : 0}> + {item.getTabLabels().map((tabLabel, idx) => ) } - {item.getDetails({tabNr, showPreview: shouldShowInlinePreview})} + {item.getDetails({ tabNr, showPreview: shouldShowInlinePreview })} ; } diff --git a/src/views-components/details-panel/workflow-details.tsx b/src/views-components/details-panel/workflow-details.tsx new file mode 100644 index 00000000..7076823c --- /dev/null +++ b/src/views-components/details-panel/workflow-details.tsx @@ -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(openRunProcess(wf.uuid, wf.ownerUuid, wf.name)), +}); + +type CssRules = 'runButton'; + +const styles: StyleRulesCallback = (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) => { + return + + {workflow && workflow.description !== "" && + + } + + + + + } /> + + + + + + + + + } /> + + ; + })); + +export class WorkflowDetails extends DetailsData { + getIcon(className?: string) { + return ; + } + + getDetails() { + return ; + } +} diff --git a/src/views-components/side-panel-button/side-panel-button.tsx b/src/views-components/side-panel-button/side-panel-button.tsx index a219e55a..c813efb0 100644 --- a/src/views-components/side-panel-button/side-panel-button.tsx +++ b/src/views-components/side-panel-button/side-panel-button.tsx @@ -107,7 +107,7 @@ export const SidePanelButton = withStyles(styles)( New collection - Run a process + Run a workflow New project diff --git a/src/views-components/side-panel-tree/side-panel-tree.tsx b/src/views-components/side-panel-tree/side-panel-tree.tsx index e8294834..7f5b8d73 100644 --- a/src/views-components/side-panel-tree/side-panel-tree.tsx +++ b/src/views-components/side-panel-tree/side-panel-tree.tsx @@ -10,7 +10,6 @@ import { TreeItem } from "components/tree/tree"; import { ProjectResource } from "models/project"; import { ListItemTextIcon } from "components/list-item-text-icon/list-item-text-icon"; import { ProcessIcon, ProjectIcon, FilterGroupIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, TrashIcon, PublicFavoriteIcon, GroupsIcon } from 'components/icon/icon'; -import { WorkflowIcon } from 'components/icon/icon'; import { activateSidePanelTreeItem, toggleSidePanelTreeItemCollapse, SIDE_PANEL_TREE, SidePanelTreeCategory } from 'store/side-panel-tree/side-panel-tree-actions'; import { openSidePanelContextMenu } from 'store/context-menu/context-menu-actions'; import { noop } from 'lodash'; @@ -42,7 +41,7 @@ const mapDispatchToProps = (dispatch: Dispatch, props: SidePanelTreeProps): Side export const SidePanelTree = connect(undefined, mapDispatchToProps)( (props: SidePanelTreeActionProps) => - + ); const renderSidePanelItem = (item: TreeItem) => { @@ -76,8 +75,6 @@ const getSidePanelIcon = (category: string) => { return ShareMeIcon; case SidePanelTreeCategory.TRASH: return TrashIcon; - case SidePanelTreeCategory.WORKFLOWS: - return WorkflowIcon; case SidePanelTreeCategory.PUBLIC_FAVORITES: return PublicFavoriteIcon; case SidePanelTreeCategory.ALL_PROCESSES: diff --git a/src/views/process-panel/process-details-attributes.tsx b/src/views/process-panel/process-details-attributes.tsx index d0e593d5..99a4404c 100644 --- a/src/views/process-panel/process-details-attributes.tsx +++ b/src/views/process-panel/process-details-attributes.tsx @@ -74,7 +74,7 @@ export const ProcessDetailsAttributes = withStyles(styles, { withTheme: true })( + linkToUuid={containerRequest.containerImage} value={containerRequest.containerImage} /> {!props.hideProcessPanelRedundantFields && - + } @@ -112,13 +112,13 @@ export const ProcessDetailsAttributes = withStyles(styles, { withTheme: true })( - {containerRequest.properties.workflowUuid && - - props.openWorkflow(containerRequest.properties.workflowUuid)}> - - - } + {containerRequest.properties.template_uuid && + + props.openWorkflow(containerRequest.properties.template_uuid)}> + + + } @@ -128,13 +128,13 @@ export const ProcessDetailsAttributes = withStyles(styles, { withTheme: true })( */} - { Object.keys(containerRequest.properties).length > 0 + {Object.keys(containerRequest.properties).length > 0 ? Object.keys(containerRequest.properties).map(k => - Array.isArray(containerRequest.properties[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)) - :
No properties
} + :
No properties
}
; } diff --git a/src/views/run-process-panel/inputs/project-input.tsx b/src/views/run-process-panel/inputs/project-input.tsx new file mode 100644 index 00000000..7b45a6d1 --- /dev/null +++ b/src/views/run-process-panel/inputs/project-input.tsx @@ -0,0 +1,136 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { connect, DispatchProp } from 'react-redux'; +import { Field } from 'redux-form'; +import { Input, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core'; +import { + GenericCommandInputParameter +} from 'models/workflow'; +import { GenericInput, GenericInputProps } from './generic-input'; +import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker'; +import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions'; +import { TreeItem } from 'components/tree/tree'; +import { ProjectsTreePickerItem } from 'views-components/projects-tree-picker/generic-projects-tree-picker'; +import { ProjectResource } from 'models/project'; +import { ResourceKind } from 'models/resource'; +import { RootState } from 'store/store'; +import { getUserUuid } from 'common/getuser'; + +export type ProjectCommandInputParameter = GenericCommandInputParameter; + +const require: any = (value?: ProjectResource) => (value === undefined); + +export interface ProjectInputProps { + input: ProjectCommandInputParameter; + options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }; +} +export const ProjectInput = ({ input, options }: ProjectInputProps) => + ; + +const format = (value?: ProjectResource) => value ? value.name : ''; + +interface ProjectInputComponentState { + open: boolean; + project?: ProjectResource; +} + +interface HasUserUuid { + userUuid: string; +}; + +const mapStateToProps = (state: RootState) => ({ userUuid: getUserUuid(state) }); + +export const ProjectInputComponent = connect(mapStateToProps)( + class ProjectInputComponent extends React.Component { + state: ProjectInputComponentState = { + open: false, + }; + + componentDidMount() { + this.props.dispatch( + initProjectsTreePicker(this.props.commandInput.id)); + } + + render() { + return <> + {this.renderInput()} + {this.renderDialog()} + ; + } + + openDialog = () => { + this.setState({ open: true }); + } + + closeDialog = () => { + this.setState({ open: false }); + } + + submit = () => { + this.closeDialog(); + this.props.input.onChange(this.state.project); + } + + setProject = (_: {}, { data }: TreeItem) => { + if ('kind' in data && data.kind === ResourceKind.PROJECT) { + this.setState({ project: data }); + } else { + this.setState({ project: undefined }); + } + } + + invalid = () => (!this.state.project || this.state.project.writableBy.indexOf(this.props.userUuid) === -1); + + renderInput() { + return + } + {...this.props} />; + } + + renderDialog() { + return + Choose a project + + + + + + + + ; + } + + }); diff --git a/src/views/run-process-panel/run-process-basic-form.tsx b/src/views/run-process-panel/run-process-basic-form.tsx index 13d882ba..32a126a4 100644 --- a/src/views/run-process-panel/run-process-basic-form.tsx +++ b/src/views/run-process-panel/run-process-basic-form.tsx @@ -6,14 +6,19 @@ import React from 'react'; import { reduxForm, Field } from 'redux-form'; import { Grid } from '@material-ui/core'; import { TextField } from 'components/text-field/text-field'; +import { ProjectInput, ProjectCommandInputParameter } from 'views/run-process-panel/inputs/project-input'; import { PROCESS_NAME_VALIDATION } from 'validators/validators'; +import { ProjectResource } from 'models/project'; +import { UserResource } from 'models/user'; export const RUN_PROCESS_BASIC_FORM = 'runProcessBasicForm'; export interface RunProcessBasicFormData { name: string; description: string; + owner?: ProjectResource | UserResource; } + export const RunProcessBasicForm = reduxForm({ form: RUN_PROCESS_BASIC_FORM @@ -24,7 +29,7 @@ export const RunProcessBasicForm = @@ -32,7 +37,14 @@ export const RunProcessBasicForm = + label="Optional description of this workflow run" /> + + + ); diff --git a/src/views/run-process-panel/run-process-panel-root.tsx b/src/views/run-process-panel/run-process-panel-root.tsx index 3c42437a..9dd5aa3f 100644 --- a/src/views/run-process-panel/run-process-panel-root.tsx +++ b/src/views/run-process-panel/run-process-panel-root.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; -import { Stepper, Step, StepLabel, StepContent } from '@material-ui/core'; +import { Stepper, Step, StepLabel, StepContent, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core'; import { RunProcessFirstStepDataProps, RunProcessFirstStepActionProps, RunProcessFirstStep } from 'views/run-process-panel/run-process-first-step'; import { RunProcessSecondStepForm } from './run-process-second-step'; @@ -17,25 +17,34 @@ export type RunProcessPanelRootActionProps = RunProcessFirstStepActionProps & { type RunProcessPanelRootProps = RunProcessPanelRootDataProps & RunProcessPanelRootActionProps; -export const RunProcessPanelRoot = ({ runProcess, currentStep, onSearch, onSetStep, onSetWorkflow, workflows, selectedWorkflow }: RunProcessPanelRootProps) => - - - Choose a workflow - - - - - - Select inputs - - onSetStep(0)} - runProcess={runProcess} /> - - - ; \ No newline at end of file +type CssRules = 'stepper'; + +const styles: StyleRulesCallback = theme => ({ + stepper: { + overflow: "scroll", + } +}); + +export const RunProcessPanelRoot = withStyles(styles)( + ({ runProcess, currentStep, onSearch, onSetStep, onSetWorkflow, workflows, selectedWorkflow, classes }: WithStyles & RunProcessPanelRootProps) => + + + Choose a workflow + + + + + + Select inputs + + onSetStep(0)} + runProcess={runProcess} /> + + + ); diff --git a/src/views/run-process-panel/run-process-second-step.tsx b/src/views/run-process-panel/run-process-second-step.tsx index 08cf4e6c..2f41dedb 100644 --- a/src/views/run-process-panel/run-process-second-step.tsx +++ b/src/views/run-process-panel/run-process-second-step.tsx @@ -13,7 +13,6 @@ import { isValid } from 'redux-form'; import { RUN_PROCESS_INPUTS_FORM } from './run-process-inputs-form'; import { RunProcessAdvancedForm, RUN_PROCESS_ADVANCED_FORM } from './run-process-advanced-form'; import { createStructuredSelector } from 'reselect'; -import { WorkflowPresetSelect } from 'views/run-process-panel/workflow-preset-select'; import { selectPreset } from 'store/run-process-panel/run-process-panel-actions'; export interface RunProcessSecondStepFormDataProps { @@ -58,14 +57,6 @@ export const RunProcessSecondStepForm = connect(mapStateToProps, { onPresetChang ({ inputs, workflow, selectedPreset, presets, onPresetChange, valid, goBack, runProcess }: RunProcessSecondStepFormProps) => - - - {workflow && selectedPreset && presets && - < WorkflowPresetSelect - {...{ workflow, selectedPreset, presets, onChange: onPresetChange }} /> - } - - @@ -75,7 +66,7 @@ export const RunProcessSecondStepForm = connect(mapStateToProps, { onPresetChang Back ); diff --git a/src/views/workflow-panel/workflow-description-card.tsx b/src/views/workflow-panel/workflow-description-card.tsx index 9c1d81c3..df1b2811 100644 --- a/src/views/workflow-panel/workflow-description-card.tsx +++ b/src/views/workflow-panel/workflow-description-card.tsx @@ -14,13 +14,13 @@ import { TableHead, TableCell, TableBody, - TableRow + TableRow, } 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 { WorkflowResource, parseWorkflowDefinition, getWorkflowInputs, getInputLabel, stringifyInputType } from 'models/workflow'; -import { WorkflowGraph } from "views/workflow-panel/workflow-graph"; +import { parseWorkflowDefinition, getWorkflowInputs, getInputLabel, stringifyInputType } from 'models/workflow'; +import { WorkflowDetailsCardDataProps, WorkflowDetailsAttributes } from 'views-components/details-panel/workflow-details'; export type CssRules = 'root' | 'tab' | 'inputTab' | 'graphTab' | 'graphTabWithChosenWorkflow' | 'descriptionTab' | 'inputsTable'; @@ -54,10 +54,6 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, }); -interface WorkflowDetailsCardDataProps { - workflow?: WorkflowResource; -} - type WorkflowDetailsCardProps = WorkflowDetailsCardDataProps & WithStyles; export const WorkflowDetailsCard = withStyles(styles)( @@ -77,16 +73,16 @@ export const WorkflowDetailsCard = withStyles(styles)( - + {value === 0 && {workflow ?
{workflow.description}
: ( - - )} + + )}
} {value === 1 && {workflow @@ -96,12 +92,12 @@ export const WorkflowDetailsCard = withStyles(styles)( messages={['Please select a workflow to see its inputs.']} /> } } - {value === 2 && + {value === 2 && {workflow - ? + ? : + messages={['Please select a workflow to see its details.']} /> } } ;