pict-section-recordset 1.0.16 → 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 +163 -30
- package/package.json +3 -3
- package/source/application/Pict-Application-RecordSet.js +2 -0
- package/source/providers/RecordSet-DynamicSolver.js +305 -0
- package/source/providers/RecordSet-Router.js +2 -0
- package/source/services/RecordsSet-MetaController.js +68 -69
- package/source/templates/Pict-Template-FilterView.js +2 -2
- package/source/views/RecordSet-Filter.js +2 -1
- package/source/views/dashboard/RecordSet-Dashboard-RecordListEntry.js +1 -1
- package/source/views/dashboard/RecordSet-Dashboard-RecordListHeader.js +12 -0
- package/source/views/dashboard/RecordSet-Dashboard.js +345 -72
- package/source/views/error/RecordSet-Error-NotFound.json +22 -0
- package/source/views/list/RecordSet-List.js +9 -5
- 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-Router.d.ts.map +1 -1
- package/types/services/RecordsSet-MetaController.d.ts +12 -11
- package/types/services/RecordsSet-MetaController.d.ts.map +1 -1
- package/types/views/RecordSet-Filter.d.ts.map +1 -1
- package/types/views/dashboard/RecordSet-Dashboard-RecordListHeader.d.ts.map +1 -1
- package/types/views/dashboard/RecordSet-Dashboard.d.ts +22 -2
- package/types/views/dashboard/RecordSet-Dashboard.d.ts.map +1 -1
- package/types/views/list/RecordSet-List.d.ts.map +1 -1
|
@@ -12,36 +12,111 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
12
12
|
"AutoRenderMainViewportViewAfterInitialize": false
|
|
13
13
|
},
|
|
14
14
|
|
|
15
|
-
"
|
|
16
|
-
|
|
15
|
+
"Manifests": // Manifest'Ohs: Breakfast of Champions
|
|
16
|
+
{
|
|
17
|
+
"Bestsellers":
|
|
17
18
|
{
|
|
18
|
-
"Scope": "
|
|
19
|
+
"Scope": "Bestsellers",
|
|
19
20
|
"CoreEntity": "Book",
|
|
20
|
-
"
|
|
21
|
+
"TitleTemplate": "Bestsellers ({~D:Record.RecordSet~} LoL)",
|
|
22
|
+
"GlobalSolvers":
|
|
21
23
|
[
|
|
22
24
|
{
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"Destination": "State.BookAuthorJoins"
|
|
25
|
+
"Ordinal": 0,
|
|
26
|
+
"Expression": "AppData.AuthorsFavoriteNumber = AVG(RecordSubset[].IDBook)",
|
|
26
27
|
},
|
|
27
28
|
{
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"Destination": "State.Authors"
|
|
29
|
+
"Ordinal": 1,
|
|
30
|
+
"Expression": "AppData.AuthorsLeastFavoriteNumber = SUM(RecordSubset[].IDBook)",
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
"JoinValue": "IDAuthor",
|
|
41
|
-
"RecordDestinationAddress": "Authors"
|
|
42
|
-
}
|
|
33
|
+
"Ordinal": 1,
|
|
34
|
+
"Expression": "AppData.BookCount = COUNT(RecordSet[])",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"Ordinal": 1,
|
|
38
|
+
"Expression": "AppData.TotalLibraryValue = SUM(RecordSet[].Price)",
|
|
39
|
+
},
|
|
43
40
|
],
|
|
44
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":
|
|
45
120
|
{
|
|
46
121
|
"Title":
|
|
47
122
|
{
|
|
@@ -53,9 +128,8 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
53
128
|
{
|
|
54
129
|
"Name": "Authors",
|
|
55
130
|
"Hash": "Authors",
|
|
56
|
-
"
|
|
131
|
+
"PictDashboard":
|
|
57
132
|
{
|
|
58
|
-
"InputType": "ReadOnly",
|
|
59
133
|
}
|
|
60
134
|
},
|
|
61
135
|
"AuthorCount":
|
|
@@ -63,20 +137,44 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
63
137
|
"Name": "Number of Authors",
|
|
64
138
|
"Hash": "AuthorCount",
|
|
65
139
|
"DataType": "Number",
|
|
66
|
-
"
|
|
140
|
+
"PictDashboard":
|
|
67
141
|
{
|
|
68
|
-
"InputType": "ReadOnly"
|
|
69
142
|
}
|
|
70
143
|
}
|
|
71
|
-
}
|
|
144
|
+
},
|
|
72
145
|
},
|
|
146
|
+
"NewReleases":
|
|
73
147
|
{
|
|
74
|
-
"Scope": "
|
|
148
|
+
"Scope": "NewReleases",
|
|
149
|
+
"CoreEntity": "Book",
|
|
75
150
|
"Descriptors":
|
|
76
151
|
{
|
|
77
|
-
|
|
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
|
+
},
|
|
78
176
|
}
|
|
79
|
-
|
|
177
|
+
},
|
|
80
178
|
"DefaultRecordSetConfigurations":
|
|
81
179
|
[
|
|
82
180
|
{
|
|
@@ -100,6 +198,11 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
100
198
|
}
|
|
101
199
|
],
|
|
102
200
|
|
|
201
|
+
"RecordSetListManifestOnly": false,
|
|
202
|
+
|
|
203
|
+
"RecordSetListManifests": [ "Bestsellers", "Underdogs", "NewReleases" ],
|
|
204
|
+
"RecordSetDashboardManifests": [ "Bestsellers" ],
|
|
205
|
+
|
|
103
206
|
"RecordSetListHasExtraColumns": true,
|
|
104
207
|
"RecordSetListExtraColumnsHeaderTemplate": "<th style=\"border-bottom: 1px solid #ccc; padding: 5px; background-color: #f2f2f2; color: #333;\">Cover</th>",
|
|
105
208
|
"RecordSetListExtraColumnRowTemplate": "<td><img src=\"{~D:Record.Data.ImageURL~}\"></td>",
|
|
@@ -108,15 +211,19 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
108
211
|
|
|
109
212
|
"RecordSetFilterURLTemplate-Default": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
110
213
|
"RecordSetFilterURLTemplate-List": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
111
|
-
"RecordSetFilterURLTemplate-Dashboard": "/PSRS/{~D:Record.RecordSet~}/
|
|
214
|
+
"RecordSetFilterURLTemplate-Dashboard": "/PSRS/{~D:Record.RecordSet~}/Dashboard/FilteredTo/{~D:Record.FilterString~}",
|
|
112
215
|
|
|
113
216
|
"RecordSetURLPrefix": "/1.0/"
|
|
114
217
|
},
|
|
115
218
|
{
|
|
116
219
|
"RecordSet": "BookstoreInventory",
|
|
220
|
+
"Title": "Bookstore Inventory",
|
|
117
221
|
|
|
118
222
|
"RecordSetType": "MeadowEndpoint", // Could be "Custom" which would require a provider to already be created for the record set.
|
|
119
223
|
"RecordSetMeadowEntity": "Book", // This leverages the /Schema endpoint to get the record set columns.
|
|
224
|
+
|
|
225
|
+
"RecordSetDashboardManifests": [ "Bestsellers", "Underdogs", "NewReleases" ],
|
|
226
|
+
|
|
120
227
|
"RecordDecorationConfiguration":
|
|
121
228
|
[
|
|
122
229
|
{
|
|
@@ -139,6 +246,27 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
139
246
|
"JoinRecordSetAddress": "State.Authors",
|
|
140
247
|
"JoinValue": "IDAuthor",
|
|
141
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"
|
|
142
270
|
}
|
|
143
271
|
],
|
|
144
272
|
"AvailableVerbs": [ "Dashboard" ],
|
|
@@ -151,7 +279,12 @@ module.exports.default_configuration.pict_configuration = (
|
|
|
151
279
|
|
|
152
280
|
"RecordSetFilterURLTemplate-Default": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
153
281
|
"RecordSetFilterURLTemplate-List": "/PSRS/{~D:Record.RecordSet~}/ListFilteredTo/{~D:Record.FilterString~}",
|
|
154
|
-
"RecordSetFilterURLTemplate-Dashboard": "/PSRS/{~D:Record.RecordSet~}/
|
|
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~}",
|
|
155
288
|
|
|
156
289
|
"RecordSetURLPrefix": "/1.0/"
|
|
157
290
|
},
|
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,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"fable-serviceproviderbase": "^3.0.15",
|
|
44
44
|
"pict-provider": "^1.0.3",
|
|
45
|
-
"pict-router": "^1.0.
|
|
45
|
+
"pict-router": "^1.0.4",
|
|
46
46
|
"pict-section-form": "^1.0.97",
|
|
47
47
|
"pict-template": "^1.0.10",
|
|
48
48
|
"pict-view": "^1.0.60"
|
|
@@ -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;
|
|
@@ -40,6 +40,8 @@ class PictRecordSetRouter extends libPictProvider
|
|
|
40
40
|
|
|
41
41
|
addRoutes(pRouter)
|
|
42
42
|
{
|
|
43
|
+
//FIXME: not working
|
|
44
|
+
this.pictRouter.addRoute('/PSRS/404', '{~D:Pict.views[RSP-RecordSet-Error-NotFound].render()~}');
|
|
43
45
|
// TODO: Create some kind of state tracking to see if these routes have already been added
|
|
44
46
|
//this.pictRouter.addRoute('/PSRS/:RecordSet/List/:Begin/:Cap', "{~LV:Record~}");
|
|
45
47
|
this.pict.views['RSP-RecordSet-List'].addRoutes(pRouter);
|