From: Lucas Di Pentima Date: Thu, 24 Sep 2020 21:58:35 +0000 (-0300) Subject: Merge branch '16592-renaming-fix' X-Git-Tag: 2.1.0~7 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/33c03ae3c79936cc1a69129f07fba33fe2d28fd8?hp=26ec4bc0f0d5ecaaedb468993400632e2d5a58c0 Merge branch '16592-renaming-fix' Closes #16592 Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/README.md b/README.md index 38a26e54..55e96af3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,17 @@ make integration-tests make integration-tests-in-docker +### Run tests interactively in container + +
+$ xhost +local:root
+$ ARVADOS_DIR=/path/to/arvados
+$ docker run -ti -v$PWD:$PWD -v$ARVADOS_DIR:/usr/src/arvados -w$PWD --env="DISPLAY" --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" workbench2-build /bin/bash
+(inside container)
+# yarn run cypress install
+# tools/run-integration-tests.sh -i -a /usr/src/arvados
+
+ ### Production build
 yarn install
diff --git a/cypress/integration/favorites.js b/cypress/integration/favorites.js
new file mode 100644
index 00000000..b38399be
--- /dev/null
+++ b/cypress/integration/favorites.js
@@ -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);
+            });
+    })
+})
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 8baa2db6..fd513998 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -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
+)
diff --git a/src/components/collection-panel-files/collection-panel-files.test.tsx b/src/components/collection-panel-files/collection-panel-files.test.tsx
new file mode 100644
index 00000000..86f823a0
--- /dev/null
+++ b/src/components/collection-panel-files/collection-panel-files.test.tsx
@@ -0,0 +1,116 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { configure, shallow, mount } from "enzyme";
+import { WithStyles } from "@material-ui/core";
+import * as Adapter from "enzyme-adapter-react-16";
+import { TreeItem, TreeItemStatus } from '../tree/tree';
+import { FileTreeData } from '../file-tree/file-tree-data';
+import { CollectionFileType } from "../../models/collection-file";
+import { CollectionPanelFilesComponent, CollectionPanelFilesProps, CssRules } from './collection-panel-files';
+import { SearchInput } from '../search-input/search-input';
+
+configure({ adapter: new Adapter() });
+
+jest.mock('~/components/file-tree/file-tree', () => ({
+    FileTree: () => 'FileTree',
+}));
+
+describe('', () => {
+    let props: CollectionPanelFilesProps & WithStyles;
+
+    beforeEach(() => {
+        props = {
+            classes: {} as Record,
+            items: [],
+            isWritable: true,
+            isLoading: false,
+            tooManyFiles: false,
+            onUploadDataClick: jest.fn(),
+            onSearchChange: jest.fn(),
+            onItemMenuOpen: jest.fn(),
+            onOptionsMenuOpen: jest.fn(),
+            onSelectionToggle: jest.fn(),
+            onCollapseToggle: jest.fn(),
+            onFileClick: jest.fn(),
+            loadFilesFunc: jest.fn(),
+            currentItemUuid: '',
+        };
+    });
+
+    it('renders properly', () => {
+        // when
+        const wrapper = shallow();
+
+        // then
+        expect(wrapper).not.toBeUndefined();
+    });
+
+    it('filters out files', () => {
+        // given
+        const searchPhrase = 'test';
+        const items: Array> = [
+            {
+                data: {
+                    url: '',
+                    type: CollectionFileType.DIRECTORY,
+                    name: 'test',
+                },
+                id: '1',
+                open: true,
+                active: true,
+                status: TreeItemStatus.LOADED,
+            },
+            {
+                data: {
+                    url: '',
+                    type: CollectionFileType.FILE,
+                    name: 'test123',
+                },
+                id: '2',
+                open: true,
+                active: true,
+                status: TreeItemStatus.LOADED,
+            },
+            {
+                data: {
+                    url: '',
+                    type: CollectionFileType.FILE,
+                    name: 'another-file',
+                },
+                id: '3',
+                open: true,
+                active: true,
+                status: TreeItemStatus.LOADED,
+            }
+        ];
+
+        // setup
+        props.items = items;
+        const wrapper = mount();
+        wrapper.find(SearchInput).simulate('change', { target: { value: searchPhrase } });
+        
+        // when
+        setTimeout(() => { // we have to use set timeout because of the debounce
+            expect(wrapper.find('FileTree').prop('items'))
+            .toEqual([
+                {
+                    data: { url: '', type: 'directory', name: 'test' },
+                    id: '1',
+                    open: true,
+                    active: true,
+                    status: 'loaded'
+                },
+                {
+                    data: { url: '', type: 'file', name: 'test123' },
+                    id: '2',
+                    open: true,
+                    active: true,
+                    status: 'loaded'
+                }
+            ]);
+        }, 0);
+    });
+});
\ No newline at end of file
diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx
index c7db48c4..29f20be2 100644
--- a/src/components/collection-panel-files/collection-panel-files.tsx
+++ b/src/components/collection-panel-files/collection-panel-files.tsx
@@ -9,6 +9,7 @@ import { FileTree } from '~/components/file-tree/file-tree';
 import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, Card, Button, Tooltip, CircularProgress } from '@material-ui/core';
 import { CustomizeTableIcon } from '~/components/icon/icon';
 import { DownloadIcon } from '~/components/icon/icon';
