Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 36 additions & 30 deletions packages/frontend/app/components/bulk-new-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { findById, mapBy } from 'ilios-common/utils/array-helpers';
import { TrackedAsyncData } from 'ember-async-data';
import YupValidations from 'ilios-common/classes/yup-validations';
import { string } from 'yup';
import { getOwner } from '@ember/application';

export default class BulkNewUsersComponent extends Component {
@service flashMessages;
Expand Down Expand Up @@ -203,6 +204,7 @@ export default class BulkNewUsersComponent extends Component {
password: isPresent(arr[8]) ? arr[8] : null,
},
existingUsernames,
getOwner(this),
);
});
const notHeaderRow = proposedUsers.filter(
Expand Down Expand Up @@ -346,37 +348,12 @@ export default class BulkNewUsersComponent extends Component {
class ProposedUser {
addedViaIlios = true;
enabled = true;
owner = null;
validations = null;
existingUsernames = [];

validations = new YupValidations(this, {
firstName: string().required().min(1).max(50),
middleName: string().nullable().min(1).max(20),
lastName: string().required().min(1).max(50),
username: string()
.nullable()
.min(1)
.max(100)
.test(
'is-username-unique',
(d) => {
return {
path: d.path,
messageKey: 'errors.exclusion',
};
},
(value) => value == null || !this.existingUsernames.includes(this.username),
),
password: string().when('username', {
is: (username) => !!username, // Check if the username field has a value
then: (schema) => schema.required(),
otherwise: (schema) => schema.notRequired(),
}),
campusId: string().nullable().min(1).max(16),
otherId: string().nullable().min(1).max(16),
email: string().nullable().email(),
phone: string().nullable().min(1).max(20),
});

constructor(userObj, existingUsernames) {
constructor(userObj, existingUsernames, owner) {
this.owner = owner;
this.firstName = userObj.firstName;
this.lastName = userObj.lastName;
this.middleName = userObj.middleName;
Expand All @@ -389,6 +366,35 @@ class ProposedUser {

this.existingUsernames = existingUsernames;

this.validations = new YupValidations(this, {
firstName: string().required().min(1).max(50),
middleName: string().nullable().min(1).max(20),
lastName: string().required().min(1).max(50),
username: string()
.nullable()
.min(1)
.max(100)
.test(
'is-username-unique',
(d) => {
return {
path: d.path,
messageKey: 'errors.exclusion',
};
},
(value) => value == null || !this.existingUsernames.includes(this.username),
),
password: string().when('username', {
is: (username) => !!username, // Check if the username field has a value
then: (schema) => schema.required(),
otherwise: (schema) => schema.notRequired(),
}),
campusId: string().nullable().min(1).max(16),
otherId: string().nullable().min(1).max(16),
email: string().nullable().email(),
phone: string().nullable().min(1).max(20),
});

this.validations.addErrorDisplayForAllFields();
}

Expand Down
39 changes: 35 additions & 4 deletions packages/ilios-common/addon/classes/yup-validations.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getProperties } from '@ember/object';
import { object, setLocale } from 'yup';
import { restartableTask, timeout } from 'ember-concurrency';
import { modifier } from 'ember-modifier';
import { getOwner } from '@ember/application';

const DEBOUNCE_MS = 100;

Expand All @@ -13,6 +14,7 @@ export default class YupValidations {
context;
schema;
shape;
intl;

@tracked error;
@tracked showAllErrors = false;
Expand All @@ -22,6 +24,13 @@ export default class YupValidations {
this.context = context;
this.shape = shape;
this.schema = object().shape(shape);
const owner = context.owner ?? getOwner(context);
if (!owner) {
throw new Error(
'No owner found for YupValidations. Either the context does not have an owner or the owner is not set.',
);
}
this.intl = owner.lookup('service:intl');
}

get errorsByKey() {
Expand Down Expand Up @@ -86,7 +95,29 @@ export default class YupValidations {
}

async isValid() {
return (await this.#validate()) === true;
const isValid = await this.#validate();
if (isValid === true) {
return true;
}
//find errors that are not visible and log them
//this makes it easier to find invisible errors when debugging
const invisibleErrors = Object.keys(this.errorsByKey).filter(
(key) => !this.visibleErrors.includes(key),
);
if (invisibleErrors.length) {
invisibleErrors.forEach((key) => {
const errors = this.errorsByKey[key].map(({ messageKey, values }) => {
if (!values) {
values = {};
}
values.description = key;
return this.intl.t(messageKey, values);
});
console.warn('Invisible errors prevented validation:\n ' + errors.join('\n '));
});
}

return false;
}

addErrorDisplaysFor = (fields) => {
Expand Down Expand Up @@ -190,7 +221,7 @@ function isEmail() {
return {
path: validationParams.path,
messageKey: 'errors.email',
values: [],
values: {},
};
};
}
Expand All @@ -200,15 +231,15 @@ function isURL() {
return {
path: validationParams.path,
messageKey: 'errors.url',
values: [],
values: {},
};
};
}

function notType() {
return ({ type, path }) => {
let messageKey,
values = [];
values = {};
switch (type) {
case 'number':
messageKey = 'errors.notANumber';
Expand Down