Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(data element forms): add validation #359

Merged
merged 28 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
636479b
test(new data element view): test required fields & cancel button
Mohammer5 Oct 2, 2023
d889805
chore: update @dhis2/ui
Mohammer5 Oct 3, 2023
1fb8cfc
chore: deduplicate @dhis2/ui to make "yarn build" work again
Mohammer5 Oct 3, 2023
6176296
feat(data element): add zod schema
Mohammer5 Oct 11, 2023
ce8b665
feat(de forms): implement validation with zod
Mohammer5 Oct 12, 2023
c0f4ab6
refactor: use initial values for value- & aggregation type
Mohammer5 Oct 16, 2023
278a2a8
refactor(de form): move all fields into the same file
Mohammer5 Oct 16, 2023
93c8a9d
fix(shortname field): use correct name
Mohammer5 Oct 25, 2023
2ae6e3d
fix(de name shortname inputs): validate max length (50 chars)
Mohammer5 Oct 30, 2023
092c881
feat(de name shortname inputs): add uniqueness validation
Mohammer5 Oct 30, 2023
185da16
feat(de schema): use name+shortName max length from schema
Mohammer5 Oct 30, 2023
fc7f96a
refactor(use has field value): add "id" field to query to suppress co…
Mohammer5 Oct 30, 2023
c1f803f
refactor(use has field value): add initial variables to create distin…
Mohammer5 Oct 30, 2023
5675c2f
refactor: rename useHasFieldValue to useIsFieldValueUnique
Mohammer5 Oct 30, 2023
1ffafb9
fix(de form async validation): prevent other field validation
Mohammer5 Oct 30, 2023
2abc128
fix(de form fields): validate only field that change, only on blur
Mohammer5 Oct 30, 2023
56a06f5
fix(de form fields): ignore "itself" when validating uniqueness
Mohammer5 Oct 30, 2023
18527eb
chore(de new test): blur fields after editing
Mohammer5 Nov 9, 2023
b874534
chore(de new test): modify resolver to me uniqueness check work
Mohammer5 Nov 9, 2023
d297d38
feat(de schema): use schema for max length for all text inputs
Mohammer5 Nov 9, 2023
d54b280
refactor: use lodash's memoize instead of custom implementation
Mohammer5 Nov 9, 2023
cfe25e6
refactor(de form fields): move formname field up
Mohammer5 Nov 9, 2023
aa62a70
feat(de name shortname inputs): get "validating" value only when that…
Mohammer5 Nov 9, 2023
f20be76
fix(de form cc field): blur on change
Mohammer5 Nov 20, 2023
e3d06d6
chore(de list spec): use regex for dynamic "years ago" time
Mohammer5 Nov 20, 2023
802e626
refactor(zod de schema): use generated types for aggregation- & value…
Mohammer5 Nov 20, 2023
5fcd7e0
chore(use load app): add link to GH comments to code comment
Mohammer5 Nov 21, 2023
0fde107
refactor(de form fields): remove superfluous validate prop from <Doma…
Mohammer5 Nov 21, 2023
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
109 changes: 71 additions & 38 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2023-10-25T11:43:32.223Z\n"
"PO-Revision-Date: 2023-10-25T11:43:32.223Z\n"
"POT-Creation-Date: 2023-11-16T09:17:45.955Z\n"
"PO-Revision-Date: 2023-11-16T09:17:45.955Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -78,14 +78,14 @@ msgstr "Aggregation level(s)"
msgid "Category combo"
msgstr "Category combo"

msgid "Default (none)"
msgstr "Default (none)"
msgid "None"
msgstr "None"

msgid "Filter legend sets"
msgstr "Filter legend sets"

msgid "None"
msgstr "None"
msgid "<No value>"
msgstr "<No value>"

msgid "Option set"
msgstr "Option set"
Expand Down Expand Up @@ -621,12 +621,64 @@ msgstr "Exit without saving"
msgid "Create data element"
msgstr "Create data element"

msgid "Loading custom attributes"
msgstr "Loading custom attributes"

