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
--- /dev/null
+// 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);
+ });
+ })
+})
}
)
+Cypress.Commands.add(
+ "trashGroup", (token, uuid) => {
+ return cy.deleteResource(token, 'groups', uuid);
+ }
+)
+
Cypress.Commands.add(
"createCollection", (token, data) => {
return cy.createResource(token, 'collections', {
}
)
+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}`);
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
+)
--- /dev/null
+// 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('<CollectionPanelFiles />', () => {
+ let props: CollectionPanelFilesProps & WithStyles<CssRules>;
+
+ beforeEach(() => {
+ props = {
+ classes: {} as Record<CssRules, string>,
+ 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(<CollectionPanelFilesComponent {...props} />);
+
+ // then
+ expect(wrapper).not.toBeUndefined();
+ });
+
+ it('filters out files', () => {
+ // given
+ const searchPhrase = 'test';
+ const items: Array<TreeItem<FileTreeData>> = [
+ {
+ 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(<CollectionPanelFilesComponent {...props} />);
+ 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
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<TreeItem<FileTreeData>>;
isLoading: boolean;
tooManyFiles: boolean;
onUploadDataClick: () => void;
+ onSearchChange: (searchValue: string) => void;
onItemMenuOpen: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>, isWritable: boolean) => void;
onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, isWritable: boolean) => void;
onSelectionToggle: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => void;
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<CssRules> = theme => ({
root: {
},
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'
},
button: {
marginRight: -theme.spacing.unit,
- marginTop: '0px'
+ marginTop: '8px'
},
centeredLabel: {
fontSize: '0.875rem',
},
});
-export const CollectionPanelFiles =
- withStyles(styles)(
- ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes,
- isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
- <Card data-cy='collection-files-panel' className={classes.root}>
- <CardHeader
- title="Files"
- className={classes.cardSubheader}
- classes={{ action: classes.button }}
- action={<>
- {isWritable &&
- <Button
- data-cy='upload-button'
- onClick={onUploadDataClick}
- variant='contained'
- color='primary'
- size='small'>
- <DownloadIcon className={classes.uploadIcon} />
- Upload data
- </Button>}
- {!tooManyFiles &&
- <Tooltip title="More options" disableFocusListener>
- <IconButton
- data-cy='collection-files-panel-options-btn'
- onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
- <CustomizeTableIcon />
- </IconButton>
- </Tooltip>}
- </>
- } />
- { tooManyFiles
- ? <div className={classes.centeredLabel}>
- File listing may take some time, please click to browse: <Button onClick={loadFilesFunc}><DownloadIcon/>Show files</Button>
+export const CollectionPanelFilesComponent = ({ onItemMenuOpen, onSearchChange, onOptionsMenuOpen, onUploadDataClick, classes,
+ isWritable, isLoading, tooManyFiles, loadFilesFunc, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) => {
+ const { useState, useEffect } = React;
+ const [searchValue, setSearchValue] = useState('');
+
+ useEffect(() => {
+ onSearchChange(searchValue);
+ }, [searchValue]);
+
+ return (<Card data-cy='collection-files-panel' className={classes.root}>
+ <CardHeader
+ title={
+ <div className={classes.cardHeaderContent}>
+ <span className={classes.cardHeaderContentTitle}>Files</span>
+ <SearchInput
+ value={searchValue}
+ onSearch={setSearchValue} />
</div>
- : <>
- <Grid container justify="space-between">
- <Typography variant="caption" className={classes.nameHeader}>
- Name
- </Typography>
- <Typography variant="caption" className={classes.fileSizeHeader}>
- File size
- </Typography>
- </Grid>
- { isLoading
+ }
+ className={classes.cardSubheader}
+ classes={{ action: classes.button }}
+ action={<>
+ {isWritable &&
+ <Button
+ data-cy='upload-button'
+ onClick={onUploadDataClick}
+ variant='contained'
+ color='primary'
+ size='small'>
+ <DownloadIcon className={classes.uploadIcon} />
+ Upload data
+ </Button>}
+ {!tooManyFiles &&
+ <Tooltip title="More options" disableFocusListener>
+ <IconButton
+ data-cy='collection-files-panel-options-btn'
+ onClick={(ev) => onOptionsMenuOpen(ev, isWritable)}>
+ <CustomizeTableIcon />
+ </IconButton>
+ </Tooltip>}
+ </>
+ } />
+ {tooManyFiles
+ ? <div className={classes.centeredLabel}>
+ File listing may take some time, please click to browse: <Button onClick={loadFilesFunc}><DownloadIcon />Show files</Button>
+ </div>
+ : <>
+ <Grid container justify="space-between">
+ <Typography variant="caption" className={classes.nameHeader}>
+ Name
+ </Typography>
+ <Typography variant="caption" className={classes.fileSizeHeader}>
+ File size
+ </Typography>
+ </Grid>
+ {isLoading
? <div className={classes.centeredLabel}><CircularProgress /></div>
- : <div style={{height: 'calc(100% - 60px)'}}><FileTree onMenuOpen={(ev, item) => onItemMenuOpen(ev, item, isWritable)} {...treeProps} /></div> }
- </>
- }
- </Card>);
+ : <div style={{ height: 'calc(100% - 60px)' }}>
+ <FileTree
+ onMenuOpen={(ev, item) => onItemMenuOpen(ev, item, isWritable)}
+ {...treeProps}
+ items={treeProps.items} /></div>}
+ </>
+ }
+ </Card>);
+};
+
+export const CollectionPanelFiles = withStyles(styles)(CollectionPanelFilesComponent);
render() {
return <form onSubmit={this.handleSubmit}>
<FormControl>
- <InputLabel>Search</InputLabel>
+ <InputLabel>Search files</InputLabel>
<Input
type="text"
value={this.state.value}
onChange={this.handleChange}
endAdornment={
<InputAdornment position="end">
- <Tooltip title='Search'>
+ <Tooltip title='Search files'>
<IconButton
onClick={this.handleSubmit}>
<SearchIcon />
export const mapTreeValues = <T, R>(mapFn: (value: T) => R) => (tree: Tree<T>): Tree<R> =>
getNodeDescendantsIds('')(tree)
.map(id => getNode(id)(tree))
+ .filter(node => !!node)
.map(mapNodeValue(mapFn))
.reduce((newTree, node) => setNode(node)(newTree), createTree<R>());
TOGGLE_COLLECTION_FILE_SELECTION: ofType<{ id: string }>(),
SELECT_ALL_COLLECTION_FILES: ofType<{}>(),
UNSELECT_ALL_COLLECTION_FILES: ofType<{}>(),
+ ON_SEARCH_CHANGE: ofType<string>(),
});
export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
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;
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];
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.',
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) {
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()
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()
});
(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({
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)
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';
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) {
.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)));
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)
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) =>
};
};
-const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps, 'onFileClick' | 'onUploadDataClick' | 'onCollapseToggle' | 'onSelectionToggle' | 'onItemMenuOpen' | 'onOptionsMenuOpen'> => ({
+const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps, 'onSearchChange' | 'onFileClick' | 'onUploadDataClick' | 'onCollapseToggle' | 'onSelectionToggle' | 'onItemMenuOpen' | 'onOptionsMenuOpen'> => ({
onUploadDataClick: () => {
dispatch<any>(openUploadCollectionFilesDialog());
},
}
));
},
+ onSearchChange: (searchValue: string) => {
+ dispatch(collectionPanelFilesAction.ON_SEARCH_CHANGE(searchValue));
+ },
onOptionsMenuOpen: (event, isWritable) => {
dispatch<any>(openCollectionFilesContextMenu(event, isWritable));
},