Skip to content

Commit

Permalink
IIIF #5 - Adding client and server side validations to projects
Browse files Browse the repository at this point in the history
  • Loading branch information
dleadbetter committed Jul 15, 2022
1 parent e5ee76a commit 0ce2522
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 3 deletions.
19 changes: 19 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,23 @@ class Project < ApplicationRecord

# ActiveStorage
has_one_attached :avatar

# Validations
validate :validate_metadata

private

def validate_metadata
return unless self.metadata?

JSON.parse(self.metadata).each do |item|
errors.add(:metadata, I18n.t('errors.project.metadata.name_empty')) unless item['name'].present?
errors.add(:metadata, I18n.t('errors.project.metadata.type_empty')) unless item['type'].present?

next unless item['type'] == 'dropdown'

errors.add(:metadata, I18n.t('errors.project.metadata.options_empty')) unless item['options'].present?
errors.add(:metadata, I18n.t('errors.project.metadata.options_duplicate')) unless item['options'].size == item['options'].uniq.size
end
end
end
2 changes: 2 additions & 0 deletions client/src/components/MetadataList.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ const MetadataList: ComponentType<any> = (props) => {
}}
>
<Form.Input
error={props.isError(`metadata[${index}][name]`)}
onChange={onUpdateItem.bind(this, index, 'name')}
placeholder={t('MetadataList.labels.name')}
value={item.name}
width={7}
/>
<Form.Dropdown
clearable
error={props.isError(`metadata[${index}][type]`)}
onChange={onUpdateItem.bind(this, index, 'type')}
options={Metadata.getOptions()}
placeholder={t('MetadataList.labels.type')}
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/MetadataOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,17 @@ const MetadataOptions: ComponentType<any> = (props: Props) => {
onChange={onUpdateOption.bind(this, index)}
value={option.value}
style={{
marginRight: '0.5em',
width: 'unset'
}}
/>
<Button
basic
color='green'
compact
icon='checkmark'
onClick={onSaveOption.bind(this, index)}
type='button'
size='tiny'
/>
</Label>
)}
Expand Down
8 changes: 8 additions & 0 deletions client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@
}
},
"Project": {
"errors": {
"metadata": {
"name": "Name is required",
"optionsDuplicate": "{{name}}: Dropdowns cannot contain duplicate options",
"optionsEmpty": "{{name}}: Dropdown options cannot be empty",
"type": "Type is required"
}
},
"labels": {
"apiKey": "API key",
"bucketName": "AWS S3 bucket",
Expand Down
38 changes: 37 additions & 1 deletion client/src/pages/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import uuid from 'react-uuid';
import { Button, Form } from 'semantic-ui-react';
import _ from 'underscore';
import AuthenticationService from '../services/Authentication';
import i18n from '../i18n/i18n';
import Metadata from '../constants/Metadata';
import MetadataList from '../components/MetadataList';
import Organization from '../transforms/Organization';
import OrganizationsService from '../services/Organizations';
Expand Down Expand Up @@ -120,13 +122,46 @@ const ProjectForm = withTranslation()((props) => {
>
<MetadataList
items={JSON.parse(props.item.metadata || '[]')}
isError={props.isError}
onChange={(items) => props.onSetState({ metadata: JSON.stringify(items) })}
/>
</SimpleEditPage.Tab>
</SimpleEditPage>
);
});

const ValidateProject = (project) => {
const errors = {};

if (project && project.metadata) {
const items = JSON.parse(project.metadata);

_.each(items, (item, index) => {
if (_.isEmpty(item.name)) {
_.extend(errors, { [`metadata[${index}][name]`]: i18n.t('Project.errors.metadata.name') });
}

if (_.isEmpty(item.type)) {
_.extend(errors, { [`metadata[${index}][type]`]: i18n.t('Project.errors.metadata.type') });
}

if (item.type === Metadata.Types.dropdown && _.isEmpty(item.options)) {
_.extend(errors, {
[`metadata[${index}][options]`]: i18n.t('Project.errors.metadata.optionsEmpty', { name: item.name })
});
}

if (item.type === Metadata.Types.dropdown && _.uniq(item.options).length !== item.options.length) {
_.extend(errors, {
[`metadata[${index}][options]`]: i18n.t('Project.errors.metadata.optionsDuplicate', { name: item.name })
});
}
});
}

return errors;
};

const Project: ComponentType<any> = withEditPage(ProjectForm, {
id: 'projectId',
onInitialize: (
Expand All @@ -139,7 +174,8 @@ const Project: ComponentType<any> = withEditPage(ProjectForm, {
.save(project)
.then(({ data }) => data.project)
),
required: ['name', 'description', 'organization_id']
required: ['name', 'description', 'organization_id'],
validate: ValidateProject
});

export default Project;
3 changes: 2 additions & 1 deletion client/src/types/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export type Project = {
avatar_url: string,
avatar_download_url: string,
avatar_preview_url: string,
avatar_thumbnail_url: string
avatar_thumbnail_url: string,
metadata: string
};
6 changes: 6 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@

en:
errors:
project:
metadata:
name_empty: "Name cannot be empty"
options_duplicate: "Dropdowns cannot contain duplicate options"
options_empty: "Dropdown options cannot be empty."
type_empty: "Type cannot be empty"
unauthorized: "You do not have access to this record."

0 comments on commit 0ce2522

Please sign in to comment.