msgid "Something went wrong with retrieving the custom attributes"
msgstr "Something went wrong with retrieving the custom attributes"

msgid "Basic information"
msgstr "Basic information"

msgid "Set up the information for this data element"
msgstr "Set up the information for this data element"

msgid "Disaggregation and Option sets"
msgstr "Disaggregation and Option sets"

msgid "Set up disaggregation and predefined options."
msgstr "Set up disaggregation and predefined options."

msgid "LegendSet"
msgstr "LegendSet"

msgid ""
"Visualize values for this data element in Analytics app. Multiple legendSet "
"can be applied."
msgstr ""
"Visualize values for this data element in Analytics app. Multiple legendSet "
"can be applied."

msgid "Aggregation levels"
msgstr "Aggregation levels"

msgid ""
"By default, the aggregation will start at the lowest assigned organisation "
"unit. If you for example select \"Chiefdom\", it means that \"Chiefdom\", "
"\"District\" and \"National\" aggregates use \"Chiefdom\" (the highest "
"aggregation level available) as the data source, and PHU data will not be "
"included. PHU will still be available for the PHU level, but not included "
"in the aggregations to the levels above."
msgstr ""
"By default, the aggregation will start at the lowest assigned organisation "
"unit. If you for example select \"Chiefdom\", it means that \"Chiefdom\", "
"\"District\" and \"National\" aggregates use \"Chiefdom\" (the highest "
"aggregation level available) as the data source, and PHU data will not be "
"included. PHU will still be available for the PHU level, but not included "
"in the aggregations to the levels above."

msgid "Custom fields for your DHIS2 instance"
msgstr "Custom fields for your DHIS2 instance"

msgid "Required"
msgstr "Required"

msgid "Cannot be longer than {{number}} character"
msgstr "Cannot be longer than {{number}} character"

msgid "The value is to long. You can use up to 255 characters"
msgstr "The value is to long. You can use up to 255 characters"

msgid "{{fieldLabel}} (required)"
msgstr "{{fieldLabel}} (required)"

Expand All @@ -648,6 +700,16 @@ msgstr "Url"
msgid "A web link that provides extra information"
msgstr "A web link that provides extra information"

msgid "Color and icon"
msgstr "Color and icon"

msgid ""
"A color and icon are helpful for identifying data elements in "
"information-dense screens."
msgstr ""
"A color and icon are helpful for identifying data elements in "
"information-dense screens."

msgid "Field mask"
msgstr "Field mask"

Expand All @@ -666,38 +728,6 @@ msgstr "An alternative name used in section or automatic data entry forms."
msgid "Store zero data values"
msgstr "Store zero data values"

msgid "Disaggregation and Option sets"
msgstr "Disaggregation and Option sets"

msgid "Set up disaggregation and predefined options."
msgstr "Set up disaggregation and predefined options."

msgid "LegendSet"
msgstr "LegendSet"

msgid ""
"Visualize values for this data element in Analytics app. Multiple legendSet "
"can be applied."
msgstr ""
"Visualize values for this data element in Analytics app. Multiple legendSet "
"can be applied."

msgid "Aggregation levels"
msgstr "Aggregation levels"

msgid "Custom fields for your DHIS2 instance"
msgstr "Custom fields for your DHIS2 instance"

msgid "Color and icon"
msgstr "Color and icon"

msgid ""
"A color and icon are helpful for identifying data elements in "
"information-dense screens."
msgstr ""
"A color and icon are helpful for identifying data elements in "
"information-dense screens."

msgid "A data element can either be aggregated or tracked data."
msgstr "A data element can either be aggregated or tracked data."

Expand Down Expand Up @@ -731,6 +761,9 @@ msgstr "Option set comment"
msgid "Choose a set of predefined comment for data entry"
msgstr "Choose a set of predefined comment for data entry"

msgid "This field requires a unique value, please choose another one"
msgstr "This field requires a unique value, please choose another one"

msgid "Metadata management"
msgstr "Metadata management"

Expand Down
7 changes: 7 additions & 0 deletions jest-setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { configure } from '@testing-library/react'
import '@testing-library/jest-dom'