+import { SearchInput } from '../search-input/search-input';
 
 export interface CollectionPanelFilesProps {
     items: Array>;
@@ -16,6 +17,7 @@ export interface CollectionPanelFilesProps {
     isLoading: boolean;
     tooManyFiles: boolean;
     onUploadDataClick: () => void;
+    onSearchChange: (searchValue: string) => void;
     onItemMenuOpen: (event: React.MouseEvent, item: TreeItem, isWritable: boolean) => void;
     onOptionsMenuOpen: (event: React.MouseEvent, isWritable: boolean) => void;
     onSelectionToggle: (event: React.MouseEvent, item: TreeItem) => void;
@@ -25,7 +27,7 @@ export interface CollectionPanelFilesProps {
     currentItemUuid?: string;
 }
 
-type CssRules = 'root' | 'cardSubheader' | 'nameHeader' | 'fileSizeHeader' | 'uploadIcon' | 'button' | 'centeredLabel';
+export type CssRules = 'root' | 'cardSubheader' | 'nameHeader' | 'fileSizeHeader' | 'uploadIcon' | 'button' | 'centeredLabel' | 'cardHeaderContent' | 'cardHeaderContentTitle';
 
 const styles: StyleRulesCallback = theme => ({
     root: {
@@ -34,7 +36,18 @@ const styles: StyleRulesCallback = theme => ({
     },
     cardSubheader: {
         paddingTop: 0,
-        paddingBottom: 0
+        paddingBottom: 0,
+        minHeight: 8 * theme.spacing.unit,
+    },
+    cardHeaderContent: {
+        display: 'flex',
+        paddingRight: 2 * theme.spacing.unit,
+        justifyContent: 'space-between',
+    },
+    cardHeaderContentTitle: {
+        paddingLeft: theme.spacing.unit,
+        paddingTop: 2 * theme.spacing.unit,
+        paddingRight: 2 * theme.spacing.unit,
     },
     nameHeader: {
         marginLeft: '75px'
@@ -47,7 +60,7 @@ const styles: StyleRulesCallback = theme => ({
     },
     button: {
         marginRight: -theme.spacing.unit,
-        marginTop: '0px'
+        marginTop: '8px'
     },
     centeredLabel: {
         fontSize: '0.875rem',
@@ -55,52 +68,71 @@ const styles: StyleRulesCallback = theme => ({
     },
 });
 
-export const CollectionPanelFiles =
-    withStyles(styles)(
-        ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes,
-            isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles) =>
-            
-                
-                        {isWritable &&
-                        }
-                        {!tooManyFiles &&
-                        
-                             onOptionsMenuOpen(ev, isWritable)}>
-                                
-                            
-                        }
-                    
-                } />
-                { tooManyFiles
-                ? 
- File listing may take some time, please click to browse: +export const CollectionPanelFilesComponent = ({ onItemMenuOpen, onSearchChange, onOptionsMenuOpen, onUploadDataClick, classes, + isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles) => { + const { useState, useEffect } = React; + const [searchValue, setSearchValue] = useState(''); + + useEffect(() => { + onSearchChange(searchValue); + }, [searchValue]); + + return ( + + Files +
- : <> - - - Name - - - File size - - - { isLoading + } + className={classes.cardSubheader} + classes={{ action: classes.button }} + action={<> + {isWritable && + } + {!tooManyFiles && + + onOptionsMenuOpen(ev, isWritable)}> + + + } + + } /> + {tooManyFiles + ?
+ File listing may take some time, please click to browse: +
+ : <> + + + Name + + + File size + + + {isLoading ?
- :
onItemMenuOpen(ev, item, isWritable)} {...treeProps} />
} - - } -
); + :
+ onItemMenuOpen(ev, item, isWritable)} + {...treeProps} + items={treeProps.items} />
} + + } + ); +}; + +export const CollectionPanelFiles = withStyles(styles)(CollectionPanelFilesComponent); diff --git a/src/components/search-input/search-input.tsx b/src/components/search-input/search-input.tsx index 64ffc396..3b4ab35a 100644 --- a/src/components/search-input/search-input.tsx +++ b/src/components/search-input/search-input.tsx @@ -60,14 +60,14 @@ export const SearchInput = withStyles(styles)( render() { return
- Search + Search files - + diff --git a/src/models/tree.ts b/src/models/tree.ts index c7713cbc..e9291388 100644 --- a/src/models/tree.ts +++ b/src/models/tree.ts @@ -74,6 +74,7 @@ export const setNodeValueWith = (mapFn: (value: T) => T) => (id: string) => ( export const mapTreeValues = (mapFn: (value: T) => R) => (tree: Tree): Tree => getNodeDescendantsIds('')(tree) .map(id => getNode(id)(tree)) + .filter(node => !!node) .map(mapNodeValue(mapFn)) .reduce((newTree, node) => setNode(node)(newTree), createTree()); diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts index 204d4c0e..704e2999 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts @@ -22,6 +22,7 @@ export const collectionPanelFilesAction = unionize({ TOGGLE_COLLECTION_FILE_SELECTION: ofType<{ id: string }>(), SELECT_ALL_COLLECTION_FILES: ofType<{}>(), UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(), + ON_SEARCH_CHANGE: ofType(), }); export type CollectionPanelFilesAction = UnionOf; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts index 08a71759..03de8e34 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.ts @@ -7,26 +7,86 @@ import { CollectionPanelFilesAction, collectionPanelFilesAction } from "./collec import { createTree, mapTreeValues, getNode, setNode, getNodeAncestorsIds, getNodeDescendantsIds, setNodeValueWith, mapTree } from "~/models/tree"; import { CollectionFileType } from "~/models/collection-file"; +let fetchedFiles: any = {}; + export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => { // Low-level tree handling setNode() func does in-place data modifications // for performance reasons, so we pass a copy of 'state' to avoid side effects. return collectionPanelFilesAction.match(action, { - SET_COLLECTION_FILES: files => - mergeCollectionPanelFilesStates({...state}, mapTree(mapCollectionFileToCollectionPanelFile)(files)), + SET_COLLECTION_FILES: files => { + fetchedFiles = files; + return mergeCollectionPanelFilesStates({ ...state }, mapTree(mapCollectionFileToCollectionPanelFile)(files)); + }, TOGGLE_COLLECTION_FILE_COLLAPSE: data => - toggleCollapse(data.id)({...state}), + toggleCollapse(data.id)({ ...state }), - TOGGLE_COLLECTION_FILE_SELECTION: data => [{...state}] + TOGGLE_COLLECTION_FILE_SELECTION: data => [{ ...state }] .map(toggleSelected(data.id)) .map(toggleAncestors(data.id)) .map(toggleDescendants(data.id))[0], + ON_SEARCH_CHANGE: (searchValue) => { + const fileIds: string[] = []; + const directoryIds: string[] = []; + const filteredFiles = Object.keys(fetchedFiles) + .filter((key: string) => { + const node = fetchedFiles[key]; + + if (node.value === undefined) { + return false; + } + + const { id, value: { type, name } } = node; + + if (type === CollectionFileType.DIRECTORY) { + directoryIds.push(id); + return true; + } + + const includeFile = name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1; + + if (includeFile) { + fileIds.push(id); + } + + return includeFile; + }) + .reduce((prev, next) => { + const node = JSON.parse(JSON.stringify(fetchedFiles[next])); + const { value: { type }, children } = node; + + node.children = node.children.filter((key: string) => { + const isFile = directoryIds.indexOf(key) === -1; + return isFile ? + fileIds.indexOf(key) > -1 : + !!fileIds.find(id => id.indexOf(key) > -1); + }); + + if (type === CollectionFileType.FILE || children.length > 0) { + prev[next] = node; + } + + return prev; + }, {}); + + return mapTreeValues((v: CollectionPanelDirectory | CollectionPanelFile) => { + if (v.type === CollectionFileType.DIRECTORY) { + return ({ + ...v, + collapsed: searchValue.length === 0, + }); + } + + return ({ ...v }); + })({ ...filteredFiles }); + }, + SELECT_ALL_COLLECTION_FILES: () => - mapTreeValues(v => ({ ...v, selected: true }))({...state}), + mapTreeValues(v => ({ ...v, selected: true }))({ ...state }), UNSELECT_ALL_COLLECTION_FILES: () => - mapTreeValues(v => ({ ...v, selected: false }))({...state}), + mapTreeValues(v => ({ ...v, selected: false }))({ ...state }), default: () => state }) as CollectionPanelFilesState; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-state.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-state.ts index 9d5b06ce..aa3bd305 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-state.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-state.ts @@ -38,7 +38,6 @@ export const mergeCollectionPanelFilesStates = (oldState: CollectionPanelFilesSt export const filterCollectionFilesBySelection = (tree: CollectionPanelFilesState, selected: boolean) => { const allFiles = getNodeDescendants('')(tree).map(node => node.value); - const selectedDirectories = allFiles.filter(file => file.selected === selected && file.type === CollectionFileType.DIRECTORY); const selectedFiles = allFiles.filter(file => file.selected === selected && !selectedDirectories.some(dir => dir.id === file.path)); return [...selectedDirectories, ...selectedFiles]; diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts index 72374e65..74fa17b3 100644 --- a/src/store/collections/collection-partial-copy-actions.ts +++ b/src/store/collections/collection-partial-copy-actions.ts @@ -61,8 +61,15 @@ export const copyCollectionPartial = ({ name, description, projectUuid }: Collec manifestText: collection.manifestText, }; const newCollection = await services.collectionService.create(collectionCopy); - const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, false).map(file => file.id); - await services.collectionService.deleteFiles(newCollection.uuid, paths); + const copiedFiles = await services.collectionService.files(newCollection.uuid); + const paths = filterCollectionFilesBySelection(state.collectionPanelFiles, true).map(file => file.id); + const filesToDelete = copiedFiles.map(({ id }) => id).filter(file => { + return !paths.find(path => path.indexOf(file.replace(newCollection.uuid, '')) > -1); + }); + await services.collectionService.deleteFiles( + '', + filesToDelete + ); dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_FORM_NAME })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'New collection created.', diff --git a/src/store/favorite-panel/favorite-panel-middleware-service.ts b/src/store/favorite-panel/favorite-panel-middleware-service.ts index f45a62f8..c9a06fe6 100644 --- a/src/store/favorite-panel/favorite-panel-middleware-service.ts +++ b/src/store/favorite-panel/favorite-panel-middleware-service.ts @@ -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() diff --git a/src/store/public-favorites-panel/public-favorites-middleware-service.ts b/src/store/public-favorites-panel/public-favorites-middleware-service.ts index 072cf4e4..b98dfdfc 100644 --- a/src/store/public-favorites-panel/public-favorites-middleware-service.ts +++ b/src/store/public-favorites-panel/public-favorites-middleware-service.ts @@ -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() }); diff --git a/src/store/public-favorites/public-favorites-actions.ts b/src/store/public-favorites/public-favorites-actions.ts index d5a5cd46..db8af2e2 100644 --- a/src/store/public-favorites/public-favorites-actions.ts +++ b/src/store/public-favorites/public-favorites-actions.ts @@ -22,7 +22,7 @@ export const togglePublicFavorite = (resource: { uuid: string; name: string }) = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { 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) diff --git a/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts b/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts index a09a3085..e5dd6d9f 100644 --- a/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts +++ b/src/store/shared-with-me-panel/shared-with-me-middleware-service.ts @@ -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(updateFavorites(response.items.map(item => item.uuid))); api.dispatch(updatePublicFavorites(response.items.map(item => item.uuid))); diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts index a09ab6ba..ff506103 100644 --- a/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/src/store/side-panel-tree/side-panel-tree-actions.ts @@ -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() .addAsc('name', GroupContentsResourcePrefix.PROJECT) diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index 5e880aad..3fa5187a 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -271,36 +271,34 @@ export const loadPublicFavoritesProject = (params: LoadFavoritesProjectParams) = async (dispatch: Dispatch, 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(receiveTreePickerData({ - id: 'Public Favorites', - pickerId, - data: items, - extractNodeData: item => ({ - id: item.headUuid, - value: item, - status: item.headKind === ResourceKind.PROJECT + dispatch(receiveTreePickerData({ + 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) => diff --git a/src/views-components/collection-panel-files/collection-panel-files.ts b/src/views-components/collection-panel-files/collection-panel-files.ts index 79970003..9859f84b 100644 --- a/src/views-components/collection-panel-files/collection-panel-files.ts +++ b/src/views-components/collection-panel-files/collection-panel-files.ts @@ -44,7 +44,7 @@ const memoizedMapStateToProps = () => { }; }; -const mapDispatchToProps = (dispatch: Dispatch): Pick => ({ +const mapDispatchToProps = (dispatch: Dispatch): Pick => ({ onUploadDataClick: () => { dispatch(openUploadCollectionFilesDialog()); }, @@ -68,6 +68,9 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick { + dispatch(collectionPanelFilesAction.ON_SEARCH_CHANGE(searchValue)); + }, onOptionsMenuOpen: (event, isWritable) => { dispatch(openCollectionFilesContextMenu(event, isWritable)); },