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

EES-5541 - Boundary level per dataset form changes #5308

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,118 +1,134 @@
import ChartBuilderSaveActions from '@admin/pages/release/datablocks/components/chart/ChartBuilderSaveActions';
import { useChartBuilderFormsContext } from '@admin/pages/release/datablocks/components/chart/contexts/ChartBuilderFormsContext';
import ChartBoundaryLevelsForm, {
ChartBoundaryLevelsFormValues,
} from '@admin/pages/release/datablocks/components/chart/ChartBoundaryLevelsForm';
import { ChartOptions } from '@admin/pages/release/datablocks/components/chart/reducers/chartBuilderReducer';
import Effect from '@common/components/Effect';
import FormProvider from '@common/components/form/FormProvider';
import Form from '@common/components/form/Form';
import FormFieldSelect from '@common/components/form/FormFieldSelect';
import {
AxisConfiguration,
MapConfig,
} from '@common/modules/charts/types/chart';
import { LegendConfiguration } from '@common/modules/charts/types/legend';
import createDataSetCategories from '@common/modules/charts/util/createDataSetCategories';
import expandDataSet from '@common/modules/charts/util/expandDataSet';
import generateDataSetKey from '@common/modules/charts/util/generateDataSetKey';
import getDataSetCategoryConfigs from '@common/modules/charts/util/getDataSetCategoryConfigs';
import { FullTableMeta } from '@common/modules/table-tool/types/fullTable';
import { TableDataResult } from '@common/services/tableBuilderService';
import parseNumber from '@common/utils/number/parseNumber';
import Yup from '@common/validation/yup';
import merge from 'lodash/merge';
import React, { ReactNode, useCallback } from 'react';

const formId = 'chartBoundaryLevelsConfigurationForm';

interface FormValues {
boundaryLevel?: number;
}
import { isEqual } from 'lodash';
import React, { ReactNode, useCallback, useMemo } from 'react';
import generateDataSetLabel from './utils/generateDataSetLabel';

interface Props {
buttons?: ReactNode;
axisMajor: AxisConfiguration;
data: TableDataResult[];
legend: LegendConfiguration;
map?: MapConfig;
meta: FullTableMeta;
options: ChartOptions;
onChange: (values: ChartOptions) => void;
onSubmit: (chartOptions: ChartOptions) => void;
onChange: (values: Partial<ChartBoundaryLevelsFormValues>) => void;
onSubmit: (values: ChartBoundaryLevelsFormValues) => void;
}

