[ARVADOS-WORKBENCH2] created: 2.1.0-186-gab4d35c3

Git user git at public.arvados.org
Tue Feb 2 16:22:28 UTC 2021


        at  ab4d35c36e17299467b0007cfd2a89cd0287860a (commit)


commit ab4d35c36e17299467b0007cfd2a89cd0287860a
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Tue Feb 2 13:20:17 2021 -0300

    17308: Adds project creation with properties integration test.
    
    Also reorganizes some test cases, and removes code duplication on the create
    new project dialog for the property editor form.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/cypress/integration/collection-panel.spec.js b/cypress/integration/collection.spec.js
similarity index 93%
rename from cypress/integration/collection-panel.spec.js
rename to cypress/integration/collection.spec.js
index 3e241ceb..67df16ab 100644
--- a/cypress/integration/collection-panel.spec.js
+++ b/cypress/integration/collection.spec.js
@@ -38,7 +38,7 @@ describe('Collection panel tests', function() {
             cy.doSearch(`${this.testCollection.uuid}`);
 
             // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
-            cy.get('[data-cy=collection-properties-form]').within(() => {
+            cy.get('[data-cy=resource-properties-form]').within(() => {
                 cy.get('[data-cy=property-field-key]').within(() => {
                     cy.get('input').type('Color');
                 });
@@ -107,7 +107,7 @@ describe('Collection panel tests', function() {
                         .and('not.contain', 'anotherValue')
                     if (isWritable === true) {
                         // Check that properties can be added.
-                        cy.get('[data-cy=collection-properties-form]').within(() => {
+                        cy.get('[data-cy=resource-properties-form]').within(() => {
                             cy.get('[data-cy=property-field-key]').within(() => {
                                 cy.get('input').type('anotherKey');
                             });
@@ -121,7 +121,7 @@ describe('Collection panel tests', function() {
                             .and('contain', 'anotherValue')
                     } else {
                         // Properties form shouldn't be displayed.
-                        cy.get('[data-cy=collection-properties-form]').should('not.exist');
+                        cy.get('[data-cy=resource-properties-form]').should('not.exist');
                     }
                     // Check that the file listing show both read & write operations
                     cy.get('[data-cy=collection-files-panel]').within(() => {
@@ -468,4 +468,30 @@ describe('Collection panel tests', function() {
                 .should('contain', 'foo').and('contain', 'bar');
         });
     });
+
+    it('creates new collection on home project', function() {
+        cy.loginAs(activeUser);
+        cy.doSearch(`${activeUser.user.uuid}`);
+        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
+        cy.get('[data-cy=breadcrumb-last]').should('not.exist');
+        // Create new collection
+        cy.get('[data-cy=side-panel-button]').click();
+        cy.get('[data-cy=side-panel-new-collection]').click();
+        const collName = `Test collection (${Math.floor(999999 * Math.random())})`;
+        cy.get('[data-cy=form-dialog]')
+            .should('contain', 'New collection')
+            .within(() => {
+                cy.get('[data-cy=parent-field]').within(() => {
+                    cy.get('input').should('have.value', 'Home project');
+                })
+                cy.get('[data-cy=name-field]').within(() => {
+                    cy.get('input').type(collName);
+                })
+            })
+        cy.get('[data-cy=form-submit-btn]').click();
+        // Confirm that the user was taken to the newly created thing
+        cy.get('[data-cy=form-dialog]').should('not.exist');
+        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
+        cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
+    });
 })
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
new file mode 100644
index 00000000..42d26d7a
--- /dev/null
+++ b/cypress/integration/project.spec.js
@@ -0,0 +1,108 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Project 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('user', 'Active', 'User', false, true)
+            .as('activeUser').then(function() {
+                activeUser = this.activeUser;
+            }
+        );
+    });
+
+    beforeEach(function() {
+        cy.clearCookies();
+        cy.clearLocalStorage();
+    });
+
+    it.only('adds creates a new project with properties', function() {
+        const projName = `Test project (${Math.floor(999999 * Math.random())})`;
+        cy.loginAs(activeUser);
+        cy.get('[data-cy=side-panel-button]').click();
+        cy.get('[data-cy=side-panel-new-project]').click();
+        cy.get('[data-cy=form-dialog]')
+            .should('contain', 'New project')
+            .within(() => {
+                cy.get('[data-cy=name-field]').within(() => {
+                    cy.get('input').type(projName);
+                });
+
+            });
+        // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
+        cy.get('[data-cy=resource-properties-form]').within(() => {
+            cy.get('[data-cy=property-field-key]').within(() => {
+                cy.get('input').type('Color');
+            });
+            cy.get('[data-cy=property-field-value]').within(() => {
+                cy.get('input').type('Magenta');
+            });
+            cy.root().submit();
+        });
+        // Confirm proper vocabulary labels are displayed on the UI.
+        cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
+
+        // Create project and confirm the properties' real values.
+        cy.get('[data-cy=form-submit-btn]').click();
+        cy.get('[data-cy=breadcrumb-last]').should('contain', projName);
+        cy.doRequest('GET', '/arvados/v1/groups', null, {
+            filters: `[["name", "=", "${projName}"], ["group_class", "=", "project"]]`,
+        })
+        .its('body.items').as('projects')
+        .then(function() {
+            expect(this.projects).to.have.lengthOf(1);
+            expect(this.projects[0].properties).to.deep.equal(
+                {IDTAGCOLORS: 'IDVALCOLORS3'});
+        });
+    });
+
+    it('creates new project on home project and then a subproject inside it', function() {
+        const createProject = function(name, parentName) {
+            cy.get('[data-cy=side-panel-button]').click();
+            cy.get('[data-cy=side-panel-new-project]').click();
+            cy.get('[data-cy=form-dialog]')
+                .should('contain', 'New project')
+                .within(() => {
+                    cy.get('[data-cy=parent-field]').within(() => {
+                        cy.get('input').invoke('val').then((val) => {
+                            expect(val).to.include(parentName);
+                        });
+                    });
+                    cy.get('[data-cy=name-field]').within(() => {
+                        cy.get('input').type(name);
+                    });
+                });
+            cy.get('[data-cy=form-submit-btn]').click();
+        }
+
+        cy.loginAs(activeUser);
+        cy.doSearch(`${activeUser.user.uuid}`);
+        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
+        cy.get('[data-cy=breadcrumb-last]').should('not.exist');
+        // Create new project
+        const projName = `Test project (${Math.floor(999999 * Math.random())})`;
+        createProject(projName, 'Home project');
+        // Confirm that the user was taken to the newly created thing
+        cy.get('[data-cy=form-dialog]').should('not.exist');
+        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
+        cy.get('[data-cy=breadcrumb-last]').should('contain', projName);
+        // Create a subproject
+        const subProjName = `Test project (${Math.floor(999999 * Math.random())})`;
+        createProject(subProjName, projName);
+        cy.get('[data-cy=form-dialog]').should('not.exist');
+        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
+        cy.get('[data-cy=breadcrumb-last]').should('contain', subProjName);
+    });
+})
\ No newline at end of file
diff --git a/cypress/integration/side-panel.spec.js b/cypress/integration/side-panel.spec.js
index fe9d8400..309037ec 100644
--- a/cypress/integration/side-panel.spec.js
+++ b/cypress/integration/side-panel.spec.js
@@ -75,68 +75,4 @@ describe('Side panel tests', function() {
                 .and('be.disabled');
         })
     })
-
-    it('creates new collection on home project', function() {
-        cy.loginAs(activeUser);
-        cy.doSearch(`${activeUser.user.uuid}`);
-        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
-        cy.get('[data-cy=breadcrumb-last]').should('not.exist');
-        // Create new collection
-        cy.get('[data-cy=side-panel-button]').click();
-        cy.get('[data-cy=side-panel-new-collection]').click();
-        const collName = `Test collection (${Math.floor(999999 * Math.random())})`;
-        cy.get('[data-cy=form-dialog]')
-            .should('contain', 'New collection')
-            .within(() => {
-                cy.get('[data-cy=parent-field]').within(() => {
-                    cy.get('input').should('have.value', 'Home project');
-                })
-                cy.get('[data-cy=name-field]').within(() => {
-                    cy.get('input').type(collName);
-                })
-            })
-        cy.get('[data-cy=form-submit-btn]').click();
-        // Confirm that the user was taken to the newly created thing
-        cy.get('[data-cy=form-dialog]').should('not.exist');
-        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
-        cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
-    })
-
-    it('creates new project on home project and then a subproject inside it', function() {
-        const createProject = function(name, parentName) {
-            cy.get('[data-cy=side-panel-button]').click();
-            cy.get('[data-cy=side-panel-new-project]').click();
-            cy.get('[data-cy=form-dialog]')
-                .should('contain', 'New project')
-                .within(() => {
-                    cy.get('[data-cy=parent-field]').within(() => {
-                        cy.get('input').invoke('val').then((val) => {
-                            expect(val).to.include(parentName);
-                        })
-                    })
-                    cy.get('[data-cy=name-field]').within(() => {
-                        cy.get('input').type(name);
-                    })
-                })
-            cy.get('[data-cy=form-submit-btn]').click();
-        }
-
-        cy.loginAs(activeUser);
-        cy.doSearch(`${activeUser.user.uuid}`);
-        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
-        cy.get('[data-cy=breadcrumb-last]').should('not.exist');
-        // Create new project
-        const projName = `Test project (${Math.floor(999999 * Math.random())})`;
-        createProject(projName, 'Home project');
-        // Confirm that the user was taken to the newly created thing
-        cy.get('[data-cy=form-dialog]').should('not.exist');
-        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
-        cy.get('[data-cy=breadcrumb-last]').should('contain', projName);
-        // Create a subproject
-        const subProjName = `Test project (${Math.floor(999999 * Math.random())})`;
-        createProject(subProjName, projName);
-        cy.get('[data-cy=form-dialog]').should('not.exist');
-        cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
-        cy.get('[data-cy=breadcrumb-last]').should('contain', subProjName);
-    })
 })
\ No newline at end of file
diff --git a/src/views-components/project-properties/create-project-properties-form.tsx b/src/views-components/project-properties/create-project-properties-form.tsx
index 385afff7..64854773 100644
--- a/src/views-components/project-properties/create-project-properties-form.tsx
+++ b/src/views-components/project-properties/create-project-properties-form.tsx
@@ -2,48 +2,26 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from 'react';
-import { reduxForm, reset, InjectedFormProps } from 'redux-form';
-import { PROJECT_CREATE_PROPERTIES_FORM_NAME, addPropertyToCreateProjectForm } from '~/store/projects/project-create-actions';
-import { ResourcePropertiesFormData } from '~/views-components/resource-properties-form/resource-properties-form';
-import { StyleRulesCallback, WithStyles, withStyles, Grid } from '@material-ui/core';
-import { ArvadosTheme } from '~/common/custom-theme';
-import { PropertyKeyField } from '~/views-components/resource-properties-form/property-key-field';
-import { PropertyValueField } from '~/views-components/resource-properties-form/property-value-field';
-import { Button } from '~/views-components/resource-properties-form/resource-properties-form';
+import { reduxForm, reset } from 'redux-form';
+import { withStyles } from '@material-ui/core';
+import {
+    PROJECT_CREATE_PROPERTIES_FORM_NAME,
+    addPropertyToCreateProjectForm
+} from '~/store/projects/project-create-actions';
+import {
+    ResourcePropertiesForm,
+    ResourcePropertiesFormData
+} from '~/views-components/resource-properties-form/resource-properties-form';
 
-type CssRules = 'root';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
-    root: {
-        paddingTop: theme.spacing.unit,
-        margin: 0
-    }
-});
-
-type CreateProjectPropertiesFormProps = InjectedFormProps<ResourcePropertiesFormData> & WithStyles<CssRules>;
-
-const Form = withStyles(styles)(
-    ({ handleSubmit, submitting, invalid, classes }: CreateProjectPropertiesFormProps) =>
-        <Grid container spacing={16} className={classes.root}>
-            <Grid item xs={5}>
-                <PropertyKeyField />
-            </Grid>
-            <Grid item xs={5}>
-                <PropertyValueField />
-            </Grid>
-            <Grid item xs={2}>
-                <Button
-                    disabled={invalid}
-                    loading={submitting}
-                    color='primary'
-                    variant='contained'
-                    onClick={handleSubmit}>
-                    Add
-                </Button>
-            </Grid>
-        </Grid>
-);
+const Form = withStyles(
+    ({ spacing }) => (
+        { container:
+            {
+                paddingTop: spacing.unit,
+                margin: 0,
+            }
+        })
+    )(ResourcePropertiesForm);
 
 export const CreateProjectPropertiesForm = reduxForm<ResourcePropertiesFormData>({
     form: PROJECT_CREATE_PROPERTIES_FORM_NAME,
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 c8d0959a..e8d2fc58 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<ResourcePropertiesFormData> & WithStyles<GridClassKey>;
 
 export const ResourcePropertiesForm = ({ handleSubmit, submitting, invalid, classes }: ResourcePropertiesFormProps ) =>
-    <form data-cy='collection-properties-form' onSubmit={handleSubmit}>
+    <form data-cy='resource-properties-form' onSubmit={handleSubmit}>
         <Grid container spacing={16} classes={classes}>
             <Grid item xs>
                 <PropertyKeyField />

commit 5d163fb5e5eb04ace4c67112d20fd34f1b0f5e7b
Author: Lucas Di Pentima <lucas at di-pentima.com.ar>
Date:   Mon Feb 1 19:27:09 2021 -0300

    17308: Adds vocabulary terms handling to new project dialog's property editor.
    
    Also, adds the ability to add multiple value properties. This makes the
    new project dialog's editor behaviour consistent with the rest of the
    application.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas at di-pentima.com.ar>

diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts
index 7881d672..ff89ca38 100644
--- a/src/store/collection-panel/collection-panel-action.ts
+++ b/src/store/collection-panel/collection-panel-action.ts
@@ -61,7 +61,7 @@ export const createCollectionTag = (data: TagProperty) =>
             dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection));
             dispatch(resourcesActions.SET_RESOURCES([updatedCollection]));
             dispatch(snackbarActions.OPEN_SNACKBAR({
-                message: "Tag has been successfully added.",
+                message: "Property has been successfully added.",
                 hideDuration: 2000,
                 kind: SnackbarKind.SUCCESS }));
             dispatch<any>(loadDetailsPanel(updatedCollection.uuid));
diff --git a/src/store/projects/project-create-actions.ts b/src/store/projects/project-create-actions.ts
index 583a4bd6..3599378c 100644
--- a/src/store/projects/project-create-actions.ts
+++ b/src/store/projects/project-create-actions.ts
@@ -13,6 +13,7 @@ import { ServiceRepository } from '~/services/services';
 import { matchProjectRoute, matchRunProcessRoute } from '~/routes/routes';
 import { ResourcePropertiesFormData } from '~/views-components/resource-properties-form/resource-properties-form';
 import { RouterState } from "react-router-redux";
+import { addProperty, deleteProperty } from "~/lib/resource-properties";
 
 export interface ProjectCreateFormDialogData {
     ownerUuid: string;
@@ -22,7 +23,7 @@ export interface ProjectCreateFormDialogData {
 }
 
 export interface ProjectProperties {
-    [key: string]: string;
+    [key: string]: string | string[];
 }
 
 export const PROJECT_CREATE_FORM_NAME = 'projectCreateFormName';
@@ -69,13 +70,19 @@ export const createProject = (project: Partial<ProjectResource>) =>
 export const addPropertyToCreateProjectForm = (data: ResourcePropertiesFormData) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const properties = { ...PROJECT_CREATE_FORM_SELECTOR(getState(), 'properties') };
-        properties[data.key] = data.value;
-        dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties));
+        const key = data.keyID || data.key;
+        const value =  data.valueID || data.value;
+        dispatch(change(
+            PROJECT_CREATE_FORM_NAME,
+            'properties',
+            addProperty(properties, key, value)));
     };
 
