openxiangda 1.0.124 → 1.0.125
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/README.md +1 -0
- package/openxiangda-skills/SKILL.md +1 -1
- package/openxiangda-skills/references/data-views.md +10 -6
- package/openxiangda-skills/references/pages/page-sdk.md +27 -7
- package/openxiangda-skills/references/workflow-v3.md +2 -0
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +1 -0
- package/package.json +1 -1
- package/packages/sdk/dist/runtime/index.cjs +19 -0
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +19 -0
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +19 -0
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +88 -1
- package/packages/sdk/dist/runtime/react.d.ts +88 -1
- package/packages/sdk/dist/runtime/react.mjs +19 -0
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
package/README.md
CHANGED
|
@@ -335,6 +335,7 @@ AI-authored workflows can use compile-time SDK resources:
|
|
|
335
335
|
- Read-only preview: `preview.json`
|
|
336
336
|
- Local check: `openxiangda workflow compile src/workflows/<resourceCode>/workflow.ts --check`
|
|
337
337
|
- Runtime operations: use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react` instead of hard-coded workflow buttons.
|
|
338
|
+
- Runtime task centers: use `sdk.workCenter.listItems({ boxType: "todo" })` and `sdk.workCenter.getStats()` for the current user's todo/done/cc/initiated lists and counts; do not build end-user todo pages by reading workflow operation logs or raw process-task tables.
|
|
338
339
|
- Delayed approval start: save a process-form draft without starting workflow via `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`; later call `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or render `StandardFormPage` with `submitBehavior="start-existing-process"` to start approval in place. Do not create a second process instance and delete the draft.
|
|
339
340
|
- AI workflow cookbook and action payload table: read `openxiangda-skills/references/workflow-v3.md` for the draft -> start existing instance -> return to initiator -> resubmit flow, and for operation payload details such as `approve` using process `instanceId` while `transfer` / `return` / `resubmit` use `taskId`.
|
|
340
341
|
|
|
@@ -164,7 +164,7 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
|
|
|
164
164
|
- Use JS_CODE for node-local cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and backend trigger orchestration. For logic that pages, automations, and workflows should reuse through a stable backend entry, prefer App Function. Do not use JS_CODE for simple UI interactions, ordinary form validation, or display-only page behavior.
|
|
165
165
|
- For workflow/automation JS_CODE nodes, prefer V2 `runtimeMode: "trusted_node"`. AI-authored source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. `pnpm build-js-code --script <scriptCode>` runs TypeScript validation before bundling, and `sourceFile.localPath` should point to the TS source; the CLI builds, uploads, and replaces it with snapshot metadata during validate/create.
|
|
166
166
|
- Form permission group resources must have stable local codes. Use unique `code` values such as `ticket_reporter_view` and `ticket_repairer_view`; do not rely on the form code when one form has multiple groups.
|
|
167
|
-
- Workflow resources should prefer `workflowFile` + `defineWorkflow` semantic DSL over hand-written raw JSON. For workflows bound by `formCode`, run `openxiangda workspace publish --form <formCode>` first so the process form schema/table is initialized, then run `openxiangda workflow compile <workflow.ts> --check` and `openxiangda resource publish --dry-run --profile <name>` before publish; use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react` for runtime buttons and timelines instead of hard-coded operation lists. For delayed approval start from an existing saved draft, first save without starting workflow via `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`; later use `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`; do not create a duplicate process instance and delete the draft. For custom workflow UI, return-to-initiator, resubmit, and action payload details, read `references/workflow-v3.md` sections `Cookbook: draft, start, return, resubmit` and `Capability action reference`.
|
|
167
|
+
- Workflow resources should prefer `workflowFile` + `defineWorkflow` semantic DSL over hand-written raw JSON. For workflows bound by `formCode`, run `openxiangda workspace publish --form <formCode>` first so the process form schema/table is initialized, then run `openxiangda workflow compile <workflow.ts> --check` and `openxiangda resource publish --dry-run --profile <name>` before publish; use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react` for runtime buttons and timelines instead of hard-coded operation lists. For end-user task centers, use `sdk.workCenter.listItems({ boxType: "todo" | "done" | "cc" | "initiated" })` and `sdk.workCenter.getStats()` instead of reading workflow logs or process-task tables. For delayed approval start from an existing saved draft, first save without starting workflow via `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`; later use `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`; do not create a duplicate process instance and delete the draft. For custom workflow UI, return-to-initiator, resubmit, and action payload details, read `references/workflow-v3.md` sections `Cookbook: draft, start, return, resubmit` and `Capability action reference`.
|
|
168
168
|
- Workflow resources can be created as drafts. After resource publish, verify `openxiangda workflow list --profile <name> --json` and run `openxiangda workflow publish <workflowCode> --profile <name>` until `isPublished: true` is visible. `workflow list` hides platform shell workflows with empty `resourceCode` by default; pass `--all` only when diagnosing raw platform rows.
|
|
169
169
|
|
|
170
170
|
## Subskills
|
|
@@ -149,6 +149,9 @@ Filters:
|
|
|
149
149
|
- Runtime query filters use output aliases such as `ticketTitle`.
|
|
150
150
|
- Aggregate definition `having` and runtime stats `having` also use output aliases such as `ticketCount`.
|
|
151
151
|
- Supported operators include `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`, `contains`, `notContains`, `in`, `isEmpty`, and `isNotEmpty`, with aliases such as `eq`, `neq`, `gte`, `lte`, `like`, `is_null`, and `is_not_null`.
|
|
152
|
+
- Runtime SDK filters may be a search group/rule object or an array, for example `{ key: "statusValue", operator: "EQ", value: "已发布" }`; `field` is also accepted. Use output aliases, not source references.
|
|
153
|
+
- If a row view outputs a full option-like JSON field, runtime string `EQ` and `IN` filters can match the JSON text for compatibility. For new data views, prefer selecting `.value` or `.label` into scalar aliases such as `statusValue` and indexing those aliases for exact, predictable filters.
|
|
154
|
+
- Data view `permissionGroups.dataPermission` should use scalar hidden scope aliases such as `ownerUserScopeKey`, `unionGroupScopeKey`, or `departmentScopeKey`. Do not rely on fuzzy JSON option matching for permission boundaries.
|
|
152
155
|
|
|
153
156
|
Refresh:
|
|
154
157
|
|
|
@@ -248,7 +251,8 @@ Direct query:
|
|
|
248
251
|
const response = await sdk.dataView.query("ticket_with_customer", {
|
|
249
252
|
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
250
253
|
filters: [
|
|
251
|
-
{
|
|
254
|
+
{ key: "customerName", operator: "contains", value: keyword },
|
|
255
|
+
{ key: "statusValue", operator: "EQ", value: "已发布" },
|
|
252
256
|
],
|
|
253
257
|
order: [{ field: "ticketTitle", isAsc: "y" }],
|
|
254
258
|
currentPage: 1,
|
|
@@ -262,10 +266,10 @@ Aggregate stats query:
|
|
|
262
266
|
const response = await sdk.dataView.stats("ticket_stats_by_customer", {
|
|
263
267
|
fields: ["customerName", "createdMonth", "ticketCount", "totalAmount"],
|
|
264
268
|
filters: [
|
|
265
|
-
{
|
|
269
|
+
{ key: "customerName", operator: "contains", value: keyword },
|
|
266
270
|
],
|
|
267
271
|
having: [
|
|
268
|
-
{
|
|
272
|
+
{ key: "ticketCount", operator: ">", value: 0 },
|
|
269
273
|
],
|
|
270
274
|
order: [{ field: "ticketCount", isAsc: "n" }],
|
|
271
275
|
currentPage: 1,
|
|
@@ -296,7 +300,7 @@ export default {
|
|
|
296
300
|
code: "ticket_with_customer",
|
|
297
301
|
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
298
302
|
defaultFilter: [
|
|
299
|
-
{
|
|
303
|
+
{ key: "ticketTitle", operator: "isNotEmpty" }
|
|
300
304
|
]
|
|
301
305
|
},
|
|
302
306
|
{
|
|
@@ -305,7 +309,7 @@ export default {
|
|
|
305
309
|
code: "ticket_stats_by_customer",
|
|
306
310
|
fields: ["customerName", "ticketCount"],
|
|
307
311
|
defaultHaving: [
|
|
308
|
-
{
|
|
312
|
+
{ key: "ticketCount", operator: ">", value: 0 }
|
|
309
313
|
]
|
|
310
314
|
}
|
|
311
315
|
]
|
|
@@ -313,7 +317,7 @@ export default {
|
|
|
313
317
|
|
|
314
318
|
const response = await sdk.dataSource.run("tickets", {
|
|
315
319
|
filters: [
|
|
316
|
-
{
|
|
320
|
+
{ key: "customerName", operator: "contains", value: keyword }
|
|
317
321
|
],
|
|
318
322
|
pageSize: 20,
|
|
319
323
|
})
|
|
@@ -10,10 +10,13 @@ Guidelines:
|
|
|
10
10
|
- Data views can be `storageMode: "materialized"` for refreshed report/list data or `storageMode: "live"` for bounded real-time joins. Page code calls the same SDK methods for both modes and should inspect `response.storageMode` / `response.lastRefreshedAt` when freshness is visible to users.
|
|
11
11
|
- Data view runtime queries can filter, sort, paginate, and select only output aliases.
|
|
12
12
|
- Keep data view page calls paginated and field-scoped. Runtime filters/order should use aliases that the data view indexes, especially for aggregate dimensions and date buckets.
|
|
13
|
+
- Runtime data view filters may use `{ key, operator, value }` or `{ field, operator, value }`. Prefer `key` in new page code to match search-expression style.
|
|
14
|
+
- If a data view exposes a full option-like JSON field, runtime string `EQ` and `IN` filters can match it for compatibility. New pages should prefer scalar output aliases such as `statusValue` or `statusLabel` declared by the data view.
|
|
13
15
|
- Use `sdk.dataSource.run()` with a page data source descriptor when the page config should own the data view code, default fields, or default filters.
|
|
14
16
|
- Use `sdk.function.invoke(code, { input })` for reusable backend business logic declared under `src/resources/functions/` and `src/functions/`. Do not implement multi-form orchestration, connector fan-out, notification orchestration, or permission-sensitive backend rules directly in a page component.
|
|
15
17
|
- Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
|
|
16
18
|
- Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
|
|
19
|
+
- Use `sdk.workCenter.listItems({ boxType })` and `sdk.workCenter.getStats()` for current-user work center lists and counts. `boxType` is one of `todo`, `done`, `cc`, or `initiated`. This is the end-user task/handled/cc/initiated surface; do not build todo pages by reading workflow operation logs, automation logs, or raw process-task tables.
|
|
17
20
|
- Use `createAuthClient` from `openxiangda/runtime` or `LoginPage` / `useAuth` from `openxiangda/runtime/react` for application login pages. Auth provider App Functions return identity assertions only; platform auth owns create/bind/reject/token decisions.
|
|
18
21
|
- Use `PublicAccessGate` from `openxiangda/runtime/react` or `createPublicAccessClient` from `openxiangda/runtime` for React SPA public pages under `/view/:appType/public/*`. Do not append old `?publicAccess=guest` links in new apps.
|
|
19
22
|
- In React SPA apps, wrap route children with `OpenXiangdaPageProvider` inside `OpenXiangdaProvider` before using `usePageSdk()`, `usePageContext()`, `useDataSource()`, or `useFormViewPermissions()`. `OpenXiangdaProvider` alone is not enough and missing the page provider causes `usePageSdkStore 必须在 PageProvider 内使用`.
|
|
@@ -31,7 +34,8 @@ Data view query:
|
|
|
31
34
|
const response = await sdk.dataView.query("ticket_with_customer", {
|
|
32
35
|
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
33
36
|
filters: [
|
|
34
|
-
{
|
|
37
|
+
{ key: "customerName", operator: "contains", value: keyword },
|
|
38
|
+
{ key: "statusValue", operator: "EQ", value: "已发布" },
|
|
35
39
|
],
|
|
36
40
|
order: [{ field: "ticketTitle", isAsc: "y" }],
|
|
37
41
|
currentPage: 1,
|
|
@@ -45,10 +49,10 @@ Data view stats:
|
|
|
45
49
|
const response = await sdk.dataView.stats("ticket_stats_by_customer", {
|
|
46
50
|
fields: ["customerName", "ticketCount", "totalAmount"],
|
|
47
51
|
filters: [
|
|
48
|
-
{
|
|
52
|
+
{ key: "customerName", operator: "contains", value: keyword },
|
|
49
53
|
],
|
|
50
54
|
having: [
|
|
51
|
-
{
|
|
55
|
+
{ key: "ticketCount", operator: ">", value: 0 },
|
|
52
56
|
],
|
|
53
57
|
order: [{ field: "ticketCount", isAsc: "n" }],
|
|
54
58
|
currentPage: 1,
|
|
@@ -67,7 +71,7 @@ export default {
|
|
|
67
71
|
code: "ticket_with_customer",
|
|
68
72
|
fields: ["ticketId", "ticketTitle", "customerName"],
|
|
69
73
|
defaultFilter: [
|
|
70
|
-
{
|
|
74
|
+
{ key: "ticketTitle", operator: "isNotEmpty" }
|
|
71
75
|
]
|
|
72
76
|
},
|
|
73
77
|
{
|
|
@@ -76,7 +80,7 @@ export default {
|
|
|
76
80
|
code: "ticket_stats_by_customer",
|
|
77
81
|
fields: ["customerName", "ticketCount"],
|
|
78
82
|
defaultHaving: [
|
|
79
|
-
{
|
|
83
|
+
{ key: "ticketCount", operator: ">", value: 0 }
|
|
80
84
|
]
|
|
81
85
|
}
|
|
82
86
|
]
|
|
@@ -84,13 +88,13 @@ export default {
|
|
|
84
88
|
|
|
85
89
|
const response = await sdk.dataSource.run("tickets", {
|
|
86
90
|
filters: [
|
|
87
|
-
{
|
|
91
|
+
{ key: "customerName", operator: "contains", value: keyword }
|
|
88
92
|
],
|
|
89
93
|
pageSize: 20,
|
|
90
94
|
})
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
Data view filters, having, and fields use output aliases such as `customerName` or `ticketCount`, not source references such as `customer.name`. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
|
|
97
|
+
Data view filters, having, and fields use output aliases such as `customerName` or `ticketCount`, not source references such as `customer.name`. For option-like fields, prefer scalar aliases such as `statusValue` in the view definition and page filters. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
|
|
94
98
|
|
|
95
99
|
App Function invocation:
|
|
96
100
|
|
|
@@ -105,6 +109,22 @@ const result = await sdk.function.invoke("reservation_reminder_summary", {
|
|
|
105
109
|
|
|
106
110
|
Use App Functions when the logic must run server-side and be reusable by pages, automations, or workflows. Runtime invocation requires app automation management permission by default. To let ordinary app users call a function from a page, declare the allowed current app roles in `definitionJson.runtimeInvoke.roleCodes`; keep sensitive admin-only functions without broad grants. Inside the function, authorize sensitive actions with trusted runtime context such as `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, or `ctx.permissions`; do not trust role codes sent in the page input. When the function writes forms with `ctx.form.createOne/updateOne/updateById`, treat the write as a trusted backend operation over declared `resources.forms`; do not open direct submit permission on internal forms just for that page action.
|
|
107
111
|
|
|
112
|
+
Work center:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const todoList = await sdk.workCenter.listItems({
|
|
116
|
+
boxType: "todo",
|
|
117
|
+
page: 1,
|
|
118
|
+
limit: 20,
|
|
119
|
+
keyword,
|
|
120
|
+
appType,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const stats = await sdk.workCenter.getStats({ appType })
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use `actionUrl` from a work-center item when present; otherwise link workflow items with both `formUuid` and `formInstanceId` to the app's process detail route. Keep lists paginated and role-scoped by the platform current user.
|
|
127
|
+
|
|
108
128
|
Application auth:
|
|
109
129
|
|
|
110
130
|
```tsx
|
|
@@ -106,6 +106,8 @@ openxiangda workflow compile src/workflows/expense_approval/workflow.ts \
|
|
|
106
106
|
|
|
107
107
|
Runtime pages should not guess buttons. Use `sdk.process.resolveCapabilities(...)` or the React helpers from `openxiangda/runtime/react`: `useProcessCapabilities`, `useProcessActions`, `ProcessActionBar`, `ProcessTimeline`, `ProcessPreviewPanel`, and `InitiatorApproverSelector`. The capability protocol returns `visible`, `enabled`, `disabledReason`, `paramsSchema`, `uiSchema`, return candidates, field permissions, and refresh hints for operations such as approve, reject, transfer, return, save, withdraw, resubmit, callback, retryException, and adminTransfer.
|
|
108
108
|
|
|
109
|
+
Runtime todo, done, cc, and initiated lists should use `sdk.workCenter.listItems({ boxType })` plus `sdk.workCenter.getStats()`. These APIs return the current user's projected work-center items with pagination, app/form filters, status/result filters, `actionUrl`, `formUuid`, `formInstanceId`, `taskId`, and node/title snapshots. Do not query workflow operation logs, automation logs, or raw process task tables for end-user task-center pages.
|
|
110
|
+
|
|
109
111
|
For delayed approval start, do not submit a process form and then delete/recreate records. Save the process-form instance without starting workflow via `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`. Later start approval on that same `formInstId` with `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`.
|
|
110
112
|
|
|
111
113
|
For return-to-initiator workflows, prefer `returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" }` or `flow.action.returnToInitiator()`. The runtime capabilities protocol exposes `resubmit` only on pending `originator_return` tasks. If a user returns to a previous approval node, continuing the flow is a normal `approve` action on that returned approval task.
|
|
@@ -24,6 +24,7 @@ Create a workflow only when the scenario has **real approval semantics**: approv
|
|
|
24
24
|
- ✅ New AI-authored automations: prefer **code-first** `automation_code_ts` (`src/automations/<code>/index.ts` + `definition.code.json` + `preview.json`).
|
|
25
25
|
- ✅ New AI-authored workflows: prefer **semantic DSL** `src/workflows/<code>/workflow.ts` using `defineWorkflow`; compile it to v3 designer JSON with `openxiangda workflow compile`.
|
|
26
26
|
- ✅ Runtime workflow pages: use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react`; do not hard-code buttons.
|
|
27
|
+
- ✅ Runtime task centers: use `sdk.workCenter.listItems({ boxType: "todo" | "done" | "cc" | "initiated" })` and `sdk.workCenter.getStats()` for current-user lists/counts; do not read workflow logs, automation logs, or raw process-task tables for end-user todo pages.
|
|
27
28
|
- ✅ Before custom workflow UI or delayed approval work, read `references/workflow-v3.md` sections **Cookbook: draft, start, return, resubmit** and **Capability action reference** for payloads, required IDs, and refresh rules.
|
|
28
29
|
- ✅ Delayed approval start: save the process-form record first with `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`, then start approval in place with `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`.
|
|
29
30
|
- ✅ Return-to-initiator flows: use `returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" | "replay" }` or `flow.action.returnToInitiator()`. The runtime capabilities protocol exposes `resubmit` only for pending `originator_return` tasks; return to a previous approval node continues through the normal `approve` action.
|
package/package.json
CHANGED
|
@@ -2486,6 +2486,24 @@ var createPageSdk = (context) => {
|
|
|
2486
2486
|
method: "post"
|
|
2487
2487
|
})
|
|
2488
2488
|
};
|
|
2489
|
+
const workCenter = {
|
|
2490
|
+
listItems: (params) => request({
|
|
2491
|
+
path: "/work-center/items",
|
|
2492
|
+
method: "get",
|
|
2493
|
+
query: {
|
|
2494
|
+
...params,
|
|
2495
|
+
appType: resolveAppType(context, params.appType)
|
|
2496
|
+
}
|
|
2497
|
+
}),
|
|
2498
|
+
getStats: (params = {}) => request({
|
|
2499
|
+
path: "/work-center/stats",
|
|
2500
|
+
method: "get",
|
|
2501
|
+
query: {
|
|
2502
|
+
...params,
|
|
2503
|
+
appType: resolveAppType(context, params.appType)
|
|
2504
|
+
}
|
|
2505
|
+
})
|
|
2506
|
+
};
|
|
2489
2507
|
const sdk = {
|
|
2490
2508
|
context,
|
|
2491
2509
|
request,
|
|
@@ -2503,6 +2521,7 @@ var createPageSdk = (context) => {
|
|
|
2503
2521
|
permission,
|
|
2504
2522
|
process: process2,
|
|
2505
2523
|
notification,
|
|
2524
|
+
workCenter,
|
|
2506
2525
|
dataView,
|
|
2507
2526
|
function: appFunction,
|
|
2508
2527
|
dataSource: {
|