@k-int/stripes-kint-components 5.13.0 → 5.15.0
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/CHANGELOG.md +14 -0
- package/es/lib/settingsHooks/useSettings.js +181 -65
- package/es/lib/utils/generateKiwtQuery.js +2 -3
- package/es/lib/utils/{generateKiwtQueryParams.js → generateKiwtQueryParams/generateKiwtQueryParams.js} +5 -2
- package/es/lib/utils/generateKiwtQueryParams/index.js +13 -0
- package/es/lib/utils/index.js +2 -2
- package/package.json +1 -1
- package/src/lib/settingsHooks/useSettings.js +202 -88
- package/src/lib/utils/README.md +1 -103
- package/src/lib/utils/generateKiwtQuery.js +1 -1
- package/src/lib/utils/generateKiwtQueryParams/README.md +199 -0
- package/src/lib/utils/{generateKiwtQueryParams.js → generateKiwtQueryParams/generateKiwtQueryParams.js} +5 -2
- package/src/lib/utils/generateKiwtQueryParams/index.js +1 -0
- package/src/lib/utils/index.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [5.15.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.14.0...v5.15.0) (2025-02-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* useSettings sections ([0c062bf](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/0c062bf203811078371820b5106b3106982845ad))
|
|
7
|
+
|
|
8
|
+
# [5.14.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.13.0...v5.14.0) (2025-02-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Added filterConfig options to generateKiwtQueryParams ([8502b91](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/8502b91beef93065a24c1b1ab6ed4b73759b851d))
|
|
14
|
+
|
|
1
15
|
# [5.13.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.12.0...v5.13.0) (2025-02-17)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _react =
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
9
|
var _reactQuery = require("react-query");
|
|
10
10
|
var _core = require("@folio/stripes/core");
|
|
@@ -15,97 +15,213 @@ var _utils = require("../utils");
|
|
|
15
15
|
var _hooks = require("../hooks");
|
|
16
16
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
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); }
|
|
19
|
+
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; }
|
|
20
|
+
// TODO work underway to make the settings hook more useful when trying to render multiple sections at a time
|
|
21
|
+
const useSettings = function () {
|
|
22
|
+
let settingsProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
23
|
+
const {
|
|
20
24
|
allowGlobalEdit = true,
|
|
21
25
|
dynamicPageExclusions,
|
|
22
26
|
intlKey: passedIntlKey,
|
|
23
27
|
intlNS: passedIntlNS,
|
|
24
28
|
labelOverrides = {},
|
|
25
|
-
|
|
29
|
+
pages,
|
|
30
|
+
// If this is present, act completely as a passthrough either in sections OR standalone
|
|
31
|
+
persistentPages = [],
|
|
32
|
+
preventQueries = false,
|
|
26
33
|
refdataEndpoint,
|
|
34
|
+
renderSingleSection = false,
|
|
35
|
+
// IF SECTIONS == NULL, this prop allows the implementor to render the pageList as a section with label etc
|
|
36
|
+
sectionPermission,
|
|
37
|
+
// If we're rendering sections, this prevents the rendering of said section (works as fallback or prevention for single section)
|
|
38
|
+
sectionRoute,
|
|
39
|
+
sections,
|
|
40
|
+
// if present, should be an array of objects with the SAME shape as the overall props here.
|
|
41
|
+
// If a prop isn't present it can fall back to the "global" prop.
|
|
27
42
|
settingEndpoint,
|
|
28
43
|
templateEndpoint
|
|
29
|
-
} =
|
|
44
|
+
} = settingsProps;
|
|
30
45
|
const ky = (0, _core.useOkapiKy)();
|
|
46
|
+
const stripes = (0, _core.useStripes)();
|
|
31
47
|
const kintIntl = (0, _hooks.useKintIntl)(passedIntlKey, passedIntlNS);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
|
|
49
|
+
// Set up array of queryParams, pageLists etc etc ready for us to do all the work in a single place
|
|
50
|
+
|
|
51
|
+
const sectionMetaArray = (0, _react.useMemo)(() => {
|
|
52
|
+
const array = [];
|
|
53
|
+
const getSectionQueryProps = sectionProps => {
|
|
54
|
+
const dqp = sectionProps.dynamicPageExclusions ?? dynamicPageExclusions;
|
|
55
|
+
const queryParams = (0, _utils.generateKiwtQueryParams)({
|
|
56
|
+
filters: dqp?.map(dpe => ({
|
|
57
|
+
path: 'section',
|
|
58
|
+
comparator: '!=',
|
|
59
|
+
value: dpe
|
|
60
|
+
})),
|
|
61
|
+
perPage: 100,
|
|
62
|
+
stats: false
|
|
63
|
+
}, {});
|
|
64
|
+
const finalSettingEndpoint = sectionProps.settingEndpoint ?? settingEndpoint;
|
|
65
|
+
const queryKey = ['stripes-kint-components', 'useSettings', 'appSettings', finalSettingEndpoint, queryParams];
|
|
66
|
+
const queryFn = () => ky(`${finalSettingEndpoint}?${queryParams?.join('&')}`).json();
|
|
67
|
+
return {
|
|
68
|
+
queryParams,
|
|
69
|
+
queryKey,
|
|
70
|
+
queryFn
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
if ((sections?.length ?? 0) > 0) {
|
|
74
|
+
array.push(...sections.map(section => {
|
|
75
|
+
const querySectionProps = getSectionQueryProps(section);
|
|
76
|
+
return {
|
|
77
|
+
...settingsProps,
|
|
78
|
+
// ensure these fall through to section based props
|
|
79
|
+
...querySectionProps,
|
|
80
|
+
...section // return original section object
|
|
81
|
+
};
|
|
82
|
+
}));
|
|
83
|
+
} else {
|
|
84
|
+
const querySectionProps = getSectionQueryProps(settingsProps);
|
|
85
|
+
array.push({
|
|
86
|
+
...settingsProps,
|
|
87
|
+
...querySectionProps
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return array;
|
|
91
|
+
}, [dynamicPageExclusions, ky, sections, settingEndpoint, settingsProps]);
|
|
92
|
+
const queries = (0, _react.useMemo)(() => sectionMetaArray.map(sma => {
|
|
93
|
+
let enabled = true;
|
|
94
|
+
if (sma.pages || sma.preventQueries || pages || preventQueries) {
|
|
95
|
+
enabled = false;
|
|
96
|
+
}
|
|
47
97
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
queryKey: sma.queryKey,
|
|
99
|
+
queryFn: sma.queryFn,
|
|
100
|
+
enabled
|
|
101
|
+
};
|
|
102
|
+
}) ?? [], [pages, preventQueries, sectionMetaArray]);
|
|
103
|
+
const queryReturnArray = (0, _reactQuery.useQueries)(queries);
|
|
104
|
+
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
105
|
+
const getDynamicPages = (0, _react.useCallback)((qra, sma) => {
|
|
106
|
+
const pagesFromQueryReturn = [...new Set(qra.data?.map(s => s.section))];
|
|
107
|
+
const dynamic = pagesFromQueryReturn.map(page => {
|
|
108
|
+
const finalSectionRoute = sma.sectionRoute ?? sectionRoute;
|
|
109
|
+
const pageRoute = (finalSectionRoute ? `${finalSectionRoute}/` : '') + page;
|
|
110
|
+
return {
|
|
111
|
+
route: pageRoute,
|
|
112
|
+
label: kintIntl.formatKintMessage({
|
|
113
|
+
id: `settings.settingsSection.${(0, _utils.toCamelCase)(page)}`,
|
|
114
|
+
fallbackMessage: page
|
|
115
|
+
}),
|
|
116
|
+
component: props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPagePane, {
|
|
59
117
|
intlKey: passedIntlKey,
|
|
60
118
|
intlNS: passedIntlNS,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
119
|
+
sectionName: page,
|
|
120
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPage, {
|
|
121
|
+
allowEdit: allowGlobalEdit,
|
|
122
|
+
intlKey: passedIntlKey,
|
|
123
|
+
intlNS: passedIntlNS,
|
|
124
|
+
labelOverrides: labelOverrides,
|
|
125
|
+
sectionName: page,
|
|
126
|
+
...props
|
|
127
|
+
})
|
|
64
128
|
})
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
const pageList = persistentPages.concat(dynamic).sort(_utils.sortByLabel);
|
|
69
|
-
const intlKey = (0, _hooks.useIntlKey)(passedIntlKey, passedIntlNS);
|
|
70
|
-
const SettingsContextProvider = _ref2 => {
|
|
71
|
-
let {
|
|
72
|
-
children
|
|
73
|
-
} = _ref2;
|
|
74
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_contexts.SettingsContext.Provider, {
|
|
75
|
-
value: {
|
|
76
|
-
intlKey,
|
|
77
|
-
// This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
|
|
78
|
-
refdataEndpoint,
|
|
79
|
-
settingEndpoint,
|
|
80
|
-
templateEndpoint
|
|
81
|
-
},
|
|
82
|
-
children: children
|
|
129
|
+
};
|
|
83
130
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
131
|
+
return {
|
|
132
|
+
pages: pagesFromQueryReturn,
|
|
133
|
+
dynamic
|
|
134
|
+
};
|
|
135
|
+
}, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, sectionRoute]);
|
|
136
|
+
(0, _react.useEffect)(() => {
|
|
137
|
+
// Handle loading
|
|
138
|
+
if (queryReturnArray.length > 0 && queryReturnArray.every(qra => qra.isLoading === false) && isLoading === true) {
|
|
139
|
+
setIsLoading(false);
|
|
140
|
+
}
|
|
141
|
+
}, [isLoading, queryReturnArray]);
|
|
142
|
+
const finalSections = (0, _react.useMemo)(() => {
|
|
143
|
+
return sectionMetaArray.map((sma, idx) => {
|
|
144
|
+
const SettingsContextProvider = _ref => {
|
|
145
|
+
let {
|
|
146
|
+
children
|
|
147
|
+
} = _ref;
|
|
148
|
+
const intlKey = (0, _hooks.useIntlKey)(passedIntlKey, passedIntlNS);
|
|
149
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_contexts.SettingsContext.Provider, {
|
|
150
|
+
value: {
|
|
151
|
+
intlKey: sma.intlKey ?? intlKey,
|
|
152
|
+
// This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
|
|
153
|
+
refdataEndpoint: sma.refdataEndpoint ?? refdataEndpoint,
|
|
154
|
+
settingEndpoint: sma.settingEndpoint ?? settingEndpoint,
|
|
155
|
+
templateEndpoint: sma.templateEndpoint ?? templateEndpoint
|
|
156
|
+
},
|
|
157
|
+
children: children
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
SettingsContextProvider.propTypes = {
|
|
161
|
+
children: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node])
|
|
162
|
+
};
|
|
163
|
+
const finalPermission = sma.sectionPermission ?? sectionPermission;
|
|
164
|
+
if (!finalPermission || stripes.hasPerm(finalPermission)) {
|
|
165
|
+
const dynamicPagesFromQueryReturn = getDynamicPages(queryReturnArray[idx], sma);
|
|
166
|
+
const finalPages = sma.pages ?? pages ?? (sma.persistentPages ?? persistentPages).concat(dynamicPagesFromQueryReturn.dynamic).sort(_utils.sortByLabel).map(p => {
|
|
167
|
+
return {
|
|
168
|
+
...p,
|
|
169
|
+
component: pCProps => /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsContextProvider, {
|
|
170
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(p.component, {
|
|
171
|
+
...pCProps
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
...sma,
|
|
178
|
+
SettingsContextProvider,
|
|
179
|
+
...dynamicPagesFromQueryReturn,
|
|
180
|
+
pageList: finalPages,
|
|
181
|
+
// DEPRECATED Here for backwards compatibility
|
|
182
|
+
pages: finalPages // ASSUMPTION MADE THAT INDICES REMAIN THE SAME
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}).filter(Boolean);
|
|
187
|
+
}, [getDynamicPages, pages, passedIntlKey, passedIntlNS, persistentPages, queryReturnArray, refdataEndpoint, sectionMetaArray, sectionPermission, settingEndpoint, stripes, templateEndpoint]);
|
|
188
|
+
const pageList = (0, _react.useMemo)(() => {
|
|
189
|
+
let finalPages = null;
|
|
190
|
+
if (finalSections && finalSections.length === 1 && !renderSingleSection) {
|
|
191
|
+
finalPages = pages ?? finalSections[0].pages;
|
|
192
|
+
}
|
|
193
|
+
return finalPages;
|
|
194
|
+
}, [finalSections, pages, renderSingleSection]);
|
|
195
|
+
const passedSections = (0, _react.useMemo)(() => {
|
|
196
|
+
if (finalSections.length > 1 || finalSections.length === 1 && renderSingleSection) {
|
|
197
|
+
return finalSections;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}, [finalSections, renderSingleSection]);
|
|
88
201
|
const SettingsComponent = props => {
|
|
89
202
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_smartComponents.Settings, {
|
|
90
203
|
pages: pageList,
|
|
91
204
|
paneTitle: kintIntl.formatKintMessage({
|
|
92
205
|
id: 'meta.title'
|
|
93
206
|
}),
|
|
207
|
+
sections: passedSections,
|
|
94
208
|
...props
|
|
95
209
|
});
|
|
96
210
|
};
|
|
97
|
-
const SettingsComponentWithContext = props => {
|
|
98
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsContextProvider, {
|
|
99
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsComponent, {
|
|
100
|
-
...props
|
|
101
|
-
})
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
211
|
return {
|
|
105
212
|
isLoading,
|
|
106
213
|
pageList,
|
|
107
|
-
|
|
108
|
-
|
|
214
|
+
sections: finalSections,
|
|
215
|
+
SettingsComponent,
|
|
216
|
+
// This doesn't make much sense if there is more than one section, here to avoid breaking changes
|
|
217
|
+
SettingsContextProvider: finalSections && finalSections.length === 1 ? finalSections[0].SettingsContextProvider : _ref2 => {
|
|
218
|
+
let {
|
|
219
|
+
children
|
|
220
|
+
} = _ref2;
|
|
221
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
222
|
+
children: [" ", children, " "]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
109
225
|
};
|
|
110
226
|
};
|
|
111
227
|
var _default = exports.default = useSettings;
|
|
@@ -4,11 +4,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _generateKiwtQueryParams =
|
|
8
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
var _generateKiwtQueryParams = require("./generateKiwtQueryParams");
|
|
9
8
|
const generateKiwtQuery = function (options, nsValues) {
|
|
10
9
|
let encode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
11
|
-
const paramsArray = (0, _generateKiwtQueryParams.
|
|
10
|
+
const paramsArray = (0, _generateKiwtQueryParams.generateKiwtQueryParams)(options, nsValues, encode);
|
|
12
11
|
return paramsArray.length ? '?' + paramsArray.join('&') : '';
|
|
13
12
|
};
|
|
14
13
|
var _default = exports.default = generateKiwtQuery;
|
|
@@ -134,8 +134,11 @@ const generateKiwtQueryParams = function (options, nsValues) {
|
|
|
134
134
|
if (filterConfigEntry) {
|
|
135
135
|
// We have a direct mapping instruction, use it
|
|
136
136
|
const filterString = filterValues.map(v => {
|
|
137
|
-
const
|
|
138
|
-
|
|
137
|
+
const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
|
|
138
|
+
const fceValue = fcValueEntry.value ?? v;
|
|
139
|
+
// This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
|
|
140
|
+
const fceComparator = fcValueEntry.comparator ?? '==';
|
|
141
|
+
return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
|
|
139
142
|
}).join('||');
|
|
140
143
|
paramsArray.push(`filters=${conditionalEncodeURIComponent(filterString, encode)}`);
|
|
141
144
|
} else if (!filterKey) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "generateKiwtQueryParams", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _generateKiwtQueryParams.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _generateKiwtQueryParams = _interopRequireDefault(require("./generateKiwtQueryParams"));
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
package/es/lib/utils/index.js
CHANGED
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "generateKiwtQuery", {
|
|
|
36
36
|
Object.defineProperty(exports, "generateKiwtQueryParams", {
|
|
37
37
|
enumerable: true,
|
|
38
38
|
get: function () {
|
|
39
|
-
return _generateKiwtQueryParams.
|
|
39
|
+
return _generateKiwtQueryParams.generateKiwtQueryParams;
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
Object.defineProperty(exports, "groupCustomPropertiesByCtx", {
|
|
@@ -136,7 +136,7 @@ Object.defineProperty(exports, "typedownQueryKey", {
|
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
var _generateKiwtQuery = _interopRequireDefault(require("./generateKiwtQuery"));
|
|
139
|
-
var _generateKiwtQueryParams =
|
|
139
|
+
var _generateKiwtQueryParams = require("./generateKiwtQueryParams");
|
|
140
140
|
var _selectorSafe = _interopRequireDefault(require("./selectorSafe"));
|
|
141
141
|
var _buildUrl = _interopRequireDefault(require("./buildUrl"));
|
|
142
142
|
var _refdataOptions = _interopRequireDefault(require("./refdataOptions"));
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { useOkapiKy } from '@folio/stripes/core';
|
|
4
|
+
import { useQueries } from 'react-query';
|
|
5
|
+
import { useOkapiKy, useStripes } from '@folio/stripes/core';
|
|
6
6
|
|
|
7
7
|
import { Settings } from '@folio/stripes/smart-components';
|
|
8
8
|
|
|
@@ -12,88 +12,207 @@ import { SettingsContext } from '../contexts';
|
|
|
12
12
|
import { generateKiwtQueryParams, sortByLabel, toCamelCase } from '../utils';
|
|
13
13
|
import { useKintIntl, useIntlKey } from '../hooks';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
// TODO work underway to make the settings hook more useful when trying to render multiple sections at a time
|
|
16
|
+
const useSettings = (settingsProps = {}) => {
|
|
17
|
+
const {
|
|
18
|
+
allowGlobalEdit = true,
|
|
19
|
+
dynamicPageExclusions,
|
|
20
|
+
intlKey: passedIntlKey,
|
|
21
|
+
intlNS: passedIntlNS,
|
|
22
|
+
labelOverrides = {},
|
|
23
|
+
pages, // If this is present, act completely as a passthrough either in sections OR standalone
|
|
24
|
+
persistentPages = [],
|
|
25
|
+
preventQueries = false,
|
|
26
|
+
refdataEndpoint,
|
|
27
|
+
renderSingleSection = false, // IF SECTIONS == NULL, this prop allows the implementor to render the pageList as a section with label etc
|
|
28
|
+
sectionPermission, // If we're rendering sections, this prevents the rendering of said section (works as fallback or prevention for single section)
|
|
29
|
+
sectionRoute,
|
|
30
|
+
sections, // if present, should be an array of objects with the SAME shape as the overall props here.
|
|
31
|
+
// If a prop isn't present it can fall back to the "global" prop.
|
|
32
|
+
settingEndpoint,
|
|
33
|
+
templateEndpoint
|
|
34
|
+
} = settingsProps;
|
|
35
|
+
|
|
26
36
|
const ky = useOkapiKy();
|
|
37
|
+
const stripes = useStripes();
|
|
27
38
|
const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
// Set up array of queryParams, pageLists etc etc ready for us to do all the work in a single place
|
|
41
|
+
|
|
42
|
+
const sectionMetaArray = useMemo(() => {
|
|
43
|
+
const array = [];
|
|
44
|
+
const getSectionQueryProps = (sectionProps) => {
|
|
45
|
+
const dqp = sectionProps.dynamicPageExclusions ?? dynamicPageExclusions;
|
|
46
|
+
const queryParams = generateKiwtQueryParams({
|
|
47
|
+
filters: dqp?.map(dpe => ({
|
|
48
|
+
path: 'section',
|
|
49
|
+
comparator: '!=',
|
|
50
|
+
value: dpe
|
|
51
|
+
})),
|
|
52
|
+
perPage: 100,
|
|
53
|
+
stats: false
|
|
54
|
+
}, {});
|
|
55
|
+
const finalSettingEndpoint = sectionProps.settingEndpoint ?? settingEndpoint;
|
|
56
|
+
const queryKey = ['stripes-kint-components', 'useSettings', 'appSettings', finalSettingEndpoint, queryParams];
|
|
57
|
+
const queryFn = () => ky(`${finalSettingEndpoint}?${queryParams?.join('&')}`).json();
|
|
58
|
+
|
|
59
|
+
return { queryParams, queryKey, queryFn };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if ((sections?.length ?? 0) > 0) {
|
|
63
|
+
array.push(...sections.map((section) => {
|
|
64
|
+
const querySectionProps = getSectionQueryProps(section);
|
|
65
|
+
|
|
66
|
+
return ({
|
|
67
|
+
...settingsProps, // ensure these fall through to section based props
|
|
68
|
+
...querySectionProps,
|
|
69
|
+
...section // return original section object
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
} else {
|
|
73
|
+
const querySectionProps = getSectionQueryProps(settingsProps);
|
|
74
|
+
array.push({
|
|
75
|
+
...settingsProps,
|
|
76
|
+
...querySectionProps,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return array;
|
|
81
|
+
}, [dynamicPageExclusions, ky, sections, settingEndpoint, settingsProps]);
|
|
82
|
+
|
|
83
|
+
const queries = useMemo(() => sectionMetaArray.map(sma => {
|
|
84
|
+
let enabled = true;
|
|
85
|
+
if (sma.pages || sma.preventQueries || pages || preventQueries) {
|
|
86
|
+
enabled = false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
queryKey: sma.queryKey,
|
|
91
|
+
queryFn: sma.queryFn,
|
|
92
|
+
enabled
|
|
93
|
+
};
|
|
94
|
+
}) ?? [], [pages, preventQueries, sectionMetaArray]);
|
|
95
|
+
|
|
96
|
+
const queryReturnArray = useQueries(queries);
|
|
97
|
+
|
|
98
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
99
|
+
|
|
100
|
+
const getDynamicPages = useCallback((qra, sma) => {
|
|
101
|
+
const pagesFromQueryReturn = [...new Set(qra.data?.map(s => s.section))];
|
|
102
|
+
const dynamic = pagesFromQueryReturn.map(page => {
|
|
103
|
+
const finalSectionRoute = sma.sectionRoute ?? sectionRoute;
|
|
104
|
+
const pageRoute = (finalSectionRoute ? `${finalSectionRoute}/` : '') + page;
|
|
105
|
+
return (
|
|
106
|
+
{
|
|
107
|
+
route: pageRoute,
|
|
108
|
+
label: kintIntl.formatKintMessage({
|
|
109
|
+
id: `settings.settingsSection.${toCamelCase(page)}`,
|
|
110
|
+
fallbackMessage: page
|
|
111
|
+
}),
|
|
112
|
+
component: (props) => (
|
|
113
|
+
<SettingPagePane
|
|
61
114
|
intlKey={passedIntlKey}
|
|
62
115
|
intlNS={passedIntlNS}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
116
|
+
sectionName={page}
|
|
117
|
+
>
|
|
118
|
+
<SettingPage
|
|
119
|
+
allowEdit={allowGlobalEdit}
|
|
120
|
+
intlKey={passedIntlKey}
|
|
121
|
+
intlNS={passedIntlNS}
|
|
122
|
+
labelOverrides={labelOverrides}
|
|
123
|
+
sectionName={page}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
</SettingPagePane>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
pages: pagesFromQueryReturn,
|
|
134
|
+
dynamic,
|
|
135
|
+
};
|
|
136
|
+
}, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, sectionRoute]);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
// Handle loading
|
|
140
|
+
if (
|
|
141
|
+
queryReturnArray.length > 0 &&
|
|
142
|
+
queryReturnArray.every(qra => qra.isLoading === false) &&
|
|
143
|
+
isLoading === true
|
|
144
|
+
) {
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}, [isLoading, queryReturnArray]);
|
|
148
|
+
|
|
149
|
+
const finalSections = useMemo(() => {
|
|
150
|
+
return sectionMetaArray.map(((sma, idx) => {
|
|
151
|
+
const SettingsContextProvider = ({ children }) => {
|
|
152
|
+
const intlKey = useIntlKey(passedIntlKey, passedIntlNS);
|
|
153
|
+
return (
|
|
154
|
+
<SettingsContext.Provider
|
|
155
|
+
value={{
|
|
156
|
+
intlKey: sma.intlKey ?? intlKey, // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
|
|
157
|
+
refdataEndpoint: sma.refdataEndpoint ?? refdataEndpoint,
|
|
158
|
+
settingEndpoint: sma.settingEndpoint ?? settingEndpoint,
|
|
159
|
+
templateEndpoint: sma.templateEndpoint ?? templateEndpoint,
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
{children}
|
|
163
|
+
</SettingsContext.Provider>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
SettingsContextProvider.propTypes = {
|
|
168
|
+
children: PropTypes.oneOfType([
|
|
169
|
+
PropTypes.func,
|
|
170
|
+
PropTypes.node
|
|
171
|
+
])
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const finalPermission = sma.sectionPermission ?? sectionPermission;
|
|
175
|
+
if (!finalPermission || stripes.hasPerm(finalPermission)) {
|
|
176
|
+
const dynamicPagesFromQueryReturn = getDynamicPages(queryReturnArray[idx], sma);
|
|
177
|
+
|
|
178
|
+
const finalPages = sma.pages ?? pages ?? (sma.persistentPages ?? persistentPages).concat(dynamicPagesFromQueryReturn.dynamic).sort(sortByLabel).map((p) => {
|
|
179
|
+
return {
|
|
180
|
+
...p,
|
|
181
|
+
component: (pCProps) => (
|
|
182
|
+
<SettingsContextProvider>
|
|
183
|
+
<p.component {...pCProps} />
|
|
184
|
+
</SettingsContextProvider>
|
|
185
|
+
)
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...sma,
|
|
191
|
+
SettingsContextProvider,
|
|
192
|
+
...dynamicPagesFromQueryReturn,
|
|
193
|
+
pageList: finalPages, // DEPRECATED Here for backwards compatibility
|
|
194
|
+
pages: finalPages // ASSUMPTION MADE THAT INDICES REMAIN THE SAME
|
|
195
|
+
};
|
|
69
196
|
}
|
|
70
|
-
|
|
71
|
-
|
|
197
|
+
return null;
|
|
198
|
+
})).filter(Boolean);
|
|
199
|
+
}, [getDynamicPages, pages, passedIntlKey, passedIntlNS, persistentPages, queryReturnArray, refdataEndpoint, sectionMetaArray, sectionPermission, settingEndpoint, stripes, templateEndpoint]);
|
|
72
200
|
|
|
73
|
-
const pageList =
|
|
201
|
+
const pageList = useMemo(() => {
|
|
202
|
+
let finalPages = null;
|
|
203
|
+
if (finalSections && finalSections.length === 1 && !renderSingleSection) {
|
|
204
|
+
finalPages = pages ?? finalSections[0].pages;
|
|
205
|
+
}
|
|
74
206
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<SettingsContext.Provider
|
|
79
|
-
value={{
|
|
80
|
-
intlKey, // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
|
|
81
|
-
refdataEndpoint,
|
|
82
|
-
settingEndpoint,
|
|
83
|
-
templateEndpoint
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
{children}
|
|
87
|
-
</SettingsContext.Provider>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
207
|
+
return finalPages;
|
|
208
|
+
}, [finalSections, pages, renderSingleSection]);
|
|
90
209
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
210
|
+
const passedSections = useMemo(() => {
|
|
211
|
+
if (finalSections.length > 1 || (finalSections.length === 1 && renderSingleSection)) {
|
|
212
|
+
return finalSections;
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}, [finalSections, renderSingleSection]);
|
|
97
216
|
|
|
98
217
|
const SettingsComponent = (props) => {
|
|
99
218
|
return (
|
|
@@ -101,26 +220,21 @@ const useSettings = ({
|
|
|
101
220
|
pages={pageList}
|
|
102
221
|
paneTitle={
|
|
103
222
|
kintIntl.formatKintMessage({
|
|
104
|
-
|
|
105
|
-
|
|
223
|
+
id: 'meta.title'
|
|
224
|
+
})}
|
|
225
|
+
sections={passedSections}
|
|
106
226
|
{...props}
|
|
107
227
|
/>
|
|
108
228
|
);
|
|
109
229
|
};
|
|
110
230
|
|
|
111
|
-
const SettingsComponentWithContext = (props) => {
|
|
112
|
-
return (
|
|
113
|
-
<SettingsContextProvider>
|
|
114
|
-
<SettingsComponent {...props} />
|
|
115
|
-
</SettingsContextProvider>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
231
|
return {
|
|
120
232
|
isLoading,
|
|
121
233
|
pageList,
|
|
122
|
-
|
|
123
|
-
|
|
234
|
+
sections: finalSections,
|
|
235
|
+
SettingsComponent,
|
|
236
|
+
// This doesn't make much sense if there is more than one section, here to avoid breaking changes
|
|
237
|
+
SettingsContextProvider: (finalSections && finalSections.length === 1) ? finalSections[0].SettingsContextProvider : ({ children }) => <> { children } </>
|
|
124
238
|
};
|
|
125
239
|
};
|
|
126
240
|
|
package/src/lib/utils/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## generateKiwtQuery
|
|
4
4
|
|
|
5
5
|
The `generateKiwtQuery` function generates a URL query string in "KIWT" (K-Int Web Toolkit) style from a SASQ (Search and Sort Query) object. It handles search terms, filters, sorting, and other parameters, making it easy to construct complex query strings for backend APIs.
|
|
6
|
-
|
|
6
|
+
This component calls and then joins the output of `generateKiwtQueryParams`. For more granular information on how this works, see the generateKiwtQueryParams README.
|
|
7
7
|
### Basic Usage
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
@@ -39,105 +39,3 @@ const queryString = generateKiwtQuery(options, nsValues);
|
|
|
39
39
|
| options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
|
|
40
40
|
| nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
|
|
41
41
|
| encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
|
|
42
|
-
|
|
43
|
-
## generateKiwtQueryParams
|
|
44
|
-
|
|
45
|
-
The `generateKiwtQueryParams` function is similar to `generateKiwtQuery`, but instead of returning a string, it returns an array of query parameters. This can be useful for more advanced use cases where you need to manipulate the parameters before constructing the final query string.
|
|
46
|
-
|
|
47
|
-
### Basic Usage
|
|
48
|
-
|
|
49
|
-
```javascript
|
|
50
|
-
import { generateKiwtQueryParams } from '@k-int/stripes-kint-components';
|
|
51
|
-
|
|
52
|
-
const nsValues = {
|
|
53
|
-
query: 'test',
|
|
54
|
-
filters: 'requestStatus.completed,journalVolume.test1,startDate.startDate>=2021-10-28'
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const options = {
|
|
58
|
-
searchKey: 'request.name',
|
|
59
|
-
filterKeys: {
|
|
60
|
-
requestStatus: 'requestStatus.value',
|
|
61
|
-
journalVolume: 'journalVolume'
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const queryArray = generateKiwtQueryParams(options, nsValues);
|
|
66
|
-
|
|
67
|
-
// queryArray will be:
|
|
68
|
-
// [
|
|
69
|
-
// "match=request.name",
|
|
70
|
-
// "term=test",
|
|
71
|
-
// "filters=requestStatus.value==completed",
|
|
72
|
-
// "filters=journalVolume==test1",
|
|
73
|
-
// "filters=startDate%3E=2021-10-28",
|
|
74
|
-
// "stats=true"
|
|
75
|
-
// ]
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Props
|
|
79
|
-
|
|
80
|
-
| Name | Type | Description | Default | Required |
|
|
81
|
-
|---|---|---|---|---|
|
|
82
|
-
| options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query array. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
|
|
83
|
-
| nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
|
|
84
|
-
| encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
### Options Parameter Structure
|
|
88
|
-
|
|
89
|
-
The `options` parameter is a powerful tool for customizing the query string generation. It has the following key elements:
|
|
90
|
-
|
|
91
|
-
* **Search:**
|
|
92
|
-
* `searchKey`: Specifies the field to search on. If using SASQ, the corresponding value in `nsValues.query` will be used as the search term. Otherwise, you can directly specify the search term using the `term` key in `options`.
|
|
93
|
-
* **Filters:** See the "Filters Object Structure" section below for details.
|
|
94
|
-
* **Sorting:**
|
|
95
|
-
* `sortKeys`: An object mapping sort field names from `nsValues.sort` to their corresponding backend keys.
|
|
96
|
-
* `sort`: An array of sort objects. Each object can have `path` (field to sort on) and `direction` (`asc` or `desc`).
|
|
97
|
-
* **Statistics:**
|
|
98
|
-
* `stats`: A boolean value indicating whether to include the `stats=true` parameter.
|
|
99
|
-
* **Other parameters:** Any other key-value pairs in the `options` object will be directly added as query parameters.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
### Filters Object Structure
|
|
103
|
-
|
|
104
|
-
The `filters` option in the `options` parameter allows you to define complex filter expressions using an array of filter objects. Each filter object can have the following properties:
|
|
105
|
-
|
|
106
|
-
* **path:** The path to the field to filter on (e.g., `item.title`).
|
|
107
|
-
* **comparator:** *(Optional)* The comparator to use for the filter (e.g., `==`, `!=`, `>`, `<`). Defaults to `==`.
|
|
108
|
-
* **value:** A single value to filter on.
|
|
109
|
-
* **values:** An array of values to filter on. If present, this property takes precedence over `value`, and an `OR` condition is implied between the values.
|
|
110
|
-
* **groupValues:** An object defining nested filter groups with `AND` or `OR` logic. This property allows for building complex nested filters. See the explanation below.
|
|
111
|
-
|
|
112
|
-
#### Nested Filter Groups (`groupValues`)
|
|
113
|
-
|
|
114
|
-
The `groupValues` property enables the creation of nested filter expressions with `AND` and `OR` logic. It is an object with either `AND` or `OR` properties (or both, with `AND` taking precedence). The value of these properties should be an array of filter objects, allowing for recursive nesting.
|
|
115
|
-
|
|
116
|
-
```javascript
|
|
117
|
-
// Example: (status==active AND type==local) OR (status==pending)
|
|
118
|
-
const filters = [
|
|
119
|
-
{
|
|
120
|
-
groupValues: {
|
|
121
|
-
OR: [
|
|
122
|
-
{
|
|
123
|
-
groupValues: {
|
|
124
|
-
AND: [
|
|
125
|
-
{ path: 'item.status', value: 'active' },
|
|
126
|
-
{ path: 'item.type', value: 'local' },
|
|
127
|
-
],
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{ path: 'item.status', value: 'pending' },
|
|
131
|
-
]
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
];
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
This structure allows you to express complex filter logic in a clear and organized way.
|
|
138
|
-
|
|
139
|
-
### Key Takeaways
|
|
140
|
-
|
|
141
|
-
* `generateKiwtQuery` provides a flexible way to construct URL query strings for backend APIs, especially when working with Stripes SASQ.
|
|
142
|
-
* The `options` parameter offers fine-grained control over the query generation process, allowing for complex filtering, sorting, and other customizations.
|
|
143
|
-
* `generateKiwtQueryParams` is useful for advanced use cases where you need to manipulate the parameters before constructing the final query string.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import generateKiwtQueryParams from './generateKiwtQueryParams';
|
|
1
|
+
import { generateKiwtQueryParams } from './generateKiwtQueryParams';
|
|
2
2
|
|
|
3
3
|
const generateKiwtQuery = (options, nsValues, encode = true) => {
|
|
4
4
|
const paramsArray = generateKiwtQueryParams(options, nsValues, encode);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# generateKiwtQueryParams
|
|
2
|
+
|
|
3
|
+
The `generateKiwtQueryParams` function is similar to `generateKiwtQuery`, but instead of returning a string, it returns an array of query parameters. This can be useful for more advanced use cases where you need to manipulate the parameters before constructing the final query string.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { generateKiwtQueryParams } from '@k-int/stripes-kint-components';
|
|
9
|
+
|
|
10
|
+
const nsValues = {
|
|
11
|
+
query: 'test',
|
|
12
|
+
filters: 'requestStatus.completed,journalVolume.test1,startDate.startDate>=2021-10-28'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const options = {
|
|
16
|
+
searchKey: 'request.name',
|
|
17
|
+
filterKeys: {
|
|
18
|
+
requestStatus: 'requestStatus.value',
|
|
19
|
+
journalVolume: 'journalVolume'
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const queryArray = generateKiwtQueryParams(options, nsValues);
|
|
24
|
+
|
|
25
|
+
// queryArray will be:
|
|
26
|
+
// [
|
|
27
|
+
// "match=request.name",
|
|
28
|
+
// "term=test",
|
|
29
|
+
// "filters=requestStatus.value==completed",
|
|
30
|
+
// "filters=journalVolume==test1",
|
|
31
|
+
// "filters=startDate%3E=2021-10-28",
|
|
32
|
+
// "stats=true"
|
|
33
|
+
// ]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Name | Type | Description | Default | Required |
|
|
39
|
+
|---|---|---|---|---|
|
|
40
|
+
| options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query array. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
|
|
41
|
+
| nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
|
|
42
|
+
| encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Options Parameter Structure
|
|
46
|
+
|
|
47
|
+
The `options` parameter is a powerful tool for customizing the query string generation manually. It has the following key elements:
|
|
48
|
+
|
|
49
|
+
* **Search:**
|
|
50
|
+
* `searchKey`: Specifies the field to search on. If using SASQ, the corresponding value in `nsValues.query` will be used as the search term. Otherwise, you can directly specify the search term using the `term` key in `options`.
|
|
51
|
+
* **Filters:** See the "Filters Object Structure" section below for details.
|
|
52
|
+
* **Sorting:**
|
|
53
|
+
* `sortKeys`: An object mapping sort field names from `nsValues.sort` to their corresponding backend keys.
|
|
54
|
+
* `sort`: An array of sort objects. Each object can have `path` (field to sort on) and `direction` (`asc` or `desc`).
|
|
55
|
+
* **Statistics:**
|
|
56
|
+
* `stats`: A boolean value indicating whether to include the `stats=true` parameter.
|
|
57
|
+
* **Other parameters:** Any other key-value pairs in the `options` object will be directly added as query parameters.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Filters Object Structure
|
|
61
|
+
|
|
62
|
+
The `filters` option in the `options` parameter allows you to define complex filter expressions using an array of filter objects. Each filter object can have the following properties:
|
|
63
|
+
|
|
64
|
+
* **path:** The path to the field to filter on (e.g., `item.title`).
|
|
65
|
+
* **comparator:** *(Optional)* The comparator to use for the filter (e.g., `==`, `!=`, `>`, `<`). Defaults to `==`.
|
|
66
|
+
* **value:** A single value to filter on.
|
|
67
|
+
* **values:** An array of values to filter on. If present, this property takes precedence over `value`, and an `OR` condition is implied between the values.
|
|
68
|
+
* **groupValues:** An object defining nested filter groups with `AND` or `OR` logic. This property allows for building complex nested filters. See the explanation below.
|
|
69
|
+
|
|
70
|
+
### Nested Filter Groups (`groupValues`)
|
|
71
|
+
|
|
72
|
+
The `groupValues` property enables the creation of nested filter expressions with `AND` and `OR` logic. It is an object with either `AND` or `OR` properties (or both, with `AND` taking precedence). The value of these properties should be an array of filter objects, allowing for recursive nesting.
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// Example: (status==active AND type==local) OR (status==pending)
|
|
76
|
+
const filters = [
|
|
77
|
+
{
|
|
78
|
+
groupValues: {
|
|
79
|
+
OR: [
|
|
80
|
+
{
|
|
81
|
+
groupValues: {
|
|
82
|
+
AND: [
|
|
83
|
+
{ path: 'item.status', value: 'active' },
|
|
84
|
+
{ path: 'item.type', value: 'local' },
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{ path: 'item.status', value: 'pending' },
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
This structure allows you to express complex filter logic in a clear and organized way.
|
|
96
|
+
|
|
97
|
+
## nsValues options
|
|
98
|
+
|
|
99
|
+
In addition to the manual filtering capabilities, `generateKiwtQueryParams` supports advanced filtering options including custom comparators and dynamic filter key overrides for the `query` nsValues passed by SASQ. These features enable you to generate complex query expressions more easily.
|
|
100
|
+
|
|
101
|
+
### Custom Comparators & Filter Key Overrides
|
|
102
|
+
When using the older `nsValues.filters` string (e.g., `"requestStatus.completed,journalVolume.test1"`), you can use `filterConfig` to customize how filters are interpreted:
|
|
103
|
+
|
|
104
|
+
- **Mapping Filter Names:**
|
|
105
|
+
Each entry in `filterConfig` should include a `name` field that matches the filter name parsed from `nsValues.filters`.
|
|
106
|
+
|
|
107
|
+
- **Overriding Values and Comparators:**
|
|
108
|
+
Optionally, a `values` array can be provided for more granular control. For each possible value, you can specify:
|
|
109
|
+
- A replacement value.
|
|
110
|
+
- A custom comparator to be used instead of the default `'=='`.
|
|
111
|
+
|
|
112
|
+
For example, if you want the filter `status` to use a comparator like `isNotSet` for a certain value, your configuration might look like this:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const options = {
|
|
116
|
+
filterKeys: { status: 'requestStatus.value' },
|
|
117
|
+
filterConfig: [
|
|
118
|
+
{
|
|
119
|
+
name: 'status',
|
|
120
|
+
values: [
|
|
121
|
+
{ name: 'notSet', value: '', comparator: ' isNotSet' },
|
|
122
|
+
{ name: 'isNotActive', value: 'active', comparator: '!=' },
|
|
123
|
+
// other value mappings... Any not present will be evaluated as is
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
With this configuration, the generated query parameter for various nsValues.filters would be:
|
|
131
|
+
|
|
132
|
+
| nsValues.filters | outcome |
|
|
133
|
+
| -- | -- |
|
|
134
|
+
| status.notSet | `filters=requestStatus.value isNotSet` |
|
|
135
|
+
| status.wibble | `filters=requestStatus.value==wibble` |
|
|
136
|
+
| status.isNotActive | `filters=requestStatus.value!=active` |
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
### Dynamic Date Example with `moreThanToday`
|
|
142
|
+
|
|
143
|
+
You can also dynamically set filter values based on runtime data. For instance, if you want to filter records where a date field is greater than or equal to today’s date, you can calculate the date value first and then reference it in your `filterConfig`:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Dynamically calculate today's date in YYYY-MM-DD format
|
|
147
|
+
const today = new Date().toISOString().split('T')[0];
|
|
148
|
+
|
|
149
|
+
const filterConfig = [
|
|
150
|
+
{
|
|
151
|
+
name: 'startDate',
|
|
152
|
+
values: [
|
|
153
|
+
{
|
|
154
|
+
name: 'moreThanToday',
|
|
155
|
+
value: today,
|
|
156
|
+
comparator: '>='
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const options = {
|
|
163
|
+
filterConfig,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const nsValues = {
|
|
167
|
+
// When 'startDate.moreThanToday' is encountered, it will use the filterConfig mapping
|
|
168
|
+
filters: 'startDate.moreThanToday'
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const queryArray = generateKiwtQueryParams(options, nsValues);
|
|
172
|
+
console.log(queryArray);
|
|
173
|
+
// Example output (URL encoded):
|
|
174
|
+
// [
|
|
175
|
+
// "filters=startDate%3E%3D2025-02-19", // The date value will reflect today's date
|
|
176
|
+
// "stats=true"
|
|
177
|
+
// ]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
In this example, when `nsValues.filters` contains `startDate.moreThanToday`, the function looks up the corresponding configuration in `filterConfig`, uses the dynamically calculated date stored in `today`, and applies the comparator `>=` to produce a query parameter like:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
filters=startDate>=2025-02-19
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
This approach makes it easy to filter records based on dynamic criteria, such as dates relative to the current day.
|
|
187
|
+
|
|
188
|
+
### How It Works
|
|
189
|
+
|
|
190
|
+
- **For `options.filters` (object-based filtering):**
|
|
191
|
+
The function handles nested filters, groups (using `groupValues` with `AND`/`OR` logic), and applies the specified comparators. This provides full flexibility when building complex queries.
|
|
192
|
+
|
|
193
|
+
- **For Legacy SASQ `nsValues.filters` (string-based filtering):**
|
|
194
|
+
The code splits the incoming comma-separated string into individual filters. For each filter:
|
|
195
|
+
- It checks if there’s a matching entry in `filterConfig`.
|
|
196
|
+
- If found, it applies the mapping from `filterConfig` and uses the comparator specified there (or defaults to `'=='`).
|
|
197
|
+
- If no mapping exists and no filter key is provided in `filterKeys`, the raw value is passed to the backend.
|
|
198
|
+
|
|
199
|
+
By utilizing these advanced filtering options, you can tailor your query string generation to meet complex backend requirements while maintaining clarity and control over your filter logic.
|
|
@@ -129,8 +129,11 @@ const generateKiwtQueryParams = (options, nsValues, encode = true) => {
|
|
|
129
129
|
if (filterConfigEntry) {
|
|
130
130
|
// We have a direct mapping instruction, use it
|
|
131
131
|
const filterString = filterValues.map(v => {
|
|
132
|
-
const
|
|
133
|
-
|
|
132
|
+
const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
|
|
133
|
+
const fceValue = fcValueEntry.value ?? v;
|
|
134
|
+
// This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
|
|
135
|
+
const fceComparator = fcValueEntry.comparator ?? '==';
|
|
136
|
+
return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
|
|
134
137
|
}).join('||');
|
|
135
138
|
|
|
136
139
|
paramsArray.push(`filters=${conditionalEncodeURIComponent(filterString, encode)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as generateKiwtQueryParams } from './generateKiwtQueryParams';
|
package/src/lib/utils/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as generateKiwtQuery } from './generateKiwtQuery';
|
|
2
|
-
export {
|
|
2
|
+
export { generateKiwtQueryParams } from './generateKiwtQueryParams';
|
|
3
3
|
export { default as selectorSafe } from './selectorSafe';
|
|
4
4
|
|
|
5
5
|
export { default as buildUrl } from './buildUrl';
|