Merge branch '16811-public-favs' refs #16811
authorPeter Amstutz <peter.amstutz@curii.com>
Thu, 24 Sep 2020 13:44:55 +0000 (09:44 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Thu, 24 Sep 2020 13:44:55 +0000 (09:44 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

README.md
cypress/integration/favorites.js [new file with mode: 0644]
cypress/support/commands.js
src/store/favorite-panel/favorite-panel-middleware-service.ts
src/store/public-favorites-panel/public-favorites-middleware-service.ts
src/store/public-favorites/public-favorites-actions.ts
src/store/shared-with-me-panel/shared-with-me-middleware-service.ts
src/store/side-panel-tree/side-panel-tree-actions.ts
src/store/tree-picker/tree-picker-actions.ts

index 38a26e54bf6a39399a3a46c349abdeb028c73ca6..55e96af3c14e2d764dac391a2c632137567e293d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -31,6 +31,17 @@ make integration-tests
 make integration-tests-in-docker
 </pre>
 
+### Run tests interactively in container
+
+<pre>
+$ xhost +local:root
+$ ARVADOS_DIR=/path/to/arvados
+$ docker run -ti -v$PWD:$PWD -v$ARVADOS_DIR:/usr/src/arvados -w$PWD --env="DISPLAY" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" workbench2-build /bin/bash
+(inside container)
+# yarn run cypress install
+# tools/run-integration-tests.sh -i -a /usr/src/arvados
+</pre>
+
 ### Production build
 <pre>
 yarn install
diff --git a/cypress/integration/favorites.js b/cypress/integration/favorites.js
new file mode 100644 (file)
index 0000000..b38399b
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Collection panel tests', function() {
+    let activeUser;
+    let adminUser;
+
+    before(function() {
+        // Only set up common users once. These aren't set up as aliases because
+        // aliases are cleaned up after every test. Also it doesn't make sense
+        // to set the same users on beforeEach() over and over again, so we
+        // separate a little from Cypress' 'Best Practices' here.
+        cy.getUser('admin', 'Admin', 'User', true, true)
+            .as('adminUser').then(function() {
+                adminUser = this.adminUser;
+            }
+        );
+        cy.getUser('collectionuser1', 'Collection', 'User', false, true)
+            .as('activeUser').then(function() {
+                activeUser = this.activeUser;
+            }
+        );
+    })
+
+    beforeEach(function() {
+        cy.clearCookies()
+        cy.clearLocalStorage()
+    })
+
+    it('checks that Public favorites does not appear under shared with me', function() {
+        cy.loginAs(adminUser);
+        cy.contains('Shared with me').click();
+        cy.get('main').contains('Public favorites').should('not.exist');
+    })
+
+    it('creates and removes a public favorite', function() {
+        cy.loginAs(adminUser);
+            cy.createGroup(adminUser.token, {
+                name: `my-favorite-project`,
+                group_class: 'project',
+            }).as('myFavoriteProject').then(function() {
+                cy.contains('Refresh').click();
+                cy.get('main').contains('my-favorite-project').rightclick();
+                cy.contains('Add to public favorites').click();
+                cy.contains('Public Favorites').click();
+                cy.get('main').contains('my-favorite-project').rightclick();
+                cy.contains('Remove from public favorites').click();
+                cy.get('main').contains('my-favorite-project').should('not.exist');
+                cy.trashGroup(adminUser.token, this.myFavoriteProject.uuid);
+            });
+    })
+})
index 8baa2db6e58398b969ecd97fdb3bc6a481ef5d3e..fd5139981fc421cf4304548c8c9980841b273710 100644 (file)
@@ -110,6 +110,12 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    "trashGroup", (token, uuid) => {
+        return cy.deleteResource(token, 'groups', uuid);
+    }
+)
+
 Cypress.Commands.add(
     "createCollection", (token, data) => {
         return cy.createResource(token, 'collections', {
@@ -129,6 +135,16 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    "deleteResource", (token, suffix, uuid) => {
+        return cy.doRequest('DELETE', '/arvados/v1/'+suffix+'/'+uuid)
+        .its('body').as('resource')
+        .then(function() {
+            return this.resource;
+        })
+    }
+)
+
 Cypress.Commands.add(
     "loginAs", (user) => {
         cy.visit(`/token/?api_token=${user.token}`);
@@ -136,4 +152,4 @@ Cypress.Commands.add(
         cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
         cy.get('div#root').should('not.contain', 'Your account is inactive');
     }
-)
\ No newline at end of file
+)
index f45a62f87c41e777471ab2aaf8adadac0de640bd..c9a06fe6fd4b3c90eb70258ced19247126610a38 100644 (file)
@@ -25,6 +25,7 @@ import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
 import { getDataExplorerColumnFilters } from '~/store/data-explorer/data-explorer-middleware-service';
 import { serializeSimpleObjectTypeFilters } from '../resource-type-filters/resource-type-filters';
 import { ResourceKind } from "~/models/resource";
+import { LinkClass } from "~/models/link";
 
 export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -59,7 +60,7 @@ export class FavoritePanelMiddlewareService extends DataExplorerMiddlewareServic
                 api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
                 const responseLinks = await this.services.linkService.list({
                     filters: new FilterBuilder()
-                        .addEqual("link_class", 'star')
+                        .addEqual("link_class", LinkClass.STAR)
                         .addEqual('tail_uuid', getUserUuid(api.getState()))
                         .addEqual('tail_kind', ResourceKind.USER)
                         .getFilters()
index 072cf4e430c49f8b47432321ce1cf36815707209..b98dfdfc210c2af35c256a330b80f38ca426aa5c 100644 (file)
@@ -54,14 +54,13 @@ export class PublicFavoritesMiddlewareService extends DataExplorerMiddlewareServ
             try {
                 api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
                 const uuidPrefix = api.getState().auth.config.uuidPrefix;
-                const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
+                const publicProjectUuid = `${uuidPrefix}-j7d0g-publicfavorites`;
                 const responseLinks = await this.services.linkService.list({
                     limit: dataExplorer.rowsPerPage,
                     offset: dataExplorer.page * dataExplorer.rowsPerPage,
                     filters: new FilterBuilder()
                         .addEqual('link_class', LinkClass.STAR)
-                        .addILike("name", dataExplorer.searchValue)
-                        .addEqual('owner_uuid', uuid)
+                        .addEqual('owner_uuid', publicProjectUuid)
                         .addIsA("head_uuid", typeFilters)
                         .getFilters()
                 });
index d5a5cd46264778a82913adeb88866aa72dc08c04..db8af2e239f5d921e90de3bbabfb906ba0a9d87a 100644 (file)
@@ -22,7 +22,7 @@ export const togglePublicFavorite = (resource: { uuid: string; name: string }) =
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
         dispatch(progressIndicatorActions.START_WORKING("togglePublicFavorite"));
         const uuidPrefix = getState().auth.config.uuidPrefix;
-        const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
+        const uuid = `${uuidPrefix}-j7d0g-publicfavorites`;
         dispatch(publicFavoritesActions.TOGGLE_PUBLIC_FAVORITE({ resourceUuid: resource.uuid }));
         const isPublicFavorite = checkPublicFavorite(resource.uuid, getState().publicFavorites);
         dispatch(snackbarActions.OPEN_SNACKBAR({
@@ -58,7 +58,7 @@ export const togglePublicFavorite = (resource: { uuid: string; name: string }) =
 export const updatePublicFavorites = (resourceUuids: string[]) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const uuidPrefix = getState().auth.config.uuidPrefix;
-        const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
+        const uuid = `${uuidPrefix}-j7d0g-publicfavorites`;
         dispatch(publicFavoritesActions.CHECK_PRESENCE_IN_PUBLIC_FAVORITES(resourceUuids));
         services.favoriteService
             .checkPresenceInFavorites(uuid, resourceUuids)
index a09a308593b6db94704c1cc2a2a54154f53da030..e5dd6d9f31b41aca67a63a99609905acd9d3725d 100644 (file)
@@ -10,7 +10,7 @@ import { getDataExplorer, DataExplorer } from '~/store/data-explorer/data-explor
 import { updateFavorites } from '~/store/favorites/favorites-actions';
 import { updateResources } from '~/store/resources/resources-actions';
 import { loadMissingProcessesInformation, getFilters } from '~/store/project-panel/project-panel-middleware-service';
-import {snackbarActions, SnackbarKind} from '~/store/snackbar/snackbar-actions';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
 import { sharedWithMePanelActions } from './shared-with-me-panel-actions';
 import { ListResults } from '~/services/common-service/common-service';
 import { GroupContentsResource, GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
@@ -20,6 +20,7 @@ import { ProjectResource } from '~/models/project';
 import { ProjectPanelColumnNames } from '~/views/project-panel/project-panel';
 import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer";
 import { updatePublicFavorites } from '~/store/public-favorites/public-favorites-actions';
+import { FilterBuilder } from '~/services/api/filter-builder';
 
 export class SharedWithMeMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -34,6 +35,7 @@ export class SharedWithMeMiddlewareService extends DataExplorerMiddlewareService
                 .contents('', {
                     ...getParams(dataExplorer),
                     excludeHomeProject: true,
+                    filters: new FilterBuilder().addDistinct('uuid', `${state.auth.config.uuidPrefix}-j7d0g-publicfavorites`).getFilters()
                 });
             api.dispatch<any>(updateFavorites(response.items.map(item => item.uuid)));
             api.dispatch<any>(updatePublicFavorites(response.items.map(item => item.uuid)));
index a09ab6ba6e41ad84d094251f1675172dc078a005..ff506103db6ce3ecf23e4e3d0fadbd26d8d5385b 100644 (file)
@@ -106,13 +106,14 @@ const loadProject = (projectUuid: string) =>
         dispatch(resourcesActions.SET_RESOURCES(items));
     };
 
-const loadSharedRoot = async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
+const loadSharedRoot = async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id: SidePanelTreeCategory.SHARED_WITH_ME, pickerId: SIDE_PANEL_TREE }));
 
     const params = {
         filters: `[${new FilterBuilder()
             .addIsA('uuid', ResourceKind.PROJECT)
             .addEqual('group_class', GroupClass.PROJECT)
+            .addDistinct('uuid', getState().auth.config.uuidPrefix + '-j7d0g-publicfavorites')
             .getFilters()}]`,
         order: new OrderBuilder<ProjectResource>()
             .addAsc('name', GroupContentsResourcePrefix.PROJECT)
index 5e880aad9587583ffaeebde865b675612b752785..3fa5187a3dcf0b9e7eb7be39bdec1ea2d0fb5e7f 100644 (file)
@@ -271,36 +271,34 @@ export const loadPublicFavoritesProject = (params: LoadFavoritesProjectParams) =
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
         const { pickerId, includeCollections = false, includeFiles = false } = params;
         const uuidPrefix = getState().auth.config.uuidPrefix;
-        const uuid = `${uuidPrefix}-j7d0g-fffffffffffffff`;
-        if (uuid) {
+        const publicProjectUuid = `${uuidPrefix}-j7d0g-publicfavorites`;
 
-            const filters = pipe(
-                (fb: FilterBuilder) => includeCollections
-                    ? fb.addIsA('head_uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
-                    : fb.addIsA('head_uuid', [ResourceKind.PROJECT]),
-                fb => fb
-                    .addEqual('link_class', LinkClass.STAR)
-                    .addEqual('owner_uuid', uuid)
-                    .getFilters(),
-            )(new FilterBuilder());
+        const filters = pipe(
+            (fb: FilterBuilder) => includeCollections
+                ? fb.addIsA('head_uuid', [ResourceKind.PROJECT, ResourceKind.COLLECTION])
+                : fb.addIsA('head_uuid', [ResourceKind.PROJECT]),
+            fb => fb
+                .addEqual('link_class', LinkClass.STAR)
+                .addEqual('owner_uuid', publicProjectUuid)
+                .getFilters(),
+        )(new FilterBuilder());
 
-            const { items } = await services.linkService.list({ filters });
+        const { items } = await services.linkService.list({ filters });
 
-            dispatch<any>(receiveTreePickerData<LinkResource>({
-                id: 'Public Favorites',
-                pickerId,
-                data: items,
-                extractNodeData: item => ({
-                    id: item.headUuid,
-                    value: item,
-                    status: item.headKind === ResourceKind.PROJECT
+        dispatch<any>(receiveTreePickerData<LinkResource>({
+            id: 'Public Favorites',
+            pickerId,
+            data: items,
+            extractNodeData: item => ({
+                id: item.headUuid,
+                value: item,
+                status: item.headKind === ResourceKind.PROJECT
+                    ? TreeNodeStatus.INITIAL
+                    : includeFiles
                         ? TreeNodeStatus.INITIAL
-                        : includeFiles
-                            ? TreeNodeStatus.INITIAL
-                            : TreeNodeStatus.LOADED
-                }),
-            }));
-        }
+                        : TreeNodeStatus.LOADED
+            }),
+        }));
     };
 
 export const receiveTreePickerProjectsData = (id: string, projects: ProjectResource[], pickerId: string) =>