diff --git a/src/openapi.yaml b/src/openapi.yaml
index ef9ad80413..1d045b7eed 100644
--- a/src/openapi.yaml
+++ b/src/openapi.yaml
@@ -1133,6 +1133,7 @@ paths:
required: true
tags:
- forms
+ - form-definitions
security:
- tokenAuth: []
- cookieAuth: []
diff --git a/src/openforms/forms/api/filters.py b/src/openforms/forms/api/filters.py
index 63baba2865..627d9b837d 100644
--- a/src/openforms/forms/api/filters.py
+++ b/src/openforms/forms/api/filters.py
@@ -1,3 +1,5 @@
+import warnings
+
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
@@ -56,5 +58,9 @@ def filter_queryset(self, queryset):
_or |= q_expr
if _or:
+ warnings.warn(
+ "Using OR filters is deprecated and will be removed starting with Open Forms 3.0",
+ DeprecationWarning,
+ )
queryset = queryset.filter(_or)
return super().filter_queryset(queryset)
diff --git a/src/openforms/forms/api/viewsets.py b/src/openforms/forms/api/viewsets.py
index 99aef7fe27..5a543cf68a 100644
--- a/src/openforms/forms/api/viewsets.py
+++ b/src/openforms/forms/api/viewsets.py
@@ -183,7 +183,7 @@ def get_serializer_class(self):
@extend_schema(
summary=_("Retrieve form definition JSON schema"),
- tags=["forms"],
+ tags=["forms", "form-definitions"],
responses=OpenApiTypes.OBJECT,
)
@action(methods=("GET",), detail=True)
diff --git a/src/openforms/js/components/admin/form_design/Context.js b/src/openforms/js/components/admin/form_design/Context.js
index f1a6d60719..8e25d34159 100644
--- a/src/openforms/js/components/admin/form_design/Context.js
+++ b/src/openforms/js/components/admin/form_design/Context.js
@@ -11,6 +11,7 @@ const FormContext = React.createContext({
components: {},
formSteps: [],
formDefinitions: [],
+ reusableFormDefinitionsLoaded: false,
formVariables: {},
registrationBackends: [],
plugins: {},
diff --git a/src/openforms/js/components/admin/form_design/NewStepFormDefinitionPicker.js b/src/openforms/js/components/admin/form_design/NewStepFormDefinitionPicker.js
index e6167586c2..ce923a8dc9 100644
--- a/src/openforms/js/components/admin/form_design/NewStepFormDefinitionPicker.js
+++ b/src/openforms/js/components/admin/form_design/NewStepFormDefinitionPicker.js
@@ -3,6 +3,7 @@ import React, {useContext, useState} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import FAIcon from 'components/admin/FAIcon';
+import Loader from 'components/admin/Loader';
import {SubmitAction} from 'components/admin/forms/ActionButton';
import Field from 'components/admin/forms/Field';
import FormRow from 'components/admin/forms/FormRow';
@@ -109,37 +110,43 @@ const NewStepFormDefinitionPicker = ({onReplace}) => {
/>
}
>
-
-
- }
- errors={validationErrors}
- required
- >
-
-
-
+ {formContext.reusableFormDefinitionsLoaded ? (
+ <>
+
+
+ }
+ errors={validationErrors}
+ required
+ >
+
+
+
-
-
-
+
+
+
+ >
+ ) : (
+
+ )}
);
diff --git a/src/openforms/js/components/admin/form_design/form-creation-form.js b/src/openforms/js/components/admin/form_design/form-creation-form.js
index 82082c40d7..894ce84813 100644
--- a/src/openforms/js/components/admin/form_design/form-creation-form.js
+++ b/src/openforms/js/components/admin/form_design/form-creation-form.js
@@ -115,6 +115,7 @@ const initialFormState = {
formSteps: [],
errors: {},
formDefinitions: [],
+ reusableFormDefinitionsLoaded: false,
availableRegistrationBackends: [],
availableAuthPlugins: [],
availablePrefillPlugins: [],
@@ -187,7 +188,7 @@ function reducer(draft, action) {
* Form-level actions
*/
case 'BACKEND_DATA_LOADED': {
- const {formData, supportingData} = action.payload;
+ const {supportingData, formData} = action.payload;
const {form, selectedAuthPlugins, steps, variables, logicRules, priceRules} = formData;
for (const [stateVar, data] of Object.entries(supportingData)) {
@@ -246,6 +247,16 @@ function reducer(draft, action) {
);
break;
}
+ case 'REUSABLE_FORM_DEFINITIONS_LOADED': {
+ const reusableFormDefinitions = action.payload;
+ const formDefinitionsIds = draft.formDefinitions.map(fd => fd.uuid);
+ draft.formDefinitions = [
+ ...draft.formDefinitions,
+ ...reusableFormDefinitions.filter(fd => !formDefinitionsIds.includes(fd.uuid)),
+ ];
+ draft.reusableFormDefinitionsLoaded = true;
+ break;
+ }
case 'ADD_REGISTRATION': {
const {key} = action.payload;
draft.form.registrationBackends.push({
@@ -953,11 +964,6 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl}) => {
const backendDataToLoad = [
{endpoint: LANGUAGE_INFO_ENDPOINT, stateVar: 'languageInfo'},
{endpoint: PAYMENT_PLUGINS_ENDPOINT, stateVar: 'availablePaymentBackends'},
- {
- endpoint: FORM_DEFINITIONS_ENDPOINT,
- query: {is_reusable: true, used_in: formUuid || ''},
- stateVar: 'formDefinitions',
- },
{endpoint: REGISTRATION_BACKENDS_ENDPOINT, stateVar: 'availableRegistrationBackends'},
{endpoint: AUTH_PLUGINS_ENDPOINT, stateVar: 'availableAuthPlugins'},
{endpoint: CATEGORIES_ENDPOINT, stateVar: 'availableCategories'},
@@ -965,6 +971,17 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl}) => {
{endpoint: STATIC_VARIABLES_ENDPOINT, stateVar: 'staticVariables'},
];
+ if (formUuid) {
+ // We only fetch FDs used in this form if it already exists, otherwise
+ // it will fetch all the FDs because the `used_in` query param will have no effect.
+ // Reusable FDs are fetched in the background afterwards to avoid long loading time.
+ backendDataToLoad.push({
+ endpoint: FORM_DEFINITIONS_ENDPOINT,
+ query: {used_in: formUuid},
+ stateVar: 'formDefinitions',
+ });
+ }
+
const {loading} = useAsync(async () => {
const promises = [loadFromBackend(backendDataToLoad), loadForm(formUuid)];
@@ -980,6 +997,20 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl}) => {
});
}, []);
+ useAsync(async () => {
+ // Waiting for the last dispatch to be done to avoid state race conditions.
+ if (!loading) {
+ const responses = await loadFromBackend([
+ {endpoint: FORM_DEFINITIONS_ENDPOINT, query: {is_reusable: true}},
+ ]);
+ const [reusableFormDefinitions] = responses;
+ dispatch({
+ type: 'REUSABLE_FORM_DEFINITIONS_LOADED',
+ payload: reusableFormDefinitions,
+ });
+ }
+ }, [loading]);
+
/**
* Functions for handling events
*/
@@ -1181,6 +1212,7 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl}) => {
components: availableComponents,
formSteps: state.formSteps,
formDefinitions: state.formDefinitions,
+ reusableFormDefinitionsLoaded: state.reusableFormDefinitionsLoaded,
formVariables: state.formVariables,
staticVariables: state.staticVariables,
plugins: {