Skip to content

Commit

Permalink
fix(#55): address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpascal committed Mar 27, 2024
1 parent 6d97948 commit ed99219
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 48 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ The `ConfigPropertyType` defines a property's validation rules and auto-formatti
| none | None | None | None |
| gender | A binary gender (eg. `Male`, `Woman`, `M`) | Formats to either `Male` or `Female` | None |
| generated | None. No user inputs. | Uses [LiquidJS](https://liquidjs.com) templates to generate data | None | [Details](#The-Generated-ConfigPropertyType)
| select_one | Single choice from a list of options | Same as `string` | None | Dictionary where the keys are the option values and the values are the corresponding labels |
| select_multiple | Multiple choice from a list of options | Same as `string` | None | Same as `select_one`

#### The Generated ConfigPropertyType
ContactProperties with `type: "generated"` use the [LiquidJS](https://liquidjs.com) template engine to populate a property with data. Here is an example of some configuration properties which use `"type": "generated"`:
Expand Down
6 changes: 5 additions & 1 deletion src/config/chis-ug/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@
{
"friendly_name": "Sex",
"property_name": "sex",
"type": "gender",
"type": "select_one",
"parameter": {
"male": "Male",
"female": "Female"
},
"required": true
}
]
Expand Down
11 changes: 9 additions & 2 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,19 @@ export class Config {
}

public static getUserRoleConfig(contactType: ContactType): ContactProperty {
const parameter = contactType.user_role.reduce(
(acc: { [key: string]: string }, curr: string) => {
acc[curr] = curr;
return acc;
}, {}
);

return {
friendly_name: 'Role(s)',
property_name: 'role',
type: 'select_role',
type: 'select_multiple',
required: true,
parameter: contactType.user_role,
parameter: parameter,
};
}

Expand Down
48 changes: 34 additions & 14 deletions src/lib/validator-select_multiple.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
import {ContactProperty} from '../config';
import {IValidator} from './validation';
import ValidatorString from './validator-string';
import ValidatorSelectOne from './validator-select_one';

export default class ValidatorSelectMultiple implements IValidator {
isValid(input: string, property: ContactProperty): boolean {
DELIMITER = ' ';

isValid(input: string, property: ContactProperty): boolean | string {
// Verify property.parameter is an object and is not null
if (!property?.parameter || typeof property.parameter !== 'object') {
throw new TypeError(`Expected property "parameter" to be an object.`);
}
const validValues = Object.keys(property.parameter);
let selectedValues;
if (Array.isArray(input)) {
selectedValues = input;
} else {
selectedValues = input.split(' ').map(value => value.trim()).filter(value => value !== '');
throw new TypeError(`Expected attribute "parameter" on property ${property.property_name} to be an object.`);
}
const invalidValues = selectedValues.filter(value => !validValues.includes(value));

const selectOneValidator = new ValidatorSelectOne();
const stringValidator = new ValidatorString();

const selectedValues = this.parseInput(input, stringValidator);
const invalidValues = selectedValues.filter(
value => !selectOneValidator.isValid(value, property)
);

if (invalidValues.length > 0) {
throw new Error(`Invalid values: ${invalidValues.join(', ')}`);
return `Invalid values: ${invalidValues.join(', ')}`;
}

// Check if any values are missing and property is required
if (selectedValues.length === 0 && property.required) {
throw new Error('Value is required');
return 'Value is required';
}

return true;
}

format(input: string): string {
return Array.isArray(input) ? input.join(' ') : input;
return Array.isArray(input) ? input.join(this.DELIMITER) : input;
}

get defaultError(): string {
return 'Invalid input';
return `Invalid input. Please use 'space' as delimiter.`;
}

private parseInput(input: string|string[], stringValidator: ValidatorString): string[] {
if (Array.isArray(input)) {
return input;
}

// If input is a string, split it by delimiter
return input
.split(this.DELIMITER)
.map(value => stringValidator.format(value))
.filter(value => value);
}
}

12 changes: 8 additions & 4 deletions src/lib/validator-select_one.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import {ContactProperty} from '../config';
import {IValidator} from './validation';
import ValidatorString from './validator-string';

export default class ValidatorSelectOne implements IValidator {
isValid(input: string, property: ContactProperty): boolean {
if (input.trim().length === 0 && property.required) {
const stringValidator = new ValidatorString();
const trimmedInput = stringValidator.format(input);

if (trimmedInput.length === 0 && property.required) {
throw new Error('Value is required');
}
// Verify property.parameter is an object
if (!property?.parameter || typeof property.parameter !== 'object') {
throw new TypeError(`Expected property "parameter" to be an object.`);
throw new TypeError(`Expected attribute "parameter" on property ${property.property_name} to be an object.`);
}

const validValues = Object.keys(property.parameter);
return validValues.includes(input.trim());
return validValues.includes(trimmedInput);
}

format(input: string): string {
Expand All @@ -21,5 +26,4 @@ export default class ValidatorSelectOne implements IValidator {
get defaultError(): string {
return 'Invalid value selected';
}

}
2 changes: 1 addition & 1 deletion src/lib/validator-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default class ValidatorString implements IValidator {
}

format(input : string) : string {
input = input.replace(/[^^a-zA-Z0-9À-ÖØ-öø-ÿ ()@./\-']/gu, '');
input = input.replace(/[^^a-zA-Z0-9À-ÖØ-öø-ÿ ()@./\-_']/gu, '');
input = input.replace(/\s\s+/g, ' ');
return input.trim();
}
Expand Down
39 changes: 19 additions & 20 deletions src/liquid/components/contact_type_property.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{% capture prop_name %}{{ include.prefix }}{{include.prop.property_name}}{% endcapture %}

{% if include.prop.type != 'generated' %}
{% capture prop_name %}{{ include.prefix }}{{include.prop.property_name}}{% endcapture %}
<div class="field">
Expand All @@ -8,24 +6,25 @@
</label>
<div class="control">
{% if include.prop.type == 'select_one' or include.prop.type == 'select_multiple' %}
<div class="select">
<select name="{{ prop_name }}" {% if include.prop.type == 'select_multiple' %}multiple{% endif %}>
{% for params in include.prop.parameter %}
<option value="{{ params[0] }}" {% if data[prop_name] == params[0] or data[prop_name] contains params[0] %}selected{% endif %}>
{{ params[1] }}
</option>
{% endfor %}
</select>
</div>
{% else %}
<input
name="{{ prop_name }}"
type="{% if include.prop.type == 'dob' %}date{% else %}text{% endif %}"
class="input"
{% if false and include.prop.type == 'regex' %} pattern="{{ include.prop.parameter }}" {% endif %}
{% if data[prop_name] %} value="{{ data[prop_name] }}" {% endif %}
/>
{% endif %}
{% assign is_multiple = include.prop.type == 'select_multiple' %}
<div class="select is-fullwidth{% if is_multiple %} is-multiple{% endif %}">
<select name="{{ prop_name }}" {% if is_multiple %}multiple{% endif %}>
{% for params in include.prop.parameter %}
<option value="{{ params[0] }}" {% if data[prop_name] == params[0] or data[prop_name] contains params[0] %}selected{% endif %}>
{{ params[1] }}
</option>
{% endfor %}
</select>
</div>
{% else %}
<input
name="{{ prop_name }}"
type="{% if include.prop.type == 'dob' %}date{% else %}text{% endif %}"
class="input"
{% if false and include.prop.type == 'regex' %} pattern="{{ include.prop.parameter }}" {% endif %}
{% if data[prop_name] %} value="{{ data[prop_name] }}" {% endif %}
/>
{% endif %}
</div>
</div>
{% endif %}
8 changes: 4 additions & 4 deletions src/liquid/components/list_cell.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<td
id="{{ include.propertyName }}"
{% if place.validationErrors[include.propertyName] %}
Expand All @@ -12,11 +11,12 @@
{{ include.values[include.property.property_name] }}
</a>
{% elsif include.property.type == 'select_one' %}
<span class="tag is-light">{{ include.property.parameter[include.values[include.property.property_name]] }}</span>
{% assign valueKey = include.values[include.property.property_name] %}
{{ include.property.parameter[valueKey] }}
{% elsif include.property.type == 'select_multiple' %}
{% assign values = include.values[include.property.property_name] | split: " " %}
{% for value in values %}
<span class="tag is-light">{{include.property.parameter[value]}}</span>
{{include.property.parameter[value]}}
{% endfor %}
{% else %}
{{ include.values[include.property.property_name] }}
Expand All @@ -26,4 +26,4 @@
<span class="material-symbols-outlined">cloud_off</span>
{% endif %}
{% endif %}
</td>
</td>
2 changes: 1 addition & 1 deletion src/liquid/place/create_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
{% if contactType.user_role.size > 1 %}
<section class="section is-small">
{%
include "components/user_role_property.html" prefix="user_" prop=userRoleProperty
include "components/contact_type_property.html" prefix="user_" prop=userRoleProperty
%}
</section>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion test/lib/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ describe('lib/validation.ts', () => {

expect(Validation.getValidationErrors(place)).to.deep.eq([{
property_name: 'user_role',
description: `Role 'stockmanager' is not allowed`,
description: 'Error in isValid for \'select_multiple\': Error: Invalid values: stockmanager'
}]);
});
});
Expand Down

0 comments on commit ed99219

Please sign in to comment.