From: Lucas Di Pentima Date: Thu, 5 Nov 2020 22:40:12 +0000 (-0300) Subject: Merge branch '16719-collection-version-basic-ui' X-Git-Tag: 2.1.1~7 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/7776b799eed223b9318443c1e319e01957a8fb45?hp=66b5f8e710c93ebea30460ad40e329b1c8f1a7e6 Merge branch '16719-collection-version-basic-ui' Closes #16719 Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/.licenseignore b/.licenseignore index 7ac3c836..853135fc 100644 --- a/.licenseignore +++ b/.licenseignore @@ -13,3 +13,4 @@ public/* .yarnrc .npmrc src/lib/cwl-svg/* +tools/arvados_config.yml diff --git a/cypress/integration/collection-panel.spec.js b/cypress/integration/collection-panel.spec.js index c14101d8..414d7e3e 100644 --- a/cypress/integration/collection-panel.spec.js +++ b/cypress/integration/collection-panel.spec.js @@ -54,7 +54,8 @@ describe('Collection panel tests', function() { // Check that name & uuid are correct. cy.get('[data-cy=collection-info-panel]') .should('contain', this.testCollection.name) - .and('contain', this.testCollection.uuid); + .and('contain', this.testCollection.uuid) + .and('not.contain', 'This is an old version'); // Check for the read-only icon cy.get('[data-cy=read-only-icon]').should(`${isWritable ? 'not.' : ''}exist`); // Check that both read and write operations are available on @@ -117,4 +118,54 @@ describe('Collection panel tests', function() { }) }) }) + + it('can correctly display old versions', function() { + const colName = `Versioned Collection ${Math.floor(Math.random() * Math.floor(999999))}`; + let colUuid = ''; + let oldVersionUuid = ''; + // Make sure no other collections with this name exist + cy.doRequest('GET', '/arvados/v1/collections', null, { + filters: `[["name", "=", "${colName}"]]`, + include_old_versions: true + }) + .its('body.items').as('collections') + .then(function() { + expect(this.collections).to.be.empty; + }); + // Creates the collection using the admin token so we can set up + // a bogus manifest text without block signatures. + cy.createCollection(adminUser.token, { + name: colName, + owner_uuid: activeUser.user.uuid, + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}) + .as('originalVersion').then(function() { + // Change the file name to create a new version. + cy.updateCollection(adminUser.token, this.originalVersion.uuid, { + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n" + }) + colUuid = this.originalVersion.uuid; + }); + // Confirm that there are 2 versions of the collection + cy.doRequest('GET', '/arvados/v1/collections', null, { + filters: `[["name", "=", "${colName}"]]`, + include_old_versions: true + }) + .its('body.items').as('collections') + .then(function() { + expect(this.collections).to.have.lengthOf(2); + this.collections.map(function(aCollection) { + expect(aCollection.current_version_uuid).to.equal(colUuid); + if (aCollection.uuid !== aCollection.current_version_uuid) { + oldVersionUuid = aCollection.uuid; + } + }); + // Check the old version displays as what it is. + cy.loginAs(activeUser) + cy.visit(`/collections/${oldVersionUuid}`); + cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version'); + cy.get('[data-cy=read-only-icon]').should('exist'); + cy.get('[data-cy=collection-info-panel]').should('contain', colName); + cy.get('[data-cy=collection-files-panel]').should('contain', 'bar'); + }); + }); }) diff --git a/cypress/integration/favorites.js b/cypress/integration/favorites.js index b38399be..0855c94e 100644 --- a/cypress/integration/favorites.js +++ b/cypress/integration/favorites.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -describe('Collection panel tests', function() { +describe('Favorites tests', function() { let activeUser; let adminUser; diff --git a/cypress/integration/login.spec.js b/cypress/integration/login.spec.js index d88c7a6c..25c8cd4b 100644 --- a/cypress/integration/login.spec.js +++ b/cypress/integration/login.spec.js @@ -93,7 +93,7 @@ describe('Login tests', function() { }) }, null, activeUser.token, true); // Should log the user out. - cy.get('[data-cy=breadcrumb-first]').click(); + cy.visit('/'); cy.get('div#root').should('contain', 'Please log in'); }) diff --git a/cypress/integration/search.spec.js b/cypress/integration/search.spec.js new file mode 100644 index 00000000..0fba64cd --- /dev/null +++ b/cypress/integration/search.spec.js @@ -0,0 +1,83 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +describe('Search 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('can search for old collection versions', function() { + const colName = `Versioned Collection ${Math.floor(Math.random() * Math.floor(999999))}`; + let colUuid = ''; + let oldVersionUuid = ''; + // Make sure no other collections with this name exist + cy.doRequest('GET', '/arvados/v1/collections', null, { + filters: `[["name", "=", "${colName}"]]`, + include_old_versions: true + }) + .its('body.items').as('collections') + .then(function() { + expect(this.collections).to.be.empty; + }); + // Creates the collection using the admin token so we can set up + // a bogus manifest text without block signatures. + cy.createCollection(adminUser.token, { + name: colName, + owner_uuid: activeUser.user.uuid, + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}) + .as('originalVersion').then(function() { + // Change the file name to create a new version. + cy.updateCollection(adminUser.token, this.originalVersion.uuid, { + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n" + }) + colUuid = this.originalVersion.uuid; + }); + // Confirm that there are 2 versions of the collection + cy.doRequest('GET', '/arvados/v1/collections', null, { + filters: `[["name", "=", "${colName}"]]`, + include_old_versions: true + }) + .its('body.items').as('collections') + .then(function() { + expect(this.collections).to.have.lengthOf(2); + this.collections.map(function(aCollection) { + expect(aCollection.current_version_uuid).to.equal(colUuid); + if (aCollection.uuid !== aCollection.current_version_uuid) { + oldVersionUuid = aCollection.uuid; + } + }); + cy.loginAs(activeUser); + const searchQuery = `${colName} type:arvados#collection`; + // Search for only collection's current version + cy.visit(`/search-results?q=${encodeURIComponent(searchQuery)}`); + cy.get('[data-cy=search-results]').should('contain', 'current'); + cy.get('[data-cy=search-results]').should('not.contain', 'old version'); + // ...and then, include old versions. + cy.visit(`/search-results?q=${encodeURIComponent(searchQuery + ' is:pastVersion')}`); + cy.get('[data-cy=search-results]').should('contain', 'current'); + cy.get('[data-cy=search-results]').should('contain', 'old version'); + }); + }); +}); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index fd513998..228e1cab 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -125,6 +125,14 @@ Cypress.Commands.add( } ) +Cypress.Commands.add( + "updateCollection", (token, uuid, data) => { + return cy.updateResource(token, 'collections', uuid, { + collection: JSON.stringify(data) + }) + } +) + Cypress.Commands.add( "createResource", (token, suffix, data) => { return cy.doRequest('POST', '/arvados/v1/'+suffix, data, null, token, true) @@ -145,6 +153,16 @@ Cypress.Commands.add( } ) +Cypress.Commands.add( + "updateResource", (token, suffix, uuid, data) => { + return cy.doRequest('PUT', '/arvados/v1/'+suffix+'/'+uuid, data, null, token, true) + .its('body').as('resource') + .then(function() { + return this.resource; + }) + } +) + Cypress.Commands.add( "loginAs", (user) => { cy.visit(`/token/?api_token=${user.token}`); diff --git a/src/common/formatters.ts b/src/common/formatters.ts index 1386338c..55fb0507 100644 --- a/src/common/formatters.ts +++ b/src/common/formatters.ts @@ -28,7 +28,7 @@ export const formatFileSize = (size?: number) => { } } } - return ""; + return "0 B"; }; export const formatTime = (time: number, seconds?: boolean) => { diff --git a/src/components/details-attribute/details-attribute.tsx b/src/components/details-attribute/details-attribute.tsx index 8f470858..4b8ee837 100644 --- a/src/components/details-attribute/details-attribute.tsx +++ b/src/components/details-attribute/details-attribute.tsx @@ -20,25 +20,21 @@ type CssRules = 'attribute' | 'label' | 'value' | 'lowercaseValue' | 'link' | 'c const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ attribute: { - display: 'flex', - alignItems: 'flex-start', - marginBottom: theme.spacing.unit + marginBottom: ".6 rem" }, label: { boxSizing: 'border-box', color: theme.palette.grey["500"], - width: '40%' + width: '100%' }, value: { boxSizing: 'border-box', - width: '60%', alignItems: 'flex-start' }, lowercaseValue: { textTransform: 'lowercase' }, link: { - width: '60%', color: theme.palette.primary.main, textDecoration: 'none', overflowWrap: 'break-word', @@ -46,9 +42,12 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, copyIcon: { marginLeft: theme.spacing.unit, - fontSize: '1.125rem', color: theme.palette.grey["500"], - cursor: 'pointer' + cursor: 'pointer', + display: 'inline', + '& svg': { + fontSize: '1rem' + } } }); @@ -102,17 +101,19 @@ export const DetailsAttribute = connect(mapStateToProps)(withStyles(styles)( valueNode = value; } return - {label} + {label} {valueNode} {children} {linkToUuid && - this.onCopy("Copied")}> - - + + this.onCopy("Copied")}> + + + } ; diff --git a/src/components/file-tree/file-tree-item.tsx b/src/components/file-tree/file-tree-item.tsx index 23273dac..6f5ab83d 100644 --- a/src/components/file-tree/file-tree-item.tsx +++ b/src/components/file-tree/file-tree-item.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { TreeItem } from "../tree/tree"; -import { ProjectIcon, MoreOptionsIcon, DefaultIcon, CollectionIcon } from "../icon/icon"; +import { DirectoryIcon, MoreOptionsIcon, DefaultIcon, FileIcon } from "../icon/icon"; import { Typography, IconButton, StyleRulesCallback, withStyles, WithStyles, Tooltip } from '@material-ui/core'; import { formatFileSize } from "~/common/formatters"; import { ListItemTextIcon } from "../list-item-text-icon/list-item-text-icon"; @@ -71,9 +71,9 @@ export const FileTreeItem = withStyles(fileTreeItemStyle)( export const getIcon = (type: string) => { switch (type) { case 'directory': - return ProjectIcon; + return DirectoryIcon; case 'file': - return CollectionIcon; + return FileIcon; default: return DefaultIcon; } diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index 2573d764..55c3c5a5 100644 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import * as React from 'react'; +import { Badge, Tooltip } from '@material-ui/core'; import Add from '@material-ui/icons/Add'; import ArrowBack from '@material-ui/icons/ArrowBack'; import ArrowDropDown from '@material-ui/icons/ArrowDropDown'; @@ -27,6 +28,7 @@ import Folder from '@material-ui/icons/Folder'; import GetApp from '@material-ui/icons/GetApp'; import Help from '@material-ui/icons/Help'; import HelpOutline from '@material-ui/icons/HelpOutline'; +import History from '@material-ui/icons/History'; import Inbox from '@material-ui/icons/Inbox'; import Info from '@material-ui/icons/Info'; import Input from '@material-ui/icons/Input'; @@ -73,6 +75,13 @@ export const ReadOnlyIcon = (props:any) => ; +export const CollectionOldVersionIcon = (props: any) => + + }> + + + ; + export type IconType = React.SFC<{ className?: string, style?: object }>; export const AddIcon: IconType = (props) => ; @@ -89,11 +98,13 @@ export const CloseIcon: IconType = (props) => ; export const CloudUploadIcon: IconType = (props) => ; export const DefaultIcon: IconType = (props) => ; export const DetailsIcon: IconType = (props) => ; +export const DirectoryIcon: IconType = (props) => ; export const DownloadIcon: IconType = (props) => ; export const EditSavedQueryIcon: IconType = (props) => ; export const ExpandIcon: IconType = (props) => ; export const ErrorIcon: IconType = (props) => ; export const FavoriteIcon: IconType = (props) => ; +export const FileIcon: IconType = (props) => ; export const HelpIcon: IconType = (props) => ; export const HelpOutlineIcon: IconType = (props) => ; export const ImportContactsIcon: IconType = (props) => ; diff --git a/src/index.tsx b/src/index.tsx index 92a2716b..569656d9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -28,7 +28,6 @@ import { collectionFilesActionSet, readOnlyCollectionFilesActionSet } from '~/vi import { collectionFilesItemActionSet, readOnlyCollectionFilesItemActionSet } from '~/views-components/context-menu/action-sets/collection-files-item-action-set'; import { collectionFilesNotSelectedActionSet } from '~/views-components/context-menu/action-sets/collection-files-not-selected-action-set'; import { collectionActionSet, readOnlyCollectionActionSet } from '~/views-components/context-menu/action-sets/collection-action-set'; -import { collectionResourceActionSet } from '~/views-components/context-menu/action-sets/collection-resource-action-set'; import { processActionSet } from '~/views-components/context-menu/action-sets/process-action-set'; import { loadWorkbench } from '~/store/workbench/workbench-actions'; import { Routes } from '~/routes/routes'; @@ -78,7 +77,6 @@ addMenuActionSet(ContextMenuKind.COLLECTION_FILES_NOT_SELECTED, collectionFilesN addMenuActionSet(ContextMenuKind.COLLECTION_FILES_ITEM, collectionFilesItemActionSet); addMenuActionSet(ContextMenuKind.READONLY_COLLECTION_FILES_ITEM, readOnlyCollectionFilesItemActionSet); addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet); -addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSet); addMenuActionSet(ContextMenuKind.READONLY_COLLECTION, readOnlyCollectionActionSet); addMenuActionSet(ContextMenuKind.TRASHED_COLLECTION, trashedCollectionActionSet); addMenuActionSet(ContextMenuKind.PROCESS, processActionSet); diff --git a/src/store/context-menu/context-menu-actions.test.ts b/src/store/context-menu/context-menu-actions.test.ts index 4bcbf9f2..c3e78679 100644 --- a/src/store/context-menu/context-menu-actions.test.ts +++ b/src/store/context-menu/context-menu-actions.test.ts @@ -71,7 +71,7 @@ describe('context-menu-actions', () => { expect(result).toEqual(ContextMenuKind.COLLECTION_ADMIN); }); - it('should return ContextMenuKind.COLLECTION_RESOURCE', () => { + it('should return ContextMenuKind.COLLECTION', () => { // given const isAdmin = false; const isEditable = true; @@ -80,7 +80,7 @@ describe('context-menu-actions', () => { const result = resourceKindToContextMenuKind(uuid, isAdmin, isEditable); // then - expect(result).toEqual(ContextMenuKind.COLLECTION_RESOURCE); + expect(result).toEqual(ContextMenuKind.COLLECTION); }); it('should return ContextMenuKind.READONLY_COLLECTION', () => { diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 1f766bd3..308d5e88 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -204,15 +204,21 @@ export const resourceKindToContextMenuKind = (uuid: string, isAdmin?: boolean, i const kind = extractUuidKind(uuid); switch (kind) { case ResourceKind.PROJECT: - return !isAdmin ? - isEditable ? ContextMenuKind.PROJECT : ContextMenuKind.READONLY_PROJECT : - ContextMenuKind.PROJECT_ADMIN; + return !isAdmin + ? isEditable + ? ContextMenuKind.PROJECT + : ContextMenuKind.READONLY_PROJECT + : ContextMenuKind.PROJECT_ADMIN; case ResourceKind.COLLECTION: - return !isAdmin ? - isEditable ? ContextMenuKind.COLLECTION_RESOURCE : ContextMenuKind.READONLY_COLLECTION : - ContextMenuKind.COLLECTION_ADMIN; + return !isAdmin + ? isEditable + ? ContextMenuKind.COLLECTION + : ContextMenuKind.READONLY_COLLECTION + : ContextMenuKind.COLLECTION_ADMIN; case ResourceKind.PROCESS: - return !isAdmin ? ContextMenuKind.PROCESS_RESOURCE : ContextMenuKind.PROCESS_ADMIN; + return !isAdmin + ? ContextMenuKind.PROCESS_RESOURCE + : ContextMenuKind.PROCESS_ADMIN; case ResourceKind.USER: return ContextMenuKind.ROOT_PROJECT; case ResourceKind.LINK: diff --git a/src/store/open-in-new-tab/open-in-new-tab.actions.ts b/src/store/open-in-new-tab/open-in-new-tab.actions.ts index 42bdc4cc..17ba7402 100644 --- a/src/store/open-in-new-tab/open-in-new-tab.actions.ts +++ b/src/store/open-in-new-tab/open-in-new-tab.actions.ts @@ -19,10 +19,7 @@ export const openInNewTabAction = (resource: any) => (dispatch: Dispatch) => { if (kind === ResourceKind.COLLECTION) { dispatch(openInNewTabActions.OPEN_COLLECTION_IN_NEW_TAB(uuid)); - } - if (kind === ResourceKind.PROJECT) { + } else if (kind === ResourceKind.PROJECT) { dispatch(openInNewTabActions.OPEN_PROJECT_IN_NEW_TAB(uuid)); } - - console.log(uuid); }; \ No newline at end of file diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts index fba2a53a..7fa6f224 100644 --- a/src/views-components/context-menu/action-sets/collection-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-action-set.ts @@ -27,7 +27,7 @@ export const readOnlyCollectionActionSet: ContextMenuActionSet = [[ }, { icon: CopyIcon, - name: "Copy to project", + name: "Make a copy", execute: (dispatch, resource) => { dispatch(openCollectionCopyDialog(resource)); } diff --git a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts index db849136..10a839d8 100644 --- a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts @@ -57,7 +57,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ }, { icon: CopyIcon, - name: "Copy to project", + name: "Make a copy", execute: (dispatch, resource) => { dispatch(openCollectionCopyDialog(resource)); } @@ -70,13 +70,6 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ dispatch(toggleDetailsPanel()); } }, - // { - // icon: ProvenanceGraphIcon, - // name: "Provenance graph", - // execute: (dispatch, resource) => { - // // add code - // } - // }, { icon: AdvancedIcon, name: "Advanced", @@ -90,11 +83,4 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ dispatch(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!)); } }, - // { - // icon: RemoveIcon, - // name: "Remove", - // execute: (dispatch, resource) => { - // // add code - // } - // } ]]; diff --git a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts deleted file mode 100644 index 5e367906..00000000 --- a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { ContextMenuActionSet } from "../context-menu-action-set"; -import { ToggleFavoriteAction } from "../actions/favorite-action"; -import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action"; -import { toggleFavorite } from "~/store/favorites/favorites-actions"; -import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon'; -import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; -import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; -import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; -import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions'; -import { toggleCollectionTrashed } from "~/store/trash/trash-actions"; -import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions"; -import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab'; -import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; - -export const collectionResourceActionSet: ContextMenuActionSet = [[ - { - icon: RenameIcon, - name: "Edit collection", - execute: (dispatch, resource) => { - dispatch(openCollectionUpdateDialog(resource)); - } - }, - { - icon: ShareIcon, - name: "Share", - execute: (dispatch, { uuid }) => { - dispatch(openSharingDialog(uuid)); - } - }, - { - component: ToggleFavoriteAction, - execute: (dispatch, resource) => { - dispatch(toggleFavorite(resource)).then(() => { - dispatch(favoritePanelActions.REQUEST_ITEMS()); - }); - } - }, - { - icon: MoveToIcon, - name: "Move to", - execute: (dispatch, resource) => { - dispatch(openMoveCollectionDialog(resource)); - } - }, - { - icon: CopyIcon, - name: "Copy to project", - execute: (dispatch, resource) => { - dispatch(openCollectionCopyDialog(resource)); - } - }, - { - icon: DetailsIcon, - name: "View details", - execute: dispatch => { - dispatch(toggleDetailsPanel()); - } - }, - { - icon: AdvancedIcon, - name: "Advanced", - execute: (dispatch, resource) => { - dispatch(openAdvancedTabDialog(resource.uuid)); - } - }, - { - component: ToggleTrashAction, - execute: (dispatch, resource) => { - dispatch(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!)); - } - }, - // { - // icon: RemoveIcon, - // name: "Remove", - // execute: (dispatch, resource) => { - // // add code - // } - // } -]]; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index db5765ee..43474dd1 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -77,7 +77,6 @@ export enum ContextMenuKind { COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected", COLLECTION = 'Collection', COLLECTION_ADMIN = 'CollectionAdmin', - COLLECTION_RESOURCE = 'CollectionResource', READONLY_COLLECTION = 'ReadOnlyCollection', TRASHED_COLLECTION = 'TrashedCollection', PROCESS = "Process", diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx index 999d4c79..625d8405 100644 --- a/src/views-components/details-panel/collection-details.tsx +++ b/src/views-components/details-panel/collection-details.tsx @@ -5,11 +5,8 @@ import * as React from 'react'; import { CollectionIcon } from '~/components/icon/icon'; import { CollectionResource } from '~/models/collection'; -import { formatDate, formatFileSize } from '~/common/formatters'; -import { resourceLabel } from '~/common/labels'; -import { ResourceKind } from '~/models/resource'; import { DetailsData } from "./details-data"; -import { DetailsAttribute } from "~/components/details-attribute/details-attribute"; +import { CollectionDetailsAttributes } from '~/views/collection-panel/collection-panel'; export class CollectionDetails extends DetailsData { @@ -18,17 +15,6 @@ export class CollectionDetails extends DetailsData { } getDetails() { - return
- - - - - - - - {/* Missing attrs */} - - -
; + return ; } } diff --git a/src/views-components/search-bar/search-bar-view.tsx b/src/views-components/search-bar/search-bar-view.tsx index 49a8ba62..20536fd7 100644 --- a/src/views-components/search-bar/search-bar-view.tsx +++ b/src/views-components/search-bar/search-bar-view.tsx @@ -177,6 +177,7 @@ export const SearchBarView = compose(connectVocabulary, withStyles(styles))(
= (theme: ArvadosTheme) => ({ @@ -67,6 +70,12 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ fontSize: '0.875rem', textAlign: 'center' }, + warningLabel: { + fontStyle: 'italic' + }, + collectionName: { + flexDirection: 'column', + }, value: { textTransform: 'none', fontSize: '0.875rem' @@ -87,6 +96,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ interface CollectionPanelDataProps { item: CollectionResource; isWritable: boolean; + isOldVersion: boolean; isLoadingFiles: boolean; tooManyFiles: boolean; } @@ -99,72 +109,75 @@ export const CollectionPanel = withStyles(styles)( const currentUserUUID = getUserUuid(state); const item = getResource(props.match.params.id)(state.resources); let isWritable = false; - if (item && item.ownerUuid === currentUserUUID) { - isWritable = true; - } else if (item) { - const itemOwner = getResource(item.ownerUuid)(state.resources); - if (itemOwner) { - isWritable = itemOwner.writableBy.indexOf(currentUserUUID || '') >= 0; + const isOldVersion = item && item.currentVersionUuid !== item.uuid; + if (item && !isOldVersion) { + if (item.ownerUuid === currentUserUUID) { + isWritable = true; + } else { + const itemOwner = getResource(item.ownerUuid)(state.resources); + if (itemOwner) { + isWritable = itemOwner.writableBy.indexOf(currentUserUUID || '') >= 0; + } } } const loadingFilesIndicator = getProgressIndicator(COLLECTION_PANEL_LOAD_FILES)(state.progressIndicator); const isLoadingFiles = loadingFilesIndicator && loadingFilesIndicator!.working || false; const tooManyFiles = !state.collectionPanel.loadBigCollections && item && item.fileCount > COLLECTION_PANEL_LOAD_FILES_THRESHOLD || false; - return { item, isWritable, isLoadingFiles, tooManyFiles }; + return { item, isWritable, isOldVersion, isLoadingFiles, tooManyFiles }; })( class extends React.Component { render() { - const { classes, item, dispatch, isWritable, isLoadingFiles, tooManyFiles } = this.props; + const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props; return item ?
}> - - - - - - {item.name} - {isWritable || - - - - } - + + + + {isOldVersion + ? + : } + + + + {item.name} + {isWritable || + + + + } + + + + + + + + + + - + {item.description} - - - - - + {(item.properties.container_request || item.properties.containerRequest) && dispatch(navigateToProcess(item.properties.container_request || item.properties.containerRequest))}> } - - - - - - - + {isOldVersion && + + This is an old version. Make a copy to make changes. Go to the head version for sharing options. + + } @@ -180,25 +193,25 @@ export const CollectionPanel = withStyles(styles)( } - { Object.keys(item.properties).length > 0 - ? Object.keys(item.properties).map(k => - Array.isArray(item.properties[k]) - ? item.properties[k].map((v: string) => - getPropertyChip( - k, v, - isWritable - ? this.handleDelete(k, item.properties[k]) - : undefined, - classes.tag)) - : getPropertyChip( - k, item.properties[k], - isWritable - ? this.handleDelete(k, item.properties[k]) - : undefined, - classes.tag) - ) - :
No properties set on this collection.
- } + {Object.keys(item.properties).length > 0 + ? Object.keys(item.properties).map(k => + Array.isArray(item.properties[k]) + ? item.properties[k].map((v: string) => + getPropertyChip( + k, v, + isWritable + ? this.handleDelete(k, item.properties[k]) + : undefined, + classes.tag)) + : getPropertyChip( + k, item.properties[k], + isWritable + ? this.handleDelete(k, item.properties[k]) + : undefined, + classes.tag) + ) + :
No properties set on this collection.
+ }
@@ -212,7 +225,7 @@ export const CollectionPanel = withStyles(styles)( dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true)); dispatch(loadCollectionFiles(this.props.item.uuid)); } - } /> + } />
: null; @@ -233,6 +246,8 @@ export const CollectionPanel = withStyles(styles)( : ContextMenuKind.COLLECTION : ContextMenuKind.READONLY_COLLECTION }; + // Avoid expanding/collapsing the panel + event.stopPropagation(); this.props.dispatch(openContextMenu(event, resource)); } @@ -261,3 +276,52 @@ export const CollectionPanel = withStyles(styles)( } ) ); + +export const CollectionDetailsAttributes = (props: { item: CollectionResource, twoCol: boolean, classes?: Record }) => { + const item = props.item; + const classes = props.classes || { label: '', value: '' }; + const isOldVersion = item && item.currentVersionUuid !== item.uuid; + const mdSize = props.twoCol ? 6 : 12; + return + + + + + + + + + + + {isOldVersion && + + + + } + + + + + + + + + + + + + + + + ; +}; diff --git a/src/views/search-results-panel/search-results-panel-view.tsx b/src/views/search-results-panel/search-results-panel-view.tsx index fbaba210..dae91bd0 100644 --- a/src/views/search-results-panel/search-results-panel-view.tsx +++ b/src/views/search-results-panel/search-results-panel-view.tsx @@ -108,7 +108,7 @@ export const SearchResultsPanelView = withStyles(styles, { withTheme: true })( (props: SearchResultsPanelProps & WithStyles) => { const homeCluster = props.user.uuid.substr(0, 5); const loggedIn = props.sessions.filter((ss) => ss.loggedIn && ss.userIsActive); - return Use Site Manager to manage which clusters will be searched.} } - />; + />; }); diff --git a/tools/arvados_config.yml b/tools/arvados_config.yml index 8882eac2..b533156d 100644 --- a/tools/arvados_config.yml +++ b/tools/arvados_config.yml @@ -7,6 +7,8 @@ Clusters: TLS: Insecure: true Collections: + CollectionVersioning: true + PreserveVersionIfIdle: 0s BlobSigningKey: zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc TrustAllContent: true ForwardSlashNameSubstitution: /