@ukhomeoffice/cop-react-form-renderer 6.15.7-alpha → 6.15.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CollectionSummary/BannerStrip.scss +4 -0
- package/dist/components/FormComponent/FormComponent.js +6 -0
- package/dist/components/FormComponent/helpers/addLabel.js +3 -2
- package/dist/components/FormPage/FormPage.js +28 -17
- package/dist/components/FormPage/FormPage.test.js +53 -0
- package/dist/components/FormRenderer/FormRenderer.js +5 -10
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/cop-reassign-task-to-rcc.json +445 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-hidden-component-show-when-in-component-and-page.json +62 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-hidden-page-same-component-reused.json +61 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/forms/form-page-same-component-reused-one-shown.json +74 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/cop-airpax-carrier.json +407 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/cop-airpax-change-what-happened-before.json +300 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-hidden-component-show-when-in-component-and-page.json +6 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-hidden-page-same-component-reused.json +6 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/data-page-same-component-reused-one-shown.json +8 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/input/reassign-to-rcc.json +72 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/cop-airpax-change-what-happened-after.json +280 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-hidden-component-show-when-in-component-and-page-removed.json +5 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-hidden-page-same-component-reused-removed.json +5 -0
- package/dist/components/FormRenderer/clear-uncompleted-routes/test-data/output/data-page-same-component-reused-one-shown-removed.json +7 -0
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.js +259 -289
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutes.test.js +89 -27
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.js +389 -0
- package/dist/components/FormRenderer/helpers/clearOutUncompletedRoutesUtils.test.js +559 -0
- package/dist/components/FormRenderer/onCYAAction.js +0 -2
- package/dist/components/FormRenderer/onCYAAction.test.js +5 -0
- package/dist/components/FormRenderer/onPageAction.js +0 -1
- package/dist/hooks/useGetRequest.js +15 -15
- package/dist/hooks/useRefData.js +3 -2
- package/dist/utils/Component/getComponentTests/getComponent.multifile.test.js +2 -1
- package/dist/utils/Component/getDefaultValueFromConfig.js +2 -1
- package/dist/utils/Condition/meetsCondition.js +26 -12
- package/dist/utils/Condition/meetsCondition.test.js +21 -0
- package/dist/utils/Data/getAutocompleteSource.js +68 -51
- package/dist/utils/Data/getAutocompleteSource.test.js +31 -18
- package/dist/utils/Operate/doesContainValue.js +34 -0
- package/dist/utils/Operate/doesContainValue.test.js +75 -0
- package/dist/utils/Operate/runPageOperations.js +2 -0
- package/dist/utils/Validate/validateOnPageLoad.js +23 -0
- package/dist/utils/Validate/validateOnPageLoad.test.js +88 -0
- package/package.json +4 -4
- package/dist/components/FormRenderer/helpers/deleteNodeByPath.js +0 -29
- package/dist/components/FormRenderer/helpers/deleteNodeByPath.test.js +0 -56
|
@@ -5,317 +5,219 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _mergeCollectionPages = _interopRequireDefault(require("../../../utils/CollectionPage/mergeCollectionPages"));
|
|
8
|
-
var
|
|
9
|
-
var _Condition = _interopRequireDefault(require("../../../utils/Condition"));
|
|
8
|
+
var Utils = _interopRequireWildcard(require("./clearOutUncompletedRoutesUtils"));
|
|
10
9
|
var _optionIsSelected = _interopRequireDefault(require("../../../utils/Component/optionIsSelected"));
|
|
10
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
11
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
11
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
13
14
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
14
15
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
15
16
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
16
17
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /* eslint-disable no-param-reassign */
|
|
17
|
-
function removeObjectWithSingleIdFieldInPlace(array) {
|
|
18
|
-
for (let i = array.length - 1; i >= 0; i -= 1) {
|
|
19
|
-
const obj = array[i];
|
|
20
|
-
if (Object.keys(obj).length === 1 && Object.keys(obj)[0] === 'id') {
|
|
21
|
-
array.splice(i, 1); // Remove the object at index i
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const getImmediateParent = path => {
|
|
26
|
-
if (typeof path !== 'string' || !path.includes('.')) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
const parts = path.split('.');
|
|
30
|
-
parts.pop();
|
|
31
|
-
return parts.join('.');
|
|
32
|
-
};
|
|
33
|
-
|
|
34
18
|
/**
|
|
35
|
-
*
|
|
19
|
+
*
|
|
20
|
+
* This function recursively reads in the non-collection pages and components and builds lists from it:
|
|
21
|
+
*
|
|
22
|
+
* 1: allComponents. This is a map of all components in the form, keyed by the fully qualified component path.
|
|
23
|
+
* If the component belongs to a page (or other parent entity) that has its own show_when rule, then this rule
|
|
24
|
+
* is combined with the show_when rule of the component, so the decision as to whether the component's data should
|
|
25
|
+
* exist can be made just by processing the component.
|
|
26
|
+
* The main purpose of this map is to allow us to build a dependency graph in the function createComponentDependenciesGraph
|
|
27
|
+
*
|
|
28
|
+
* 2: componentsToKeep: This is an object containing a count of how many times each component is defined. This is to support the
|
|
29
|
+
* fact that a component can be used more than once in a form (but with mutually exclusive show_when rules so only used
|
|
30
|
+
* once with the same path).
|
|
31
|
+
*
|
|
32
|
+
* @param {Array} condensedPages All pages in the form, with the collection pages collated into a single object per collection
|
|
33
|
+
* @param {Object} formData The form payload
|
|
34
|
+
* @param {Map} componentByIdMap Map of all components, to enable performant lookup by id
|
|
35
|
+
* @param {Map} componentByFieldIdMap Map of all components, to enable performant lookup by fieldId
|
|
36
|
+
* @param {Object}
|
|
37
|
+
* @return {Map, Object, Map} allComponents All components in form (including nested), keyed by path:
|
|
38
|
+
* componentsToKeep A count of how many times each component is used, to prevent us deleting components defined > 1 times
|
|
39
|
+
* allCollections A map of collection objects, which are a grouping of the pages that make up a single collection
|
|
36
40
|
*/
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const recursivelyMapFieldsAndDeleteHiddenNested = (page, component, path, allComponents, componentsToKeep, formData) => {
|
|
42
|
-
var _component$data;
|
|
43
|
-
// Many component, such as warnings, html and details do not have data so we can ignore them
|
|
44
|
-
if (!component.fieldId) return;
|
|
45
|
-
path = path ? "".concat(path, ".").concat(component.fieldId) : component.fieldId;
|
|
46
|
-
|
|
47
|
-
// Components can be referenced from more than one page. We need to keep track of the count of each component path, to avoid pruning paths that exist elsewhere
|
|
48
|
-
componentsToKeep[path] = (componentsToKeep[path] || 0) + 1;
|
|
49
|
-
|
|
50
|
-
// add the fully qualified path to the component, which will be required when resolving the dependencies later
|
|
51
|
-
component.path = path;
|
|
41
|
+
const createComponentMapsFromForm = (condensedPages, componentByIdMap, componentByFieldIdMap, formData) => {
|
|
42
|
+
const allComponents = new Map();
|
|
43
|
+
const componentsToKeep = {};
|
|
44
|
+
const allCollections = new Map();
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
/*
|
|
47
|
+
* Inner function to support the recursion required to traverse through the nested structures of the form
|
|
48
|
+
*/
|
|
49
|
+
const recursivelyMapFieldsAndDeleteHiddenNested = (page, component, path) => {
|
|
50
|
+
var _component$data;
|
|
51
|
+
if (!component.fieldId) return; // Many component, such as warnings, html and details do not have data so we can ignore them
|
|
52
|
+
path = path ? "".concat(path, ".").concat(component.fieldId) : component.fieldId; // Build up the path to reflect nested components
|
|
53
|
+
componentsToKeep[path] = (componentsToKeep[path] || 0) + 1; // We need to keep track of the count of each component path, to avoid pruning paths that exist elsewhere
|
|
54
|
+
component.path = path; // Add the fully qualified path to the component, which will be required when resolving the dependencies later
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* Perform processing for any options, and any options containing nested questions.
|
|
66
|
-
* - set hidden nested questions when the parent option is not selected
|
|
67
|
-
* - any nested questions must be treated as components which can be shown/hidden
|
|
68
|
-
*/
|
|
69
|
-
component === null || component === void 0 || (_component$data2 = component.data) === null || _component$data2 === void 0 || (_component$data2 = _component$data2.options) === null || _component$data2 === void 0 || _component$data2.forEach(option => {
|
|
70
|
-
if (option.nested) {
|
|
71
|
-
if (!(0, _optionIsSelected.default)(formData[component.id], option)) {
|
|
72
|
-
option.nested.forEach(nestedComponent => {
|
|
73
|
-
// It is safe to delete this now, as we know this data item can not be required as the associated option is not selected
|
|
74
|
-
(0, _deleteNodeByPath.default)(formData, getNestedQuestionPath(path, nestedComponent.fieldId));
|
|
75
|
-
});
|
|
76
|
-
} else {
|
|
77
|
-
// If the option is selected, then add any nested components to the allComponents list. If the nested block has a show_when, pass it down to the child questions
|
|
78
|
-
const blockShowWhen = option.show_when;
|
|
79
|
-
option.nested.forEach(nestedComponent => {
|
|
80
|
-
if (blockShowWhen) nestedComponent.show_when = blockShowWhen;
|
|
81
|
-
recursivelyMapFieldsAndDeleteHiddenNested(page, nestedComponent, getImmediateParent(path), allComponents, componentsToKeep, formData);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
const findShowWhenFields = (element, showWhenFields) => {
|
|
89
|
-
if (typeof element === 'object' && element !== null) {
|
|
90
|
-
if (Array.isArray(element)) {
|
|
91
|
-
element.forEach(value => {
|
|
92
|
-
findShowWhenFields(value, showWhenFields);
|
|
56
|
+
// If the parent page has a rule, combine it with any component rule as we will be resolving dependencies for components only
|
|
57
|
+
if (page.show_when) {
|
|
58
|
+
component.show_when = component.show_when ? [].concat(Utils.toArray(page.show_when), Utils.toArray(component.show_when)) : page.show_when;
|
|
59
|
+
}
|
|
60
|
+
Utils.addValue(path, component, allComponents); // There can be more than one component per path, so keep a map of <path, array>
|
|
61
|
+
// recurse if there is nesting
|
|
62
|
+
if (component.components) {
|
|
63
|
+
component.components.forEach(c => {
|
|
64
|
+
recursivelyMapFieldsAndDeleteHiddenNested(page, c, path, allComponents, componentsToKeep, formData);
|
|
93
65
|
});
|
|
94
|
-
} else {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
} else if (component !== null && component !== void 0 && (_component$data = component.data) !== null && _component$data !== void 0 && _component$data.options) {
|
|
67
|
+
var _component$data2;
|
|
68
|
+
component === null || component === void 0 || (_component$data2 = component.data) === null || _component$data2 === void 0 || (_component$data2 = _component$data2.options) === null || _component$data2 === void 0 || _component$data2.forEach(option => {
|
|
69
|
+
if (option.nested) {
|
|
70
|
+
if (!(0, _optionIsSelected.default)(formData[component.id], option)) {
|
|
71
|
+
option.nested.forEach(nestedComponent => {
|
|
72
|
+
// delete hidden nested question payload items when the parent option is not selected
|
|
73
|
+
Utils.deleteNodeByPath(formData, Utils.getNestedQuestionPath(path, nestedComponent.fieldId));
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
// If the option is selected, then add any nested components to the allComponents list. If the nested block has a show_when, pass it down to the child questions
|
|
77
|
+
const blockShowWhen = option.show_when;
|
|
78
|
+
option.nested.forEach(nestedComponent => {
|
|
79
|
+
if (blockShowWhen) nestedComponent.show_when = blockShowWhen;
|
|
80
|
+
recursivelyMapFieldsAndDeleteHiddenNested(page, nestedComponent, Utils.getImmediateParent(path), allComponents, componentsToKeep, formData);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
98
83
|
}
|
|
99
|
-
findShowWhenFields(element[key], showWhenFields);
|
|
100
84
|
});
|
|
101
85
|
}
|
|
102
|
-
}
|
|
103
|
-
return showWhenFields;
|
|
104
|
-
};
|
|
105
|
-
function getDependencies(entity) {
|
|
106
|
-
let dependencies = null;
|
|
107
|
-
if (entity.show_when) {
|
|
108
|
-
var _findShowWhenFields;
|
|
109
|
-
// Set dependencies. Maybe multiple as show_whens can be compound and nested
|
|
110
|
-
dependencies = new Set();
|
|
111
|
-
const showWhenFields = [];
|
|
112
|
-
(_findShowWhenFields = findShowWhenFields(entity.show_when, showWhenFields)) === null || _findShowWhenFields === void 0 || _findShowWhenFields.forEach(showWhenField => {
|
|
113
|
-
dependencies.add(showWhenField);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
return dependencies;
|
|
117
|
-
}
|
|
118
|
-
function addEntityToGraph(entity, componentDependencies, allDependencyRelationships) {
|
|
119
|
-
if (componentDependencies) {
|
|
120
|
-
allDependencyRelationships.set(entity.path, componentDependencies);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const recursivelyMapDependencies = (component, allDependencyRelationships) => {
|
|
124
|
-
// Non data components can be ignored - can we remove this when doing the allcomponents thing?
|
|
125
|
-
if (!component.fieldId) return;
|
|
126
|
-
const componentDependencies = getDependencies(component);
|
|
127
|
-
addEntityToGraph(component, componentDependencies, allDependencyRelationships);
|
|
128
|
-
if (component.components) {
|
|
129
|
-
component.components.forEach(c => {
|
|
130
|
-
recursivelyMapDependencies(c, allDependencyRelationships);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* Some show_when field values point to data items that are within objects provided by external calls to refdata, eg modeOfTransport.id
|
|
136
|
-
* These won't map directly to the path keyed components in allComponents, so go back up levels in the path until we find the component.
|
|
137
|
-
* The neighbour component might not be found at all, as the dependency might be on a field that is either provided by cop-ui (eg jobHolderStaffDetails.linemanagerEmail),
|
|
138
|
-
* or by the "addToFormData" function (eg epmsSubmitted). These will be leaf level in a dependency chain so component is not required.
|
|
139
|
-
*/
|
|
86
|
+
};
|
|
140
87
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
var _entity$show_when;
|
|
152
|
-
// If there is no rule set, then the entity can be shown
|
|
153
|
-
if (!entity.show_when) {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
if (((_entity$show_when = entity.show_when) === null || _entity$show_when === void 0 ? void 0 : _entity$show_when.type) === "or") {
|
|
157
|
-
return _Condition.default.meetsOne(entity, data);
|
|
158
|
-
}
|
|
159
|
-
return _Condition.default.meetsAll(entity, data);
|
|
160
|
-
};
|
|
161
|
-
const pruneSingleComponent = (formData, path, component, componentsToKeep) => {
|
|
162
|
-
// If there is more than one entry for this component, then it is being used elsewhere in a visible page. Don't prune, but reduce the count by 1
|
|
163
|
-
if (componentsToKeep[path] > 1) {
|
|
164
|
-
componentsToKeep[path] -= 1;
|
|
165
|
-
} else {
|
|
166
|
-
var _component$data3;
|
|
167
|
-
(0, _deleteNodeByPath.default)(formData, path);
|
|
168
|
-
// If the component has options, go through each option removing the data for any nested fields. Required as nested options are in the payload at the same heirarchical level.
|
|
169
|
-
if (component !== null && component !== void 0 && (_component$data3 = component.data) !== null && _component$data3 !== void 0 && _component$data3.options) {
|
|
170
|
-
var _component$data4;
|
|
171
|
-
component === null || component === void 0 || (_component$data4 = component.data) === null || _component$data4 === void 0 || (_component$data4 = _component$data4.options) === null || _component$data4 === void 0 || _component$data4.forEach(option => {
|
|
172
|
-
var _option$nested;
|
|
173
|
-
(_option$nested = option.nested) === null || _option$nested === void 0 || _option$nested.forEach(nested => {
|
|
174
|
-
(0, _deleteNodeByPath.default)(formData, getNestedQuestionPath(path, nested.fieldId));
|
|
175
|
-
});
|
|
88
|
+
// Entry point for the recursive traverse of the form specification.
|
|
89
|
+
condensedPages === null || condensedPages === void 0 || condensedPages.forEach(page => {
|
|
90
|
+
// Make the distinction between collections (which will be processed separately) and non-collections, which will be processed here.
|
|
91
|
+
if (page.childPages) {
|
|
92
|
+
allCollections.set(page.collection.name, page);
|
|
93
|
+
} else {
|
|
94
|
+
var _page$components;
|
|
95
|
+
(_page$components = page.components) === null || _page$components === void 0 || _page$components.forEach(useComponentInPage => {
|
|
96
|
+
const componentObj = Utils.findComponentDefinitionInForm(useComponentInPage, componentByIdMap, componentByFieldIdMap);
|
|
97
|
+
recursivelyMapFieldsAndDeleteHiddenNested(page, componentObj, null, allComponents, componentsToKeep, formData);
|
|
176
98
|
});
|
|
177
99
|
}
|
|
178
|
-
}
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
allComponents,
|
|
103
|
+
componentsToKeep,
|
|
104
|
+
allCollections
|
|
105
|
+
};
|
|
179
106
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
throw new Error(message);
|
|
203
|
-
}
|
|
204
|
-
;
|
|
205
|
-
});
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* This function iterates through the allComponents list. For each component, it will derive all payload paths
|
|
110
|
+
* that this component is dependent on. The same component might be defined more than once in the form, so add the dependencies
|
|
111
|
+
* for all the component definitions.
|
|
112
|
+
*
|
|
113
|
+
* @param {Map} allDependencyRelationships A map of each component with dependencies. The key is the fully qualified path and the value is a Set of all the paths that this component is dependent on.
|
|
114
|
+
* @param {Map} allComponents All components in form (including nested), keyed by path. Used to build the allDependencyRelationships
|
|
115
|
+
* @return {Map }allDependencyRelationships graph of the relationships between all entities
|
|
116
|
+
*/
|
|
117
|
+
const createComponentDependenciesGraph = allComponents => {
|
|
118
|
+
const allDependencyRelationships = new Map();
|
|
119
|
+
allComponents === null || allComponents === void 0 || allComponents.forEach(componentArray => {
|
|
120
|
+
componentArray === null || componentArray === void 0 || componentArray.forEach(component => {
|
|
121
|
+
if (!component.fieldId) return;
|
|
122
|
+
const componentDependencies = Utils.getDependencies(component);
|
|
123
|
+
if (componentDependencies) {
|
|
124
|
+
if (allDependencyRelationships.has(component.path)) {
|
|
125
|
+
const existingSet = allDependencyRelationships.get(component.path);
|
|
126
|
+
componentDependencies.forEach(dep => existingSet.add(dep));
|
|
127
|
+
} else {
|
|
128
|
+
allDependencyRelationships.set(component.path, new Set(componentDependencies));
|
|
206
129
|
}
|
|
207
130
|
}
|
|
208
|
-
}
|
|
131
|
+
});
|
|
209
132
|
});
|
|
210
|
-
|
|
211
|
-
if (!isShowEntity(dependentEntity, formData)) {
|
|
212
|
-
pruneSingleComponent(formData, dependentEntity.path, dependentEntity, componentsToKeep);
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
const findComponentDefinitionInForm = (useComponentInPage, form) => {
|
|
216
|
-
var _ref, _form$components$find;
|
|
217
|
-
const componentInForm = (_ref = (_form$components$find = form.components.find(c => c.fieldId === useComponentInPage.use)) !== null && _form$components$find !== void 0 ? _form$components$find : form.components.find(c => c.id === useComponentInPage.use)) !== null && _ref !== void 0 ? _ref : useComponentInPage;
|
|
218
|
-
// Retun clone of component, so subsequent processing can make changes to it without changing the form
|
|
219
|
-
return JSON.parse(JSON.stringify(componentInForm));
|
|
220
|
-
};
|
|
221
|
-
const deleteCorrespondingMetaInfo = (component, collectionDataObject, formData) => {
|
|
222
|
-
const {
|
|
223
|
-
meta: {
|
|
224
|
-
documents
|
|
225
|
-
}
|
|
226
|
-
} = formData;
|
|
227
|
-
const fileDataBeingDeleted = collectionDataObject[component.fieldId];
|
|
228
|
-
if (!documents || !fileDataBeingDeleted) return;
|
|
229
|
-
const fileDataAsArray = Array.isArray(fileDataBeingDeleted) ? fileDataBeingDeleted : [fileDataBeingDeleted];
|
|
230
|
-
const metaDocumentIndex = documents.findIndex(document => fileDataAsArray.some(fileItem => document.url === fileItem.url));
|
|
231
|
-
if (metaDocumentIndex !== -1) {
|
|
232
|
-
documents.splice(metaDocumentIndex, 1);
|
|
233
|
-
}
|
|
133
|
+
return allDependencyRelationships;
|
|
234
134
|
};
|
|
235
|
-
const pruneCollectionEntry = (pathsToKeep, componentsToPrune, collectionDataObject, formData) => {
|
|
236
|
-
componentsToPrune.forEach(component => {
|
|
237
|
-
if (!pathsToKeep.has(component.fieldId)) {
|
|
238
|
-
var _component$data5;
|
|
239
|
-
if (component.type === "multifile") {
|
|
240
|
-
deleteCorrespondingMetaInfo(component, collectionDataObject, formData);
|
|
241
|
-
}
|
|
242
|
-
(0, _deleteNodeByPath.default)(collectionDataObject, component.fieldId);
|
|
243
135
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* This function loops through each entry in the allDependencyRelationships map. Each entry will contain a Set of all
|
|
139
|
+
* paths that the key path is dependent on. Recursively delve into each dependency path and repeat the process until we
|
|
140
|
+
* have reached an entry that has no dependencies. At this point we can safely resolve the dependency using its show_when rule,
|
|
141
|
+
* and unwind the recursion.
|
|
142
|
+
*
|
|
143
|
+
* Because all the components are inter-connected, this could result in resolving the same entry repeatedly, so keep a 'visited'
|
|
144
|
+
* array to prevent this.
|
|
145
|
+
*
|
|
146
|
+
* @param {Object} formData The form payload
|
|
147
|
+
* @param {Map} allComponents All components in form (including nested), keyed by path
|
|
148
|
+
* @param {Object} componentsToKeep A count of how many times each component is used, to prevent us deleting components defined > 1 times
|
|
149
|
+
* @param {Map} allDependencyRelationships A map of each component with dependencies. The key is the fully qualified path and the value is a Set of all the paths that this component is dependent on.
|
|
150
|
+
* @param {Map} allCollections A map of collection objects, which are a grouping of the pages that make up a single collection
|
|
151
|
+
* @param {Object} form The form specification
|
|
152
|
+
*/
|
|
153
|
+
const resolveComponentDependenciesGraph = (allDependencyRelationships, allComponents, componentsToKeep, allCollections, form, formData) => {
|
|
154
|
+
/*
|
|
155
|
+
* Inner function to support the recursion required to traverse through the nested nature of the allDependencyRelationships
|
|
156
|
+
*/
|
|
157
|
+
const recursivelyResolveDependencies = (visited, dependentComponent, dependencyRelationships) => {
|
|
158
|
+
if (visited.has(dependentComponent)) return;
|
|
159
|
+
visited.add(dependentComponent);
|
|
160
|
+
const dependencies = dependencyRelationships.get(dependentComponent.path);
|
|
161
|
+
dependencies === null || dependencies === void 0 || dependencies.forEach(dependencyPath => {
|
|
162
|
+
const dependencyComponents = Utils.getDependencyObjectFromPath(dependencyPath, allComponents);
|
|
163
|
+
if (dependencyComponents) {
|
|
164
|
+
dependencyComponents.forEach(dependency => {
|
|
165
|
+
if (!visited.has(dependency)) {
|
|
166
|
+
recursivelyResolveDependencies(visited, dependency, dependencyRelationships);
|
|
167
|
+
}
|
|
253
168
|
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
};
|
|
258
|
-
function removeEmptyArraysAndUnusedCollectionIDs(payload) {
|
|
259
|
-
if (Array.isArray(payload)) {
|
|
260
|
-
for (let i = payload.length - 1; i >= 0; i -= 1) {
|
|
261
|
-
if (Array.isArray(payload[i]) && payload[i].length === 0) {
|
|
262
|
-
payload.splice(i, 1);
|
|
263
169
|
} else {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
170
|
+
// We are here if the dependency path was not found in the list of allComponents. This is valid and can happen when
|
|
171
|
+
// a component is dependent on a field that was generated by cop-ui, e.g. jobHolderStaffDetails.linemanagerEmail.
|
|
172
|
+
// However, there is an edge case (described in the message below) that we need to validate for.
|
|
173
|
+
const arrayNamePattern = /([a-zA-Z_$][\w$]*)\[\d+\]/;
|
|
174
|
+
const match = dependencyPath.match(arrayNamePattern);
|
|
175
|
+
if (match) {
|
|
176
|
+
const collectionName = match[1];
|
|
177
|
+
const collection = allCollections.get(collectionName);
|
|
178
|
+
if (collection) {
|
|
179
|
+
var _collection$childPage;
|
|
180
|
+
(_collection$childPage = collection.childPages) === null || _collection$childPage === void 0 || _collection$childPage.forEach(childPage => {
|
|
181
|
+
if (childPage.show_when) {
|
|
182
|
+
const message = "It is not possible to reliably clean hidden data when a component is dependent on a \n collection's data, and that collection is itself dependent on data elsewhere in the form.\n The dependency path is ".concat(dependencyPath, ", and the chained show_when is ").concat(JSON.stringify(childPage.show_when), ".\n The form will need to be changed to break this chained dependency");
|
|
183
|
+
throw new Error(message);
|
|
184
|
+
}
|
|
185
|
+
;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
279
188
|
}
|
|
280
|
-
} else {
|
|
281
|
-
removeEmptyArraysAndUnusedCollectionIDs(payload[key]); // Recurse for nested structures
|
|
282
189
|
}
|
|
283
190
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Recurse through the whole form, building a map of all fields and their owning component id.
|
|
288
|
-
// This is required to build the component->dependency graph in the next step, as the dependencies reference fields, not components.
|
|
289
|
-
|
|
290
|
-
const condensedPages = (0, _mergeCollectionPages.default)(form.pages);
|
|
291
|
-
const allComponents = new Map();
|
|
292
|
-
const componentsToKeep = {};
|
|
293
|
-
const allCollections = new Map();
|
|
294
|
-
condensedPages === null || condensedPages === void 0 || condensedPages.forEach(page => {
|
|
295
|
-
if (page.childPages) {
|
|
296
|
-
allCollections.set(page.collection.name, page);
|
|
297
|
-
} else {
|
|
298
|
-
var _page$components;
|
|
299
|
-
(_page$components = page.components) === null || _page$components === void 0 || _page$components.forEach(useComponentInPage => {
|
|
300
|
-
const componentObj = findComponentDefinitionInForm(useComponentInPage, form);
|
|
301
|
-
recursivelyMapFieldsAndDeleteHiddenNested(page, componentObj, null, allComponents, componentsToKeep, formData);
|
|
302
|
-
});
|
|
191
|
+
// Whilst unravelling the recursion, we can start deleting from the leaf upwards
|
|
192
|
+
if (!Utils.isShowEntity(dependentComponent, formData)) {
|
|
193
|
+
Utils.deleteComponentData(formData, dependentComponent.path, dependentComponent, componentsToKeep);
|
|
303
194
|
}
|
|
304
|
-
}
|
|
305
|
-
const allDependencyRelationships = new Map();
|
|
306
|
-
allComponents === null || allComponents === void 0 || allComponents.forEach(component => {
|
|
307
|
-
recursivelyMapDependencies(component, allDependencyRelationships);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Visit all components with 'show_whens' and resolve the dependency. Only delete the data at the leaf element, or when un-winding the recursion
|
|
311
|
-
// to deal with the edge case that an element is dependent on a data item that is hidden by a different constraint
|
|
195
|
+
};
|
|
312
196
|
const visited = new Set();
|
|
313
|
-
allDependencyRelationships === null || allDependencyRelationships === void 0 || allDependencyRelationships.forEach((
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
197
|
+
allDependencyRelationships === null || allDependencyRelationships === void 0 || allDependencyRelationships.forEach((dependencies, dependentComponentPath) => {
|
|
198
|
+
// Each path that a component is dependent on may have > 1 possible dependent components, if a field is used twice
|
|
199
|
+
const dependentComponents = allComponents.get(dependentComponentPath);
|
|
200
|
+
dependentComponents === null || dependentComponents === void 0 || dependentComponents.forEach(dependentComponent => {
|
|
201
|
+
if (!visited.has(dependentComponent)) {
|
|
202
|
+
recursivelyResolveDependencies(visited, dependentComponent, allDependencyRelationships, form, formData, allComponents, componentsToKeep, allCollections);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
318
205
|
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* For each collection, iterate through the payload array's objects, and apply the collections show_when
|
|
210
|
+
* rules for each payload object.
|
|
211
|
+
*
|
|
212
|
+
* Some of the rules may be dependent on payload items outside the collection. These will already have been cleansed
|
|
213
|
+
* so will be in a reliable state.
|
|
214
|
+
*
|
|
215
|
+
* @param {Map} allCollections A map of collection objects, which are a grouping of the pages that make up a single collection
|
|
216
|
+
* @param {Object} formData The form payload
|
|
217
|
+
* @param {Map} componentByIdMap Map of all components, to enable performant lookup by id
|
|
218
|
+
* @param {Map} componentByFieldIdMap Map of all components, to enable performant lookup by fieldId
|
|
219
|
+
*/
|
|
220
|
+
const cleanseCollectionData = (allCollections, formData, componentByIdMap, componentByFieldIdMap) => {
|
|
319
221
|
allCollections === null || allCollections === void 0 || allCollections.forEach((collection, collectionName) => {
|
|
320
222
|
const collectionDataArray = formData[collectionName];
|
|
321
223
|
collectionDataArray === null || collectionDataArray === void 0 || collectionDataArray.forEach(collectionDataEntry => {
|
|
@@ -327,27 +229,95 @@ const clearOutUncompletedRoutes = (form, formData) => {
|
|
|
327
229
|
const componentsToPrune = new Map();
|
|
328
230
|
(_collection$childPage2 = collection.childPages) === null || _collection$childPage2 === void 0 || _collection$childPage2.forEach(childPage => {
|
|
329
231
|
var _childPage$components;
|
|
330
|
-
const showPage = isShowEntity(childPage, dataForEvaluation);
|
|
232
|
+
const showPage = Utils.isShowEntity(childPage, dataForEvaluation);
|
|
331
233
|
(_childPage$components = childPage.components) === null || _childPage$components === void 0 || _childPage$components.forEach(useComponentInPage => {
|
|
332
|
-
const componentObj = findComponentDefinitionInForm(useComponentInPage,
|
|
333
|
-
|
|
234
|
+
const componentObj = Utils.findComponentDefinitionInForm(useComponentInPage, componentByIdMap, componentByFieldIdMap);
|
|
334
235
|
// Non-data components can be ignored (eg html)
|
|
335
236
|
if (!componentObj.fieldId) return;
|
|
336
|
-
const showComponentOnPage = isShowEntity(useComponentInPage, dataForEvaluation);
|
|
337
|
-
if (showPage && showComponentOnPage && isShowEntity(componentObj, dataForEvaluation)) {
|
|
338
|
-
// There may be
|
|
237
|
+
const showComponentOnPage = Utils.isShowEntity(useComponentInPage, dataForEvaluation);
|
|
238
|
+
if (showPage && showComponentOnPage && Utils.isShowEntity(componentObj, dataForEvaluation)) {
|
|
239
|
+
// There may be >1 components with the same path (eg quantity in EAB2), so don't delete hidden components if they are required elsewhere
|
|
339
240
|
pathsToKeep.add(componentObj.fieldId);
|
|
340
241
|
} else {
|
|
341
242
|
componentsToPrune.set(componentObj.id, componentObj);
|
|
342
243
|
}
|
|
343
244
|
});
|
|
344
245
|
});
|
|
345
|
-
pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataEntry, formData);
|
|
246
|
+
Utils.pruneCollectionEntry(pathsToKeep, componentsToPrune, collectionDataEntry, formData);
|
|
346
247
|
});
|
|
347
|
-
//
|
|
348
|
-
if (collectionDataArray)
|
|
248
|
+
//
|
|
249
|
+
if (collectionDataArray) Utils.removeObjectWithOnlySingleIdField(collectionDataArray);
|
|
349
250
|
});
|
|
350
|
-
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
*
|
|
255
|
+
* The purpose of this function is to apply the show_when rules defined in the form specification to the
|
|
256
|
+
* form payload (formData), and remove any data that should not be shown according to those rules.
|
|
257
|
+
* (the reason this data exists in the payload is that users can answer a question in such a way that
|
|
258
|
+
* other questions are 'shown' to them, which are then complete. The user can then go back and change the original
|
|
259
|
+
* question to then 'hide' those questions, but the answers they gave persist in the payload. The must be cleansed
|
|
260
|
+
* before being submitted.
|
|
261
|
+
*
|
|
262
|
+
* There are 2 types of payload data that have to be treated differently by this function for it to work.
|
|
263
|
+
*
|
|
264
|
+
* 1. Data from non-repeating pages, containing components.
|
|
265
|
+
*
|
|
266
|
+
* Each data item captured will be written to the payload as a field with the name of the fieldId of the payload.
|
|
267
|
+
* In the case of components that are nested within 'container' components, the payload will reflect the nesting.
|
|
268
|
+
* There is no limit to the level of nesting in a form.
|
|
269
|
+
*
|
|
270
|
+
* To cleanse the payload for this type of data, we do the following (high level description, see method comments for detail):
|
|
271
|
+
*
|
|
272
|
+
* - build a map of all components, keyed by their payload path (required for the next step)
|
|
273
|
+
* - for all of these components that have dependencies (show_when rules), create a graph datastructure to show all components
|
|
274
|
+
* on which a component is dependent, and then all components that those components may be dependent on. There is no limit to
|
|
275
|
+
* the depth of these chained dependencies.
|
|
276
|
+
* - the reason for building a graph of these dependency chains is so we know the sequence in which we must resolve the dependencies.
|
|
277
|
+
* For any given chain of dependencies it is essential that we resolve the dependencies starting at the end of the chains, and work our way
|
|
278
|
+
* back up the chain. If not, we could be resolving a dependency based on a payload item that itself will later be deleted.
|
|
279
|
+
* Therefore, the final step is to recursively traverse the dependency graph, resolving the dependency rule for the components in
|
|
280
|
+
* the chain from the leaf back up to the parent node.
|
|
281
|
+
*
|
|
282
|
+
* 2. Page collection data
|
|
283
|
+
*
|
|
284
|
+
* A page collection is a set of one or more pages which (as a group) can be filled in as many times is required
|
|
285
|
+
* by the user. For example, in EAB, you can add as many item-seal-details to an EAB form as are required. An item-seal-detail
|
|
286
|
+
* is made up of several different pages, all grouped. The data saved will be an array representing the whole collection, with
|
|
287
|
+
* each object in the array representing a single collection entry (a single item-seal-detail in this example).
|
|
288
|
+
*
|
|
289
|
+
* Cleanse collection data has to be driven from the payload rather than the form, as there are repeated objects in an array
|
|
290
|
+
* representing the data. Each one has to be treated independently as the data to be cleansed can be different in each one.
|
|
291
|
+
*
|
|
292
|
+
* - For each collection type, iterate through each payload object and treat it like an independent payload..
|
|
293
|
+
* - For each collection payload object, iterate through the collection's pages and components, and apply all the show_when rules
|
|
294
|
+
* found at both page and component level to the payload.
|
|
295
|
+
* - Repeat for each payload object
|
|
296
|
+
* - Repeat for all collections
|
|
297
|
+
*
|
|
298
|
+
* @param {*} form
|
|
299
|
+
* @param {*} formData
|
|
300
|
+
* @returns {*} cleansed form data
|
|
301
|
+
*/
|
|
302
|
+
const clearOutUncompletedRoutes = (form, formData) => {
|
|
303
|
+
// Load components into maps keyed on id and field for subsequent performant access
|
|
304
|
+
const componentByIdMap = new Map(form.components.map(c => [c.id, c]));
|
|
305
|
+
const componentByFieldIdMap = new Map(form.components.map(c => [c.fieldId, c]));
|
|
306
|
+
|
|
307
|
+
// Group all pages relating to a page-collection into a single object to aid collection
|
|
308
|
+
// processing, and to allow us to differentiate between a collection and non-collection page.
|
|
309
|
+
const condensedPages = (0, _mergeCollectionPages.default)(form.pages);
|
|
310
|
+
const {
|
|
311
|
+
allComponents,
|
|
312
|
+
componentsToKeep,
|
|
313
|
+
allCollections
|
|
314
|
+
} = createComponentMapsFromForm(condensedPages, componentByIdMap, componentByFieldIdMap, formData);
|
|
315
|
+
const allDependencyRelationships = createComponentDependenciesGraph(allComponents);
|
|
316
|
+
resolveComponentDependenciesGraph(allDependencyRelationships, allComponents, componentsToKeep, allCollections, form, formData);
|
|
317
|
+
cleanseCollectionData(allCollections, formData, componentByIdMap, componentByFieldIdMap);
|
|
318
|
+
|
|
319
|
+
// The cleansing above may have left empty arrays and collection IDs. Tidy these up.
|
|
320
|
+
Utils.removeEmptyArraysAndUnusedCollectionIDs(formData);
|
|
351
321
|
return formData;
|
|
352
322
|
};
|
|
353
323
|
var _default = exports.default = clearOutUncompletedRoutes;
|