// Not defined on nodejs
window.IntersectionObserver = jest.fn(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}))

beforeEach(() => {
configure({ testIdAttribute: 'data-test' })
})
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ interface SearchableSingleSelectPropTypes {
showEndLoader: boolean
loading: boolean
selected?: string
invalid?: boolean
error?: string
showAllOption?: boolean
onBlur?: () => void
onFocus?: () => void
}

export const SearchableSingleSelect = ({
invalid,
error,
loading,
placeholder,
Expand Down Expand Up @@ -121,6 +123,7 @@ export const SearchableSingleSelect = ({
// fetched the corresponding label yet. Therefore we don't want to pass in
// any value to the "selected" prop, as otherwise an error will be thrown
selected={hasSelectedInOptionList ? selected : ''}
error={invalid}
onChange={onChange}
placeholder={placeholder}
onBlur={onBlur}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface AggregationLevelMultiSelectProps {
onChange: ({ selected }: { selected: string[] }) => void
onRetryClick: () => void
inputWidth?: string
invalid?: boolean
placeholder?: string
selected?: string[]
showAllOption?: boolean
Expand All @@ -43,6 +44,7 @@ export const AggregationLevelMultiSelect = forwardRef(
{
onChange,
inputWidth,
invalid,
selected,
showAllOption,
placeholder = i18n.t('Aggregation level(s)'),
Expand All @@ -67,7 +69,7 @@ export const AggregationLevelMultiSelect = forwardRef(
onChange={({ selected }: { selected: string[] }) => {
onChange({ selected })
}}
error={!!optionsQuery.error}
error={!!optionsQuery.error || invalid}
selected={loading ? [] : selected}
loading={loading}
onBlur={onBlur}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type CategoryComboSelectProps = Omit<
export const CategoryComboSelect = forwardRef(function CategoryComboSelect(
{
onChange,
invalid,
placeholder = i18n.t('Category combo'),
required,
selected,
Expand All @@ -26,6 +27,7 @@ export const CategoryComboSelect = forwardRef(function CategoryComboSelect(
<ModelSingleSelect
ref={ref}
required={required}
invalid={invalid}
useInitialOptionQuery={useInitialOptionQuery}
useOptionsQuery={useOptionsQuery}
placeholder={placeholder}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type UseInitialOptionQuery = ({
export interface ModelSingleSelectProps {
onChange: ({ selected }: { selected: string }) => void
required?: boolean
invalid?: boolean
placeholder?: string
selected?: string
showAllOption?: boolean
Expand All @@ -71,6 +72,7 @@ export interface ModelSingleSelectProps {
export const ModelSingleSelect = forwardRef(function ModelSingleSelect(
{
onChange,
invalid,
placeholder = '',
required,
selected,
Expand Down Expand Up @@ -150,6 +152,7 @@ export const ModelSingleSelect = forwardRef(function ModelSingleSelect(

return (
<SearchableSingleSelect
invalid={invalid}
placeholder={placeholder}
showAllOption={showAllOption}
onChange={({ selected }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type OptionSetSelectProps = Omit<
export const OptionSetSelect = forwardRef(function OptionSetSelect(
{
onChange,
invalid,
placeholder = i18n.t('Option set'),
required,
selected,
Expand All @@ -26,6 +27,7 @@ export const OptionSetSelect = forwardRef(function OptionSetSelect(
<ModelSingleSelect
ref={ref}
required={required}
invalid={invalid}
useInitialOptionQuery={useInitialOptionQuery}
useOptionsQuery={useOptionsQuery}
placeholder={placeholder}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export * from './constants'
export * from './debounce'
export * from './models'
export * from './schemas'
export * from './useLoadApp'
export { useLoadApp } from './useLoadApp'
export type { ModelSchemas, Schema } from './useLoadApp'
export * from './errors'
export * from './user'
export * from './sections'
Expand Down
12 changes: 9 additions & 3 deletions src/lib/useLoadApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useSetSchemas } from './schemas'
import { useSetSystemSettings } from './systemSettings'
import { useSetCurrentUser } from './user'

export const schemaFields = [
const schemaFields = [
'authorities',
'displayName',
'name',
Expand All @@ -22,12 +22,12 @@ export const schemaFields = [
// readonly types (even though it should)
const schemaFieldsFilter = schemaFields.concat()

export type SchemaPropertyFields = (typeof schemaFieldsFilter)[number]
type SchemaPropertyFields = (typeof schemaFieldsFilter)[number]
export type Schema = PickSchemaProperties<SchemaPropertyFields>
export type ModelSchemas = ModelSchemasBase<Schema>

// same fields as headbar-request to hit the cache
export const userFields = [
const userFields = [
'authorities',
'avatar',
'email',
Expand All @@ -41,6 +41,12 @@ const userFieldsFilter = userFields.concat()
type UserPropertyFields = (typeof userFields)[number]
type CurrentUserResponse = Pick<CurrentUserBase, UserPropertyFields>

/**
* !!! WARNING !!!
* There's already a `CurrentUser` type exported from the generated schema
* types. We need to think about the name of this type, see:
* https://github.com/dhis2/maintenance-app-beta/pull/359#discussion_r1399267866
*/
export interface CurrentUser extends Omit<CurrentUserResponse, 'authorities'> {
authorities: Set<string> // use a set for faster lookup
}
Expand Down
16 changes: 13 additions & 3 deletions src/pages/dataElements/Edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ import { JsonPatchOperation } from '../../types'
import { Attribute, DataElement } from '../../types/generated'
import { createJsonPatchOperations } from './edit/'
import classes from './Edit.module.css'
import { DataElementFormFields, useCustomAttributesQuery } from './form'
import { FormValues } from './form/types'
import {
DataElementFormFields,
useCustomAttributesQuery,
useValidate,
} from './form'
import type { FormValues } from './form'

type FinalFormFormApi = FormApi<FormValues>

Expand Down Expand Up @@ -143,6 +147,7 @@ export const Component = () => {
const dataElementQuery = useDataElementQuery(dataElementId)
const customAttributesQuery = useCustomAttributesQuery()
const patchDirtyFields = usePatchDirtyFields()
const validate = useValidate()

async function onSubmit(values: FormValues, form: FinalFormFormApi) {
const errors = await patchDirtyFields({
Expand Down Expand Up @@ -170,7 +175,12 @@ export const Component = () => {
queryResponse={customAttributesQuery}
label={i18n.t('Custom attributes')}
>
<Form onSubmit={onSubmit} initialValues={initialValues}>
<Form
validateOnBlur
onSubmit={onSubmit}
validate={validate}
initialValues={initialValues}
>
{({ handleSubmit, submitting, submitError }) => (
<form onSubmit={handleSubmit}>
<FormContents
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dataElements/List.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import dataElementsMock from '../../__mocks__/gists/dataElementsMock.json'
import filteredDataElementsMock from '../../__mocks__/gists/filteredDataElementsMock.json'
import dataElementSchemaMock from '../../__mocks__/schema/dataElementsSchema.json'
import { useModelListView } from '../../components/sectionList/listView'

Check warning on line 13 in src/pages/dataElements/List.spec.tsx

View workflow job for this annotation

GitHub Actions / lint

'useModelListView' is defined but never used
import { SECTIONS_MAP } from '../../lib'
import { useSchemaStore } from '../../lib/schemas/schemaStore'
import { ModelSchemas } from '../../lib/useLoadApp'
Expand Down Expand Up @@ -75,7 +75,7 @@
const { id } = dataElementsMock.result[0]
const firstRow = getByTestId(`section-list-row-${id}`)
expect(firstRow).toHaveTextContent(
'Accute Flaccid Paralysis (Deaths < 5 yrs)AggregateNumber6 years agoPublic can edit'
/Accute Flaccid Paralysis \(Deaths < 5 yrs\)AggregateNumber\d+ years agoPublic can edit/
)
})
it('should display all the columns', async () => {
Expand Down
Loading
Loading