pict-section-recordset 1.0.15 → 1.0.18
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/example_applications/simple_entity/Simple-RecordSet-Application.js +246 -0
- package/package.json +4 -4
- package/source/application/Pict-Application-RecordSet.js +2 -0
- package/source/providers/RecordSet-DynamicSolver.js +305 -0
- package/source/providers/RecordSet-RecordProvider-Base.js +28 -0
- package/source/providers/RecordSet-RecordProvider-MeadowEndpoints.js +53 -3
- package/source/providers/RecordSet-Router.js +2 -0
- package/source/services/RecordsSet-MetaController.js +84 -2
- package/source/templates/Pict-Template-FilterView.js +172 -0
- package/source/views/RecordSet-Filter.js +47 -9
- package/source/views/dashboard/RecordSet-Dashboard-HeaderDashboard.js +17 -17
- package/source/views/dashboard/RecordSet-Dashboard-PaginationBottom.js +68 -0
- package/source/views/dashboard/RecordSet-Dashboard-PaginationTop.js +128 -0
- package/source/views/dashboard/RecordSet-Dashboard-RecordList.js +80 -0
- package/source/views/dashboard/RecordSet-Dashboard-RecordListEntry.js +121 -0
- package/source/views/dashboard/RecordSet-Dashboard-RecordListHeader.js +111 -0
- package/source/views/dashboard/RecordSet-Dashboard-Title.js +67 -0
- package/source/views/dashboard/RecordSet-Dashboard.js +627 -37
- package/source/views/error/RecordSet-Error-NotFound.json +22 -0
- package/source/views/list/RecordSet-List.js +20 -39
- package/types/application/Pict-Application-RecordSet.d.ts.map +1 -1
- package/types/providers/RecordSet-DynamicSolver.d.ts +158 -0
- package/types/providers/RecordSet-DynamicSolver.d.ts.map +1 -0
- package/types/providers/RecordSet-RecordProvider-Base.d.ts +19 -0
- package/types/providers/RecordSet-RecordProvider-Base.d.ts.map +1 -1
- package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts +38 -5
- package/types/providers/RecordSet-RecordProvider-MeadowEndpoints.d.ts.map +1 -1
- package/types/providers/RecordSet-Router.d.ts.map +1 -1
- package/types/services/RecordsSet-MetaController.d.ts +20 -2
- package/types/services/RecordsSet-MetaController.d.ts.map +1 -1
- package/types/templates/Pict-Template-FilterView.d.ts +18 -0
- package/types/templates/Pict-Template-FilterView.d.ts.map +1 -0
- package/types/views/RecordSet-Filter.d.ts +28 -2
- package/types/views/RecordSet-Filter.d.ts.map +1 -1
- package/types/views/dashboard/RecordSet-Dashboard-HeaderDashboard.d.ts +5 -5
- package/types/views/dashboard/RecordSet-Dashboard-HeaderDashboard.d.ts.map +1 -1
- package/types/views/dashboard/RecordSet-Dashboard-PaginationBottom.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-PaginationBottom.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard-PaginationTop.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-PaginationTop.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordList.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordList.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordListEntry.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordListEntry.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordListHeader.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-RecordListHeader.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard-Title.d.ts +11 -0
- package/types/views/dashboard/RecordSet-Dashboard-Title.d.ts.map +1 -0
- package/types/views/dashboard/RecordSet-Dashboard.d.ts +38 -0
- package/types/views/dashboard/RecordSet-Dashboard.d.ts.map +1 -1
- package/types/views/list/RecordSet-List.d.ts +0 -1
- package/types/views/list/RecordSet-List.d.ts.map +1 -1
- package/source/views/dashboard/RecordSet-Dashboard-RecordSetDashboard.js +0 -64
|
@@ -12,6 +12,169 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
12
12
|
"AutoRenderMainViewportViewAfterInitialize": false
|
|
13
13
|
},
|
|
14
14
|
|
|
15
|
+
"Manifests": // Manifest'Ohs: Breakfast of Champions
|
|
16
|
+
{
|
|
17
|
+
"Bestsellers":
|
|
18
|
+
{
|
|
19
|
+
"Scope": "Bestsellers",
|
|
20
|
+
"CoreEntity": "Book",
|
|
21
|
+
"TitleTemplate": "Bestsellers ({~D:Record.RecordSet~} LoL)",
|
|
22
|
+
"GlobalSolvers":
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
"Ordinal": 0,
|
|
26
|
+
"Expression": "AppData.AuthorsFavoriteNumber = AVG(RecordSubset[].IDBook)",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"Ordinal": 1,
|
|
30
|
+
"Expression": "AppData.AuthorsLeastFavoriteNumber = SUM(RecordSubset[].IDBook)",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"Ordinal": 1,
|
|
34
|
+
"Expression": "AppData.BookCount = COUNT(RecordSet[])",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"Ordinal": 1,
|
|
38
|
+
"Expression": "AppData.TotalLibraryValue = SUM(RecordSet[].Price)",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
"Descriptors":
|
|
42
|
+
{
|
|
43
|
+
"IDBook":
|
|
44
|
+
{
|
|
45
|
+
"Name": "Book Identifier",
|
|
46
|
+
"Hash": "BookIdentifire",
|
|
47
|
+
},
|
|
48
|
+
"PublicationYear":
|
|
49
|
+
{
|
|
50
|
+
"Name": "PublicationYear",
|
|
51
|
+
"Hash": "PublicationYear",
|
|
52
|
+
"PictDashboard":
|
|
53
|
+
{
|
|
54
|
+
"Equation": "ROUND(SQRT(PublicationYear), 3)",
|
|
55
|
+
"ValueTemplate": "{~D:Record.Payload.PublicationYear~} ({~SBR:Record.Data.PictDashboard.Equation:Record.Payload:Pict.PictSectionRecordSet.getManifest(Record.Data.ManifestHash)~})",
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"Title":
|
|
59
|
+
{
|
|
60
|
+
"Name": "Title",
|
|
61
|
+
"Hash": "Title",
|
|
62
|
+
"PictDashboard":
|
|
63
|
+
{
|
|
64
|
+
"ValueTemplate": "{~D:Record.Payload.Title~} ({~D:AppData.RSP-Provider-BookstoreInventory.Authors.length~} authors in cohort)"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"AuthorBookCount":
|
|
68
|
+
{
|
|
69
|
+
"Name": "Author Book Count",
|
|
70
|
+
"Hash": "AuthorBookCount",
|
|
71
|
+
},
|
|
72
|
+
"Authors":
|
|
73
|
+
{
|
|
74
|
+
"Name": "Authors",
|
|
75
|
+
"Hash": "BookAuthors",
|
|
76
|
+
"DataType": "Array",
|
|
77
|
+
"PictDashboard":
|
|
78
|
+
{
|
|
79
|
+
"ValueTemplate": "{~PJU:, ^Name^Record.Payload.Authors~}"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"AuthorCount":
|
|
83
|
+
{
|
|
84
|
+
"Name": "Number of Authors",
|
|
85
|
+
"Hash": "AuthorCount",
|
|
86
|
+
"DataType": "Number",
|
|
87
|
+
"PictDashboard":
|
|
88
|
+
{
|
|
89
|
+
"EquationNamespaceScope": "Full",
|
|
90
|
+
"Equation": "Payload.Authors.length + 0", //FIXME: having to + 0 here seems sketchy
|
|
91
|
+
"Solvers":
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"Ordinal": 0,
|
|
95
|
+
"Expression": "Price = ROUND(RANDOMFLOATBETWEEN(0.5, 40), 2)",
|
|
96
|
+
},
|
|
97
|
+
"AuthorCount = COS(Authors.length)",
|
|
98
|
+
],
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"AuthorSineWave":
|
|
102
|
+
{
|
|
103
|
+
"Name": "Number of Authors in Orbit",
|
|
104
|
+
"Hash": "AuthorSineWave",
|
|
105
|
+
"DataType": "Number",
|
|
106
|
+
"PictDashboard":
|
|
107
|
+
{
|
|
108
|
+
"Equation": "AuthorSineWave = ROUND(SIN(BookIdentifire / 100)^3,5)", //FIXME: having to + 0 here seems sketchy
|
|
109
|
+
//"ValueTemplate": "{~D:Record.Payload.AuthorsInOrbit~}",
|
|
110
|
+
"Solvers": [ "AuthorsInOrbit = SIN(Authors.length)" ],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
"Underdogs":
|
|
116
|
+
{
|
|
117
|
+
"Scope": "Underdogs",
|
|
118
|
+
"CoreEntity": "Book",
|
|
119
|
+
"Descriptors":
|
|
120
|
+
{
|
|
121
|
+
"Title":
|
|
122
|
+
{
|
|
123
|
+
"Name": "Title",
|
|
124
|
+
"Hash": "Title",
|
|
125
|
+
"DataType": "String"
|
|
126
|
+
},
|
|
127
|
+
"Authors":
|
|
128
|
+
{
|
|
129
|
+
"Name": "Authors",
|
|
130
|
+
"Hash": "Authors",
|
|
131
|
+
"PictDashboard":
|
|
132
|
+
{
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"AuthorCount":
|
|
136
|
+
{
|
|
137
|
+
"Name": "Number of Authors",
|
|
138
|
+
"Hash": "AuthorCount",
|
|
139
|
+
"DataType": "Number",
|
|
140
|
+
"PictDashboard":
|
|
141
|
+
{
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
"NewReleases":
|
|
147
|
+
{
|
|
148
|
+
"Scope": "NewReleases",
|
|
149
|
+
"CoreEntity": "Book",
|
|
150
|
+
"Descriptors":
|
|
151
|
+
{
|
|
152
|
+
"Title":
|
|
153
|
+
{
|
|
154
|
+
"Name": "Title",
|
|
155
|
+
"Hash": "Title",
|
|
156
|
+
"DataType": "String"
|
|
157
|
+
},
|
|
158
|
+
"Authors":
|
|
159
|
+
{
|
|
160
|
+
"Name": "Authors",
|
|
161
|
+
"Hash": "Authors",
|
|
162
|
+
"PictDashboard":
|
|
163
|
+
{
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"AuthorCount":
|
|
167
|
+
{
|
|
168
|
+
"Name": "Number of Authors",
|
|
169
|
+
"Hash": "AuthorCount",
|
|
170
|
+
"DataType": "Number",
|
|
171
|
+
"PictDashboard":
|
|
172
|
+
{
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
},
|
|
15
178
|
"DefaultRecordSetConfigurations":
|
|
16
179
|
[
|
|
17
180
|
{
|
|
@@ -35,11 +198,94 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
35
198
|
}
|
|
36
199
|
],
|
|
37
200
|
|
|
201
|
+
"RecordSetListManifestOnly": false,
|
|
202
|
+
|
|
203
|
+
"RecordSetListManifests": [ "Bestsellers", "Underdogs", "NewReleases" ],
|
|
204
|
+
"RecordSetDashboardManifests": [ "Bestsellers" ],
|
|
205
|
+
|
|
206
|
+
"RecordSetListHasExtraColumns": true,
|
|
207
|
+
"RecordSetListExtraColumnsHeaderTemplate": "<th style=\"border-bottom: 1px solid #ccc; padding: 5px; background-color: #f2f2f2; color: #333;\">Cover</th>",
|
|
208
|
+
"RecordSetListExtraColumnRowTemplate": "<td><img src=\"{~D:Record.Data.ImageURL~}\"></td>",
|
|
209
|
+
|
|
210
|
+
"SearchFields": [ "Title" ],
|
|
211
|
+
|
|
212
|
+
"RecordSetFilterURLTemplate-Default": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
213
|
+
"RecordSetFilterURLTemplate-List": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
214
|
+
"RecordSetFilterURLTemplate-Dashboard": "/PSRS/{~D:Record.RecordSet~}/Dashboard/FilteredTo/{~D:Record.FilterString~}",
|
|
215
|
+
|
|
216
|
+
"RecordSetURLPrefix": "/1.0/"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"RecordSet": "BookstoreInventory",
|
|
220
|
+
"Title": "Bookstore Inventory",
|
|
221
|
+
|
|
222
|
+
"RecordSetType": "MeadowEndpoint", // Could be "Custom" which would require a provider to already be created for the record set.
|
|
223
|
+
"RecordSetMeadowEntity": "Book", // This leverages the /Schema endpoint to get the record set columns.
|
|
224
|
+
|
|
225
|
+
"RecordSetDashboardManifests": [ "Bestsellers", "Underdogs", "NewReleases" ],
|
|
226
|
+
|
|
227
|
+
"RecordDecorationConfiguration":
|
|
228
|
+
[
|
|
229
|
+
{
|
|
230
|
+
"Entity": "BookAuthorJoin",
|
|
231
|
+
"Filter": "FBL~IDBook~INN~{~PJU:,^IDBook^Record.State.CoreEntityRecordSubset~}",
|
|
232
|
+
"Destination": "State.BookAuthorJoins"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"Entity": "Author",
|
|
236
|
+
"Filter": "FBL~IDAuthor~INN~{~PJU:,^IDAuthor^Record.State.BookAuthorJoins~}",
|
|
237
|
+
"Destination": "State.Authors"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"Type": "MapJoin",
|
|
241
|
+
"DestinationRecordSetAddress": "State.CoreEntityRecordSubset",
|
|
242
|
+
"DestinationJoinValue": "IDBook",
|
|
243
|
+
"JoinJoinValueLHS": "IDBook",
|
|
244
|
+
"Joins": "State.BookAuthorJoins",
|
|
245
|
+
"JoinJoinValueRHS": "IDAuthor",
|
|
246
|
+
"JoinRecordSetAddress": "State.Authors",
|
|
247
|
+
"JoinValue": "IDAuthor",
|
|
248
|
+
"RecordDestinationAddress": "Authors"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"Entity": "BookAuthorJoin",
|
|
252
|
+
"Filter": "FBL~IDAuthor~INN~{~PJU:,^IDAuthor^Record.State.Authors~}",
|
|
253
|
+
"Destination": "State.BookAuthorJoinsRev"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"Entity": "Book",
|
|
257
|
+
"Filter": "FBL~IDBook~INN~{~PJU:,^IDBook^Record.State.BookAuthorJoinsRev~}",
|
|
258
|
+
"Destination": "State.BooksForAuthors"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"Type": "MapJoin",
|
|
262
|
+
"DestinationRecordSetAddress": "State.Authors",
|
|
263
|
+
"DestinationJoinValue": "IDAuthor",
|
|
264
|
+
"JoinJoinValueLHS": "IDAuthor",
|
|
265
|
+
"Joins": "State.BookAuthorJoinsRev",
|
|
266
|
+
"JoinJoinValueRHS": "IDBook",
|
|
267
|
+
"JoinRecordSetAddress": "State.BooksForAuthors",
|
|
268
|
+
"JoinValue": "IDBook",
|
|
269
|
+
"RecordDestinationAddress": "Books"
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
"AvailableVerbs": [ "Dashboard" ],
|
|
273
|
+
|
|
38
274
|
"RecordSetListHasExtraColumns": true,
|
|
39
275
|
"RecordSetListExtraColumnsHeaderTemplate": "<th style=\"border-bottom: 1px solid #ccc; padding: 5px; background-color: #f2f2f2; color: #333;\">Cover</th>",
|
|
40
276
|
"RecordSetListExtraColumnRowTemplate": "<td><img src=\"{~D:Record.Data.ImageURL~}\"></td>",
|
|
277
|
+
|
|
41
278
|
"SearchFields": [ "Title" ],
|
|
42
279
|
|
|
280
|
+
"RecordSetFilterURLTemplate-Default": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
281
|
+
"RecordSetFilterURLTemplate-List": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
282
|
+
"RecordSetFilterURLTemplate-Dashboard": "/PSRS/{~D:Record.RecordSet~}/Dashboard/FilteredTo/{~D:Record.FilterString~}",
|
|
283
|
+
//TODO: something like this to reduce boilerplate
|
|
284
|
+
"RecordSetFilterURLTemplate-Dashboard-Specific": "/PSRS/{~D:Record.RecordSet~}/SpecificDashboard/$$DASHBOARD_HASH$$/FilteredTo/{~D:Record.FilterString~}",
|
|
285
|
+
"RecordSetFilterURLTemplate-Dashboard-Bestsellers": "/PSRS/{~D:Record.RecordSet~}/SpecificDashboard/Bestsellers/FilteredTo/{~D:Record.FilterString~}",
|
|
286
|
+
"RecordSetFilterURLTemplate-Dashboard-Underdogs": "/PSRS/{~D:Record.RecordSet~}/SpecificDashboard/Underdogs/FilteredTo/{~D:Record.FilterString~}",
|
|
287
|
+
"RecordSetFilterURLTemplate-Dashboard-NewReleases": "/PSRS/{~D:Record.RecordSet~}/SpecificDashboard/NewReleases/FilteredTo/{~D:Record.FilterString~}",
|
|
288
|
+
|
|
43
289
|
"RecordSetURLPrefix": "/1.0/"
|
|
44
290
|
},
|
|
45
291
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-section-recordset",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "Pict dynamic record set management views",
|
|
5
5
|
"main": "source/Pict-Section-RecordSet.js",
|
|
6
6
|
"directories": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"browser-env": "^3.3.0",
|
|
34
34
|
"eslint": "^9.27.0",
|
|
35
35
|
"jquery": "^3.7.1",
|
|
36
|
-
"pict": "^1.0.
|
|
36
|
+
"pict": "^1.0.267",
|
|
37
37
|
"pict-application": "^1.0.25",
|
|
38
38
|
"pict-service-commandlineutility": "^1.0.15",
|
|
39
39
|
"quackage": "^1.0.41",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"fable-serviceproviderbase": "^3.0.15",
|
|
44
44
|
"pict-provider": "^1.0.3",
|
|
45
|
-
"pict-router": "^1.0.
|
|
46
|
-
"pict-section-form": "^1.0.
|
|
45
|
+
"pict-router": "^1.0.4",
|
|
46
|
+
"pict-section-form": "^1.0.97",
|
|
47
47
|
"pict-template": "^1.0.10",
|
|
48
48
|
"pict-view": "^1.0.60"
|
|
49
49
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const libPictApplication = require('pict-application');
|
|
2
2
|
|
|
3
3
|
const libPictSectionRecordSet = require('../Pict-Section-RecordSet.js');
|
|
4
|
+
const libDynamicSolver = require('../providers/RecordSet-DynamicSolver.js');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents a PictSectionRecordSetApplication.
|
|
@@ -20,6 +21,7 @@ class PictSectionRecordSetApplication extends libPictApplication
|
|
|
20
21
|
this.pict;
|
|
21
22
|
// Add the pict recordset meta controller service
|
|
22
23
|
this.pict.addServiceType('PictSectionRecordSet', libPictSectionRecordSet);
|
|
24
|
+
this.fable.addProviderSingleton('DynamicSolver', libDynamicSolver.default_configuration, libDynamicSolver);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
onInitialize()
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
const libPictProvider = require('pict-provider');
|
|
2
|
+
|
|
3
|
+
/** @type {Record<string, any>} */
|
|
4
|
+
const _DefaultProviderConfiguration = (
|
|
5
|
+
{
|
|
6
|
+
"ProviderIdentifier": "Pict-DynamicForms-Solver",
|
|
7
|
+
|
|
8
|
+
"AutoInitialize": true,
|
|
9
|
+
"AutoInitializeOrdinal": 0,
|
|
10
|
+
|
|
11
|
+
"AutoSolveWithApp": false
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The PictDynamicSolver class is a provider that solves configuration-generated dynamic views.
|
|
16
|
+
*/
|
|
17
|
+
class RecordSetDynamicSolver extends libPictProvider
|
|
18
|
+
{
|
|
19
|
+
/**
|
|
20
|
+
* Creates an instance of the PictDynamicSolver class.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} pFable - The fable object.
|
|
23
|
+
* @param {object} pOptions - The options object.
|
|
24
|
+
* @param {object} pServiceHash - The service hash object.
|
|
25
|
+
*/
|
|
26
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
27
|
+
{
|
|
28
|
+
let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(_DefaultProviderConfiguration)), pOptions);
|
|
29
|
+
super(pFable, tmpOptions, pServiceHash);
|
|
30
|
+
|
|
31
|
+
/** @type {import('pict') & {
|
|
32
|
+
* instantiateServiceProviderIfNotExists: (hash: string) => any,
|
|
33
|
+
* ExpressionParser: any,
|
|
34
|
+
* PictSectionRecordSet: InstanceType<import('../Pict-Section-RecordSet.js')>
|
|
35
|
+
* }} */
|
|
36
|
+
this.pict;
|
|
37
|
+
/** @type {import('pict') & { instantiateServiceProviderIfNotExists: (hash: string) => any, ExpressionParser: any }} */
|
|
38
|
+
this.fable;
|
|
39
|
+
/** @type {any} */
|
|
40
|
+
this.log;
|
|
41
|
+
/** @type {string} */
|
|
42
|
+
this.UUID;
|
|
43
|
+
/** @type {string} */
|
|
44
|
+
this.Hash;
|
|
45
|
+
|
|
46
|
+
// Initialize the solver service if it isn't up
|
|
47
|
+
this.fable.instantiateServiceProviderIfNotExists('ExpressionParser');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks the solver and returns the solver object if it passes the checks.
|
|
52
|
+
*
|
|
53
|
+
* Automatically converts string solvers to have an Ordinal of 1.
|
|
54
|
+
*
|
|
55
|
+
* @param {string|object} pSolver - The solver to be checked. It can be either a string or an object.
|
|
56
|
+
* @param {boolean} [pFiltered=false] - Indicates whether the solvers should be filtered.
|
|
57
|
+
* @param {number} [pOrdinal] - The ordinal value to compare with the solver's ordinal value when filtered.
|
|
58
|
+
* @returns {object|undefined} - The solver object if it passes the checks, otherwise undefined.
|
|
59
|
+
*/
|
|
60
|
+
checkSolver(pSolver, pFiltered, pOrdinal)
|
|
61
|
+
{
|
|
62
|
+
let tmpSolver = pSolver;
|
|
63
|
+
if (tmpSolver === undefined)
|
|
64
|
+
{
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (typeof(tmpSolver) === 'string')
|
|
68
|
+
{
|
|
69
|
+
tmpSolver = {Expression:tmpSolver, Ordinal:1};
|
|
70
|
+
}
|
|
71
|
+
if (!('Expression' in tmpSolver))
|
|
72
|
+
{
|
|
73
|
+
this.log.error(`Dashboard solver ${pOrdinal} is missing the Expression property.`, { Solver: pSolver });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!(`Ordinal` in tmpSolver))
|
|
77
|
+
{
|
|
78
|
+
tmpSolver.Ordinal = 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// This filters the solvers
|
|
82
|
+
if (pFiltered && (tmpSolver.Ordinal != pOrdinal))
|
|
83
|
+
{
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return tmpSolver;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Runs each RecordSet solver formulae for a dynamic view group at a given ordinal.
|
|
92
|
+
*
|
|
93
|
+
* Or for all ordinals if no ordinal is passed.
|
|
94
|
+
*
|
|
95
|
+
* @param {import('manyfest')} pManifest - The manifest for the RecordSet.
|
|
96
|
+
* @param {array} pCellSolverArray - An array of Solvers from the groups to solve.
|
|
97
|
+
* @param {number} pOrdinal - The ordinal value to filter to. Optional.
|
|
98
|
+
* @param {Array<Record<string, any>>} pRecords - The records to solve against.
|
|
99
|
+
*/
|
|
100
|
+
executeCellSolvers(pManifest, pCellSolverArray, pOrdinal, pRecords)
|
|
101
|
+
{
|
|
102
|
+
// This is purely for readability of the code below ... uglify optimizes it out.
|
|
103
|
+
let tmpFiltered = (typeof(pOrdinal) === 'undefined') ? false : true;
|
|
104
|
+
|
|
105
|
+
// Solve the group RecordSet solvers first
|
|
106
|
+
for (let j = 0; j < pCellSolverArray.length; j++)
|
|
107
|
+
{
|
|
108
|
+
let tmpSolver = this.checkSolver(pCellSolverArray[j].Solver, tmpFiltered, pOrdinal);
|
|
109
|
+
if (typeof(tmpSolver) === 'undefined')
|
|
110
|
+
{
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
tmpSolver.StartTimeStamp = Date.now();
|
|
115
|
+
tmpSolver.Hash = `CellSolver-${j}`;
|
|
116
|
+
|
|
117
|
+
if (this.pict.LogNoisiness > 1)
|
|
118
|
+
{
|
|
119
|
+
this.log.trace(`Cell solving RecordSet ordinal ${tmpSolver.Ordinal} [${tmpSolver.Expression}]`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const tmpRecords = Array.isArray(pRecords) ? pRecords : pRecords ? Object.values(pRecords) : [];
|
|
123
|
+
for (let l = 0; l < tmpRecords.length; l++)
|
|
124
|
+
{
|
|
125
|
+
let tmpRecord = tmpRecords[l];
|
|
126
|
+
tmpSolver.ResultsObject = {};
|
|
127
|
+
const tmpSolverRecord = this.buildCellContextRecord(tmpRecord, pRecords, pManifest);
|
|
128
|
+
let tmpSolutionValue = this.pict.ExpressionParser.solve(tmpSolver.Expression, tmpSolverRecord, tmpSolver.ResultsObject,
|
|
129
|
+
pManifest, tmpRecord);
|
|
130
|
+
if (this.pict.LogNoisiness > 1)
|
|
131
|
+
{
|
|
132
|
+
this.log.trace(`Cell solver [${tmpSolver.Expression}] record ${l} result was ${tmpSolutionValue}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
tmpSolver.EndTimeStamp = Date.now();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {Record<string, any>} pRecord - The record to build the context for.
|
|
141
|
+
* @param {Array<Record<string, any>>} pRecords - The records to build the context from.
|
|
142
|
+
* @param {import('manyfest')} pManifest - The manifest for the RecordSet.
|
|
143
|
+
*/
|
|
144
|
+
buildCellContextRecord(pRecord, pRecords, pManifest)
|
|
145
|
+
{
|
|
146
|
+
return Object.assign({}, pRecord, this.buildGlobalContextRecord(pRecords, pManifest));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {Array<Record<string, any>>} pRecords - The records to build the context from.
|
|
151
|
+
* @param {import('manyfest')} pManifest - The manifest for the RecordSet.
|
|
152
|
+
*/
|
|
153
|
+
buildGlobalContextRecord(pRecords, pManifest)
|
|
154
|
+
{
|
|
155
|
+
return {
|
|
156
|
+
Pict: this.pict,
|
|
157
|
+
AppData: this.pict.AppData,
|
|
158
|
+
RecordSubset: pRecords,
|
|
159
|
+
Manifest: pManifest,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Executes the section solvers at a given ordinal (or all if no ordinal is passed).
|
|
165
|
+
*
|
|
166
|
+
* @param {import('manyfest')} pManifest - The manifest for the RecordSet.
|
|
167
|
+
* @param {Array} pGlobalSolverArray - The array of view section solvers.
|
|
168
|
+
* @param {number} pOrdinal - The ordinal value.
|
|
169
|
+
* @param {Array<Record<string, any>>} pRecords - The records to solve against.
|
|
170
|
+
*/
|
|
171
|
+
executeDashboardSolvers(pManifest, pGlobalSolverArray, pOrdinal, pRecords)
|
|
172
|
+
{
|
|
173
|
+
let tmpFiltered = (typeof(pOrdinal) === 'undefined') ? false : true;
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < pGlobalSolverArray.length; i++)
|
|
176
|
+
{
|
|
177
|
+
let tmpSolver = this.checkSolver(pGlobalSolverArray[i].Solver, tmpFiltered, pOrdinal);
|
|
178
|
+
if (typeof(tmpSolver) === 'undefined')
|
|
179
|
+
{
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
tmpSolver.StartTimeStamp = Date.now();
|
|
184
|
+
tmpSolver.Hash = `DashboardSolver-${i}`;
|
|
185
|
+
|
|
186
|
+
// TODO: Precompile the solvers (it's super easy)
|
|
187
|
+
if (this.pict.LogNoisiness > 1)
|
|
188
|
+
{
|
|
189
|
+
this.log.trace(`Dashboard solving equation ${i} ordinal ${tmpSolver.Ordinal}`);
|
|
190
|
+
}
|
|
191
|
+
tmpSolver.ResultsObject = {};
|
|
192
|
+
const tmpRecord = this.buildGlobalContextRecord(pRecords, pManifest);
|
|
193
|
+
let tmpSolutionValue = this.pict.ExpressionParser.solve(tmpSolver.Expression, tmpRecord, tmpSolver.ResultsObject, pManifest, tmpRecord);
|
|
194
|
+
if (this.pict.LogNoisiness > 1)
|
|
195
|
+
{
|
|
196
|
+
this.log.trace(`[${tmpSolver.Expression}] result was ${tmpSolutionValue}`);
|
|
197
|
+
}
|
|
198
|
+
tmpSolver.EndTimeStamp = Date.now();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Checks if the given ordinal exists in the provided ordinal set.
|
|
204
|
+
*
|
|
205
|
+
* If not, it adds the ordinal to the set.
|
|
206
|
+
*
|
|
207
|
+
* @param {number} pOrdinal - The ordinal to check.
|
|
208
|
+
* @param {Object} pOrdinalSet - The ordinal set to check against.
|
|
209
|
+
* @returns {Object} - The ordinal object from the ordinal set.
|
|
210
|
+
*/
|
|
211
|
+
checkAutoSolveOrdinal(pOrdinal, pOrdinalSet)
|
|
212
|
+
{
|
|
213
|
+
if (!(pOrdinal.toString() in pOrdinalSet))
|
|
214
|
+
{
|
|
215
|
+
pOrdinalSet[pOrdinal.toString()] = { DashboardSolvers:[], CellSolvers:[] };
|
|
216
|
+
}
|
|
217
|
+
return pOrdinalSet[pOrdinal];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Solves the views based on the provided view hashes or all views in pict.
|
|
222
|
+
*
|
|
223
|
+
* If non-dynamic views are also passed in, they are solved as well.
|
|
224
|
+
*
|
|
225
|
+
* This algorithm is particularly complex because it solves views in
|
|
226
|
+
* order across two dimensions:
|
|
227
|
+
*
|
|
228
|
+
* 1. The order of the views in the view hash array.
|
|
229
|
+
* 2. Precedence order (based on Ordinal)
|
|
230
|
+
*
|
|
231
|
+
* The way it manages the precedence order solving is by enumerating the
|
|
232
|
+
* view hash array multiple times until it exhausts the solution set.
|
|
233
|
+
*
|
|
234
|
+
* In dynamic views, when there are collisions in precedence order between
|
|
235
|
+
* Section Solvers and Group RecordSet Solvers, it prefers the RecordSet
|
|
236
|
+
* solvers first. The thinking behind this is that a RecordSet solver is
|
|
237
|
+
* a "tier down" from the core Section it resides within. These are
|
|
238
|
+
* leaves on the tree.
|
|
239
|
+
*
|
|
240
|
+
* @param {Record<string, any>} pManifestDefinition
|
|
241
|
+
* @param {Array<Record<string, any>>} pRecords - The records to solve against.
|
|
242
|
+
*/
|
|
243
|
+
solveDashboard(pManifestDefinition, pRecords)
|
|
244
|
+
{
|
|
245
|
+
let tmpSolveOutcome = {};
|
|
246
|
+
tmpSolveOutcome.StartTimeStamp = Date.now();
|
|
247
|
+
|
|
248
|
+
let tmpOrdinalsToSolve = {};
|
|
249
|
+
tmpSolveOutcome.SolveOrdinals = tmpOrdinalsToSolve;
|
|
250
|
+
const tmpCellDefinitions = Object.values(pManifestDefinition.Descriptors);
|
|
251
|
+
for (const tmpCell of tmpCellDefinitions)
|
|
252
|
+
{
|
|
253
|
+
if (!Array.isArray(tmpCell?.PictDashboard?.Solvers))
|
|
254
|
+
{
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
for (const tmpRawSolver of tmpCell.PictDashboard.Solvers)
|
|
258
|
+
{
|
|
259
|
+
if (tmpRawSolver)
|
|
260
|
+
{
|
|
261
|
+
const tmpSolver = this.checkSolver(tmpRawSolver, false);
|
|
262
|
+
const tmpOrdinalContainer = this.checkAutoSolveOrdinal(tmpSolver.Ordinal, tmpOrdinalsToSolve);
|
|
263
|
+
tmpOrdinalContainer.CellSolvers.push({ CellHash: tmpCell.Hash, Solver:tmpSolver });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (Array.isArray(pManifestDefinition.GlobalSolvers))
|
|
268
|
+
{
|
|
269
|
+
// Add the section solver(s)
|
|
270
|
+
for (const tmpRawSolver of pManifestDefinition.GlobalSolvers)
|
|
271
|
+
{
|
|
272
|
+
if (tmpRawSolver)
|
|
273
|
+
{
|
|
274
|
+
const tmpSolver = this.checkSolver(tmpRawSolver, false);
|
|
275
|
+
let tmpOrdinalContainer = this.checkAutoSolveOrdinal(tmpSolver.Ordinal, tmpOrdinalsToSolve);
|
|
276
|
+
tmpOrdinalContainer.DashboardSolvers.push({ Solver: tmpSolver });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Now sort the ordinal container keys
|
|
282
|
+
let tmpOrdinalKeys = Object.keys(tmpOrdinalsToSolve);
|
|
283
|
+
tmpOrdinalKeys.sort();
|
|
284
|
+
const tmpManifest = this.pict.PictSectionRecordSet.getManifest(pManifestDefinition.Scope);
|
|
285
|
+
// Now enumerate the keys and solve each layer of the solution set
|
|
286
|
+
for (let i = 0; i < tmpOrdinalKeys.length; i++)
|
|
287
|
+
{
|
|
288
|
+
if (this.pict.LogNoisiness > 1)
|
|
289
|
+
{
|
|
290
|
+
this.log.trace(`DynamicSolver [${this.UUID}]::[${this.Hash}] Solving ordinal ${tmpOrdinalKeys[i]}`);
|
|
291
|
+
}
|
|
292
|
+
let tmpOrdinalContainer = tmpOrdinalsToSolve[tmpOrdinalKeys[i]];
|
|
293
|
+
this.executeCellSolvers(tmpManifest, tmpOrdinalContainer.CellSolvers, Number(tmpOrdinalKeys[i]), pRecords);
|
|
294
|
+
this.executeDashboardSolvers(tmpManifest, tmpOrdinalContainer.DashboardSolvers, Number(tmpOrdinalKeys[i]), pRecords);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
tmpSolveOutcome.EndTimeStamp = Date.now();
|
|
298
|
+
|
|
299
|
+
// It's up to the developer to decide if they want to use this information somewhere.
|
|
300
|
+
this.lastSolveOutcome = tmpSolveOutcome;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = RecordSetDynamicSolver;
|
|
305
|
+
module.exports.default_configuration = _DefaultProviderConfiguration;
|
|
@@ -104,6 +104,20 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
104
104
|
return { Records: [], Facets: { } };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Read records from the provider.
|
|
109
|
+
*
|
|
110
|
+
* @param {RecordSetFilter} pOptions - Options for the read operation.
|
|
111
|
+
*
|
|
112
|
+
* @return {Promise<RecordSetResult>} - The result of the read operation.
|
|
113
|
+
*/
|
|
114
|
+
async getDecoratedRecords(pOptions)
|
|
115
|
+
{
|
|
116
|
+
const tmpRecords = await this.getRecords(pOptions);
|
|
117
|
+
await this.decorateCoreRecords(tmpRecords.Records);
|
|
118
|
+
return tmpRecords;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
/**
|
|
108
122
|
* Read records from the provider.
|
|
109
123
|
*
|
|
@@ -133,6 +147,7 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
133
147
|
* Create a new record.
|
|
134
148
|
*
|
|
135
149
|
* @param {Record<string, any>} pRecord - The record to create.
|
|
150
|
+
* @return {Promise<Record<string, any>>} - The created record.
|
|
136
151
|
*/
|
|
137
152
|
async createRecord(pRecord)
|
|
138
153
|
{
|
|
@@ -144,6 +159,7 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
144
159
|
* Update a record.
|
|
145
160
|
*
|
|
146
161
|
* @param {Record<string, any>} pRecord - The record to update.
|
|
162
|
+
* @return {Promise<Record<string, any>>} - The updated record.
|
|
147
163
|
*/
|
|
148
164
|
async updateRecord(pRecord)
|
|
149
165
|
{
|
|
@@ -155,6 +171,7 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
155
171
|
* Delete a record.
|
|
156
172
|
*
|
|
157
173
|
* @param {Record<string, any>} pRecord - The record to delete.
|
|
174
|
+
* @return {Promise<void>}
|
|
158
175
|
*/
|
|
159
176
|
async deleteRecord(pRecord)
|
|
160
177
|
{
|
|
@@ -188,6 +205,7 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
188
205
|
* Clone a record.
|
|
189
206
|
*
|
|
190
207
|
* @param {Record<string, any>} pRecord - The record to clone.
|
|
208
|
+
* @return {Promise<Record<string, any>>} - The cloned record.
|
|
191
209
|
*/
|
|
192
210
|
async cloneRecord(pRecord)
|
|
193
211
|
{
|
|
@@ -211,6 +229,16 @@ class RecordSetProviderBase extends libPictProvider
|
|
|
211
229
|
{
|
|
212
230
|
return { };
|
|
213
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Abstract decoration method for core records. Subclasses should implement this method to decorate records with additional information.
|
|
235
|
+
*
|
|
236
|
+
* @param {Array<Record<string, any>>} pRecords - The records to decorate.
|
|
237
|
+
* @return {Promise<void>}
|
|
238
|
+
*/
|
|
239
|
+
async decorateCoreRecords(pRecords)
|
|
240
|
+
{
|
|
241
|
+
}
|
|
214
242
|
}
|
|
215
243
|
|
|
216
244
|
module.exports = RecordSetProviderBase;
|