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

LF-4309 Complete form breaks when not completed fast enough allowing page to time out or something #3324

Merged
21 changes: 21 additions & 0 deletions packages/api/src/models/soilAmendmentTaskModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ class SoilAmendmentTaskModel extends Model {
return 'task_id';
}

async $beforeUpdate(queryContext) {
await super.$beforeUpdate(queryContext);

if (this.method_id) {
const { key } = await soilAmendmentMethodModel
.query(queryContext.transaction)
.findById(this.method_id)
.select('key')
.first();

if (key !== 'OTHER') {
this.other_application_method = null;
}

if (key !== 'FURROW_HOLE') {
this.furrow_hole_depth = null;
this.furrow_hole_depth_unit = null;
}
}
}

// Optional JSON schema. This is not the database schema! Nothing is generated
// based on this. This is only used for validation. Whenever a model instance
// is created it is checked against this schema. http://json-schema.org/.
Expand Down
5 changes: 5 additions & 0 deletions packages/webapp/src/components/Form/Unit/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const Unit = ({
hasLeaf,
autoConversion,
onInputChange,
shouldUnregister,
...props
}) => {
const { t } = useTranslation(['translation', 'common']);
Expand Down Expand Up @@ -165,6 +166,7 @@ const Unit = ({
<Controller
control={control}
name={displayUnitName}
shouldUnregister={shouldUnregister}
render={({ field: { onBlur, value, ref } }) => (
<Select
data-cy="unit-select"
Expand Down Expand Up @@ -202,6 +204,7 @@ const Unit = ({
valueAsNumber: true,
max: { value: getMax(), message: t('UNIT.VALID_VALUE') + max },
min: { value: 0, message: t('UNIT.VALID_VALUE') + max },
shouldUnregister,
})}
data-testid={`${testId}-hiddeninput`}
/>
Expand Down Expand Up @@ -283,6 +286,8 @@ Unit.propTypes = {
onBlur: PropTypes.func,
/** testId used for component testing */
'data-testid': PropTypes.string,
/** react hook form shouldUnregister - unmounting input removes value */
shouldUnregister: PropTypes.bool,
};

export default Unit;
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const SoilAmendmentProductCard = ({
label={t('ADD_TASK.SOIL_AMENDMENT_VIEW.OTHER_PURPOSE')}
name={OTHER_PURPOSE}
disabled={isReadOnly}
hookFormRegister={register(OTHER_PURPOSE)}
hookFormRegister={register(OTHER_PURPOSE, { shouldUnregister: true })}
optional
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ const PureSoilAmendmentTask = ({
defaultValue={undefined} // TODO
system={system}
placeholder={t('ADD_TASK.SOIL_AMENDMENT_VIEW.FURROW_HOLE_DEPTH_PLACEHOLDER')}
shouldUnregister={true}
/>
</>
)}
Expand All @@ -191,7 +192,7 @@ const PureSoilAmendmentTask = ({
label={t('ADD_TASK.SOIL_AMENDMENT_VIEW.OTHER_METHOD')}
name={OTHER_APPLICATION_METHOD}
disabled={disabled}
hookFormRegister={register(OTHER_APPLICATION_METHOD)}
hookFormRegister={register(OTHER_APPLICATION_METHOD, { shouldUnregister: true })}
optional
placeholder={t('ADD_TASK.SOIL_AMENDMENT_VIEW.OTHER_METHOD_PLACEHOLDER')}
/>
Expand Down
52 changes: 4 additions & 48 deletions packages/webapp/src/containers/Task/saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,7 @@ import {
onLoadingHarvestUseTypeStart,
} from '../harvestUseTypeSlice';
import { managementPlanWithCurrentLocationEntitiesSelector } from './TaskCrops/managementPlansWithLocationSelector';
import {
formatSoilAmendmentTaskToDBStructure,
formatSoilAmendmentProductToDBStructure,
} from '../../util/task';
import { api } from '../../store/api/apiSlice';
import { formatSoilAmendmentProductToDBStructure } from '../../util/task';

const taskTypeEndpoint = [
'cleaning_task',
Expand Down Expand Up @@ -485,20 +481,11 @@ const getIrrigationTaskBody = (data, endpoint, managementPlanWithCurrentLocation
);
};

const getSoilAmendmentTaskBody = (
data,
endpoint,
managementPlanWithCurrentLocationEntities,
{ purposes, methods },
) => {
const getSoilAmendmentTaskBody = (data, endpoint, managementPlanWithCurrentLocationEntities) => {
return {
...getPostTaskBody(data, endpoint, managementPlanWithCurrentLocationEntities),
soil_amendment_task: formatSoilAmendmentTaskToDBStructure(data.soil_amendment_task, {
methods,
}),
soil_amendment_task_products: formatSoilAmendmentProductToDBStructure(
data.soil_amendment_task_products,
{ purposes },
),
};
};
Expand All @@ -519,15 +506,13 @@ const getPostTaskReqBody = (
task_translation_key,
isCustomTask,
managementPlanWithCurrentLocationEntities,
taskTypeSpecificData,
) => {
if (isCustomTask)
return getPostTaskBody(data, endpoint, managementPlanWithCurrentLocationEntities);
return taskTypeGetPostTaskBodyFunctionMap[task_translation_key](
data,
endpoint,
managementPlanWithCurrentLocationEntities,
taskTypeSpecificData,
);
};

Expand All @@ -541,17 +526,6 @@ export function* createTaskSaga({ payload }) {
const { task_translation_key, farm_id: task_farm_id } = yield select(
taskTypeSelector(data.task_type_id),
);
const taskTypeSpecificData = {};
if (task_translation_key === 'SOIL_AMENDMENT_TASK') {
// Access cached data
// https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks#accessing-cached-data--request-status
const purposes = yield select((state) =>
api.endpoints.getSoilAmendmentPurposes.select()(state),
);
taskTypeSpecificData.purposes = purposes.data;
const methods = yield select((state) => api.endpoints.getSoilAmendmentMethods.select()(state));
taskTypeSpecificData.methods = methods.data;
}
const header = getHeader(user_id, farm_id);
const isCustomTask = !!task_farm_id;
const isHarvest = task_translation_key === 'HARVEST_TASK';
Expand All @@ -574,7 +548,6 @@ export function* createTaskSaga({ payload }) {
task_translation_key,
isCustomTask,
managementPlanWithCurrentLocationEntities,
taskTypeSpecificData,
),
header,
);
Expand Down Expand Up @@ -720,18 +693,12 @@ const getCompleteIrrigationTaskBody = (task_translation_key) => (data) => {
);
};

const getCompleteSoilAmendmentTaskBody = (data, taskTypeSpecificData) => {
const soilAmendmentTask = formatSoilAmendmentTaskToDBStructure(
data.soil_amendment_task,
taskTypeSpecificData,
);
const getCompleteSoilAmendmentTaskBody = (data) => {
const soilAmendmentTaskProducts = formatSoilAmendmentProductToDBStructure(
data.soil_amendment_task_products,
taskTypeSpecificData,
);
return {
...data.taskData,
soil_amendment_task: soilAmendmentTask,
soil_amendment_task_products: soilAmendmentTaskProducts,
};
};
Expand All @@ -752,19 +719,8 @@ export function* completeTaskSaga({ payload: { task_id, data, returnPath } }) {
const { task_translation_key, isCustomTaskType } = data;
const header = getHeader(user_id, farm_id);
const endpoint = isCustomTaskType ? 'custom_task' : task_translation_key.toLowerCase();
const taskTypeSpecificData = {};
if (task_translation_key === 'SOIL_AMENDMENT_TASK') {
// Access cached data
// https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks#accessing-cached-data--request-status
const purposes = yield select((state) =>
api.endpoints.getSoilAmendmentPurposes.select()(state),
);
taskTypeSpecificData.purposes = purposes.data;
const methods = yield select((state) => api.endpoints.getSoilAmendmentMethods.select()(state));
taskTypeSpecificData.methods = methods.data;
}
const taskData = taskTypeGetCompleteTaskBodyFunctionMap[task_translation_key]
? taskTypeGetCompleteTaskBodyFunctionMap[task_translation_key](data, taskTypeSpecificData)
? taskTypeGetCompleteTaskBodyFunctionMap[task_translation_key](data)
: data.taskData;

try {
Expand Down
77 changes: 11 additions & 66 deletions packages/webapp/src/util/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

import { SoilAmendmentMethod, SoilAmendmentPurpose } from '../store/api/types';
import { getUnitOptionMap } from './convert-units/getUnitOptionMap';

interface UnitOption {
label: string;
value: string;
Expand Down Expand Up @@ -61,39 +58,29 @@ type FormSoilAmendmentTaskProduct = {
};

type DBSoilAmendmentTask = {
furrow_hole_depth?: number;
furrow_hole_depth_unit?: string;
other_application_method?: string;
[key: string]: any;
};

type FormSoilAmendmentTask = {
furrow_hole_depth?: number;
furrow_hole_depth_unit?: UnitOption;
other_application_method?: string;
[key: string]: any;
};

type DBTask = {
soil_amendment_task_products: DBSoilAmendmentTaskProduct[];
[key: string]: any;
};

type FormTask = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if I would have undid the updated naming since soil_amendment_task has meaning and does not have property of soil_amendment_task_products.

type FormSoilAmendmentTask = {
soil_amendment_task_products: FormSoilAmendmentTaskProduct[];
[key: string]: any;
};

// Type guard
function isFormSoilAmendmentTask(task: DBTask | FormTask): task is FormTask {
function isFormSoilAmendmentTask(
task: DBSoilAmendmentTask | FormSoilAmendmentTask,
): task is FormSoilAmendmentTask {
return (
task.soil_amendment_task_products?.[0] && 'purposes' in task.soil_amendment_task_products[0]
);
}

export const formatSoilAmendmentTaskToFormStructure = (task: DBTask | FormTask): FormTask => {
export const formatSoilAmendmentTaskToFormStructure = (
task: DBSoilAmendmentTask | FormSoilAmendmentTask,
): FormSoilAmendmentTask => {
if (isFormSoilAmendmentTask(task)) {
return task as FormTask;
return task as FormSoilAmendmentTask;
}

const taskClone = structuredClone(task);
Expand Down Expand Up @@ -138,10 +125,9 @@ export const formatSoilAmendmentTaskToFormStructure = (task: DBTask | FormTask):
const formatPurposeIdsToRelationships = (
purposeIds: number[],
otherPurpose: string | undefined,
otherPurposeId: number,
): PurposeRelationship[] => {
return purposeIds.map((purpose_id) => {
return { purpose_id, other_purpose: purpose_id === otherPurposeId ? otherPurpose : undefined };
return { purpose_id, other_purpose: otherPurpose };
});
};

Expand All @@ -152,15 +138,10 @@ type RemainingFormSATProductKeys = keyof Omit<

export const formatSoilAmendmentProductToDBStructure = (
soilAmendmentTaskProducts: FormSoilAmendmentTaskProduct[] | undefined,
{ purposes }: { purposes: SoilAmendmentPurpose[] },
): DBSoilAmendmentTaskProduct[] | undefined => {
if (!soilAmendmentTaskProducts) {
return undefined;
}
const otherPurposeId = purposes?.find(({ key }) => key === 'OTHER')?.id;
if (!otherPurposeId) {
throw Error('id for OTHER purpose does not exist');
}
return soilAmendmentTaskProducts.map((formTaskProduct) => {
const { purposes: purposeIds, other_purpose, is_weight, ...rest } = formTaskProduct;

Expand All @@ -184,53 +165,17 @@ export const formatSoilAmendmentProductToDBStructure = (
application_rate_volume_unit: !is_weight
? (rest.application_rate_volume_unit as UnitOption)?.value
: undefined,
purpose_relationships: formatPurposeIdsToRelationships(
purposeIds,
other_purpose,
otherPurposeId,
),
purpose_relationships: formatPurposeIdsToRelationships(purposeIds, other_purpose),
};
});
};

export const formatSoilAmendmentTaskToDBStructure = (
soilAmendmentTask: FormSoilAmendmentTask | undefined,
{ methods }: { methods: SoilAmendmentMethod[] },
): DBSoilAmendmentTask | undefined => {
if (!soilAmendmentTask) {
return undefined;
}
const {
method_id,
furrow_hole_depth,
furrow_hole_depth_unit,
other_application_method,
...rest
} = soilAmendmentTask;
const furrowHoleId = methods?.find(({ key }) => key === 'FURROW_HOLE')?.id;
const otherMethodId = methods?.find(({ key }) => key === 'OTHER')?.id;
if (!furrowHoleId) {
throw Error('id for FURROW_HOLE method does not exist');
}
if (!otherMethodId) {
throw Error('id for OTHER method does not exist');
}
return {
...rest,
method_id,
furrow_hole_depth: method_id === furrowHoleId ? furrow_hole_depth : undefined,
furrow_hole_depth_unit: method_id === furrowHoleId ? furrow_hole_depth_unit?.value : undefined,
other_application_method:
soilAmendmentTask.method_id === otherMethodId ? other_application_method : undefined,
};
};

export const formatTaskReadOnlyDefaultValues = (task: {
taskType?: { task_translation_key: string };
[key: string]: any;
}) => {
if (task.taskType?.task_translation_key === 'SOIL_AMENDMENT_TASK') {
return formatSoilAmendmentTaskToFormStructure(task as DBTask);
return formatSoilAmendmentTaskToFormStructure(task as DBSoilAmendmentTask);
}

return structuredClone(task);
Expand Down