From fc898afbfa340c57062887083c96bc197fc283d0 Mon Sep 17 00:00:00 2001 From: Lucas Di Pentima Date: Mon, 18 May 2020 10:12:23 -0300 Subject: [PATCH] 16118: Adds test checking writable/readonly collection UI changes. WIP: There's deactivated code that shows a snackbar whenever a service request returns an error, no matter if the error is handled somewhere up in the stack. I think that isn't a good approach, also it prevents the 'readonly' case to work because the snackbar appears in fron of a menu button and cannot be clicked. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- cypress/integration/collection-panel.spec.js | 97 ++++++++++++++++--- .../collection-panel-files.tsx | 11 ++- src/components/context-menu/context-menu.tsx | 2 +- src/components/file-tree/file-tree-item.tsx | 1 + src/index.tsx | 16 ++- .../property-key-field.tsx | 2 + .../property-value-field.tsx | 2 + .../resource-properties-form.tsx | 2 +- .../collection-panel/collection-panel.tsx | 12 ++- 9 files changed, 112 insertions(+), 33 deletions(-) diff --git a/cypress/integration/collection-panel.spec.js b/cypress/integration/collection-panel.spec.js index 287bfe10..0ae72809 100644 --- a/cypress/integration/collection-panel.spec.js +++ b/cypress/integration/collection-panel.spec.js @@ -28,21 +28,90 @@ describe('Collection panel tests', function() { cy.clearLocalStorage() }) - it('shows a collection by URL', function() { + it('shows collection by URL', function() { cy.loginAs(activeUser); - cy.createCollection(adminUser.token, { - name: 'Test collection', - owner_uuid: activeUser.user.uuid, - manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}) - .as('testCollection').then(function() { - cy.visit(`/collections/${this.testCollection.uuid}`); - cy.get('[data-cy=collection-info-panel]') - .should('contain', this.testCollection.name) - .and('contain', this.testCollection.uuid); - cy.get('[data-cy=collection-files-panel]') - .should('contain', 'bar'); + [true, false].map(function(isWritable) { + // Creates the collection using the admin token so we can set up + // a bogus manifest text without block signatures. + cy.createCollection(adminUser.token, { + name: 'Test collection', + owner_uuid: isWritable + ? activeUser.user.uuid + : adminUser.user.uuid, + properties: {someKey: 'someValue'}, + manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}) + .as('testCollection').then(function() { + if (isWritable === false) { + // Share collection as read-only + cy.do_request('POST', '/arvados/v1/links', { + link: JSON.stringify({ + name: 'can_read', + link_class: 'permission', + head_uuid: this.testCollection.uuid, + tail_uuid: activeUser.user.uuid + }) + }, null, adminUser.token, null); + } + cy.visit(`/collections/${this.testCollection.uuid}`); + // Check that name & uuid are correct. + cy.get('[data-cy=collection-info-panel]') + .should('contain', this.testCollection.name) + .and(`${isWritable ? 'not.': ''}contain`, 'Read-only') + .and('contain', this.testCollection.uuid); + // Check that both read and write operations are available on + // the 'More options' menu. + cy.get('[data-cy=collection-panel-options-btn]') + .click() + cy.get('[data-cy=context-menu]') + .should('contain', 'Add to favorites') + .and(`${isWritable ? '' : 'not.'}contain`, 'Edit collection') + .type('{esc}'); // Collapse the options menu + cy.get('[data-cy=collection-properties-panel]') + .should('contain', 'someKey') + .and('contain', 'someValue') + .and('not.contain', 'anotherKey') + .and('not.contain', 'anotherValue') + // Check that properties can be added. + if (isWritable === true) { + cy.get('[data-cy=collection-properties-form]').within(() => { + cy.get('[data-cy=property-field-key]').within(() => { + cy.get('input').type('anotherKey'); + }); + cy.get('[data-cy=property-field-value]').within(() => { + cy.get('input').type('anotherValue'); + }); + cy.root().submit(); + }) + cy.get('[data-cy=collection-properties-panel]') + .should('contain', 'anotherKey') + .and('contain', 'anotherValue') + } + // Check that the file listing show both read & write operations + cy.get('[data-cy=collection-files-panel]').within(() => { + cy.root().should('contain', 'bar'); + cy.get('[data-cy=upload-button]') + .should(`${isWritable ? '' : 'not.'}contain`, 'Upload data'); + }); + // Hamburger 'more options' menu button + cy.get('[data-cy=collection-files-panel-options-btn]') + .click() + cy.get('[data-cy=context-menu]') + .should('contain', 'Select all') + .click() + cy.get('[data-cy=collection-files-panel-options-btn]') + .click() + cy.get('[data-cy=context-menu]') + .should('contain', 'Download selected') + .and(`${isWritable ? '' : 'not.'}contain`, 'Remove selected') + .type('{esc}'); // Collapse the options menu + // File item 'more options' button + cy.get('[data-cy=file-item-options-btn') + .click() + cy.get('[data-cy=context-menu]') + .should('contain', 'Download') + .and(`${isWritable ? '' : 'not.'}contain`, 'Remove') + .type('{esc}'); // Collapse + }) }) }) - - // it('') }) \ 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 3de4068f..48b36be1 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -50,12 +50,15 @@ const styles: StyleRulesCallback = theme => ({ export const CollectionPanelFiles = withStyles(styles)( ({ onItemMenuOpen, onOptionsMenuOpen, onUploadDataClick, classes, isWritable, ...treeProps }: CollectionPanelFilesProps & WithStyles) => - + @@ -67,7 +70,9 @@ export const CollectionPanelFiles = className={classes.cardSubheader} action={ - onOptionsMenuOpen(ev, isWritable)}> + onOptionsMenuOpen(ev, isWritable)}> diff --git a/src/components/context-menu/context-menu.tsx b/src/components/context-menu/context-menu.tsx index 98456dad..ecade812 100644 --- a/src/components/context-menu/context-menu.tsx +++ b/src/components/context-menu/context-menu.tsx @@ -32,7 +32,7 @@ export class ContextMenu extends React.PureComponent { transformOrigin={DefaultTransformOrigin} anchorOrigin={DefaultTransformOrigin} onContextMenu={this.handleContextMenu}> - + {items.map((group, groupIndex) => {group.map((item, actionIndex) => diff --git a/src/components/file-tree/file-tree-item.tsx b/src/components/file-tree/file-tree-item.tsx index dc8f09b9..23273dac 100644 --- a/src/components/file-tree/file-tree-item.tsx +++ b/src/components/file-tree/file-tree-item.tsx @@ -53,6 +53,7 @@ export const FileTreeItem = withStyles(fileTreeItemStyle)( variant="caption">{formatFileSize(item.data.size)} diff --git a/src/index.tsx b/src/index.tsx index 16759b7f..eec60dc1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -104,18 +104,14 @@ fetchConfig() }, errorFn: (id, error) => { console.error("Backend error:", error); - if (error.errors) { + if (false) { // WIP: Should we mix backend with UI code? store.dispatch(snackbarActions.OPEN_SNACKBAR({ - message: `${error.errors[0]}`, + message: `${error.errors + ? error.errors[0] + : error.message}`, kind: SnackbarKind.ERROR, - hideDuration: 8000 - })); - } else { - store.dispatch(snackbarActions.OPEN_SNACKBAR({ - message: `${error.message}`, - kind: SnackbarKind.ERROR, - hideDuration: 8000 - })); + hideDuration: 8000}) + ); } } }); diff --git a/src/views-components/resource-properties-form/property-key-field.tsx b/src/views-components/resource-properties-form/property-key-field.tsx index 1f921188..d17f50d4 100644 --- a/src/views-components/resource-properties-form/property-key-field.tsx +++ b/src/views-components/resource-properties-form/property-key-field.tsx @@ -16,11 +16,13 @@ export const PROPERTY_KEY_FIELD_ID = 'keyID'; export const PropertyKeyField = connectVocabulary( ({ vocabulary, skipValidation }: VocabularyProp & ValidationProp) => + + ); const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) => diff --git a/src/views-components/resource-properties-form/property-value-field.tsx b/src/views-components/resource-properties-form/property-value-field.tsx index 99745199..c5a5071f 100644 --- a/src/views-components/resource-properties-form/property-value-field.tsx +++ b/src/views-components/resource-properties-form/property-value-field.tsx @@ -28,11 +28,13 @@ const connectVocabularyAndPropertyKey = compose( export const PropertyValueField = connectVocabularyAndPropertyKey( ({ skipValidation, ...props }: PropertyValueFieldProps) => + + ); const PropertyValueInput = ({ vocabulary, propertyKey, ...props }: WrappedFieldProps & PropertyValueFieldProps) => diff --git a/src/views-components/resource-properties-form/resource-properties-form.tsx b/src/views-components/resource-properties-form/resource-properties-form.tsx index db40e4a7..0632b97c 100644 --- a/src/views-components/resource-properties-form/resource-properties-form.tsx +++ b/src/views-components/resource-properties-form/resource-properties-form.tsx @@ -20,7 +20,7 @@ export interface ResourcePropertiesFormData { export type ResourcePropertiesFormProps = InjectedFormProps & WithStyles; export const ResourcePropertiesForm = ({ handleSubmit, submitting, invalid, classes }: ResourcePropertiesFormProps ) => -
+ diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index be2afc72..defe73b9 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -29,7 +29,7 @@ import { GroupResource } from '~/models/group'; import { UserResource } from '~/models/user'; import { getUserUuid } from '~/common/getuser'; -type CssRules = 'card' | 'iconHeader' | 'tag' | 'label' | 'value' | 'link' | 'centeredLabel'; +type CssRules = 'card' | 'iconHeader' | 'tag' | 'label' | 'value' | 'link' | 'centeredLabel' | 'readOnlyChip'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ card: { @@ -60,6 +60,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ '&:hover': { cursor: 'pointer' } + }, + readOnlyChip: { + marginLeft: theme.spacing.unit } }); @@ -101,6 +104,7 @@ export const CollectionPanel = withStyles(styles)( action={ @@ -111,7 +115,7 @@ export const CollectionPanel = withStyles(styles)( {item.name} - {isWritable || } label="Read-only"/>} + {isWritable || } label="Read-only" className={classes.readOnlyChip} />} } titleTypographyProps={this.titleProps} @@ -142,7 +146,7 @@ export const CollectionPanel = withStyles(styles)( - + @@ -173,7 +177,7 @@ export const CollectionPanel = withStyles(styles)( -
+
-- 2.30.2