From: Lucas Di Pentima Date: Wed, 14 Apr 2021 13:33:32 +0000 (-0300) Subject: Merge branch '16159-logout-request-with-token' X-Git-Tag: 2.2.0~15 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/547664ecb6ac881103fb9e94a51b2a2746b2c6ae?hp=c15afcee286bb8ab694f488e2ccd490b56794e47 Merge branch '16159-logout-request-with-token' Closes #16159 Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/README.md b/README.md index f6a7e485..8bb50dbe 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,17 @@ Install [redux-devtools-extension](https://chrome.google.com/webstore/detail/red yarn start ``` +## Start project for development inside Docker container + +``` +make workbench2-build-image +# (create public/config.json, see "Run time configuration" below) +docker run -ti -v$PWD:$PWD -p 3000:3000 -w$PWD workbench2-build /bin/bash +# (inside docker container) +yarn install +yarn start +``` + ## Run unit tests ``` make unit-tests @@ -37,12 +48,12 @@ make integration-tests-in-docker ## Run tests interactively in container ``` -$ xhost +local:root -$ ARVADOS_DIR=/path/to/arvados -$ docker run -ti -v$PWD:$PWD -v$ARVADOS_DIR:/usr/src/arvados -w$PWD --env="DISPLAY" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" workbench2-build /bin/bash +xhost +local:root +ARVADOS_DIR=/path/to/arvados +docker run -ti -v$PWD:$PWD -v$ARVADOS_DIR:/usr/src/arvados -w$PWD --env="DISPLAY" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" workbench2-build /bin/bash (inside container) -# yarn run cypress install -# tools/run-integration-tests.sh -i -a /usr/src/arvados +yarn run cypress install +tools/run-integration-tests.sh -i -a /usr/src/arvados ``` ## Production build diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js index 2afdf92e..c745e267 100644 --- a/cypress/integration/favorites.spec.js +++ b/cypress/integration/favorites.spec.js @@ -185,6 +185,13 @@ describe('Favorites tests', function () { }) .as('testWorkflow'); + cy.createWorkflow(adminUser.token, { + name: `TestWorkflow2-${Math.floor(Math.random() * 999999)}.cwl`, + definition: "{ \"$graph\": [ { \"$namespaces\": { \"arv\": \"http://arvados.org/cwl#\" }, \"class\": \"Workflow\", \"doc\": \"Detect blurriness of WSI data.\", \"id\": \"#main\", \"inputs\": [ { \"default\": { \"basename\": \"3d3cb547725e72ddb442bc620adbc342+2463\", \"class\": \"Directory\", \"location\": \"keep:3d3cb547725e72ddb442bc620adbc342+2463\" }, \"doc\": \"Collection containing all pipeline input images\", \"id\": \"#main/image_collection\", \"type\": \"Directory\" } ], \"outputs\": [ { \"id\": \"#main/blur_report\", \"outputSource\": \"#main/blurdetection/report\", \"type\": \"Any\" } ], \"steps\": [ { \"id\": \"#main/blurdetection\", \"in\": [ { \"id\": \"#main/blurdetection/image_collection\", \"source\": \"#main/image_collection\" } ], \"out\": [ \"#main/blurdetection/report\" ], \"run\": \"#blurdetection.cwl\" } ] }, { \"arguments\": [ \"--num_workers\", \"0\", \"--wsi_dir\", \"$(inputs.image_collection)\", \"--tile_out_dir\", \"$(runtime.outdir)\" ], \"baseCommand\": [ \"python3\", \"/updated_blur_on_folder.py\" ], \"class\": \"CommandLineTool\", \"hints\": [ { \"class\": \"DockerRequirement\", \"dockerPull\": \"updated_score_aws:cpu2\", \"http://arvados.org/cwl#dockerCollectionPDH\": \"0d6702518d1408ce2c471ffec40695cf+4924\" }, { \"class\": \"ResourceRequirement\", \"coresMin\": 8, \"ramMin\": 20000 }, { \"class\": \"http://arvados.org/cwl#RuntimeConstraints\", \"keep_cache\": 2000 } ], \"id\": \"#blurdetection.cwl\", \"inputs\": [ { \"doc\": \"Collection containing all pipeline input images\", \"id\": \"#blurdetection.cwl/image_collection\", \"type\": \"Directory\" } ], \"outputs\": [ { \"id\": \"#blurdetection.cwl/report\", \"outputBinding\": { \"glob\": \"*.csv\" }, \"type\": \"Any\" } ] } ], \"cwlVersion\": \"v1.0\" }", + owner_uuid: myProject1.uuid, + }) + .as('testWorkflow2'); + cy.contains('Shared with me').click(); cy.doSearch(`${activeUser.user.uuid}`); @@ -204,6 +211,21 @@ describe('Favorites tests', function () { cy.get('[data-cy=projects-tree-favourites-tree-picker]').contains('Favorites').closest('ul').find('i').click(); cy.get('@chooseFileDialog').find(`[data-id=${mySharedWritableProject.uuid}]`); cy.get('@chooseFileDialog').find(`[data-id=${mySharedReadonlyProject.uuid}]`); + cy.get('button').contains('Cancel').click(); + }); + + cy.get('button').contains('Back').click(); + + cy.get('@testWorkflow2') + .then((testWorkflow2) => { + 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=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}]`); + cy.get('@chooseDirectoryDialog').find(`[data-id=${mySharedReadonlyProject.uuid}]`); }); }); }); diff --git a/src/services/api/filter-builder.ts b/src/services/api/filter-builder.ts index 489f7b89..d1a4fd08 100644 --- a/src/services/api/filter-builder.ts +++ b/src/services/api/filter-builder.ts @@ -60,6 +60,9 @@ export class FilterBuilder { public addExists(value?: string, resourcePrefix?: string) { return this.addCondition("properties", "exists", value, "", "", resourcePrefix); } + public addDoesNotExist(field: string, resourcePrefix?: string) { + return this.addCondition("properties." + field, "exists", false, "", "", resourcePrefix); + } public addFullTextSearch(value: string) { const terms = value.trim().split(/(\s+)/); 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 8646164e..d9686feb 100644 --- a/src/store/run-process-panel/run-process-panel-actions.ts +++ b/src/store/run-process-panel/run-process-panel-actions.ts @@ -21,7 +21,6 @@ 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 { matchProjectRoute } from '~/routes/routes'; export const runProcessPanelActions = unionize({ SET_PROCESS_PATHNAME: ofType(), @@ -144,9 +143,8 @@ export const runProcess = async (dispatch: Dispatch, getState: () => RootSt const inputsForm = getFormValues(RUN_PROCESS_INPUTS_FORM)(state) as WorkflowInputsData; const userUuid = getUserUuid(getState()); if (!userUuid) { return; } - const pathname = getState().runProcessPanel.processPathname; const { processOwnerUuid, selectedWorkflow } = state.runProcessPanel; - const ownerUUid = !matchProjectRoute(pathname) ? userUuid : processOwnerUuid; + const ownerUUid = processOwnerUuid ? processOwnerUuid : userUuid; if (selectedWorkflow) { const advancedForm = getFormValues(RUN_PROCESS_ADVANCED_FORM)(state) as RunProcessAdvancedFormData || getWorkflowRunnerSettings(selectedWorkflow); const newProcessData = { diff --git a/src/store/workflow-panel/workflow-panel-actions.test.ts b/src/store/workflow-panel/workflow-panel-actions.test.ts new file mode 100644 index 00000000..4eff45dc --- /dev/null +++ b/src/store/workflow-panel/workflow-panel-actions.test.ts @@ -0,0 +1,89 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { API_TOKEN_KEY } from "~/services/auth-service/auth-service"; + +import 'jest-localstorage-mock'; +import { ServiceRepository, createServices } from "~/services/services"; +import { configureStore, RootStore } from "../store"; +import { createBrowserHistory } from "history"; +import { Config, mockConfig } from '~/common/config'; +import { ApiActions } from "~/services/api/api-actions"; +import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service'; +import Axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { ImportMock } from 'ts-mock-imports'; +import * as servicesModule from "~/services/services"; +import { SessionStatus } from "~/models/session"; +import { openRunProcess } from './workflow-panel-actions'; +import { runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions'; +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 { ResourceKind } from '~/models/resource'; +import { WorkflowResource } from '~/models/workflow'; + +describe('workflow-panel-actions', () => { + const axiosInst = Axios.create({ headers: {} }); + const axiosMock = new MockAdapter(axiosInst); + + let store: RootStore; + let services: ServiceRepository; + const config: any = {}; + const actions: ApiActions = { + progressFn: (id: string, working: boolean) => { }, + errorFn: (id: string, message: string) => { } + }; + let importMocks: any[]; + + beforeEach(() => { + axiosMock.reset(); + services = createServices(mockConfig({}), actions, axiosInst); + store = configureStore(createBrowserHistory(), services, config); + localStorage.clear(); + importMocks = []; + }); + + afterEach(() => { + importMocks.map(m => m.restore()); + }); + + it('opens the run process panel', async () => { + const wflist: WorkflowResource[] = [{ + uuid: "zzzzz-7fd4e-0123456789abcde", + name: "foo", + description: "", + definition: "$graph: []", + kind: ResourceKind.WORKFLOW, + ownerUuid: "", + createdAt: "", + modifiedByClientUuid: "", + modifiedByUserUuid: "", + modifiedAt: "", + href: "", + etag: "" + }]; + axiosMock + .onGet("/workflows") + .reply(200, { + items: wflist + }).onGet("/links") + .reply(200, { + items: [] + }); + + const dispatchMock = jest.fn(); + const dispatchWrapper = (action: any) => { + dispatchMock(action); + return store.dispatch(action); + }; + + 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 4cfcd8b9..b4825209 100644 --- a/src/store/workflow-panel/workflow-panel-actions.ts +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -12,6 +12,8 @@ import { navigateToRunProcess } from '~/store/navigation/navigation-action'; import { goToStep, runProcessPanelActions, loadPresets, getWorkflowRunnerSettings } from '~/store/run-process-panel/run-process-panel-actions'; import { snackbarActions } from '~/store/snackbar/snackbar-actions'; 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'; export const WORKFLOW_PANEL_ID = "workflowPanel"; @@ -33,17 +35,30 @@ export const getUuidPrefix = (state: RootState) => { return state.properties.uuidPrefix; }; -export const openRunProcess = (uuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { +export const openRunProcess = (workflowUuid: string, ownerUuid?: string, name?: string, inputObj?: { [key: string]: any }) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const response = await services.workflowService.list(); + dispatch(runProcessPanelActions.SET_WORKFLOWS(response.items)); + const workflows = getState().runProcessPanel.searchWorkflows; - const workflow = workflows.find(workflow => workflow.uuid === uuid); + const workflow = workflows.find(workflow => workflow.uuid === workflowUuid); if (workflow) { dispatch(navigateToRunProcess); dispatch(goToStep(1)); dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(workflow)); dispatch(loadPresets(workflow.uuid)); + dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, getWorkflowRunnerSettings(workflow))); + if (ownerUuid) { + dispatch(runProcessPanelActions.SET_PROCESS_OWNER_UUID(ownerUuid)); + } + if (name) { + dispatch(initialize(RUN_PROCESS_BASIC_FORM, { name })); + } + if (inputObj) { + dispatch(initialize(RUN_PROCESS_INPUTS_FORM, inputObj)); + } } else { dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't run this process` })); } diff --git a/src/views/run-process-panel/inputs/directory-input.tsx b/src/views/run-process-panel/inputs/directory-input.tsx index 29ccd6e0..f687e28d 100644 --- a/src/views/run-process-panel/inputs/directory-input.tsx +++ b/src/views/run-process-panel/inputs/directory-input.tsx @@ -24,14 +24,18 @@ import { ERROR_MESSAGE } from '~/validators/require'; export interface DirectoryInputProps { input: DirectoryCommandInputParameter; + options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }; } -export const DirectoryInput = ({ input }: DirectoryInputProps) => +export const DirectoryInput = ({ input, options }: DirectoryInputProps) => ; const format = (value?: Directory) => value ? value.basename : ''; @@ -56,7 +60,9 @@ interface DirectoryInputComponentState { } const DirectoryInputComponent = connect()( - class FileInputComponent extends React.Component { + class FileInputComponent extends React.Component { state: DirectoryInputComponentState = { open: false, }; @@ -113,12 +119,14 @@ const DirectoryInputComponent = connect()( open={this.state.open} onClose={this.closeDialog} fullWidth + data-cy="choose-a-directory-dialog" maxWidth='md'> Choose a directory diff --git a/src/views/run-process-panel/run-process-inputs-form.tsx b/src/views/run-process-panel/run-process-inputs-form.tsx index 3a0afd34..e6a504db 100644 --- a/src/views/run-process-panel/run-process-inputs-form.tsx +++ b/src/views/run-process-panel/run-process-inputs-form.tsx @@ -92,7 +92,7 @@ const getInputComponent = (input: CommandInputParameter) => { return ; case isPrimitiveOfType(input, CWLType.DIRECTORY): - return ; + return ; case typeof input.type === 'object' && !(input.type instanceof Array) &&