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.
Files changed (24) hide show
  1. package/example_applications/simple_entity/Simple-RecordSet-Application.js +163 -30
  2. package/package.json +3 -3
  3. package/source/application/Pict-Application-RecordSet.js +2 -0
  4. package/source/providers/RecordSet-DynamicSolver.js +305 -0
  5. package/source/providers/RecordSet-Router.js +2 -0
  6. package/source/services/RecordsSet-MetaController.js +68 -69
  7. package/source/templates/Pict-Template-FilterView.js +2 -2
  8. package/source/views/RecordSet-Filter.js +2 -1
  9. package/source/views/dashboard/RecordSet-Dashboard-RecordListEntry.js +1 -1
  10. package/source/views/dashboard/RecordSet-Dashboard-RecordListHeader.js +12 -0
  11. package/source/views/dashboard/RecordSet-Dashboard.js +345 -72
  12. package/source/views/error/RecordSet-Error-NotFound.json +22 -0
  13. package/source/views/list/RecordSet-List.js +9 -5
  14. package/types/application/Pict-Application-RecordSet.d.ts.map +1 -1
  15. package/types/providers/RecordSet-DynamicSolver.d.ts +158 -0
  16. package/types/providers/RecordSet-DynamicSolver.d.ts.map +1 -0
  17. package/types/providers/RecordSet-Router.d.ts.map +1 -1
  18. package/types/services/RecordsSet-MetaController.d.ts +12 -11
  19. package/types/services/RecordsSet-MetaController.d.ts.map +1 -1
  20. package/types/views/RecordSet-Filter.d.ts.map +1 -1
  21. package/types/views/dashboard/RecordSet-Dashboard-RecordListHeader.d.ts.map +1 -1
  22. package/types/views/dashboard/RecordSet-Dashboard.d.ts +22 -2
  23. package/types/views/dashboard/RecordSet-Dashboard.d.ts.map +1 -1
  24. 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
- "DefaultDashboards":
16
- [
15
+ "Manifests": // Manifest'Ohs: Breakfast of Champions
16
+ {
17
+ "Bestsellers":
17
18
  {
18
- "Scope": "Bookstore",
19
+ "Scope": "Bestsellers",
19
20
  "CoreEntity": "Book",
20
- "RecordDecorationConfiguration":
21
+ "TitleTemplate": "Bestsellers ({~D:Record.RecordSet~} LoL)",
22
+ "GlobalSolvers":
21
23
  [
22
24
  {
23
- "Entity": "BookAuthorJoin",
24
- "Filter": "FBL~IDBook~INN~{~PJU:,^IDBook^Record.State.CoreEntityRecordSubset~}",
25
- "Destination": "State.BookAuthorJoins"
25
+ "Ordinal": 0,
26
+ "Expression": "AppData.AuthorsFavoriteNumber = AVG(RecordSubset[].IDBook)",
26
27
  },
27
28
  {
28
- "Entity": "Author",
29
- "Filter": "FBL~IDAuthor~INN~{~PJU:,^IDAuthor^Record.State.BookAuthorJoins~}",
30
- "Destination": "State.Authors"
29
+ "Ordinal": 1,
30
+ "Expression": "AppData.AuthorsLeastFavoriteNumber = SUM(RecordSubset[].IDBook)",
31
31
  },
32
32
  {
33
- "Type": "MapJoin",
34
- "DestinationRecordSetAddress": "State.CoreEntityRecordSubset",
35
- "DestinationJoinValue": "IDBook",
36
- "JoinJoinValueLHS": "IDBook",
37
- "Joins": "State.BookAuthorJoins",
38
- "JoinJoinValueRHS": "IDAuthor",
39
- "JoinRecordSetAddress": "State.Authors",
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
- "PictForm":
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
- "PictForm":
140
+ "PictDashboard":
67
141
  {
68
- "InputType": "ReadOnly"
69
142
  }
70
143
  }
71
- }
144
+ },
72
145
  },
146
+ "NewReleases":
73
147
  {
74
- "Scope": "AuthorSummary",
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~}/DashboardFilteredTo/{~D:Record.FilterString~}",
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~}/DashboardFilteredTo/{~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~}",
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.16",
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.262",
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.3",
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);