-export const removePropertyFromCreateProjectForm = (key: string) =>
+export const removePropertyFromCreateProjectForm = (key: string, value: string) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const properties = { ...PROJECT_CREATE_FORM_SELECTOR(getState(), 'properties') };
-        delete properties[key];
-        dispatch(change(PROJECT_CREATE_FORM_NAME, 'properties', properties));
+        dispatch(change(
+            PROJECT_CREATE_FORM_NAME,
+            'properties',
+            deleteProperty(properties, key, value)));
     };
diff --git a/src/views-components/project-properties/create-project-properties-list.tsx b/src/views-components/project-properties/create-project-properties-list.tsx
index 1d2050fe..b1de75b2 100644
--- a/src/views-components/project-properties/create-project-properties-list.tsx
+++ b/src/views-components/project-properties/create-project-properties-list.tsx
@@ -5,10 +5,15 @@
 import * as React from 'react';
 import { connect } from 'react-redux';
 import { Dispatch } from 'redux';
-import { withStyles, StyleRulesCallback, WithStyles, Chip } from '@material-ui/core';
+import {
+    withStyles,
+    StyleRulesCallback,
+    WithStyles,
+} from '@material-ui/core';
 import { RootState } from '~/store/store';
 import { removePropertyFromCreateProjectForm, PROJECT_CREATE_FORM_SELECTOR, ProjectProperties } from '~/store/projects/project-create-actions';
 import { ArvadosTheme } from '~/common/custom-theme';
