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');
});
it('shows collection by URL', function() {
cy.loginAs(activeUser);
[true, false].map(function(isWritable) {
+ // Using different file names to avoid test flakyness: the second iteration
+ // on this loop may pass an assertion from the first iteration by looking
+ // for the same file name.
+ const fileName = isWritable ? 'bar' : 'foo';
cy.createGroup(adminUser.token, {
name: 'Shared project',
group_class: 'project',
name: 'Test collection',
owner_uuid: this.sharedGroup.uuid,
properties: {someKey: 'someValue'},
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"})
+ manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`})
.as('testCollection').then(function() {
// Share the group with active user.
cy.createLink(adminUser.token, {
.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(() => {
- cy.root().should('contain', 'bar');
+ cy.root().should('contain', fileName);
if (isWritable) {
cy.get('[data-cy=upload-button]')
.should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
}
});
cy.get('[data-cy=collection-files-panel]')
- .contains('bar').rightclick();
+ .contains(fileName).rightclick();
cy.get('[data-cy=context-menu]')
.should('contain', 'Download')
.and('contain', 'Open in new tab')
.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
onValueClick?: () => void;
linkToUuid?: string;
copyValue?: string;
+ uuidEnhancer?: Function;
}
type DetailsAttributeProps = DetailsAttributeDataProps & WithStyles<CssRules> & FederationConfig & DispatchProp;
}
render() {
- const { label, link, value, children, classes, classLabel,
+ const { uuidEnhancer, label, link, value, children, classes, classLabel,
classValue, lowercaseValue, onValueClick, linkToUuid,
localCluster, remoteHostsConfig, sessions, copyValue } = this.props;
let valueNode: React.ReactNode;
if (linkToUuid) {
+ const uuid = uuidEnhancer ? uuidEnhancer(linkToUuid) : linkToUuid;
const linkUrl = getNavUrl(linkToUuid || "", { localCluster, remoteHostsConfig, sessions });
if (linkUrl[0] === '/') {
- valueNode = <Link to={linkUrl} className={classes.link}>{linkToUuid}</Link>;
+ valueNode = <Link to={linkUrl} className={classes.link}>{uuid}</Link>;
} else {
- valueNode = <a href={linkUrl} className={classes.link} target='_blank'>{linkToUuid}</a>;
+ valueNode = <a href={linkUrl} className={classes.link} target='_blank'>{uuid}</a>;
}
} else if (link) {
valueNode = <a href={link} className={classes.link} target='_blank'>{value}</a>;
} else {
valueNode = value;
}
+
return <Typography component="div" className={classes.attribute}>
<Typography component="div" className={classnames([classes.label, classLabel])}>{label}</Typography>
<Typography
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)));
};
export const updateResources = (resources: Resource[]) => resourcesActions.SET_RESOURCES(resources);
-export const loadResource = (uuid: string) =>
+export const loadResource = (uuid: string, showErrors?: boolean) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
try {
const kind = extractUuidKind(uuid);
const service = getResourceService(kind)(services);
if (service) {
- const resource = await service.get(uuid);
+ const resource = await service.get(uuid, showErrors);
dispatch<any>(updateResources([resource]));
return resource;
}
async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
const state = api.getState();
+ const parentContainerRequestUuid = state.processPanel.containerRequestUuid;
+ if (parentContainerRequestUuid === "") { return; }
const dataExplorer = getDataExplorer(state.dataExplorer, this.getId());
try {
api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
- const parentContainerRequestUuid = state.processPanel.containerRequestUuid;
const parentContainerRequest = await this.services.containerRequestService.get(parentContainerRequestUuid);
const containerRequests = await this.services.containerRequestService.list(
{ ...getParams(dataExplorer, parentContainerRequest) });
import { ResourceStatus as WorkflowStatus } from '~/views/workflow-panel/workflow-panel-view';
import { getUuidPrefix, openRunProcess } from '~/store/workflow-panel/workflow-panel-actions';
import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions';
-import { UserResource } from '~/models/user';
+import { getUserFullname, User, UserResource } from '~/models/user';
import { toggleIsActive, toggleIsAdmin } from '~/store/users/users-actions';
import { LinkResource } from '~/models/link';
import { navigateTo } from '~/store/navigation/navigation-action';
import { withResourceData } from '~/views-components/data-explorer/with-resources';
import { CollectionResource } from '~/models/collection';
import { IllegalNamingWarning } from '~/components/warning/warning';
+import { loadResource } from '~/store/resources/resources-actions';
const renderName = (dispatch: Dispatch, item: GroupContentsResource) =>
<Grid container alignItems="center" wrap="nowrap" spacing={16}>
return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
})((props: { owner: string }) => renderOwner(props.owner));
+export const ResourceOwnerWithName =
+ compose(
+ connect(
+ (state: RootState, props: { uuid: string }) => {
+ let ownerName = '';
+ const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
+
+ if (resource) {
+ ownerName = getUserFullname(resource as User) || (resource as GroupContentsResource).name;
+ }
+
+ return { uuid: props.uuid, ownerName };
+ }),
+ withStyles({}, { withTheme: true }))
+ ((props: { uuid: string, ownerName: string, dispatch: Dispatch, theme: ArvadosTheme }) => {
+ const { uuid, ownerName, dispatch, theme } = props;
+
+ if (ownerName === '') {
+ dispatch<any>(loadResource(uuid, false));
+ return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
+ {uuid}
+ </Typography>;
+ }
+
+ return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
+ {ownerName} ({uuid})
+ </Typography>;
+ });
+
const renderType = (type: string) =>
<Typography noWrap>
{resourceLabel(type)}
})((props: { type: string }) => renderType(props.type));
export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
- return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
- })((props: { resource: GroupContentsResource }) =>
- (props.resource && props.resource.kind === ResourceKind.COLLECTION)
+ return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
+})((props: { resource: GroupContentsResource }) =>
+ (props.resource && props.resource.kind === ResourceKind.COLLECTION)
? <CollectionStatus uuid={props.resource.uuid} />
: <ProcessStatus uuid={props.resource.uuid} />
- );
+);
export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
- return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
- })((props: { collection: CollectionResource }) =>
- (props.collection.uuid !== props.collection.currentVersionUuid)
+ return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
+})((props: { collection: CollectionResource }) =>
+ (props.collection.uuid !== props.collection.currentVersionUuid)
? <Typography>version {props.collection.version}</Typography>
: <Typography>head version</Typography>
- );
+);
export const ProcessStatus = compose(
connect((state: RootState, props: { uuid: string }) => {
export const ProcessStartDate = connect(
(state: RootState, props: { uuid: string }) => {
const process = getProcess(props.uuid)(state.resources);
- return { date: ( process && process.container ) ? process.container.startedAt : '' };
+ return { date: (process && process.container) ? process.container.startedAt : '' };
})((props: { date: string }) => renderDate(props.date));
export const renderRunTime = (time: number) =>
import { resourceLabel } from '~/common/labels';
import { DetailsData } from "./details-data";
import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
+import { ResourceOwnerWithName } from '../data-explorer/renderers';
export class ProcessDetails extends DetailsData<ProcessResource> {
getDetails() {
return <div>
<DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
- <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid} />
+ <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid}
+ uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
<DetailsAttribute label='Status' value={this.item.state} />
<DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
import { ArvadosTheme } from '~/common/custom-theme';
import { Dispatch } from 'redux';
import { getPropertyChip } from '../resource-properties-form/property-chip';
+import { ResourceOwnerWithName } from '../data-explorer/renderers';
export class ProjectDetails extends DetailsData<ProjectResource> {
getIcon(className?: string) {
withStyles(styles)(
({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
<DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROJECT)} />
- <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid} lowercaseValue={true} />
+ <DetailsAttribute label='Owner' linkToUuid={project.ownerUuid}
+ uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
<DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
<DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
<DetailsAttribute label='Project UUID' linkToUuid={project.uuid} value={project.uuid} />
//
// 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 />
import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
import { Link } from 'react-router-dom';
import { Link as ButtonLink } from '@material-ui/core';
+import { ResourceOwnerWithName } from '~/views-components/data-explorer/renderers';
type CssRules = 'root'
| 'button'
</Grid>
<Grid item xs={12} md={mdSize}>
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
- label='Owner' linkToUuid={item.ownerUuid} />
+ label='Owner' linkToUuid={item.ownerUuid}
+ uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
</Grid>
<Grid item xs={12} md={mdSize}>
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
<DetailsAttribute
classLabel={classes.label} classValue={classes.value}
label='Version number'
- value={ showVersionBrowser !== undefined
+ value={showVersionBrowser !== undefined
? <Tooltip title="Open version browser"><ButtonLink underline='none' className={classes.button} onClick={() => showVersionBrowser()}>
{<span data-cy='collection-version-number'>{item.version}</span>}
</ButtonLink></Tooltip>