export default function ChartBoundaryLevelsConfiguration({
buttons,
axisMajor,
data,
legend,
map,
meta,
options,
onChange,
onSubmit,
}: Props) {
const { updateForm, submitForms } = useChartBuilderFormsContext();

const normalizeValues = useCallback(
(values: FormValues): ChartOptions => {
(
values: Partial<ChartBoundaryLevelsFormValues>,
): ChartBoundaryLevelsFormValues => {
// Use `merge` as we want to avoid potential undefined
// values from overwriting existing values
return merge({}, options, values, {
return {
boundaryLevel: values.boundaryLevel
? parseNumber(values.boundaryLevel)
: undefined,
});
dataSetConfigs:
values.dataSetConfigs?.map(({ boundaryLevel, dataSet }) => ({
boundaryLevel: parseNumber(boundaryLevel),
dataSet,
})) ?? [],
};
},
[options],
[],
);

const handleSubmit = useCallback(
(values: ChartBoundaryLevelsFormValues) => {
onSubmit(normalizeValues(values));
},
[onSubmit, normalizeValues],
);
const handleChange = useCallback(
(values: FormValues) => {
(values: Partial<ChartBoundaryLevelsFormValues>) => {
onChange(normalizeValues(values));
},
[normalizeValues, onChange],
[onChange, normalizeValues],
);

return (
<FormProvider
enableReinitialize
initialValues={{ boundaryLevel: options.boundaryLevel }}
validationSchema={Yup.object<FormValues>({
boundaryLevel: Yup.number()
.transform(value => (Number.isNaN(value) ? undefined : value))
.nullable()
.oneOf(meta.boundaryLevels.map(level => level.id))
.required('Choose a boundary level'),
})}
>
{({ formState, watch }) => {
const values = watch();
return (
<Form
id={formId}
onSubmit={async () => {
onSubmit(normalizeValues(values));
await submitForms();
}}
>
<Effect value={values} onChange={handleChange} />
<Effect
value={{
formKey: 'boundaryLevels',
isValid: formState.isValid,
submitCount: formState.submitCount,
}}
onChange={updateForm}
onMount={updateForm}
/>
<FormFieldSelect<FormValues>
label="Boundary level"
hint="Select a version of geographical data to use"
name="boundaryLevel"
order={[]}
options={[
{
label: 'Please select',
value: '',
},
...meta.boundaryLevels.map(({ id, label }) => ({
value: id,
label,
})),
]}
/>
const { dataSetConfigs, boundaryLevel } =
useMemo<ChartBoundaryLevelsFormValues>(() => {
const dataSetCategories = createDataSetCategories({
axisConfiguration: {
...axisMajor,
groupBy: 'locations',
},
data,
meta,
});

const dataSetCategoryConfigs = getDataSetCategoryConfigs({
dataSetCategories,
legendItems: legend.items,
meta,
deprecatedDataClassification: options.dataClassification,
deprecatedDataGroups: options.dataGroups,
});

return {
boundaryLevel: options.boundaryLevel,
dataSetConfigs: dataSetCategoryConfigs.map(({ rawDataSet }) => ({
dataSet: rawDataSet,
boundaryLevel: map?.dataSetConfigs.find(config =>
isEqual(config.dataSet, rawDataSet),
)?.boundaryLevel,
})),
};
}, [axisMajor, data, meta, legend.items, map, options]);

<ChartBuilderSaveActions
formId={formId}
formKey="boundaryLevels"
disabled={formState.isSubmitting}
>
{buttons}
</ChartBuilderSaveActions>
</Form>
);
}}
</FormProvider>
const mappedDataSetConfigs = useMemo(() => {
return Object.values(dataSetConfigs).map(dataSetConfig => {
const expandedDataSet = expandDataSet(dataSetConfig.dataSet, meta);
const label = generateDataSetLabel(expandedDataSet);
const key = generateDataSetKey(dataSetConfig.dataSet);

return {
label,
key,
};
});
}, [meta, dataSetConfigs]);

return (
<ChartBoundaryLevelsForm
hasDataSetBoundaryLevels={false}
buttons={buttons}
boundaryLevelOptions={meta.boundaryLevels.map(({ id, label }) => ({
label,
value: id,
}))}
dataSetRows={mappedDataSetConfigs}
initialValues={{ boundaryLevel, dataSetConfigs }}
onChange={handleChange}
onSubmit={handleSubmit}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import ChartBuilderSaveActions from '@admin/pages/release/datablocks/components/chart/ChartBuilderSaveActions';
import { useChartBuilderFormsContext } from '@admin/pages/release/datablocks/components/chart/contexts/ChartBuilderFormsContext';
import Effect from '@common/components/Effect';
import Form from '@common/components/form/Form';
import FormFieldSelect from '@common/components/form/FormFieldSelect';
import FormProvider from '@common/components/form/FormProvider';
import { MapDataSetConfig } from '@common/modules/charts/types/chart';
import Yup from '@common/validation/yup';
import React, { ReactNode, useMemo } from 'react';
import { AnyObject, NumberSchema, ObjectSchema } from 'yup';

const formId = 'chartBoundaryLevelsConfigurationForm';

export interface ChartBoundaryLevelsFormValues {
boundaryLevel?: number;
dataSetConfigs: Omit<MapDataSetConfig, 'dataGrouping'>[];
}

interface Props {
hasDataSetBoundaryLevels?: boolean; // feature flag (remove once)
buttons?: ReactNode;
boundaryLevelOptions: { label: string; value: number }[];
initialValues?: ChartBoundaryLevelsFormValues;
dataSetRows: { key: string; label: string }[];
onChange: (values: Partial<ChartBoundaryLevelsFormValues>) => void;
onSubmit: (values: ChartBoundaryLevelsFormValues) => void;
}

export default function ChartBoundaryLevelsForm({
hasDataSetBoundaryLevels = true,
buttons,
boundaryLevelOptions,
dataSetRows,
initialValues,
onChange,
onSubmit,
}: Props) {
const { updateForm, submitForms } = useChartBuilderFormsContext();

const validationSchema = useMemo<
ObjectSchema<ChartBoundaryLevelsFormValues>
>(() => {
return Yup.object({
boundaryLevel: Yup.number()
.transform(value => (Number.isNaN(value) ? undefined : value))
.nullable()
.oneOf(boundaryLevelOptions.map(({ value }) => value))
.required('Choose a boundary level'),
dataSetConfigs: Yup.array()
.of(
Yup.object({
boundaryLevel: Yup.mixed<number | ''>().test(
'dataset-boundary-is-number-or-empty',
'Must be a number or an empty string',
value => !Number.isNaN(value) || value === '',
) as NumberSchema<number, AnyObject, undefined, ''>,
dataSet: Yup.object({
filters: Yup.array().required(),
}).required(),
}),
)
.required(),
});
}, [boundaryLevelOptions]);

return (
<FormProvider
validationSchema={validationSchema}
initialValues={initialValues}
>
{({ formState, watch }) => {
const values = watch();
return (
<Form<ChartBoundaryLevelsFormValues>
id={formId}
onSubmit={onSubmit}
onChange={onChange}
>
<Effect
value={{
formKey: 'boundaryLevels',
isValid: formState.isValid,
submitCount: 0,
}}
onChange={updateForm}
onMount={updateForm}
/>
<FormFieldSelect<ChartBoundaryLevelsFormValues>
label={
hasDataSetBoundaryLevels
? 'Default boundary level'
: 'Boundary level'
}
hint={`Select a version of geographical data to use${
hasDataSetBoundaryLevels
? "across any data sets that don't have a specific one set"
: ''
}`}
name="boundaryLevel"
order={[]}
options={[
{
label: 'Please select',
value: '',
},
...boundaryLevelOptions,
]}
/>
{hasDataSetBoundaryLevels && dataSetRows.length > 1 && (
<>
<h4>Set boundary levels per data set</h4>
<table data-testid="chart-dataset-boundary-levels">
<thead>
<tr>
<th>Data set</th>
<th>Boundary</th>
</tr>
</thead>
<tbody>
{dataSetRows.map(({ key, label }, index) => {
return (
<tr key={key}>
<td>{label}</td>
<td>
<FormFieldSelect
label={`Boundary level for dataset: ${label}`}
hideLabel
name={`dataSetConfigs[${index}].boundaryLevel`}
order={[]}
options={[
{
label: 'Use default',
value: '',
},
...boundaryLevelOptions,
]}
/>
</td>
</tr>
);
})}
</tbody>
</table>
</>
)}

<ChartBuilderSaveActions
formId={formId}
formKey="boundaryLevels"
disabled={formState.isSubmitting}
onClick={async () => {
onSubmit(values);
await submitForms();
}}
>
{buttons}
</ChartBuilderSaveActions>
</Form>
);
}}
</FormProvider>
);
}
Loading
Loading