+import { getPropertyChip } from '../resource-properties-form/property-chip';
 
 type CssRules = 'tag';
 
@@ -24,7 +29,7 @@ interface CreateProjectPropertiesListDataProps {
 }
 
 interface CreateProjectPropertiesListActionProps {
-    handleDelete: (key: string) => void;
+    handleDelete: (key: string, value: string) => void;
 }
 
 const mapStateToProps = (state: RootState): CreateProjectPropertiesListDataProps => {
@@ -33,21 +38,28 @@ const mapStateToProps = (state: RootState): CreateProjectPropertiesListDataProps
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): CreateProjectPropertiesListActionProps => ({
-    handleDelete: (key: string) => dispatch<any>(removePropertyFromCreateProjectForm(key))
+    handleDelete: (key: string, value: string) => dispatch<any>(removePropertyFromCreateProjectForm(key, value))
 });
 
-type CreateProjectPropertiesListProps = CreateProjectPropertiesListDataProps & 
+type CreateProjectPropertiesListProps = CreateProjectPropertiesListDataProps &
     CreateProjectPropertiesListActionProps & WithStyles<CssRules>;
 
 const List = withStyles(styles)(
     ({ classes, handleDelete, properties }: CreateProjectPropertiesListProps) =>
         <div>
             {properties &&
-                Object.keys(properties).map(k => {
-                    return <Chip key={k} className={classes.tag}
-                        onDelete={() => handleDelete(k)}
-                        label={`${k}: ${properties[k]}`} />;
-                })}
+                Object.keys(properties).map(k =>
+                    Array.isArray(properties[k])
+                    ? (properties[k] as string[]).map((v: string) =>
+                        getPropertyChip(
+                            k, v,
+                            () => handleDelete(k, v),
+                            classes.tag))
+                    : getPropertyChip(
+                        k, (properties[k] as string),
+                        () => handleDelete(k, (properties[k] as string)),
+                        classes.tag))
+                }
         </div>
 );
 
diff --git a/src/views-components/resource-properties-form/property-chip.tsx b/src/views-components/resource-properties-form/property-chip.tsx
index 1fba8a40..b9a13fbb 100644
--- a/src/views-components/resource-properties-form/property-chip.tsx
+++ b/src/views-components/resource-properties-form/property-chip.tsx
@@ -51,7 +51,7 @@ export const PropertyChipComponent = connect(mapStateToProps, mapDispatchToProps
     }
 );
 
-export const getPropertyChip = (k:string, v:string, handleDelete:any, className:string) =>
+export const getPropertyChip = (k: string, v: string, handleDelete: any, className: string) =>
     <PropertyChipComponent
         key={`${k}-${v}`} className={className}
         onDelete={handleDelete}

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list