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
4 changes: 1 addition & 3 deletions src/features/attachments/AttachmentsStorePlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,8 @@ export class AttachmentsStorePlugin extends NodeDataPlugin<AttachmentsStorePlugi
tagsToRemove.map((tag) => removeTag({ dataElementId: attachment.data.id, tagToRemove: tag })),
);
}
fulfill(action);
optimisticallyUpdateDataElement(dataElementId, (dataElement) => ({ ...dataElement, tags }));

return;
fulfill(action);
} catch (error) {
reject(action, error);
toast(lang('form_filler.file_uploader_validation_error_update'), { type: 'error' });
Expand Down
21 changes: 8 additions & 13 deletions src/features/formData/FormDataWrite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
IgnoredValidators,
} from 'src/features/validation';
import { useIsUpdatingInitialValidations } from 'src/features/validation/backendValidation/backendValidationQuery';
import { Validation } from 'src/features/validation/validationContext';
import { useAsRef } from 'src/hooks/useAsRef';
import { useWaitForState } from 'src/hooks/useWaitForState';
import { doPatchMultipleFormData } from 'src/queries/queries';
Expand Down Expand Up @@ -103,6 +104,8 @@ function useFormDataSaveMutation() {
const isStateless = useApplicationMetadata().isStatelessApp;
const debounce = useSelector((s) => s.debounce);
const selectedPartyId = useSelectedParty()?.partyId;
const updateBackendValidations = Validation.useLaxUpdateBackendValidations();

const waitFor = useWaitForState<
{ prev: { [dataType: string]: object }; next: { [dataType: string]: object } },
FormDataContext
Expand Down Expand Up @@ -352,6 +355,9 @@ function useFormDataSaveMutation() {
if (result) {
updateQueryCache(result);
saveFinished(result);
if (updateBackendValidations !== ContextNotProvided) {
updateBackendValidations(undefined, { incremental: result.validationIssues });
}
} else {
cancelSave();
}
Expand Down Expand Up @@ -621,9 +627,8 @@ const useWaitForSave = () => {
return Promise.resolve(undefined);
}

return await waitFor((state, setReturnValue) => {
return await waitFor((state) => {
if (state === ContextNotProvided) {
setReturnValue(undefined);
return true;
}

Expand All @@ -632,12 +637,7 @@ const useWaitForSave = () => {
return false;
}

if (hasUnsavedChanges(state)) {
return false;
}

setReturnValue(state.validationIssues);
return true;
return !hasUnsavedChanges(state);
});
},
[requestSave, dataTypes, waitFor],
Expand Down Expand Up @@ -1155,11 +1155,6 @@ export const FD = {
*/
useRemoveValueFromList: () => useStaticSelector((s) => s.removeValueFromList),

/**
* Returns the latest validation issues from the backend, from the last time the form data was saved.
*/
useLastSaveValidationIssues: () => useSelector((s) => s.validationIssues),

useRemoveIndexFromList: () => useStaticSelector((s) => s.removeIndexFromList),

useGetDataTypeForElementId: () => {
Expand Down
6 changes: 1 addition & 5 deletions src/features/formData/FormDataWriteStateMachine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ type FormDataState = {
// as a way to immediately save the data model to the server, for example before locking the data model.
manualSaveRequested: boolean;

// This contains the validation issues we receive from the server last time we saved the data model.
validationIssues: BackendValidationIssueGroups | undefined;

// This is used to track which component is currently blocking the auto-saving feature. If this is set to a string
// value, auto-saving will be disabled, even if the autoSaving flag is set to true. This is useful when you want
// to temporarily disable auto-saving, for example when clicking a CustomButton and waiting for the server to
Expand Down Expand Up @@ -260,9 +257,8 @@ function makeActions(
}

function processChanges(state: FormDataContext, toProcess: FDSaveFinished) {
const { validationIssues, savedData, newDataModels, instance } = toProcess;
const { savedData, newDataModels, instance } = toProcess;
state.manualSaveRequested = false;
state.validationIssues = validationIssues;

if (instance) {
changeInstance(() => instance);
Expand Down
11 changes: 0 additions & 11 deletions src/features/validation/backendValidation/BackendValidation.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { useEffect } from 'react';

import { DataModels } from 'src/features/datamodel/DataModelsProvider';
import { FD } from 'src/features/formData/FormDataWrite';
import { useBackendValidationQuery } from 'src/features/validation/backendValidation/backendValidationQuery';
import {
mapBackendIssuesToTaskValidations,
mapBackendValidationsToValidatorGroups,
mapValidatorGroupsToDataModelValidations,
useShouldValidateInitial,
} from 'src/features/validation/backendValidation/backendValidationUtils';
import { useUpdateIncrementalValidations } from 'src/features/validation/backendValidation/useUpdateIncrementalValidations';
import { Validation } from 'src/features/validation/validationContext';

export function BackendValidation() {
const updateBackendValidations = Validation.useUpdateBackendValidations();
const defaultDataElementId = DataModels.useDefaultDataElementId();
const lastSaveValidations = FD.useLastSaveValidationIssues();
const enabled = useShouldValidateInitial();
const { data: initialValidations, isFetching: isFetchingInitial } = useBackendValidationQuery({ enabled });
const updateIncrementalValidations = useUpdateIncrementalValidations();

// Initial validation
useEffect(() => {
Expand All @@ -30,12 +26,5 @@ export function BackendValidation() {
}
}, [defaultDataElementId, initialValidations, isFetchingInitial, updateBackendValidations]);

// Incremental validation: Update validators and propagate changes to validation context
useEffect(() => {
if (lastSaveValidations) {
updateIncrementalValidations(lastSaveValidations);
}
}, [lastSaveValidations, updateIncrementalValidations]);

return null;
}
13 changes: 6 additions & 7 deletions src/features/validation/validationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,12 @@ function useWaitForValidation(): WaitForValidation {

// Wait until we've saved changed to backend, and we've processed the backend validations we got from that save
await waitForNodesReady();
const validationsFromSave = await waitForSave(forceSave);
await waitForSave(forceSave);
// If validationsFromSave is not defined, we check if initial validations are done processing
await waitForState(async (state) => {
const { isFetching, cachedInitialValidations } = getCachedInitialValidations();
const incrementalMatch = deepEqual(state.processedLast.incremental, validationsFromSave);
const initialMatch = deepEqual(state.processedLast.initial, cachedInitialValidations);

const validationsReady = incrementalMatch && initialMatch && !isFetching;
const validationsReady = initialMatch && !isFetching;

if (validationsReady) {
await waitForNodesToValidate(state.processedLast);
Expand Down Expand Up @@ -266,9 +264,9 @@ function UpdateShowAllErrors() {
* Call /validate manually whenever a data element changes to get updated non-incremental validations.
* This should happen whenever any data element changes, so we should check the lastChanged on each data element,
* or if new data elements are added. Single-patch does not return updated instance data so for now we need to
* also check useLastSaveValidationIssues which will change on each patch.
* also check useGetIncrementalValidations which will change on each patch.
*/
const lastSaved = FD.useLastSaveValidationIssues();
const lastSaved = Validation.useGetIncrementalValidations();
const instanceDataChanges = useInstanceDataQuery({
select: (instance) => instance.data.map(({ id, lastChanged }) => ({ id, lastChanged })),
}).data;
Expand Down Expand Up @@ -362,7 +360,8 @@ export const Validation = {
useValidating: () => useSelector((state) => state.validating!),
useUpdateDataModelValidations: () => useStaticSelector((state) => state.updateDataModelValidations),
useUpdateBackendValidations: () => useStaticSelector((state) => state.updateBackendValidations),

useLaxUpdateBackendValidations: () => useLaxShallowSelector((state) => state.updateBackendValidations),
useGetIncrementalValidations: () => useSelector((state) => state.processedLast.incremental),
useFullState: <U,>(selector: (state: ValidationContext & Internals) => U): U =>
useMemoSelector((state) => selector(state)),
useGetProcessedLast: () => {
Expand Down
12 changes: 6 additions & 6 deletions src/layout/FileUploadWithTag/EditWindowComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ export function EditWindowComponent({

const hasErrors = hasValidationErrors(attachmentValidations);

const formatSelectedValue = (tags: string[]): string | SuggestionItem | undefined => {
function formatSelectedValue(tags: string[]): string | SuggestionItem | undefined {
const tag = tags[0];
if (!tag) {
return undefined;
}
const option = options?.find((o) => o.value === tag);
return option ? { value: option.value, label: langAsString(option.label) } : tag;
};
}

const handleSave = async () => {
async function handleSave() {
if (!uploadedAttachment) {
return;
}
Expand All @@ -78,15 +78,15 @@ export function EditWindowComponent({
}
setEditIndex(-1);
await onAttachmentSave(baseComponentId, uploadedAttachment.data.id);
};
}

const setAttachmentTag = async (tags: string[]) => {
async function setAttachmentTag(tags: string[]) {
if (!isAttachmentUploaded(attachment)) {
return;
}

await updateAttachment({ attachment, nodeId, tags });
};
}

const isLoading = attachment.updating || !attachment.uploaded || isFetching || options?.length === 0;
const uniqueId = isAttachmentUploaded(attachment) ? attachment.data.id : attachment.data.temporaryId;
Expand Down
Loading