[ARVADOS-WORKBENCH2] created: 2.3.2.1
Git user
git at public.arvados.org
Fri Jan 7 19:47:48 UTC 2022
at 6b368635bf1a768e89237c011de76a4230dc9d6d (commit)
commit 6b368635bf1a768e89237c011de76a4230dc9d6d
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Wed Dec 15 19:32:01 2021 -0300
Merge branch '18584-collection-copy-fix'. Closes #18584.
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js
index eb06a06c..bd211b1a 100644
--- a/cypress/integration/collection.spec.js
+++ b/cypress/integration/collection.spec.js
@@ -595,6 +595,86 @@ describe('Collection panel tests', function () {
})
});
+ it('moves a collection to a different project', function () {
+ const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
+ const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
+ const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
+
+ cy.createCollection(adminUser.token, {
+ name: collName,
+ owner_uuid: activeUser.user.uuid,
+ manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
+ }).as('testCollection');
+ cy.createGroup(adminUser.token, {
+ name: projName,
+ group_class: 'project',
+ owner_uuid: activeUser.user.uuid,
+ }).as('testProject');
+
+ cy.getAll('@testCollection', '@testProject')
+ .then(function ([testCollection, testProject]) {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/collections/${testCollection.uuid}`);
+ cy.get('[data-cy=collection-files-panel]').should('contain', fileName);
+ cy.get('[data-cy=collection-info-panel]')
+ .should('not.contain', projName)
+ .and('not.contain', testProject.uuid);
+ cy.get('[data-cy=collection-panel-options-btn]').click();
+ cy.get('[data-cy=context-menu]').contains('Move to').click();
+ cy.get('[data-cy=form-dialog]')
+ .should('contain', 'Move to')
+ .within(() => {
+ cy.get('[data-cy=projects-tree-home-tree-picker]')
+ .find('i')
+ .click();
+ cy.get('[data-cy=projects-tree-home-tree-picker]')
+ .contains(projName)
+ .click();
+ });
+ cy.get('[data-cy=form-submit-btn]').click();
+ cy.get('[data-cy=snackbar]')
+ .contains('Collection has been moved')
+ cy.get('[data-cy=collection-info-panel]')
+ .contains(projName).and('contain', testProject.uuid);
+ // Double check that the collection is in the project
+ cy.goToPath(`/projects/${testProject.uuid}`);
+ cy.get('[data-cy=project-panel]').should('contain', collName);
+ });
+ });
+
+ it('makes a copy of an existing collection', function() {
+ const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
+ const copyName = `Copy of: ${collName}`;
+
+ cy.createCollection(adminUser.token, {
+ name: collName,
+ owner_uuid: activeUser.user.uuid,
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
+ }).as('collection').then(function () {
+ cy.loginAs(activeUser)
+ cy.goToPath(`/collections/${this.collection.uuid}`);
+ cy.get('[data-cy=collection-files-panel]')
+ .should('contain', 'some-file');
+ cy.get('[data-cy=collection-panel-options-btn]').click();
+ cy.get('[data-cy=context-menu]').contains('Make a copy').click();
+ cy.get('[data-cy=form-dialog]')
+ .should('contain', 'Make a copy')
+ .within(() => {
+ cy.get('[data-cy=projects-tree-home-tree-picker]')
+ .contains('Projects')
+ .click();
+ cy.get('[data-cy=form-submit-btn]').click();
+ });
+ cy.get('[data-cy=snackbar]')
+ .contains('Collection has been copied.')
+ cy.get('[data-cy=snackbar-goto-action]').click();
+ cy.get('[data-cy=project-panel]')
+ .contains(copyName).click();
+ cy.get('[data-cy=collection-files-panel]')
+ .should('contain', 'some-file');
+ });
+ });
+
it('uses the collection version browser to view a previous version', function () {
const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js
index 13a2c467..9bc90ebd 100644
--- a/cypress/integration/favorites.spec.js
+++ b/cypress/integration/favorites.spec.js
@@ -44,7 +44,8 @@ describe('Favorites tests', function () {
});
});
- it('can copy selected into the collection', () => {
+ // Disabled while addressing #18587
+ it.skip('can copy selected into the collection', () => {
cy.createCollection(adminUser.token, {
name: `Test source collection ${Math.floor(Math.random() * 999999)}`,
manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
diff --git a/src/store/collections/collection-copy-actions.ts b/src/store/collections/collection-copy-actions.ts
index 9d812783..eb9c64fd 100644
--- a/src/store/collections/collection-copy-actions.ts
+++ b/src/store/collections/collection-copy-actions.ts
@@ -12,6 +12,8 @@ import { getCommonResourceServiceError, CommonResourceServiceError } from 'servi
import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions';
+import { getResource } from "store/resources/resources";
+import { CollectionResource } from "models/collection";
export const COLLECTION_COPY_FORM_NAME = 'collectionCopyFormName';
@@ -27,9 +29,15 @@ export const openCollectionCopyDialog = (resource: { name: string, uuid: string
export const copyCollection = (resource: CopyFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(startSubmit(COLLECTION_COPY_FORM_NAME));
+ let collection = getResource<CollectionResource>(resource.uuid)(getState().resources);
try {
- const collection = await services.collectionService.get(resource.uuid);
- const newCollection = await services.collectionService.create({ ...collection, ownerUuid: resource.ownerUuid, name: resource.name });
+ if (!collection) {
+ collection = await services.collectionService.get(resource.uuid);
+ }
+ const collManifestText = await services.collectionService.get(resource.uuid, undefined, ['manifestText']);
+ collection.manifestText = collManifestText.manifestText;
+ const {href, ...collectionRecord} = collection;
+ const newCollection = await services.collectionService.create({ ...collectionRecord, ownerUuid: resource.ownerUuid, name: resource.name });
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_COPY_FORM_NAME }));
return newCollection;
} catch (e) {
diff --git a/src/store/collections/collection-move-actions.ts b/src/store/collections/collection-move-actions.ts
index d056b6e5..929f1612 100644
--- a/src/store/collections/collection-move-actions.ts
+++ b/src/store/collections/collection-move-actions.ts
@@ -14,6 +14,8 @@ import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
import { resetPickerProjectTree } from 'store/project-tree-picker/project-tree-picker-actions';
import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions';
+import { getResource } from "store/resources/resources";
+import { CollectionResource } from "models/collection";
export const COLLECTION_MOVE_FORM_NAME = 'collectionMoveFormName';
@@ -28,13 +30,17 @@ export const openMoveCollectionDialog = (resource: { name: string, uuid: string
export const moveCollection = (resource: MoveToFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(startSubmit(COLLECTION_MOVE_FORM_NAME));
+ let cachedCollection = getResource<CollectionResource>(resource.uuid)(getState().resources);
try {
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_MOVE_FORM_NAME));
+ if (!cachedCollection) {
+ cachedCollection = await services.collectionService.get(resource.uuid);
+ }
const collection = await services.collectionService.update(resource.uuid, { ownerUuid: resource.ownerUuid });
dispatch(projectPanelActions.REQUEST_ITEMS());
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_MOVE_FORM_NAME }));
dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_MOVE_FORM_NAME));
- return collection;
+ return {...cachedCollection, ...collection};
} catch (e) {
const error = getCommonResourceServiceError(e);
if (error === CommonResourceServiceError.UNIQUE_NAME_VIOLATION) {
diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts
index d898c500..9f478d74 100644
--- a/src/store/collections/collection-partial-copy-actions.ts
+++ b/src/store/collections/collection-partial-copy-actions.ts
@@ -52,13 +52,13 @@ export const copyCollectionPartial = ({ name, description, projectUuid }: Collec
if (currentCollection) {
try {
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_FORM_NAME));
- const collection = await services.collectionService.get(currentCollection.uuid);
+ const collectionManifestText = await services.collectionService.get(currentCollection.uuid, undefined, ['manifestText']);
const collectionCopy = {
name,
description,
ownerUuid: projectUuid,
uuid: undefined,
- manifestText: collection.manifestText,
+ manifestText: collectionManifestText.manifestText,
};
const newCollection = await services.collectionService.create(collectionCopy);
const copiedFiles = await services.collectionService.files(newCollection.uuid);
@@ -67,7 +67,7 @@ export const copyCollectionPartial = ({ name, description, projectUuid }: Collec
return !paths.find(path => path.indexOf(file.replace(newCollection.uuid, '')) > -1);
});
await services.collectionService.deleteFiles(
- '',
+ newCollection.uuid,
filesToDelete
);
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME }));
@@ -135,7 +135,7 @@ export const copyCollectionPartialToSelectedCollection = ({ collectionUuid }: Co
});
const diffPathToRemove = difference(paths, pathsToRemove);
await services.collectionService.deleteFiles(selectedCollection.uuid, pathsToRemove.map(path => path.replace(currentCollection.uuid, collectionUuid)));
- const collectionWithDeletedFiles = await services.collectionService.get(collectionUuid);
+ const collectionWithDeletedFiles = await services.collectionService.get(collectionUuid, undefined, ['uuid', 'manifestText']);
await services.collectionService.update(collectionUuid, { manifestText: `${collectionWithDeletedFiles.manifestText}${(currentCollection.manifestText ? currentCollection.manifestText : currentCollection.unsignedManifestText) || ''}` });
await services.collectionService.deleteFiles(collectionWithDeletedFiles.uuid, diffPathToRemove.map(path => path.replace(currentCollection.uuid, collectionUuid)));
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
diff --git a/src/views-components/context-menu/action-sets/collection-files-action-set.ts b/src/views-components/context-menu/action-sets/collection-files-action-set.ts
index 3aeec4c0..59a5f368 100644
--- a/src/views-components/context-menu/action-sets/collection-files-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-files-action-set.ts
@@ -26,12 +26,13 @@ export const readOnlyCollectionFilesActionSet: ContextMenuActionSet = [[
dispatch<any>(openCollectionPartialCopyDialog());
}
},
- {
- name: "Copy selected into the collection",
- execute: dispatch => {
- dispatch<any>(openCollectionPartialCopyToSelectedCollectionDialog());
- }
- }
+ // Disabled while addressing #18587
+ // {
+ // name: "Copy selected into the collection",
+ // execute: dispatch => {
+ // dispatch<any>(openCollectionPartialCopyToSelectedCollectionDialog());
+ // }
+ // }
]];
export const collectionFilesActionSet: ContextMenuActionSet = readOnlyCollectionFilesActionSet.concat([[
diff --git a/src/views-components/projects-tree-picker/projects-tree-picker.tsx b/src/views-components/projects-tree-picker/projects-tree-picker.tsx
index 2f3ea611..ee8ce1d5 100644
--- a/src/views-components/projects-tree-picker/projects-tree-picker.tsx
+++ b/src/views-components/projects-tree-picker/projects-tree-picker.tsx
@@ -31,11 +31,17 @@ export const ProjectsTreePicker = ({ pickerId, ...props }: ProjectsTreePickerPro
disableActivation
};
return <div>
- <HomeTreePicker pickerId={home} {...p} />
- <SharedTreePicker pickerId={shared} {...p} />
- <PublicFavoritesTreePicker pickerId={publicFavorites} {...p} />
+ <div data-cy="projects-tree-home-tree-picker">
+ <HomeTreePicker pickerId={home} {...p} />
+ </div>
+ <div data-cy="projects-tree-shared-tree-picker">
+ <SharedTreePicker pickerId={shared} {...p} />
+ </div>
+ <div data-cy="projects-tree-public-favourites-tree-picker">
+ <PublicFavoritesTreePicker pickerId={publicFavorites} {...p} />
+ </div>
<div data-cy="projects-tree-favourites-tree-picker">
- <FavoritesTreePicker pickerId={favorites} {...p} />
+ <FavoritesTreePicker pickerId={favorites} {...p} />
</div>
</div>;
};
diff --git a/src/views-components/snackbar/snackbar.tsx b/src/views-components/snackbar/snackbar.tsx
index 2a63a31a..a33b6968 100644
--- a/src/views-components/snackbar/snackbar.tsx
+++ b/src/views-components/snackbar/snackbar.tsx
@@ -116,7 +116,7 @@ export const Snackbar = withStyles(styles)(connect(mapStateToProps, mapDispatchT
onExited={props.onExited}
anchorOrigin={props.anchorOrigin}
autoHideDuration={props.autoHideDuration}>
- <SnackbarContent
+ <div data-cy="snackbar"><SnackbarContent
className={classNames(cssClass)}
aria-describedby="client-snackbar"
message={
@@ -126,7 +126,7 @@ export const Snackbar = withStyles(styles)(connect(mapStateToProps, mapDispatchT
</span>
}
action={actions(props)}
- />
+ /></div>
</MaterialSnackbar>
);
}
@@ -151,7 +151,7 @@ const actions = (props: SnackbarProps) => {
color="inherit"
className={classes.linkButton}
onClick={() => onClick(link)}>
- Go To
+ <span data-cy='snackbar-goto-action'>Go To</span>
</Button>
);
}
diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx
index 67264511..eaf48d14 100644
--- a/src/views/project-panel/project-panel.tsx
+++ b/src/views/project-panel/project-panel.tsx
@@ -147,7 +147,7 @@ export const ProjectPanel = withStyles(styles)(
class extends React.Component<ProjectPanelProps> {
render() {
const { classes } = this.props;
- return <div className={classes.root}>
+ return <div data-cy='project-panel' className={classes.root}>
<DataExplorer
id={PROJECT_PANEL_ID}
onRowClick={this.handleRowClick}
commit d0df6414bef82439ff46836f059632250d2de3c2
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Wed Dec 1 17:36:55 2021 -0300
Merge branch '18484-collection-manifest-fix' into main. Closes #18484.
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/src/services/collection-service/collection-service.test.ts b/src/services/collection-service/collection-service.test.ts
index c0aa85f1..b759fd1a 100644
--- a/src/services/collection-service/collection-service.test.ts
+++ b/src/services/collection-service/collection-service.test.ts
@@ -30,6 +30,41 @@ describe('collection-service', () => {
collectionService.update = jest.fn();
});
+ describe('get', () => {
+ it('should make a list request with uuid filtering', async () => {
+ serverApi.get = jest.fn(() => Promise.resolve(
+ { data: { items: [{}] } }
+ ));
+ const uuid = 'zzzzz-4zz18-0123456789abcde'
+ await collectionService.get(uuid);
+ expect(serverApi.get).toHaveBeenCalledWith(
+ '/collections', {
+ params: {
+ filters: `[["uuid","=","zzzzz-4zz18-0123456789abcde"]]`,
+ include_old_versions: true,
+ },
+ }
+ );
+ });
+
+ it('should be able to request specific fields', async () => {
+ serverApi.get = jest.fn(() => Promise.resolve(
+ { data: { items: [{}] } }
+ ));
+ const uuid = 'zzzzz-4zz18-0123456789abcde'
+ await collectionService.get(uuid, undefined, ['manifestText']);
+ expect(serverApi.get).toHaveBeenCalledWith(
+ '/collections', {
+ params: {
+ filters: `[["uuid","=","zzzzz-4zz18-0123456789abcde"]]`,
+ include_old_versions: true,
+ select: `["manifest_text"]`
+ },
+ }
+ );
+ });
+ });
+
describe('update', () => {
it('should call put selecting updated fields + others', async () => {
serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index 48e797c5..b6272650 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -11,6 +11,8 @@ import { extractFilesData } from "./collection-service-files-response";
import { TrashableResourceService } from "services/common-service/trashable-resource-service";
import { ApiActions } from "services/api/api-actions";
import { customEncodeURI } from "common/url";
+import { FilterBuilder } from "services/api/filter-builder";
+import { ListArguments } from "services/common-service/common-service";
export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
@@ -28,6 +30,18 @@ export class CollectionService extends TrashableResourceService<CollectionResour
]);
}
+ async get(uuid: string, showErrors?: boolean, select?: string[]) {
+ super.validateUuid(uuid);
+ // We use a filtered list request to avoid getting the manifest text
+ const filters = new FilterBuilder().addEqual('uuid', uuid).getFilters();
+ const listArgs: ListArguments = {filters, includeOldVersions: true};
+ if (select) {
+ listArgs.select = select;
+ }
+ const lst = await super.list(listArgs, showErrors);
+ return lst.items[0];
+ }
+
create(data?: Partial<CollectionResource>) {
return super.create({ ...data, preserveVersion: true });
}
diff --git a/src/services/common-service/common-service.ts b/src/services/common-service/common-service.ts
index 82777342..f66fad74 100644
--- a/src/services/common-service/common-service.ts
+++ b/src/services/common-service/common-service.ts
@@ -68,7 +68,7 @@ export class CommonService<T> {
}
}
- private validateUuid(uuid: string) {
+ protected validateUuid(uuid: string) {
if (uuid === "") {
throw new Error('UUID cannot be empty string');
}
@@ -124,18 +124,21 @@ export class CommonService<T> {
);
}
- list(args: ListArguments = {}): Promise<ListResults<T>> {
- const { filters, order, ...other } = args;
+ list(args: ListArguments = {}, showErrors?: boolean): Promise<ListResults<T>> {
+ const { filters, select, ...other } = args;
const params = {
...CommonService.mapKeys(snakeCase)(other),
filters: filters ? `[${filters}]` : undefined,
- order: order ? order : undefined
+ select: select
+ ? `[${select.map(snakeCase).map(s => `"${s}"`).join(', ')}]`
+ : undefined
};
if (QueryString.stringify(params).length <= 1500) {
return CommonService.defaultResponse(
this.serverApi.get(`/${this.resourceType}`, { params }),
- this.actions
+ this.actions,
+ showErrors
);
} else {
// Using the POST special case to avoid URI length 414 errors.
@@ -152,7 +155,8 @@ export class CommonService<T> {
_method: 'GET'
}
}),
- this.actions
+ this.actions,
+ showErrors
);
}
}
diff --git a/src/store/advanced-tab/advanced-tab.tsx b/src/store/advanced-tab/advanced-tab.tsx
index 0f8bf3cb..25d90195 100644
--- a/src/store/advanced-tab/advanced-tab.tsx
+++ b/src/store/advanced-tab/advanced-tab.tsx
@@ -411,7 +411,7 @@ const containerRequestApiResponse = (apiResponse: ContainerRequestResource) => {
const collectionApiResponse = (apiResponse: CollectionResource) => {
const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, description, properties, portableDataHash, replicationDesired,
- replicationConfirmedAt, replicationConfirmed, manifestText, deleteAt, trashAt, isTrashed, storageClassesDesired,
+ replicationConfirmedAt, replicationConfirmed, deleteAt, trashAt, isTrashed, storageClassesDesired,
storageClassesConfirmed, storageClassesConfirmedAt, currentVersionUuid, version, preserveVersion, fileCount, fileSizeTotal } = apiResponse;
const response = `
"uuid": "${uuid}",
@@ -424,7 +424,6 @@ const collectionApiResponse = (apiResponse: CollectionResource) => {
"replication_desired": ${stringify(replicationDesired)},
"replication_confirmed_at": ${stringify(replicationConfirmedAt)},
"replication_confirmed": ${stringify(replicationConfirmed)},
-"manifest_text": ${stringify(manifestText)},
"name": ${stringify(name)},
"description": ${stringify(description)},
"properties": ${stringifyObject(properties)},
diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts
index 49900f2c..d898c500 100644
--- a/src/store/collections/collection-partial-copy-actions.ts
+++ b/src/store/collections/collection-partial-copy-actions.ts
@@ -114,7 +114,7 @@ export const copyCollectionPartialToSelectedCollection = ({ collectionUuid }: Co
const currentCollection = state.collectionPanel.item;
if (currentCollection && !currentCollection.manifestText) {
- const fetchedCurrentCollection = await services.collectionService.get(currentCollection.uuid);
+ const fetchedCurrentCollection = await services.collectionService.get(currentCollection.uuid, undefined, ['manifestText']);
currentCollection.manifestText = fetchedCurrentCollection.manifestText;
currentCollection.unsignedManifestText = fetchedCurrentCollection.unsignedManifestText;
}
diff --git a/src/store/collections/collection-version-actions.ts b/src/store/collections/collection-version-actions.ts
index c0a58432..7d2511ed 100644
--- a/src/store/collections/collection-version-actions.ts
+++ b/src/store/collections/collection-version-actions.ts
@@ -9,6 +9,8 @@ import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
import { resourcesActions } from "../resources/resources-actions";
import { navigateTo } from "../navigation/navigation-action";
import { dialogActions } from "../dialog/dialog-actions";
+import { getResource } from "store/resources/resources";
+import { CollectionResource } from "models/collection";
export const COLLECTION_RESTORE_VERSION_DIALOG = 'collectionRestoreVersionDialog';
@@ -28,9 +30,15 @@ export const openRestoreCollectionVersionDialog = (uuid: string) =>
export const restoreVersion = (resourceUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
try {
- // Request que entire record because stored old versions usually
- // don't include the manifest_text field.
- const oldVersion = await services.collectionService.get(resourceUuid);
+ // Request the manifest text because stored old versions usually
+ // don't include them.
+ let oldVersion = getResource<CollectionResource>(resourceUuid)(getState().resources);
+ if (!oldVersion) {
+ oldVersion = await services.collectionService.get(resourceUuid);
+ }
+ const oldVersionManifest = await services.collectionService.get(resourceUuid, undefined, ['manifestText']);
+ oldVersion.manifestText = oldVersionManifest.manifestText;
+
const { uuid, version, ...rest} = oldVersion;
const headVersion = await services.collectionService.update(
oldVersion.currentVersionUuid,
commit e9f10ba7ae13cfa4909f0b0dc6ef2911f7d15af3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Tue Nov 16 14:47:22 2021 -0300
Merge branch '18215-collection-update-without-manifest' into main.
Closes #18215
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js
index 9f4e2b84..13a2c467 100644
--- a/cypress/integration/favorites.spec.js
+++ b/cypress/integration/favorites.spec.js
@@ -150,7 +150,7 @@ describe('Favorites tests', function () {
cy.getAll('@mySharedWritableProject', '@testTargetCollection')
.then(function ([mySharedWritableProject, testTargetCollection]) {
cy.loginAs(adminUser);
-
+
cy.get('[data-cy=side-panel-tree]').contains('My Favorites').click();
const newProjectName = `New project name ${mySharedWritableProject.name}`;
@@ -160,7 +160,7 @@ describe('Favorites tests', function () {
cy.testEditProjectOrCollection('main', mySharedWritableProject.name, newProjectName, newProjectDescription);
cy.testEditProjectOrCollection('main', testTargetCollection.name, newCollectionName, newCollectionDescription, false);
-
+
cy.get('[data-cy=side-panel-tree]').contains('Projects').click();
cy.get('main').contains(newProjectName).rightclick();
@@ -171,7 +171,7 @@ describe('Favorites tests', function () {
cy.get('[data-cy=side-panel-tree]').contains('Public Favorites').click();
cy.testEditProjectOrCollection('main', newProjectName, mySharedWritableProject.name, 'newProjectDescription');
- cy.testEditProjectOrCollection('main', newCollectionName, testTargetCollection.name, 'newCollectionDescription', false);
+ cy.testEditProjectOrCollection('main', newCollectionName, testTargetCollection.name, 'newCollectionDescription', false);
});
});
diff --git a/src/services/collection-service/collection-service.test.ts b/src/services/collection-service/collection-service.test.ts
index 061a45ec..c0aa85f1 100644
--- a/src/services/collection-service/collection-service.test.ts
+++ b/src/services/collection-service/collection-service.test.ts
@@ -2,30 +2,53 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { AxiosInstance } from 'axios';
-import { WebDAV } from 'common/webdav';
-import { ApiActions } from '../api/api-actions';
+import axios, { AxiosInstance } from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { CollectionResource } from 'models/collection';
import { AuthService } from '../auth-service/auth-service';
import { CollectionService } from './collection-service';
describe('collection-service', () => {
let collectionService: CollectionService;
- let serverApi;
+ let serverApi: AxiosInstance;
+ let axiosMock: MockAdapter;
let webdavClient: any;
let authService;
let actions;
beforeEach(() => {
- serverApi = {} as AxiosInstance;
+ serverApi = axios.create();
+ axiosMock = new MockAdapter(serverApi);
webdavClient = {
delete: jest.fn(),
} as any;
authService = {} as AuthService;
- actions = {} as ApiActions;
+ actions = {
+ progressFn: jest.fn(),
+ } as any;
collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
collectionService.update = jest.fn();
});
+ describe('update', () => {
+ it('should call put selecting updated fields + others', async () => {
+ serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
+ const data: Partial<CollectionResource> = {
+ name: 'foo',
+ };
+ const expected = {
+ collection: {
+ ...data,
+ preserve_version: true,
+ },
+ select: ['uuid', 'name', 'version', 'modified_at'],
+ }
+ collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
+ await collectionService.update('uuid', data);
+ expect(serverApi.put).toHaveBeenCalledWith('/collections/uuid', expected);
+ });
+ });
+
describe('deleteFiles', () => {
it('should remove no files', async () => {
// given
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index 52fbf1a5..48e797c5 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -33,7 +33,8 @@ export class CollectionService extends TrashableResourceService<CollectionResour
}
update(uuid: string, data: Partial<CollectionResource>) {
- return super.update(uuid, { ...data, preserveVersion: true });
+ const select = [...Object.keys(data), 'version', 'modifiedAt'];
+ return super.update(uuid, { ...data, preserveVersion: true }, select);
}
async files(uuid: string) {
diff --git a/src/services/common-service/common-resource-service.ts b/src/services/common-service/common-resource-service.ts
index 66e694a0..c6306779 100644
--- a/src/services/common-service/common-resource-service.ts
+++ b/src/services/common-service/common-resource-service.ts
@@ -37,13 +37,16 @@ export class CommonResourceService<T extends Resource> extends CommonService<T>
return super.create(payload);
}
- update(uuid: string, data: Partial<T>) {
+ update(uuid: string, data: Partial<T>, select?: string[]) {
let payload: any;
if (data !== undefined) {
this.readOnlyFields.forEach( field => delete data[field] );
payload = {
[this.resourceType.slice(0, -1)]: CommonService.mapKeys(snakeCase)(data),
};
+ if (select !== undefined && select.length > 0) {
+ payload.select = ['uuid', ...select.map(field => snakeCase(field))];
+ };
}
return super.update(uuid, payload);
}
diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts
index ca9542c5..ee476524 100644
--- a/src/store/collection-panel/collection-panel-action.ts
+++ b/src/store/collection-panel/collection-panel-action.ts
@@ -17,6 +17,7 @@ import { SnackbarKind } from 'store/snackbar/snackbar-actions';
import { navigateTo } from 'store/navigation/navigation-action';
import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
import { addProperty, deleteProperty } from "lib/resource-properties";
+import { getResource } from "store/resources/resources";
export const collectionPanelActions = unionize({
SET_COLLECTION: ofType<CollectionResource>(),
@@ -39,7 +40,6 @@ export const loadCollectionPanel = (uuid: string, forceReload = false) =>
dispatch(resourcesActions.SET_RESOURCES([collection]));
if (collection.fileCount <= COLLECTION_PANEL_LOAD_FILES_THRESHOLD &&
!getState().collectionPanel.loadBigCollections) {
- // dispatch<any>(loadCollectionFiles(collection.uuid));
}
return collection;
};
@@ -52,11 +52,13 @@ export const createCollectionTag = (data: TagProperty) =>
const properties = Object.assign({}, item.properties);
const key = data.keyID || data.key;
const value = data.valueID || data.value;
+ const cachedCollection = getResource<CollectionResource>(item.uuid)(getState().resources);
services.collectionService.update(
item.uuid, {
properties: addProperty(properties, key, value)
}
).then(updatedCollection => {
+ updatedCollection = {...cachedCollection, ...updatedCollection};
dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
dispatch(snackbarActions.OPEN_SNACKBAR({
@@ -89,11 +91,13 @@ export const deleteCollectionTag = (key: string, value: string) =>
if (!item) { return; }
const properties = Object.assign({}, item.properties);
+ const cachedCollection = getResource<CollectionResource>(item.uuid)(getState().resources);
services.collectionService.update(
item.uuid, {
properties: deleteProperty(properties, key, value)
}
).then(updatedCollection => {
+ updatedCollection = {...cachedCollection, ...updatedCollection};
dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Tag has been successfully deleted.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
diff --git a/src/store/collections/collection-update-actions.ts b/src/store/collections/collection-update-actions.ts
index a9077cfb..04f42b8d 100644
--- a/src/store/collections/collection-update-actions.ts
+++ b/src/store/collections/collection-update-actions.ts
@@ -14,6 +14,7 @@ import { progressIndicatorActions } from "store/progress-indicator/progress-indi
import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
import { updateResources } from "../resources/resources-actions";
import { loadDetailsPanel } from "../details-panel/details-panel-action";
+import { getResource } from "store/resources/resources";
export interface CollectionUpdateFormDialogData {
uuid: string;
@@ -36,11 +37,13 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) =>
dispatch(startSubmit(COLLECTION_UPDATE_FORM_NAME));
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_UPDATE_FORM_NAME));
+ const cachedCollection = getResource<CollectionResource>(collection.uuid)(getState().resources);
services.collectionService.update(uuid, {
name: collection.name,
storageClassesDesired: collection.storageClassesDesired,
description: collection.description }
).then(updatedCollection => {
+ updatedCollection = {...cachedCollection, ...updatedCollection};
dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource }));
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_UPDATE_FORM_NAME }));
dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_UPDATE_FORM_NAME));
diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx
index 4270cbbd..e78b1f3d 100644
--- a/src/views/collection-panel/collection-panel.tsx
+++ b/src/views/collection-panel/collection-panel.tsx
@@ -120,7 +120,7 @@ export const CollectionPanel = withStyles(styles)(
isWritable = true;
} else {
const itemOwner = getResource<GroupResource | UserResource>(item.ownerUuid)(state.resources);
- if (itemOwner) {
+ if (itemOwner && itemOwner.writableBy) {
isWritable = itemOwner.writableBy.indexOf(currentUserUUID || '') >= 0;
}
}
commit 176f06e8893b71d4426d8917900a698d8de6a24b
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Wed Dec 1 17:25:35 2021 -0300
Merge branch '18257-chips-error-fix' into main. Closes #18257.
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/src/components/chips/chips.tsx b/src/components/chips/chips.tsx
index eb68ed7a..c4724d1b 100644
--- a/src/components/chips/chips.tsx
+++ b/src/components/chips/chips.tsx
@@ -38,7 +38,7 @@ export const Chips = withStyles(styles)(
render() {
const { values, filler } = this.props;
return <Grid container spacing={8} className={this.props.classes.root}>
- {values.map(this.renderChip)}
+ {values && values.map(this.renderChip)}
{filler && <Grid item xs>{filler}</Grid>}
</Grid>;
}
diff --git a/src/views/run-process-panel/inputs/float-array-input.tsx b/src/views/run-process-panel/inputs/float-array-input.tsx
index 780cbc90..3f0a5334 100644
--- a/src/views/run-process-panel/inputs/float-array-input.tsx
+++ b/src/views/run-process-panel/inputs/float-array-input.tsx
@@ -30,7 +30,7 @@ const validationSelector = createSelector(
);
const required = (value: string[]) =>
- value.length > 0
+ value && value.length > 0
? undefined
: ERROR_MESSAGE;
diff --git a/src/views/run-process-panel/inputs/int-array-input.tsx b/src/views/run-process-panel/inputs/int-array-input.tsx
index 03cb07ea..8077f28a 100644
--- a/src/views/run-process-panel/inputs/int-array-input.tsx
+++ b/src/views/run-process-panel/inputs/int-array-input.tsx
@@ -30,7 +30,7 @@ const validationSelector = createSelector(
);
const required = (value: string[]) =>
- value.length > 0
+ value && value.length > 0
? undefined
: ERROR_MESSAGE;
diff --git a/src/views/run-process-panel/inputs/string-array-input.tsx b/src/views/run-process-panel/inputs/string-array-input.tsx
index cabbf749..8955009a 100644
--- a/src/views/run-process-panel/inputs/string-array-input.tsx
+++ b/src/views/run-process-panel/inputs/string-array-input.tsx
@@ -31,7 +31,7 @@ const validationSelector = createSelector(
);
const required = (value: string[] = []) =>
- value.length > 0
+ value && value.length > 0
? undefined
: ERROR_MESSAGE;
commit 72e38dc2f3ff6f587e23d8512e93a041e7a71ce1
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date: Tue Nov 30 08:46:51 2021 +0100
Merge branch '18482-Info-Button-for-projects-is-broken' into main
closes #18482
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
index af2d93e3..1c175952 100644
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@ -179,4 +179,19 @@ describe('Project tests', function() {
cy.get('[data-cy=not-found-page]').should('not.exist');
});
});
+
+ it('shows details panel when clicking on the info icon', () => {
+ cy.createGroup(activeUser.token, {
+ name: `Test root project ${Math.floor(Math.random() * 999999)}`,
+ group_class: 'project',
+ }).as('testRootProject').then(function(testRootProject) {
+ cy.loginAs(activeUser);
+
+ cy.get('[data-cy=side-panel-tree]').contains(testRootProject.name).click();
+
+ cy.get('[data-cy=additional-info-icon]').click();
+
+ cy.contains(testRootProject.uuid).should('exist');
+ });
+ });
});
\ No newline at end of file
diff --git a/src/views-components/main-content-bar/main-content-bar.tsx b/src/views-components/main-content-bar/main-content-bar.tsx
index 10ae1790..480150cb 100644
--- a/src/views-components/main-content-bar/main-content-bar.tsx
+++ b/src/views-components/main-content-bar/main-content-bar.tsx
@@ -60,7 +60,7 @@ export const MainContentBar =
buttonVisible: isButtonVisible(state),
projectUuid: state.detailsPanel.resourceUuid,
}), (dispatch) => ({
- onDetailsPanelToggle: toggleDetailsPanel,
+ onDetailsPanelToggle: () => dispatch<any>(toggleDetailsPanel()),
onRefreshButtonClick: (id) => {
dispatch<any>(loadSidePanelTreeProjects(id));
dispatch<any>(reloadProjectMatchingUuid([id]));
@@ -80,7 +80,7 @@ export const MainContentBar =
</Grid>
<Grid item>
{props.buttonVisible && <Tooltip title="Additional Info">
- <IconButton color="inherit" className={props.classes.infoTooltip} onClick={props.onDetailsPanelToggle}>
+ <IconButton data-cy="additional-info-icon" color="inherit" className={props.classes.infoTooltip} onClick={props.onDetailsPanelToggle}>
<DetailsIcon />
</IconButton>
</Tooltip>}
commit dffa004174b744e4fb42fb1d63d282f41ecebbc9
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date: Fri Nov 26 09:05:56 2021 +0100
Merge branch '18169-cancel-button-not-working' into main
closes #18169
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
diff --git a/.licenseignore b/.licenseignore
index 853135fc..9b943a1f 100644
--- a/.licenseignore
+++ b/.licenseignore
@@ -14,3 +14,4 @@ public/*
.npmrc
src/lib/cwl-svg/*
tools/arvados_config.yml
+cypress/fixtures/files/5mb.bin
diff --git a/cypress/fixtures/files/5mb.bin b/cypress/fixtures/files/5mb.bin
new file mode 100644
index 00000000..d52f252e
Binary files /dev/null and b/cypress/fixtures/files/5mb.bin differ
diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js
index 3e06d7e5..eb06a06c 100644
--- a/cypress/integration/collection.spec.js
+++ b/cypress/integration/collection.spec.js
@@ -793,4 +793,83 @@ describe('Collection panel tests', function () {
.contains(adminUser.user.uuid);
});
});
+
+ describe('file upload', () => {
+ beforeEach(() => {
+ cy.createCollection(adminUser.token, {
+ name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+ })
+ .as('testCollection1');
+ });
+
+ it('allows to cancel running upload', () => {
+ cy.getAll('@testCollection1')
+ .then(function([testCollection1]) {
+ cy.loginAs(activeUser);
+
+ cy.goToPath(`/collections/${testCollection1.uuid}`);
+
+ cy.get('[data-cy=upload-button]').click();
+
+ cy.fixture('files/5mb.bin', 'base64').then(content => {
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
+
+ cy.get('[data-cy=form-submit-btn]').click();
+
+ cy.get('button').contains('Cancel').click();
+
+ cy.get('[data-cy=form-submit-btn]').should('not.exist');
+ });
+ });
+ });
+
+ it('allows to cancel single file from the running upload', () => {
+ cy.getAll('@testCollection1')
+ .then(function([testCollection1]) {
+ cy.loginAs(activeUser);
+
+ cy.goToPath(`/collections/${testCollection1.uuid}`);
+
+ cy.get('[data-cy=upload-button]').click();
+
+ cy.fixture('files/5mb.bin', 'base64').then(content => {
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
+
+ cy.get('[data-cy=form-submit-btn]').click();
+
+ cy.get('button[aria-label=Remove]').eq(1).click();
+
+ cy.get('[data-cy=form-submit-btn]').should('not.exist');
+
+ cy.get('[data-cy=collection-files-panel]').contains('5mb_a.bin').should('exist');
+ });
+ });
+ });
+
+ it('allows to cancel all files from the running upload', () => {
+ cy.getAll('@testCollection1')
+ .then(function([testCollection1]) {
+ cy.loginAs(activeUser);
+
+ cy.goToPath(`/collections/${testCollection1.uuid}`);
+
+ cy.get('[data-cy=upload-button]').click();
+
+ cy.fixture('files/5mb.bin', 'base64').then(content => {
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
+ cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
+
+ cy.get('[data-cy=form-submit-btn]').click();
+
+ cy.get('button[aria-label=Remove]').click({ multiple: true });
+
+ cy.get('[data-cy=form-submit-btn]').should('not.exist');
+ });
+ });
+ });
+ });
})
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 069ed96d..07290e55 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -280,4 +280,42 @@ Cypress.Commands.add('createProject', ({
cy.addToFavorites(user.token, user.user.uuid, project.uuid);
}
});
-});
\ No newline at end of file
+});
+
+Cypress.Commands.add(
+ 'upload',
+ {
+ prevSubject: 'element',
+ },
+ (subject, file, fileName) => {
+ cy.window().then(window => {
+ const blob = b64toBlob(file, '', 512);
+ const testFile = new window.File([blob], fileName);
+
+ cy.wrap(subject).trigger('drop', {
+ dataTransfer: { files: [testFile] },
+ });
+ })
+ }
+)
+
+function b64toBlob(b64Data, contentType = '', sliceSize = 512) {
+ const byteCharacters = atob(b64Data)
+ const byteArrays = []
+
+ for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
+ const slice = byteCharacters.slice(offset, offset + sliceSize);
+
+ const byteNumbers = new Array(slice.length);
+ for (let i = 0; i < slice.length; i++) {
+ byteNumbers[i] = slice.charCodeAt(i);
+ }
+
+ const byteArray = new Uint8Array(byteNumbers);
+
+ byteArrays.push(byteArray);
+ }
+
+ const blob = new Blob(byteArrays, { type: contentType });
+ return blob
+}
\ No newline at end of file
diff --git a/src/common/webdav.ts b/src/common/webdav.ts
index 758a5e18..93ec21cb 100644
--- a/src/common/webdav.ts
+++ b/src/common/webdav.ts
@@ -84,6 +84,15 @@ export class WebDAV {
.keys(headers)
.forEach(key => r.setRequestHeader(key, headers[key]));
+ if (!(window as any).cancelTokens) {
+ Object.assign(window, { cancelTokens: {} });
+ }
+
+ (window as any).cancelTokens[config.url] = () => {
+ resolve(r);
+ r.abort();
+ }
+
if (config.onUploadProgress) {
r.upload.addEventListener('progress', config.onUploadProgress);
}
diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index 97cbc8ce..a7001a61 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -517,7 +517,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
<Button
className={classes.uploadButton}
data-cy='upload-button'
- onClick={onUploadDataClick}
+ onClick={() => {
+ if (!collectionAutofetchEnabled) {
+ setCollectionAutofetchEnabled(true);
+ }
+ onUploadDataClick();
+ }}
variant='contained'
color='primary'
size='small'>
diff --git a/src/components/file-upload/file-upload.tsx b/src/components/file-upload/file-upload.tsx
index 617529cd..54d5b5db 100644
--- a/src/components/file-upload/file-upload.tsx
+++ b/src/components/file-upload/file-upload.tsx
@@ -123,6 +123,17 @@ export const FileUpload = withStyles(styles)(
if (!disabled) {
onDelete(file);
}
+
+ let interval = setInterval(() => {
+ const key = Object.keys((window as any).cancelTokens).find(key => key.indexOf(file.file.name) > -1);
+
+ if (key) {
+ clearInterval(interval);
+ (window as any).cancelTokens[key]();
+ delete (window as any).cancelTokens[key];
+ }
+ }, 100);
+
}
render() {
const { classes, onDrop, disabled, files } = this.props;
@@ -140,6 +151,7 @@ export const FileUpload = withStyles(styles)(
inputs[0].focus();
}
}}
+ data-cy="drag-and-drop"
disabled={disabled}
inputProps={{
onFocus: () => {
diff --git a/src/components/form-dialog/form-dialog.tsx b/src/components/form-dialog/form-dialog.tsx
index 19145cea..0fc799de 100644
--- a/src/components/form-dialog/form-dialog.tsx
+++ b/src/components/form-dialog/form-dialog.tsx
@@ -42,7 +42,9 @@ interface DialogProjectDataProps {
dialogTitle: string;
formFields: React.ComponentType<InjectedFormProps<any> & WithDialogProps<any>>;
submitLabel?: string;
+ cancelCallback?: Function;
enableWhenPristine?: boolean;
+ doNotDisableCancel?: boolean;
}
type DialogProjectProps = DialogProjectDataProps & WithDialogProps<{}> & InjectedFormProps<any> & WithStyles<CssRules>;
@@ -65,10 +67,18 @@ export const FormDialog = withStyles(styles)((props: DialogProjectProps) =>
<DialogActions className={props.classes.dialogActions}>
<Button
data-cy='form-cancel-btn'
- onClick={props.closeDialog}
+ onClick={() => {
+ props.closeDialog();
+
+ if (props.cancelCallback) {
+ props.cancelCallback();
+ props.reset();
+ props.initialize({});
+ }
+ }}
className={props.classes.button}
color="primary"
- disabled={props.submitting}>
+ disabled={props.doNotDisableCancel ? false : props.submitting}>
{props.cancelLabel || 'Cancel'}
</Button>
<Button
diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts
index 92437806..52fbf1a5 100644
--- a/src/services/collection-service/collection-service.ts
+++ b/src/services/collection-service/collection-service.ts
@@ -107,7 +107,7 @@ export class CollectionService extends TrashableResourceService<CollectionResour
},
onUploadProgress: (e: ProgressEvent) => {
onProgress(fileId, e.loaded, e.total, Date.now());
- }
+ },
};
return this.webdavClient.upload(fileURL, [file], requestConfig);
}
diff --git a/src/store/collections/collection-upload-actions.ts b/src/store/collections/collection-upload-actions.ts
index 8f85ea18..0ca681b9 100644
--- a/src/store/collections/collection-upload-actions.ts
+++ b/src/store/collections/collection-upload-actions.ts
@@ -14,12 +14,14 @@ import { progressIndicatorActions } from "store/progress-indicator/progress-indi
import { collectionPanelFilesAction } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
import { createTree } from 'models/tree';
import { loadCollectionPanel } from '../collection-panel/collection-panel-action';
+import * as WorkbenchActions from 'store/workbench/workbench-actions';
export const uploadCollectionFiles = (collectionUuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
dispatch(fileUploaderActions.START_UPLOAD());
const files = getState().fileUploader.map(file => file.file);
await services.collectionService.uploadFiles(collectionUuid, files, handleUploadProgress(dispatch));
+ dispatch(WorkbenchActions.loadCollection(collectionUuid));
dispatch(fileUploaderActions.CLEAR_UPLOAD());
};
diff --git a/src/store/file-uploader/file-uploader-actions.ts b/src/store/file-uploader/file-uploader-actions.ts
index 8436c485..a397bbd8 100644
--- a/src/store/file-uploader/file-uploader-actions.ts
+++ b/src/store/file-uploader/file-uploader-actions.ts
@@ -24,6 +24,7 @@ export const fileUploaderActions = unionize({
SET_UPLOAD_PROGRESS: ofType<{ fileId: number, loaded: number, total: number, currentTime: number }>(),
START_UPLOAD: ofType(),
DELETE_UPLOAD_FILE: ofType<UploadFile>(),
+ CANCEL_FILES_UPLOAD: ofType(),
});
export type FileUploaderAction = UnionOf<typeof fileUploaderActions>;
diff --git a/src/store/file-uploader/file-uploader-reducer.ts b/src/store/file-uploader/file-uploader-reducer.ts
index c1f9c681..4218fbee 100644
--- a/src/store/file-uploader/file-uploader-reducer.ts
+++ b/src/store/file-uploader/file-uploader-reducer.ts
@@ -43,6 +43,21 @@ export const fileUploaderReducer = (state: UploaderState = initialState, action:
return updatedState;
},
+ CANCEL_FILES_UPLOAD: () => {
+ state.forEach((file) => {
+ let interval = setInterval(() => {
+ const key = Object.keys((window as any).cancelTokens).find(key => key.indexOf(file.file.name) > -1);
+
+ if (key) {
+ clearInterval(interval);
+ (window as any).cancelTokens[key]();
+ delete (window as any).cancelTokens[key];
+ }
+ }, 100);
+ });
+
+ return [];
+ },
START_UPLOAD: () => {
const startTime = Date.now();
return state.map(f => ({ ...f, startTime, prevTime: startTime }));
diff --git a/src/views-components/dialog-upload/dialog-collection-files-upload.tsx b/src/views-components/dialog-upload/dialog-collection-files-upload.tsx
index 2f662bfa..f65bdabf 100644
--- a/src/views-components/dialog-upload/dialog-collection-files-upload.tsx
+++ b/src/views-components/dialog-upload/dialog-collection-files-upload.tsx
@@ -10,16 +10,30 @@ import { FormDialog } from 'components/form-dialog/form-dialog';
import { require } from 'validators/require';
import { FileUploaderField } from 'views-components/file-uploader/file-uploader';
import { WarningCollection } from 'components/warning-collection/warning-collection';
+import { fileUploaderActions } from 'store/file-uploader/file-uploader-actions';
+import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
type DialogCollectionFilesUploadProps = WithDialogProps<{}> & InjectedFormProps<CollectionCreateFormDialogData>;
-export const DialogCollectionFilesUpload = (props: DialogCollectionFilesUploadProps) =>
- <FormDialog
+export const DialogCollectionFilesUpload = (props: DialogCollectionFilesUploadProps) => {
+
+ return <FormDialog
dialogTitle='Upload data'
formFields={UploadCollectionFilesFields}
submitLabel='Upload data'
+ doNotDisableCancel
+ cancelCallback={() => {
+ const { submitting, dispatch } = (props as any);
+
+ if (submitting) {
+ dispatch(progressIndicatorActions.STOP_WORKING('uploadCollectionFilesDialog'));
+ dispatch(fileUploaderActions.CANCEL_FILES_UPLOAD());
+ dispatch(fileUploaderActions.CLEAR_UPLOAD());
+ }
+ }}
{...props}
/>;
+}
const UploadCollectionFilesFields = () => <>
<Field
diff --git a/src/views-components/file-uploader/file-uploader.tsx b/src/views-components/file-uploader/file-uploader.tsx
index 82e400f7..cde286c4 100644
--- a/src/views-components/file-uploader/file-uploader.tsx
+++ b/src/views-components/file-uploader/file-uploader.tsx
@@ -30,7 +30,7 @@ const mapDispatchToProps = (dispatch: Dispatch, { onDrop }: FileUploaderProps):
onDrop(files);
}
},
- onDelete: file => dispatch(fileUploaderActions.DELETE_UPLOAD_FILE(file)),
+ onDelete: file => dispatch(fileUploaderActions.DELETE_UPLOAD_FILE(file))
});
export const FileUploader = connect(mapStateToProps, mapDispatchToProps)(FileUpload);
@@ -38,5 +38,5 @@ export const FileUploader = connect(mapStateToProps, mapDispatchToProps)(FileUpl
export const FileUploaderField = (props: WrappedFieldProps & { label?: string }) =>
<div>
<Typography variant='caption'>{props.label}</Typography>
- <FileUploader disabled={props.meta.submitting} onDrop={props.input.onChange} />
+ <FileUploader disabled={false} onDrop={props.input.onChange} />
</div>;
commit 9610e9d15c6c00b0684753d1f3ec90d550e700cf
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date: Sun Nov 14 19:52:15 2021 +0100
Merge branch '18195-Refresh-Button-does-not-seem-to-refresh-the-sidebar-tree' into main
closes #18195
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
diff --git a/cypress/integration/side-panel.spec.js b/cypress/integration/side-panel.spec.js
index 912e68eb..f9d4dca3 100644
--- a/cypress/integration/side-panel.spec.js
+++ b/cypress/integration/side-panel.spec.js
@@ -114,4 +114,30 @@ describe('Side panel tests', function() {
});
});
+ it('side panel react to refresh when project data changes', () => {
+ const project = 'writableProject';
+
+ cy.createProject({
+ owningUser: activeUser,
+ targetUser: activeUser,
+ projectName: project,
+ canWrite: true,
+ addToFavorites: false
+ });
+
+ cy.getAll('@writableProject')
+ .then(function ([writableProject]) {
+ cy.loginAs(activeUser);
+
+ cy.get('[data-cy=side-panel-tree]').contains('Projects').click();
+
+ cy.get('[data-cy=side-panel-tree]').contains(writableProject.name).should('exist');
+
+ cy.trashGroup(activeUser.token, writableProject.uuid);
+
+ cy.contains('Refresh').click();
+
+ cy.contains(writableProject.name).should('not.exist');
+ });
+ });
})
diff --git a/src/components/refresh-button/refresh-button.tsx b/src/components/refresh-button/refresh-button.tsx
index f2c41d28..9971547b 100644
--- a/src/components/refresh-button/refresh-button.tsx
+++ b/src/components/refresh-button/refresh-button.tsx
@@ -22,13 +22,20 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
},
});
-export const RefreshButton = ({ history, classes }: RouteComponentProps & WithStyles<CssRules>) =>
+interface RefreshButtonProps {
+ onClick?: () => void;
+}
+
+export const RefreshButton = ({ history, classes, onClick }: RouteComponentProps & WithStyles<CssRules> & RefreshButtonProps) =>
<Button
color="primary"
size="small"
variant="contained"
onClick={() => {
history.replace(window.location.pathname);
+ if (onClick) {
+ onClick();
+ }
}}
className={classNames(classes.buttonRight, classes.button)}>
<ReRunProcessIcon />
diff --git a/src/views-components/main-content-bar/main-content-bar.tsx b/src/views-components/main-content-bar/main-content-bar.tsx
index 6e1368c0..10ae1790 100644
--- a/src/views-components/main-content-bar/main-content-bar.tsx
+++ b/src/views-components/main-content-bar/main-content-bar.tsx
@@ -12,6 +12,8 @@ import { RootState } from 'store/store';
import * as Routes from 'routes/routes';
import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
import RefreshButton from "components/refresh-button/refresh-button";
+import { reloadProjectMatchingUuid } from "store/workbench/workbench-actions";
+import { loadSidePanelTreeProjects } from "store/side-panel-tree/side-panel-tree-actions";
type CssRules = "infoTooltip";
@@ -55,10 +57,15 @@ const isButtonVisible = ({ router }: RootState) => {
export const MainContentBar =
connect((state: RootState) => ({
- buttonVisible: isButtonVisible(state)
- }), {
+ buttonVisible: isButtonVisible(state),
+ projectUuid: state.detailsPanel.resourceUuid,
+ }), (dispatch) => ({
onDetailsPanelToggle: toggleDetailsPanel,
- })(
+ onRefreshButtonClick: (id) => {
+ dispatch<any>(loadSidePanelTreeProjects(id));
+ dispatch<any>(reloadProjectMatchingUuid([id]));
+ }
+ }))(
withStyles(styles)(
(props: MainContentBarProps & WithStyles<CssRules> & any) =>
<Toolbar>
@@ -67,7 +74,9 @@ export const MainContentBar =
<Breadcrumbs />
</Grid>
<Grid item>
- <RefreshButton />
+ <RefreshButton onClick={() => {
+ props.onRefreshButtonClick(props.projectUuid);
+ }} />
</Grid>
<Grid item>
{props.buttonVisible && <Tooltip title="Additional Info">
commit bb1b66c4f7858fb19075d9541ac43ecb7ac955f3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Thu Nov 11 14:53:00 2021 -0300
Merge branch '17944-vocabulary-endpoint-retrieval' into main. Closes #17944
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/README.md b/README.md
index 8bb50dbe..4ec4bd1c 100644
--- a/README.md
+++ b/README.md
@@ -82,7 +82,6 @@ Currently this configuration schema is supported:
```
{
"API_HOST": "string",
- "VOCABULARY_URL": "string",
"FILE_VIEWERS_CONFIG_URL": "string",
}
```
@@ -93,12 +92,6 @@ The Arvados base URL.
The `REACT_APP_ARVADOS_API_HOST` environment variable can be used to set the default URL if the run time configuration is unreachable.
-### VOCABULARY_URL
-Local path, or any URL that allows cross-origin requests. See
-[Vocabulary JSON file example](public/vocabulary-example.json).
-
-To use the URL defined in the Arvados cluster configuration, remove the entire `VOCABULARY_URL` entry from the runtime configuration. Found in `/config.json` by default.
-
## FILE_VIEWERS_CONFIG_URL
Local path, or any URL that allows cross-origin requests. See:
diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js
index fb126af6..3e06d7e5 100644
--- a/cypress/integration/collection.spec.js
+++ b/cypress/integration/collection.spec.js
@@ -97,14 +97,37 @@ describe('Collection panel tests', function () {
});
// Confirm proper vocabulary labels are displayed on the UI.
cy.get('[data-cy=collection-properties-panel]')
- .should('contain', 'Color')
- .and('contain', 'Magenta');
+ .should('contain', 'Color: Magenta');
// Confirm proper vocabulary IDs were saved on the backend.
cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
.its('body').as('collection')
.then(function () {
expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3');
});
+
+ // Case-insensitive on-blur auto-selection test
+ // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2)
+ cy.get('[data-cy=resource-properties-form]').within(() => {
+ cy.get('[data-cy=property-field-key]').within(() => {
+ cy.get('input').type('sIzE');
+ });
+ cy.get('[data-cy=property-field-value]').within(() => {
+ cy.get('input').type('sMaLL');
+ });
+ // Cannot "type()" TAB on Cypress so let's click another field
+ // to trigger the onBlur event.
+ cy.get('[data-cy=property-field-key]').click();
+ cy.root().submit();
+ });
+ // Confirm proper vocabulary labels are displayed on the UI.
+ cy.get('[data-cy=collection-properties-panel]')
+ .should('contain', 'Size: S');
+ // Confirm proper vocabulary IDs were saved on the backend.
+ cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
+ .its('body').as('collection')
+ .then(function () {
+ expect(this.collection.properties.IDTAGSIZES).to.equal('IDVALSIZES2');
+ });
});
});
diff --git a/src/common/config.ts b/src/common/config.ts
index 56f7c488..2518c95e 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -51,7 +51,6 @@ export interface ClusterConfigJSON {
};
Workbench: {
ArvadosDocsite: string;
- VocabularyURL: string;
FileViewersConfigURL: string;
WelcomePageHTML: string;
InactivePageHTML: string;
@@ -204,15 +203,10 @@ remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`);
}
config.fileViewersConfigUrl = fileViewerConfigUrl;
- let vocabularyUrl;
if (workbenchConfig.VOCABULARY_URL !== undefined) {
- warnLocalConfig("VOCABULARY_URL");
- vocabularyUrl = workbenchConfig.VOCABULARY_URL;
+ console.warn(`A value for VOCABULARY_URL was found in ${WORKBENCH_CONFIG_URL}. It will be ignored as the cluster already provides its own endpoint, you can safely remove it.`)
}
- else {
- vocabularyUrl = config.clusterConfig.Workbench.VocabularyURL || "/vocabulary-example.json";
- }
- config.vocabularyUrl = vocabularyUrl;
+ config.vocabularyUrl = getVocabularyURL(workbenchConfig.API_HOST);
return { config, apiHost: workbenchConfig.API_HOST };
});
@@ -240,7 +234,6 @@ export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): Clust
},
Workbench: {
ArvadosDocsite: "",
- VocabularyURL: "",
FileViewersConfigURL: "",
WelcomePageHTML: "",
InactivePageHTML: "",
@@ -315,5 +308,7 @@ const getDefaultConfig = (): WorkbenchConfig => {
export const ARVADOS_API_PATH = "arvados/v1";
export const CLUSTER_CONFIG_PATH = "arvados/v1/config";
+export const VOCABULARY_PATH = "arvados/v1/vocabulary";
export const DISCOVERY_DOC_PATH = "discovery/v1/apis/arvados/v1/rest";
-export const getClusterConfigURL = (apiHost: string) => `${window.location.protocol}//${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`;
+export const getClusterConfigURL = (apiHost: string) => `https://${apiHost}/${CLUSTER_CONFIG_PATH}?nocache=${(new Date()).getTime()}`;
+export const getVocabularyURL = (apiHost: string) => `https://${apiHost}/${VOCABULARY_PATH}?nocache=${(new Date()).getTime()}`;
diff --git a/src/models/vocabulary.ts b/src/models/vocabulary.ts
index 03f28c07..3c542844 100644
--- a/src/models/vocabulary.ts
+++ b/src/models/vocabulary.ts
@@ -47,7 +47,7 @@ export const getTagValueID = (tagKeyID:string, tagValueLabel:string, vocabulary:
(tagKeyID && vocabulary.tags[tagKeyID] && vocabulary.tags[tagKeyID].values)
? Object.keys(vocabulary.tags[tagKeyID].values!).find(
k => vocabulary.tags[tagKeyID].values![k].labels.find(
- l => l.label === tagValueLabel) !== undefined) || ''
+ l => l.label.toLowerCase() === tagValueLabel.toLowerCase()) !== undefined) || ''
: '';
export const getTagValueLabel = (tagKeyID:string, tagValueID:string, vocabulary: Vocabulary) =>
@@ -94,7 +94,7 @@ export const getTags = ({ tags }: Vocabulary) => {
export const getTagKeyID = (tagKeyLabel:string, vocabulary: Vocabulary) =>
Object.keys(vocabulary.tags).find(
k => vocabulary.tags[k].labels.find(
- l => l.label === tagKeyLabel) !== undefined
+ l => l.label.toLowerCase() === tagKeyLabel.toLowerCase()) !== undefined
) || '';
export const getTagKeyLabel = (tagKeyID:string, vocabulary: Vocabulary) =>
diff --git a/src/services/vocabulary-service/vocabulary-service.ts b/src/services/vocabulary-service/vocabulary-service.ts
index ff2de159..38163f77 100644
--- a/src/services/vocabulary-service/vocabulary-service.ts
+++ b/src/services/vocabulary-service/vocabulary-service.ts
@@ -10,9 +10,9 @@ export class VocabularyService {
private url: string
) { }
- getVocabulary() {
- return Axios
- .get<Vocabulary>(this.url)
- .then(response => response.data);
+ async getVocabulary() {
+ const response = await Axios
+ .get<Vocabulary>(this.url);
+ return response.data;
}
}
diff --git a/src/store/vocabulary/vocabulary-actions.ts b/src/store/vocabulary/vocabulary-actions.ts
index 2ca344bb..d73c01fe 100644
--- a/src/store/vocabulary/vocabulary-actions.ts
+++ b/src/store/vocabulary/vocabulary-actions.ts
@@ -10,7 +10,6 @@ import { isVocabulary } from 'models/vocabulary';
export const loadVocabulary = async (dispatch: Dispatch, _: {}, { vocabularyService }: ServiceRepository) => {
const vocabulary = await vocabularyService.getVocabulary();
-
dispatch(propertiesActions.SET_PROPERTY({
key: VOCABULARY_PROPERTY_NAME,
value: isVocabulary(vocabulary)
diff --git a/src/views-components/resource-properties-form/property-key-field.tsx b/src/views-components/resource-properties-form/property-key-field.tsx
index 029d44cc..791949f5 100644
--- a/src/views-components/resource-properties-form/property-key-field.tsx
+++ b/src/views-components/resource-properties-form/property-key-field.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import { WrappedFieldProps, Field, FormName, reset, change, WrappedFieldInputProps, WrappedFieldMetaProps } from 'redux-form';
import { memoize } from 'lodash';
import { Autocomplete } from 'components/autocomplete/autocomplete';
-import { Vocabulary, getTags, getTagKeyID } from 'models/vocabulary';
+import { Vocabulary, getTags, getTagKeyID, getTagKeyLabel } from 'models/vocabulary';
import {
handleSelect,
handleBlur,
@@ -39,7 +39,14 @@ const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & Vocabula
label='Key'
suggestions={getSuggestions(props.input.value, vocabulary)}
onSelect={handleSelect(PROPERTY_KEY_FIELD_ID, data.form, props.input, props.meta)}
- onBlur={handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, getTagKeyID(props.input.value, vocabulary))}
+ onBlur={() => {
+ // Case-insensitive search for the key in the vocabulary
+ const foundKeyID = getTagKeyID(props.input.value, vocabulary);
+ if (foundKeyID !== '') {
+ props.input.value = getTagKeyLabel(foundKeyID, vocabulary);
+ }
+ handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, foundKeyID)();
+ }}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const newValue = e.currentTarget.value;
handleChange(data.form, props.input, props.meta, newValue);
diff --git a/src/views-components/resource-properties-form/property-value-field.tsx b/src/views-components/resource-properties-form/property-value-field.tsx
index a2b53b3c..b023e412 100644
--- a/src/views-components/resource-properties-form/property-value-field.tsx
+++ b/src/views-components/resource-properties-form/property-value-field.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import { WrappedFieldProps, Field, formValues, FormName, WrappedFieldInputProps, WrappedFieldMetaProps, change } from 'redux-form';
import { compose } from 'redux';
import { Autocomplete } from 'components/autocomplete/autocomplete';
-import { Vocabulary, isStrictTag, getTagValues, getTagValueID } from 'models/vocabulary';
+import { Vocabulary, isStrictTag, getTagValues, getTagValueID, getTagValueLabel } from 'models/vocabulary';
import { PROPERTY_KEY_FIELD_ID, PROPERTY_KEY_FIELD_NAME } from 'views-components/resource-properties-form/property-key-field';
import {
handleSelect,
@@ -60,7 +60,14 @@ const PropertyValueInput = ({ vocabulary, propertyKeyId, propertyKeyName, ...pro
disabled={props.disabled}
suggestions={getSuggestions(props.input.value, propertyKeyId, vocabulary)}
onSelect={handleSelect(PROPERTY_VALUE_FIELD_ID, data.form, props.input, props.meta)}
- onBlur={handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, getTagValueID(propertyKeyId, props.input.value, vocabulary))}
+ onBlur={() => {
+ // Case-insensitive search for the value in the vocabulary
+ const foundValueID = getTagValueID(propertyKeyId, props.input.value, vocabulary);
+ if (foundValueID !== '') {
+ props.input.value = getTagValueLabel(propertyKeyId, foundValueID, vocabulary);
+ }
+ handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, foundValueID)();
+ }}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const newValue = e.currentTarget.value;
const tagValueID = getTagValueID(propertyKeyId, newValue, vocabulary);
diff --git a/tools/arvados_config.yml b/tools/arvados_config.yml
index 369046e6..55dc8a02 100644
--- a/tools/arvados_config.yml
+++ b/tools/arvados_config.yml
@@ -4,6 +4,7 @@ Clusters:
SystemRootToken: systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy
API:
RequestTimeout: 30s
+ VocabularyPath: ""
TLS:
Insecure: true
Collections:
diff --git a/public/vocabulary-example.json b/tools/example-vocabulary.json
similarity index 100%
rename from public/vocabulary-example.json
rename to tools/example-vocabulary.json
diff --git a/tools/run-integration-tests.sh b/tools/run-integration-tests.sh
index 159bfc1c..bf4c3ba4 100755
--- a/tools/run-integration-tests.sh
+++ b/tools/run-integration-tests.sh
@@ -70,6 +70,7 @@ echo "ARVADOS_DIR is ${ARVADOS_DIR}"
ARVADOS_LOG=${ARVADOS_DIR}/arvados.log
ARVADOS_CONF=${WB2_DIR}/tools/arvados_config.yml
+VOCABULARY_CONF=${WB2_DIR}/tools/example-vocabulary.json
if [ ! -f "${WB2_DIR}/src/index.tsx" ]; then
echo "ERROR: '${WB2_DIR}' isn't workbench2's directory"
@@ -104,6 +105,9 @@ echo "Installing dev dependencies..."
~/go/bin/arvados-server install -type test || exit 1
echo "Launching arvados in test mode..."
+VOC_DIR=$(mktemp -d | cut -d \/ -f3) # Removes the /tmp/ part
+cp ${VOCABULARY_CONF} /tmp/${VOC_DIR}/voc.json
+sed -i "s/VocabularyPath: \".*\"/VocabularyPath: \"\/tmp\/${VOC_DIR}\/voc.json\"/" ${ARVADOS_CONF}
coproc arvboot (~/go/bin/arvados-server boot \
-type test \
-config ${ARVADOS_CONF} \
commit 736b6143c9f22e755344ba907aa90669c001386d
Author: Peter Amstutz <peter.amstutz at curii.com>
Date: Fri Oct 29 16:46:20 2021 -0400
Update version of Go used in the docker image.
Add a "make packages-in-docker" target.
no issue #
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>
diff --git a/Makefile b/Makefile
index aaf2271c..26361bb1 100644
--- a/Makefile
+++ b/Makefile
@@ -130,5 +130,16 @@ copy: $(DEB_FILE) $(RPM_FILE)
# use FPM to create DEB and RPM
packages: copy
+packages-in-docker: workbench2-build-image
+ docker run --env ci="true" \
+ --env ARVADOS_DIRECTORY=/tmp/arvados \
+ --env APP_NAME=${APP_NAME} \
+ --env ITERATION=${ITERATION} \
+ --env TARGETS="${TARGETS}" \
+ -w="/tmp/workbench2" \
+ -t -v ${WORKSPACE}:/tmp/workbench2 \
+ -v ${ARVADOS_DIRECTORY}:/tmp/arvados workbench2-build:latest \
+ make packages
+
workbench2-build-image:
(cd docker && docker build -t workbench2-build .)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 729d62c4..3bffcac4 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -11,8 +11,16 @@ RUN apt-get update && \
libsecret-1-0 libsecret-1-dev rpm ruby ruby-dev rubygems build-essential \
libpam0g-dev libgbm1 git && \
apt-get clean
-RUN apt-get -yq --no-install-recommends -t buster-backports install golang-go && \
- apt-get clean
+
+# Get Go 1.16.9
+RUN cd /usr/src && \
+ wget https://golang.org/dl/go1.16.9.linux-amd64.tar.gz && \
+ tar xzf go1.16.9.linux-amd64.tar.gz && \
+ ln -s /usr/src/go/bin/go /usr/local/bin/go-1.16.9 && \
+ ln -s /usr/src/go/bin/gofmt /usr/local/bin/gofmt-1.16.9 && \
+ ln -s /usr/local/bin/go-1.16.9 /usr/local/bin/go && \
+ ln -s /usr/local/bin/gofmt-1.16.9 /usr/local/bin/gofmt
+
RUN gem install --no-ri --no-rdoc fpm
RUN git clone https://git.arvados.org/arvados.git && cd arvados && \
go mod download && \
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list