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');
});
.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');
});
.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(() => {
.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);
+ });
})
--- /dev/null
+// 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('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
.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
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));
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;
}
export interface ProjectProperties {
- [key: string]: string;
+ [key: string]: string | string[];
}
export const PROJECT_CREATE_FORM_NAME = 'projectCreateFormName';
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)));
};
//
// 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,
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';
}
interface CreateProjectPropertiesListActionProps {
- handleDelete: (key: string) => void;
+ handleDelete: (key: string, value: string) => void;
}
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>
);
}
);
-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}
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 />