[ARVADOS-WORKBENCH2] updated: 2.3.1-5-gd0df6414

Git user git at public.arvados.org
Mon Dec 6 21:27:47 UTC 2021


Summary of changes:
 cypress/integration/favorites.spec.js              |  6 ++--
 .../collection-service/collection-service.test.ts  | 40 ++++++++++++----------
 .../collection-service/collection-service.ts       |  3 +-
 .../common-service/common-resource-service.ts      |  5 ++-
 .../collection-panel/collection-panel-action.ts    |  6 +++-
 src/store/collections/collection-update-actions.ts |  3 ++
 src/views/collection-panel/collection-panel.tsx    |  2 +-
 7 files changed, 40 insertions(+), 25 deletions(-)

  discards  0b84326ca02a4ce6b9d8bd5b9d92e633eb629682 (commit)
  discards  bfeea246674f46989aee854d8e32b117143b6e6e (commit)
       via  d0df6414bef82439ff46836f059632250d2de3c2 (commit)
       via  e9f10ba7ae13cfa4909f0b0dc6ef2911f7d15af3 (commit)
       via  176f06e8893b71d4426d8917900a698d8de6a24b (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (0b84326ca02a4ce6b9d8bd5b9d92e633eb629682)
            \
             N -- N -- N (d0df6414bef82439ff46836f059632250d2de3c2)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


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;
 

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list