@nocobase/flow-engine 2.2.0-beta.15 → 2.2.0-beta.17
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/lib/acl/Acl.d.ts +2 -1
- package/lib/acl/Acl.js +28 -0
- package/lib/components/FlowContextSelector.js +7 -1
- package/lib/components/MobilePopup.js +14 -3
- package/lib/components/subModel/LazyDropdown.js +41 -26
- package/lib/flowContext.d.ts +5 -1
- package/lib/flowContext.js +18 -6
- package/lib/locale/en-US.json +2 -0
- package/lib/locale/index.d.ts +4 -0
- package/lib/locale/zh-CN.json +2 -0
- package/lib/utils/associationObjectVariable.d.ts +10 -0
- package/lib/utils/associationObjectVariable.js +10 -7
- package/lib/utils/dateVariable.d.ts +22 -0
- package/lib/utils/dateVariable.js +123 -16
- package/lib/utils/index.d.ts +3 -3
- package/lib/utils/index.js +8 -0
- package/lib/utils/params-resolvers.d.ts +3 -0
- package/lib/utils/params-resolvers.js +10 -0
- package/lib/utils/variablesParams.js +5 -0
- package/lib/views/createViewMeta.d.ts +1 -0
- package/lib/views/createViewMeta.js +53 -22
- package/package.json +4 -4
- package/src/__tests__/createViewMeta.popup.test.ts +84 -1
- package/src/__tests__/flowContext.test.ts +8 -0
- package/src/__tests__/objectVariable.test.ts +6 -1
- package/src/acl/Acl.tsx +36 -1
- package/src/acl/__tests__/Acl.test.tsx +70 -0
- package/src/components/FlowContextSelector.tsx +7 -1
- package/src/components/MobilePopup.tsx +16 -4
- package/src/components/__tests__/MobilePopup.test.tsx +42 -1
- package/src/components/subModel/LazyDropdown.tsx +44 -26
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +85 -2
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +35 -0
- package/src/flowContext.ts +31 -5
- package/src/locale/__tests__/index.test.ts +21 -0
- package/src/locale/en-US.json +2 -0
- package/src/locale/zh-CN.json +2 -0
- package/src/utils/__tests__/dateVariable.test.ts +57 -4
- package/src/utils/__tests__/variablesParams.test.ts +28 -1
- package/src/utils/associationObjectVariable.ts +9 -6
- package/src/utils/dateVariable.ts +145 -18
- package/src/utils/index.ts +17 -2
- package/src/utils/params-resolvers.ts +12 -0
- package/src/utils/variablesParams.ts +10 -0
- package/src/views/createViewMeta.ts +52 -18
|
@@ -43,13 +43,15 @@ __export(dateVariable_exports, {
|
|
|
43
43
|
isCtxDateExpression: () => isCtxDateExpression,
|
|
44
44
|
isCtxDatePathPrefix: () => isCtxDatePathPrefix,
|
|
45
45
|
parseCtxDateExpression: () => parseCtxDateExpression,
|
|
46
|
+
parseCtxDateExpressionConfig: () => parseCtxDateExpressionConfig,
|
|
46
47
|
resolveCtxDatePath: () => resolveCtxDatePath,
|
|
48
|
+
serializeCtxDateExpressionConfig: () => serializeCtxDateExpressionConfig,
|
|
47
49
|
serializeCtxDateValue: () => serializeCtxDateValue
|
|
48
50
|
});
|
|
49
51
|
module.exports = __toCommonJS(dateVariable_exports);
|
|
50
52
|
var import_dayjs = __toESM(require("dayjs"));
|
|
51
53
|
const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
|
|
52
|
-
const
|
|
54
|
+
const PRESET_KEY_LIST = [
|
|
53
55
|
"today",
|
|
54
56
|
"now",
|
|
55
57
|
"yesterday",
|
|
@@ -66,9 +68,11 @@ const PRESET_KEYS = /* @__PURE__ */ new Set([
|
|
|
66
68
|
"thisYear",
|
|
67
69
|
"lastYear",
|
|
68
70
|
"nextYear"
|
|
69
|
-
]
|
|
71
|
+
];
|
|
72
|
+
const PRESET_KEYS = new Set(PRESET_KEY_LIST);
|
|
70
73
|
const RELATIVE_DIRECTIONS = /* @__PURE__ */ new Set(["next", "past"]);
|
|
71
74
|
const RELATIVE_UNITS = /* @__PURE__ */ new Set(["day", "week", "month", "year"]);
|
|
75
|
+
const MAX_DATE_FORMAT_LENGTH = 128;
|
|
72
76
|
function parseCtxDateSegments(value) {
|
|
73
77
|
if (typeof value !== "string") return null;
|
|
74
78
|
const trimmed = value.trim();
|
|
@@ -79,8 +83,7 @@ function parseCtxDateSegments(value) {
|
|
|
79
83
|
return rawPath.split(".").map((seg) => seg.trim()).filter(Boolean);
|
|
80
84
|
}
|
|
81
85
|
__name(parseCtxDateSegments, "parseCtxDateSegments");
|
|
82
|
-
function
|
|
83
|
-
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
86
|
+
function isBaseCtxDatePathPrefix(segments) {
|
|
84
87
|
if (segments[0] !== "date") return false;
|
|
85
88
|
if (segments.length === 1) return true;
|
|
86
89
|
if (segments[1] === "preset") {
|
|
@@ -117,6 +120,34 @@ function isCtxDatePathPrefix(pathSegments) {
|
|
|
117
120
|
}
|
|
118
121
|
return false;
|
|
119
122
|
}
|
|
123
|
+
__name(isBaseCtxDatePathPrefix, "isBaseCtxDatePathPrefix");
|
|
124
|
+
function decodeFormatToken(token) {
|
|
125
|
+
const raw = String(token || "");
|
|
126
|
+
if (!raw.startsWith("v")) return void 0;
|
|
127
|
+
const decoded = decodeBase64Url(raw.slice(1));
|
|
128
|
+
if (!decoded || decoded.length > MAX_DATE_FORMAT_LENGTH) return void 0;
|
|
129
|
+
return decoded;
|
|
130
|
+
}
|
|
131
|
+
__name(decodeFormatToken, "decodeFormatToken");
|
|
132
|
+
function splitFormattedDateSegments(segments) {
|
|
133
|
+
if (segments[0] !== "date") return null;
|
|
134
|
+
if (segments[1] !== "format") return { baseSegments: segments };
|
|
135
|
+
if (segments.length < 4) return null;
|
|
136
|
+
const format = decodeFormatToken(segments[2]);
|
|
137
|
+
if (!format) return null;
|
|
138
|
+
return { baseSegments: ["date", ...segments.slice(3)], format };
|
|
139
|
+
}
|
|
140
|
+
__name(splitFormattedDateSegments, "splitFormattedDateSegments");
|
|
141
|
+
function isCtxDatePathPrefix(pathSegments) {
|
|
142
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
143
|
+
if (segments[0] !== "date") return false;
|
|
144
|
+
if (segments.length === 1) return true;
|
|
145
|
+
if (segments[1] !== "format") return isBaseCtxDatePathPrefix(segments);
|
|
146
|
+
if (segments.length === 2) return true;
|
|
147
|
+
if (segments.length === 3) return typeof decodeFormatToken(segments[2]) === "string";
|
|
148
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
149
|
+
return formatted ? isBaseCtxDatePathPrefix(formatted.baseSegments) : false;
|
|
150
|
+
}
|
|
120
151
|
__name(isCtxDatePathPrefix, "isCtxDatePathPrefix");
|
|
121
152
|
function withDatePrefix(pathSegments) {
|
|
122
153
|
if (pathSegments[0] === "date") {
|
|
@@ -225,26 +256,31 @@ __name(isCtxDateExpression, "isCtxDateExpression");
|
|
|
225
256
|
function isCompleteCtxDatePath(pathSegments) {
|
|
226
257
|
if (!isCtxDatePathPrefix(pathSegments)) return false;
|
|
227
258
|
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
259
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
260
|
+
if (!formatted) return false;
|
|
261
|
+
const baseSegments = formatted.baseSegments;
|
|
262
|
+
if (baseSegments[1] === "preset") {
|
|
263
|
+
return baseSegments.length === 3 && PRESET_KEYS.has(baseSegments[2]);
|
|
231
264
|
}
|
|
232
|
-
if (
|
|
233
|
-
if (
|
|
234
|
-
return RELATIVE_DIRECTIONS.has(
|
|
265
|
+
if (baseSegments[1] === "relative") {
|
|
266
|
+
if (baseSegments.length !== 5) return false;
|
|
267
|
+
return RELATIVE_DIRECTIONS.has(baseSegments[2]) && RELATIVE_UNITS.has(baseSegments[3]) && typeof parseNumberToken(baseSegments[4]) === "number";
|
|
235
268
|
}
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
269
|
+
if (baseSegments[1] === "exact" && baseSegments[2] === "single" && baseSegments[3] === "date") {
|
|
270
|
+
return baseSegments.length === 5 && /^v.+/.test(baseSegments[4]);
|
|
238
271
|
}
|
|
239
|
-
if (
|
|
240
|
-
return
|
|
272
|
+
if (baseSegments[1] === "exact" && baseSegments[2] === "range" && baseSegments[3] === "date") {
|
|
273
|
+
return baseSegments.length === 6 && /^v.+/.test(baseSegments[4]) && /^v.+/.test(baseSegments[5]);
|
|
241
274
|
}
|
|
242
275
|
return false;
|
|
243
276
|
}
|
|
244
277
|
__name(isCompleteCtxDatePath, "isCompleteCtxDatePath");
|
|
245
278
|
function parseCtxDateExpression(value) {
|
|
246
279
|
if (!isCtxDateExpression(value)) return void 0;
|
|
247
|
-
const
|
|
280
|
+
const rawSegments = withDatePrefix(parseCtxDateSegments(value) || []);
|
|
281
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
282
|
+
if (!formatted) return void 0;
|
|
283
|
+
const segments = formatted.baseSegments;
|
|
248
284
|
if (segments[1] === "preset" && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
|
|
249
285
|
return { type: segments[2] };
|
|
250
286
|
}
|
|
@@ -272,6 +308,60 @@ function parseCtxDateExpression(value) {
|
|
|
272
308
|
return void 0;
|
|
273
309
|
}
|
|
274
310
|
__name(parseCtxDateExpression, "parseCtxDateExpression");
|
|
311
|
+
function parseCtxDateExpressionConfig(value) {
|
|
312
|
+
if (!isCtxDateExpression(value)) return void 0;
|
|
313
|
+
const segments = withDatePrefix(parseCtxDateSegments(value) || []);
|
|
314
|
+
const formatted = splitFormattedDateSegments(segments);
|
|
315
|
+
if (!formatted) return void 0;
|
|
316
|
+
const parsed = parseCtxDateExpression(value);
|
|
317
|
+
const formatConfig = formatted.format ? { format: formatted.format } : {};
|
|
318
|
+
if (typeof parsed === "string") {
|
|
319
|
+
return { kind: "exact", value: parsed, ...formatConfig };
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(parsed) && parsed.length === 2 && typeof parsed[0] === "string" && typeof parsed[1] === "string") {
|
|
322
|
+
return { kind: "exact", value: [parsed[0], parsed[1]], ...formatConfig };
|
|
323
|
+
}
|
|
324
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
325
|
+
const typed = parsed;
|
|
326
|
+
if (typed.type === "past" || typed.type === "next") {
|
|
327
|
+
if (typeof typed.unit !== "string" || !RELATIVE_UNITS.has(typed.unit) || typeof typed.number !== "number") {
|
|
328
|
+
return void 0;
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
kind: "relative",
|
|
332
|
+
direction: typed.type,
|
|
333
|
+
amount: typed.number,
|
|
334
|
+
unit: typed.unit,
|
|
335
|
+
...formatConfig
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (typeof typed.type === "string" && PRESET_KEYS.has(typed.type)) {
|
|
339
|
+
return { kind: "preset", preset: typed.type, ...formatConfig };
|
|
340
|
+
}
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
__name(parseCtxDateExpressionConfig, "parseCtxDateExpressionConfig");
|
|
344
|
+
function serializeCtxDateExpressionConfig(config) {
|
|
345
|
+
let legacyValue;
|
|
346
|
+
if (config.kind === "preset") {
|
|
347
|
+
if (!PRESET_KEYS.has(config.preset)) return void 0;
|
|
348
|
+
legacyValue = { type: config.preset };
|
|
349
|
+
} else if (config.kind === "relative") {
|
|
350
|
+
if (!RELATIVE_DIRECTIONS.has(config.direction) || !RELATIVE_UNITS.has(config.unit)) return void 0;
|
|
351
|
+
const amount = Math.floor(Number(config.amount));
|
|
352
|
+
if (!Number.isFinite(amount) || amount <= 0) return void 0;
|
|
353
|
+
legacyValue = { type: config.direction, unit: config.unit, number: amount };
|
|
354
|
+
} else {
|
|
355
|
+
legacyValue = config.value;
|
|
356
|
+
}
|
|
357
|
+
const expression = serializeCtxDateValue(legacyValue);
|
|
358
|
+
if (!expression || !config.format) return expression;
|
|
359
|
+
const format = String(config.format);
|
|
360
|
+
if (!format.trim() || format.length > MAX_DATE_FORMAT_LENGTH) return void 0;
|
|
361
|
+
const segments = withDatePrefix(parseCtxDateSegments(expression) || []);
|
|
362
|
+
return toCtxDateExpression(["date", "format", `v${encodeBase64Url(format)}`, ...segments.slice(1)]);
|
|
363
|
+
}
|
|
364
|
+
__name(serializeCtxDateExpressionConfig, "serializeCtxDateExpressionConfig");
|
|
275
365
|
function serializeCtxDateValue(value) {
|
|
276
366
|
if (isCtxDateExpression(value)) {
|
|
277
367
|
return String(value).trim();
|
|
@@ -317,8 +407,23 @@ function serializeCtxDateValue(value) {
|
|
|
317
407
|
return void 0;
|
|
318
408
|
}
|
|
319
409
|
__name(serializeCtxDateValue, "serializeCtxDateValue");
|
|
410
|
+
function formatResolvedDateValue(value, format) {
|
|
411
|
+
const formatValue = /* @__PURE__ */ __name((item) => {
|
|
412
|
+
if (typeof item !== "string") return item;
|
|
413
|
+
const parsed = (0, import_dayjs.default)(item);
|
|
414
|
+
return parsed.isValid() ? parsed.format(format) : item;
|
|
415
|
+
}, "formatValue");
|
|
416
|
+
return Array.isArray(value) ? value.map(formatValue) : formatValue(value);
|
|
417
|
+
}
|
|
418
|
+
__name(formatResolvedDateValue, "formatResolvedDateValue");
|
|
320
419
|
function resolveCtxDatePath(pathSegments) {
|
|
321
|
-
const
|
|
420
|
+
const rawSegments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
421
|
+
const formatted = splitFormattedDateSegments(rawSegments);
|
|
422
|
+
if (!formatted) return void 0;
|
|
423
|
+
if (formatted.format) {
|
|
424
|
+
return formatResolvedDateValue(resolveCtxDatePath(formatted.baseSegments), formatted.format);
|
|
425
|
+
}
|
|
426
|
+
const segments = formatted.baseSegments;
|
|
322
427
|
if (segments[0] !== "date") return void 0;
|
|
323
428
|
if (segments[1] === "preset" && segments.length === 3) {
|
|
324
429
|
const key = segments[2];
|
|
@@ -375,6 +480,8 @@ __name(resolveCtxDatePath, "resolveCtxDatePath");
|
|
|
375
480
|
isCtxDateExpression,
|
|
376
481
|
isCtxDatePathPrefix,
|
|
377
482
|
parseCtxDateExpression,
|
|
483
|
+
parseCtxDateExpressionConfig,
|
|
378
484
|
resolveCtxDatePath,
|
|
485
|
+
serializeCtxDateExpressionConfig,
|
|
379
486
|
serializeCtxDateValue
|
|
380
487
|
});
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -11,17 +11,17 @@ export { escapeT, getT, tExpr } from './translation';
|
|
|
11
11
|
export { FlowCancelSaveException, FlowExitAllException, FlowExitException } from './exceptions';
|
|
12
12
|
export { defineAction } from './flow-definitions';
|
|
13
13
|
export { isInheritedFrom } from './inheritance';
|
|
14
|
-
export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
|
|
14
|
+
export { buildFlowModelResolveDescriptor, resolveCreateModelOptions, resolveDefaultParams, resolveExpressions, } from './params-resolvers';
|
|
15
15
|
export { compileUiSchema, resolveStepUiSchema, resolveStepDisabledInSettings, resolveUiMode, shouldHideEventInSettings, shouldHideStepInSettings, } from './schema-utils';
|
|
16
16
|
export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
17
17
|
export { createCollectionContextMeta } from './createCollectionContextMeta';
|
|
18
|
-
export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver } from './associationObjectVariable';
|
|
18
|
+
export { createAssociationAwareObjectMetaFactory, createAssociationSubpathResolver, getAssociationFilterByTk, } from './associationObjectVariable';
|
|
19
19
|
export { buildRecordMeta, collectContextParamsForTemplate, createCurrentRecordMetaFactory, createRecordResolveOnServerWithLocal, createRecordMetaFactory, extractUsedVariableNames, extractUsedVariablePaths, inferRecordRef, type RecordParamsBuilder, } from './variablesParams';
|
|
20
20
|
export { extractPropertyPath, formatPathToVariable, isVariableExpression } from './context';
|
|
21
21
|
export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
|
|
22
22
|
export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
|
|
23
23
|
export { createOpenViewRouteState, decodeOpenViewRouteState, encodeOpenViewRouteState, isOpenViewRouteStateToken, RUNJS_OPEN_VIEW_ROUTE_STATE, type OpenViewRouteMode, type OpenViewRouteSize, type OpenViewRouteState, } from './openViewRouteState';
|
|
24
|
-
export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, resolveCtxDatePath, serializeCtxDateValue, } from './dateVariable';
|
|
24
|
+
export { decodeBase64Url, encodeBase64Url, isCompleteCtxDatePath, isCtxDatePathPrefix, isCtxDateExpression, parseCtxDateExpression, parseCtxDateExpressionConfig, resolveCtxDatePath, serializeCtxDateExpressionConfig, serializeCtxDateValue, type CtxDateExpressionConfig, type CtxDatePreset, type CtxDateRelativeDirection, type CtxDateRelativeUnit, } from './dateVariable';
|
|
25
25
|
export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
|
|
26
26
|
export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
|
|
27
27
|
export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
|
package/lib/utils/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __export(utils_exports, {
|
|
|
34
34
|
FlowExitException: () => import_exceptions.FlowExitException,
|
|
35
35
|
MENU_KEYS: () => import_constants.MENU_KEYS,
|
|
36
36
|
RUNJS_OPEN_VIEW_ROUTE_STATE: () => import_openViewRouteState.RUNJS_OPEN_VIEW_ROUTE_STATE,
|
|
37
|
+
buildFlowModelResolveDescriptor: () => import_params_resolvers.buildFlowModelResolveDescriptor,
|
|
37
38
|
buildRecordMeta: () => import_variablesParams.buildRecordMeta,
|
|
38
39
|
clearAutoFlowError: () => import_autoFlowError.clearAutoFlowError,
|
|
39
40
|
collectContextParamsForTemplate: () => import_variablesParams.collectContextParamsForTemplate,
|
|
@@ -57,6 +58,7 @@ __export(utils_exports, {
|
|
|
57
58
|
extractUsedVariablePaths: () => import_variablesParams.extractUsedVariablePaths,
|
|
58
59
|
extractUsedVariablePathsFromRunJS: () => import_runjsValue.extractUsedVariablePathsFromRunJS,
|
|
59
60
|
formatPathToVariable: () => import_context.formatPathToVariable,
|
|
61
|
+
getAssociationFilterByTk: () => import_associationObjectVariable.getAssociationFilterByTk,
|
|
60
62
|
getAutoFlowError: () => import_autoFlowError.getAutoFlowError,
|
|
61
63
|
getT: () => import_translation.getT,
|
|
62
64
|
inferRecordRef: () => import_variablesParams.inferRecordRef,
|
|
@@ -71,6 +73,7 @@ __export(utils_exports, {
|
|
|
71
73
|
isVariableExpression: () => import_context.isVariableExpression,
|
|
72
74
|
normalizeRunJSValue: () => import_runjsValue.normalizeRunJSValue,
|
|
73
75
|
parseCtxDateExpression: () => import_dateVariable.parseCtxDateExpression,
|
|
76
|
+
parseCtxDateExpressionConfig: () => import_dateVariable.parseCtxDateExpressionConfig,
|
|
74
77
|
parsePathnameToViewParams: () => import_parsePathnameToViewParams.parsePathnameToViewParams,
|
|
75
78
|
prepareRunJsCode: () => import_runjsTemplateCompat.prepareRunJsCode,
|
|
76
79
|
preprocessRunJsTemplates: () => import_runjsTemplateCompat.preprocessRunJsTemplates,
|
|
@@ -85,6 +88,7 @@ __export(utils_exports, {
|
|
|
85
88
|
resolveStepDisabledInSettings: () => import_schema_utils.resolveStepDisabledInSettings,
|
|
86
89
|
resolveStepUiSchema: () => import_schema_utils.resolveStepUiSchema,
|
|
87
90
|
resolveUiMode: () => import_schema_utils.resolveUiMode,
|
|
91
|
+
serializeCtxDateExpressionConfig: () => import_dateVariable.serializeCtxDateExpressionConfig,
|
|
88
92
|
serializeCtxDateValue: () => import_dateVariable.serializeCtxDateValue,
|
|
89
93
|
setAutoFlowError: () => import_autoFlowError.setAutoFlowError,
|
|
90
94
|
setupRuntimeContextSteps: () => import_setupRuntimeContextSteps.setupRuntimeContextSteps,
|
|
@@ -127,6 +131,7 @@ var import_randomId = require("./randomId");
|
|
|
127
131
|
FlowExitException,
|
|
128
132
|
MENU_KEYS,
|
|
129
133
|
RUNJS_OPEN_VIEW_ROUTE_STATE,
|
|
134
|
+
buildFlowModelResolveDescriptor,
|
|
130
135
|
buildRecordMeta,
|
|
131
136
|
clearAutoFlowError,
|
|
132
137
|
collectContextParamsForTemplate,
|
|
@@ -150,6 +155,7 @@ var import_randomId = require("./randomId");
|
|
|
150
155
|
extractUsedVariablePaths,
|
|
151
156
|
extractUsedVariablePathsFromRunJS,
|
|
152
157
|
formatPathToVariable,
|
|
158
|
+
getAssociationFilterByTk,
|
|
153
159
|
getAutoFlowError,
|
|
154
160
|
getT,
|
|
155
161
|
inferRecordRef,
|
|
@@ -164,6 +170,7 @@ var import_randomId = require("./randomId");
|
|
|
164
170
|
isVariableExpression,
|
|
165
171
|
normalizeRunJSValue,
|
|
166
172
|
parseCtxDateExpression,
|
|
173
|
+
parseCtxDateExpressionConfig,
|
|
167
174
|
parsePathnameToViewParams,
|
|
168
175
|
prepareRunJsCode,
|
|
169
176
|
preprocessRunJsTemplates,
|
|
@@ -178,6 +185,7 @@ var import_randomId = require("./randomId");
|
|
|
178
185
|
resolveStepDisabledInSettings,
|
|
179
186
|
resolveStepUiSchema,
|
|
180
187
|
resolveUiMode,
|
|
188
|
+
serializeCtxDateExpressionConfig,
|
|
181
189
|
serializeCtxDateValue,
|
|
182
190
|
setAutoFlowError,
|
|
183
191
|
setupRuntimeContextSteps,
|
|
@@ -27,10 +27,13 @@ export type JSONValue = string | {
|
|
|
27
27
|
[key: string]: JSONValue;
|
|
28
28
|
} | JSONValue[];
|
|
29
29
|
type BatchPayload = {
|
|
30
|
+
contractRd?: string;
|
|
31
|
+
rd?: string;
|
|
30
32
|
template: JSONValue;
|
|
31
33
|
contextParams?: ServerContextParams | undefined;
|
|
32
34
|
};
|
|
33
35
|
export declare function enqueueVariablesResolve(ctx: FlowRuntimeContext, payload: BatchPayload): Promise<unknown>;
|
|
36
|
+
export declare function buildFlowModelResolveDescriptor(ctx: Pick<FlowModelContext, 'api'>, flowModelUid?: string | number | null): any;
|
|
34
37
|
/**
|
|
35
38
|
* 解析参数中的 {{xxx}} 表达式,自动处理异步属性访问
|
|
36
39
|
*/
|
|
@@ -37,6 +37,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
37
37
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
38
|
var params_resolvers_exports = {};
|
|
39
39
|
__export(params_resolvers_exports, {
|
|
40
|
+
buildFlowModelResolveDescriptor: () => buildFlowModelResolveDescriptor,
|
|
40
41
|
enqueueVariablesResolve: () => enqueueVariablesResolve,
|
|
41
42
|
preprocessExpression: () => preprocessExpression,
|
|
42
43
|
resolveCreateModelOptions: () => resolveCreateModelOptions,
|
|
@@ -45,6 +46,7 @@ __export(params_resolvers_exports, {
|
|
|
45
46
|
});
|
|
46
47
|
module.exports = __toCommonJS(params_resolvers_exports);
|
|
47
48
|
var import_shared = require("@nocobase/shared");
|
|
49
|
+
var import_client = require("@nocobase/utils/client");
|
|
48
50
|
var import_lodash = __toESM(require("lodash"));
|
|
49
51
|
async function resolveDefaultParams(defaultParams, ctx) {
|
|
50
52
|
if (!defaultParams) {
|
|
@@ -144,6 +146,8 @@ function enqueueVariablesResolve(ctx, payload) {
|
|
|
144
146
|
try {
|
|
145
147
|
const batch = items.map((it) => ({
|
|
146
148
|
id: it.id,
|
|
149
|
+
contractRd: it.payload.contractRd,
|
|
150
|
+
rd: it.payload.rd,
|
|
147
151
|
template: it.payload.template,
|
|
148
152
|
contextParams: it.payload.contextParams || {}
|
|
149
153
|
}));
|
|
@@ -187,6 +191,11 @@ function enqueueVariablesResolve(ctx, payload) {
|
|
|
187
191
|
return p;
|
|
188
192
|
}
|
|
189
193
|
__name(enqueueVariablesResolve, "enqueueVariablesResolve");
|
|
194
|
+
function buildFlowModelResolveDescriptor(ctx, flowModelUid) {
|
|
195
|
+
var _a, _b;
|
|
196
|
+
return (0, import_client.generateFlowModelRdFromToken)(flowModelUid, (_b = (_a = ctx == null ? void 0 : ctx.api) == null ? void 0 : _a.auth) == null ? void 0 : _b.token);
|
|
197
|
+
}
|
|
198
|
+
__name(buildFlowModelResolveDescriptor, "buildFlowModelResolveDescriptor");
|
|
190
199
|
async function resolveExpressions(params, ctx) {
|
|
191
200
|
const compile = /* @__PURE__ */ __name(async (source) => {
|
|
192
201
|
if (typeof source === "string" && /\{\{.*?\}\}/.test(source)) {
|
|
@@ -308,6 +317,7 @@ async function compileExpression(expression, ctx) {
|
|
|
308
317
|
__name(compileExpression, "compileExpression");
|
|
309
318
|
// Annotate the CommonJS export names for ESM import in node:
|
|
310
319
|
0 && (module.exports = {
|
|
320
|
+
buildFlowModelResolveDescriptor,
|
|
311
321
|
enqueueVariablesResolve,
|
|
312
322
|
preprocessExpression,
|
|
313
323
|
resolveCreateModelOptions,
|
|
@@ -210,6 +210,11 @@ async function collectContextParamsForTemplate(ctx, template) {
|
|
|
210
210
|
input[key] = built;
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
const viewPaths = usage.view || [];
|
|
214
|
+
if (!input.view && viewPaths.some((path) => path === "record" || path.startsWith("record.") || path.startsWith("record["))) {
|
|
215
|
+
const recordRef = inferViewRecordRef(ctx);
|
|
216
|
+
if (recordRef) input.view = { record: recordRef };
|
|
217
|
+
}
|
|
213
218
|
return (0, import_serverContextParams.buildServerContextParams)(ctx, input);
|
|
214
219
|
}
|
|
215
220
|
__name(collectContextParamsForTemplate, "collectContextParamsForTemplate");
|
|
@@ -28,6 +28,7 @@ interface PopupNodeResource {
|
|
|
28
28
|
interface PopupNode {
|
|
29
29
|
uid?: string;
|
|
30
30
|
resource: PopupNodeResource;
|
|
31
|
+
sourceRecord?: unknown;
|
|
31
32
|
parent?: PopupNode;
|
|
32
33
|
}
|
|
33
34
|
export declare function buildPopupRuntime(ctx: FlowContext, view: FlowView): Promise<PopupNode | undefined>;
|
|
@@ -33,6 +33,13 @@ __export(createViewMeta_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(createViewMeta_exports);
|
|
35
35
|
var import_variablesParams = require("../utils/variablesParams");
|
|
36
|
+
function buildPopupSourceRecordRef(ref) {
|
|
37
|
+
if ((ref == null ? void 0 : ref.sourceId) == null || ref.sourceId === "" || typeof ref.associationName !== "string") return void 0;
|
|
38
|
+
const collection = ref.associationName.split(".")[0];
|
|
39
|
+
if (!collection) return void 0;
|
|
40
|
+
return { collection, dataSourceKey: ref.dataSourceKey || "main", filterByTk: ref.sourceId };
|
|
41
|
+
}
|
|
42
|
+
__name(buildPopupSourceRecordRef, "buildPopupSourceRecordRef");
|
|
36
43
|
function isDefined(value) {
|
|
37
44
|
return value !== void 0 && value !== null;
|
|
38
45
|
}
|
|
@@ -306,9 +313,11 @@ function createPopupMeta(ctx, anchorView) {
|
|
|
306
313
|
let level = 1;
|
|
307
314
|
let parentRef = await getParentRecordRef(level, c);
|
|
308
315
|
while (parentRef) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
316
|
+
const parent = { record: parentRef };
|
|
317
|
+
const sourceRecord = buildPopupSourceRecordRef(parentRef);
|
|
318
|
+
if (sourceRecord) parent.sourceRecord = sourceRecord;
|
|
319
|
+
cur.parent = parent;
|
|
320
|
+
cur = parent;
|
|
312
321
|
level += 1;
|
|
313
322
|
parentRef = await getParentRecordRef(level, c);
|
|
314
323
|
}
|
|
@@ -317,19 +326,12 @@ function createPopupMeta(ctx, anchorView) {
|
|
|
317
326
|
(_b = (_a = c.logger) == null ? void 0 : _a.debug) == null ? void 0 : _b.call(_a, { err }, "[FlowEngine] buildVariablesParams: build parent-chain failed");
|
|
318
327
|
}
|
|
319
328
|
try {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
params.sourceRecord = {
|
|
327
|
-
collection: parentCollectionName,
|
|
328
|
-
dataSourceKey: dsKey,
|
|
329
|
-
filterByTk: srcId
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
}
|
|
329
|
+
const sourceRecord = buildPopupSourceRecordRef({
|
|
330
|
+
sourceId: inputArgs == null ? void 0 : inputArgs.sourceId,
|
|
331
|
+
associationName: inputArgs == null ? void 0 : inputArgs.associationName,
|
|
332
|
+
dataSourceKey: inputArgs == null ? void 0 : inputArgs.dataSourceKey
|
|
333
|
+
});
|
|
334
|
+
if (sourceRecord) params.sourceRecord = sourceRecord;
|
|
333
335
|
} catch (err) {
|
|
334
336
|
(_d = (_c = c.logger) == null ? void 0 : _c.debug) == null ? void 0 : _d.call(_c, { err }, "[FlowEngine] buildVariablesParams: infer sourceRecord failed");
|
|
335
337
|
}
|
|
@@ -426,10 +428,11 @@ function createPopupMeta(ctx, anchorView) {
|
|
|
426
428
|
}
|
|
427
429
|
__name(createPopupMeta, "createPopupMeta");
|
|
428
430
|
async function buildPopupRuntime(ctx, view) {
|
|
429
|
-
var _a;
|
|
431
|
+
var _a, _b, _c;
|
|
430
432
|
const stack = getViewStack(view);
|
|
431
433
|
const currentIndex = getAnchoredViewStackIndex(view, stack);
|
|
432
|
-
const
|
|
434
|
+
const sourceRecord = (_b = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem) == null ? void 0 : _b.value;
|
|
435
|
+
const openerUids = (_c = view == null ? void 0 : view.inputArgs) == null ? void 0 : _c.openerUids;
|
|
433
436
|
const hasOpener = Array.isArray(openerUids) && openerUids.length > 0;
|
|
434
437
|
const hasStackPopup = currentIndex >= 1;
|
|
435
438
|
const isPopup = hasStackPopup || hasOpener;
|
|
@@ -446,16 +449,17 @@ async function buildPopupRuntime(ctx, view) {
|
|
|
446
449
|
associationName: args.associationName,
|
|
447
450
|
filterByTk: args.filterByTk,
|
|
448
451
|
sourceId: args.sourceId
|
|
449
|
-
}
|
|
452
|
+
},
|
|
453
|
+
...typeof sourceRecord !== "undefined" ? { sourceRecord } : {}
|
|
450
454
|
};
|
|
451
455
|
}
|
|
452
456
|
const buildNode = /* @__PURE__ */ __name(async (idx) => {
|
|
453
|
-
var _a2,
|
|
457
|
+
var _a2, _b2, _c2, _d, _e, _f;
|
|
454
458
|
if (idx < 0 || !((_a2 = stack[idx]) == null ? void 0 : _a2.viewUid)) return void 0;
|
|
455
459
|
const viewUid = stack[idx].viewUid;
|
|
456
|
-
let model = (
|
|
460
|
+
let model = (_b2 = ctx.engine) == null ? void 0 : _b2.getModel(viewUid, true);
|
|
457
461
|
if (!model) {
|
|
458
|
-
model = await ((
|
|
462
|
+
model = await ((_c2 = ctx.engine) == null ? void 0 : _c2.loadModel({ uid: viewUid }));
|
|
459
463
|
}
|
|
460
464
|
const p = ((_d = model == null ? void 0 : model.getStepParams) == null ? void 0 : _d.call(model, "popupSettings", "openView")) || {};
|
|
461
465
|
const collectionName = p == null ? void 0 : p.collectionName;
|
|
@@ -475,16 +479,43 @@ async function buildPopupRuntime(ctx, view) {
|
|
|
475
479
|
return node;
|
|
476
480
|
}, "buildNode");
|
|
477
481
|
const currentNode = await buildNode(currentIndex);
|
|
482
|
+
if (currentNode && typeof sourceRecord !== "undefined") {
|
|
483
|
+
currentNode.sourceRecord = sourceRecord;
|
|
484
|
+
}
|
|
478
485
|
return currentNode;
|
|
479
486
|
}
|
|
480
487
|
__name(buildPopupRuntime, "buildPopupRuntime");
|
|
481
488
|
function registerPopupVariable(ctx, view) {
|
|
482
489
|
const POPUP_SERVER_PATH_RE = /^(?:record|sourceRecord)(?:\.|$)|^parent(?:\.parent)*(?:\.(?:record|sourceRecord))(?:\.|$)/;
|
|
490
|
+
const shouldResolveSourceRecordOnServer = /* @__PURE__ */ __name((path) => {
|
|
491
|
+
var _a, _b;
|
|
492
|
+
if (path !== "sourceRecord" && !path.startsWith("sourceRecord.")) return false;
|
|
493
|
+
const parentItem = (_a = view == null ? void 0 : view.inputArgs) == null ? void 0 : _a.parentItem;
|
|
494
|
+
if (typeof (parentItem == null ? void 0 : parentItem.value) === "undefined") return true;
|
|
495
|
+
const sourcePath = path === "sourceRecord" ? "" : path.slice("sourceRecord.".length);
|
|
496
|
+
if (!sourcePath) return false;
|
|
497
|
+
const parentItemResolver = (_b = view == null ? void 0 : view.inputArgs) == null ? void 0 : _b.parentItemResolver;
|
|
498
|
+
if (typeof parentItemResolver === "function") {
|
|
499
|
+
return parentItemResolver(`value.${sourcePath}`);
|
|
500
|
+
}
|
|
501
|
+
const segments = sourcePath.split(".").filter(Boolean);
|
|
502
|
+
let current = parentItem.value;
|
|
503
|
+
for (const segment of segments) {
|
|
504
|
+
if (current === null || typeof current !== "object" || !(segment in current)) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
current = current[segment];
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
510
|
+
}, "shouldResolveSourceRecordOnServer");
|
|
483
511
|
ctx.defineProperty("popup", {
|
|
484
512
|
get: /* @__PURE__ */ __name(async () => buildPopupRuntime(ctx, view), "get"),
|
|
485
513
|
meta: createPopupMeta(ctx, view),
|
|
486
514
|
resolveOnServer: /* @__PURE__ */ __name((p) => {
|
|
487
515
|
try {
|
|
516
|
+
if (p === "sourceRecord" || p.startsWith("sourceRecord.")) {
|
|
517
|
+
return shouldResolveSourceRecordOnServer(p);
|
|
518
|
+
}
|
|
488
519
|
return !!p && POPUP_SERVER_PATH_RE.test(p);
|
|
489
520
|
} catch (_) {
|
|
490
521
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.2.0-beta.
|
|
3
|
+
"version": "2.2.0-beta.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.2.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.2.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.2.0-beta.17",
|
|
12
|
+
"@nocobase/shared": "2.2.0-beta.17",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "d7a69c4f47fe38b17a2ce1408258902cf64f6c83"
|
|
41
41
|
}
|
|
@@ -11,7 +11,7 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
11
11
|
import { FlowContext } from '../flowContext';
|
|
12
12
|
import { FlowEngine } from '../flowEngine';
|
|
13
13
|
import type { FlowView } from '../views/FlowView';
|
|
14
|
-
import { buildPopupRuntime, createPopupMeta } from '../views/createViewMeta';
|
|
14
|
+
import { buildPopupRuntime, createPopupMeta, registerPopupVariable } from '../views/createViewMeta';
|
|
15
15
|
|
|
16
16
|
describe('createPopupMeta - popup variables', () => {
|
|
17
17
|
function makeCtx() {
|
|
@@ -167,6 +167,7 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
167
167
|
it('buildPopupRuntime anchors current popup even when the navigation stack already has a child popup', async () => {
|
|
168
168
|
const { engine, ctx } = makeCtx();
|
|
169
169
|
const parentView = makeNestedPopupView('parent-popup-uid', 13);
|
|
170
|
+
parentView.inputArgs.parentItem = { value: { id: 13, phone: '9999' } };
|
|
170
171
|
mockNestedPopupModels(engine);
|
|
171
172
|
|
|
172
173
|
const popup = await buildPopupRuntime(ctx, parentView);
|
|
@@ -179,6 +180,7 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
179
180
|
filterByTk: 13,
|
|
180
181
|
sourceId: 13,
|
|
181
182
|
});
|
|
183
|
+
expect(popup?.sourceRecord).toEqual({ id: 13, phone: '9999' });
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
it('buildVariablesParams(record) keeps the parent view record when a child popup is open', async () => {
|
|
@@ -314,4 +316,85 @@ describe('createPopupMeta - popup variables', () => {
|
|
|
314
316
|
const props = typeof meta.properties === 'function' ? await (meta.properties as any)() : meta.properties || {};
|
|
315
317
|
expect(props.record).toBeUndefined();
|
|
316
318
|
});
|
|
319
|
+
|
|
320
|
+
it('uses the current parent item value for popup sourceRecord fields', async () => {
|
|
321
|
+
const { ctx } = makeCtx();
|
|
322
|
+
const parentItemResolver = vi.fn(() => false);
|
|
323
|
+
const anchorView: FlowView = {
|
|
324
|
+
type: 'dialog',
|
|
325
|
+
inputArgs: {
|
|
326
|
+
openerUids: ['opener-uid-1'],
|
|
327
|
+
viewUid: 'popup-uid',
|
|
328
|
+
dataSourceKey: 'main',
|
|
329
|
+
collectionName: 'roles',
|
|
330
|
+
associationName: 'users.roles',
|
|
331
|
+
sourceId: 1,
|
|
332
|
+
parentItem: { value: { id: 1, phone: '9999' } },
|
|
333
|
+
parentItemResolver,
|
|
334
|
+
},
|
|
335
|
+
Header: null,
|
|
336
|
+
Footer: null,
|
|
337
|
+
close: () => void 0,
|
|
338
|
+
update: () => void 0,
|
|
339
|
+
} as any;
|
|
340
|
+
|
|
341
|
+
registerPopupVariable(ctx, anchorView);
|
|
342
|
+
|
|
343
|
+
await expect(ctx.popup).resolves.toMatchObject({
|
|
344
|
+
sourceRecord: { id: 1, phone: '9999' },
|
|
345
|
+
});
|
|
346
|
+
expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.phone')).toBe(false);
|
|
347
|
+
expect(parentItemResolver).toHaveBeenCalledWith('value.phone');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('resolves a popup sourceRecord field from the current parent item without a server request', async () => {
|
|
351
|
+
const engine = new FlowEngine();
|
|
352
|
+
const ctx = engine.context;
|
|
353
|
+
const anchorView: FlowView = {
|
|
354
|
+
type: 'dialog',
|
|
355
|
+
inputArgs: {
|
|
356
|
+
openerUids: ['opener-uid-1'],
|
|
357
|
+
viewUid: 'popup-uid',
|
|
358
|
+
dataSourceKey: 'main',
|
|
359
|
+
collectionName: 'roles',
|
|
360
|
+
associationName: 'users.roles',
|
|
361
|
+
sourceId: 1,
|
|
362
|
+
parentItem: { value: { id: 1, phone: '9999' } },
|
|
363
|
+
parentItemResolver: () => false,
|
|
364
|
+
},
|
|
365
|
+
Header: null,
|
|
366
|
+
Footer: null,
|
|
367
|
+
close: () => void 0,
|
|
368
|
+
update: () => void 0,
|
|
369
|
+
} as any;
|
|
370
|
+
|
|
371
|
+
registerPopupVariable(ctx, anchorView);
|
|
372
|
+
|
|
373
|
+
await expect(ctx.resolveJsonTemplate('{{ ctx.popup.sourceRecord.phone }}')).resolves.toBe('9999');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('keeps server resolution for popup sourceRecord association subpaths', async () => {
|
|
377
|
+
const { ctx } = makeCtx();
|
|
378
|
+
const anchorView: FlowView = {
|
|
379
|
+
type: 'dialog',
|
|
380
|
+
inputArgs: {
|
|
381
|
+
openerUids: ['opener-uid-1'],
|
|
382
|
+
viewUid: 'popup-uid',
|
|
383
|
+
dataSourceKey: 'main',
|
|
384
|
+
collectionName: 'roles',
|
|
385
|
+
associationName: 'users.roles',
|
|
386
|
+
sourceId: 1,
|
|
387
|
+
parentItem: { value: { id: 1, departmentId: 2 } },
|
|
388
|
+
parentItemResolver: (path: string) => path === 'value.department.title',
|
|
389
|
+
},
|
|
390
|
+
Header: null,
|
|
391
|
+
Footer: null,
|
|
392
|
+
close: () => void 0,
|
|
393
|
+
update: () => void 0,
|
|
394
|
+
} as any;
|
|
395
|
+
|
|
396
|
+
registerPopupVariable(ctx, anchorView);
|
|
397
|
+
|
|
398
|
+
expect(ctx.getPropertyOptions('popup')?.resolveOnServer?.('sourceRecord.department.title')).toBe(true);
|
|
399
|
+
});
|
|
317
400
|
});
|