fable 3.0.116 → 3.0.118

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fable",
3
- "version": "3.0.116",
3
+ "version": "3.0.118",
4
4
  "description": "Anentity behavior management and API bundling library.",
5
5
  "main": "source/Fable.js",
6
6
  "scripts": {
@@ -1,25 +1,20 @@
1
1
  module.exports = (
2
- {
3
- "Metadata": {
4
- "UUID": false,
5
- "Hash": false,
2
+ {
3
+ "Metadata": {
4
+ "UUID": false,
5
+ "Hash": false,
6
6
 
7
- "Name": "",
8
- "Summary": "",
7
+ "Name": "",
8
+ "Summary": "",
9
9
 
10
- "Version": 0
11
- },
12
- "Status": {
13
- "Completed": false,
14
-
15
- "CompletionProgress": 0,
16
- "CompletionTimeElapsed": 0,
17
-
18
- "TimeStart": 0,
19
- "TimeEnd": 0
20
- },
21
- "Steps": [],
22
- "Errors": [],
23
- "Log": []
24
- }
10
+ "Version": 0
11
+ },
12
+ "Status": {
13
+ "Completed": false,
14
+ "StepCount": 0
15
+ },
16
+ "Steps": [],
17
+ "Errors": [],
18
+ "Log": []
19
+ }
25
20
  );
@@ -1,3 +1,4 @@
1
+ const { PE } = require('big.js');
1
2
  const libFableServiceBase = require('fable-serviceproviderbase');
2
3
 
3
4
  const _OperationStatePrototypeString = JSON.stringify(require('./Fable-Service-Operation-DefaultSettings.js'));
@@ -12,9 +13,6 @@ class FableOperation extends libFableServiceBase
12
13
  // Timestamps will just be the long ints
13
14
  this.timeStamps = {};
14
15
 
15
- // ProgressTrackers have an object format of: {Hash:'SomeHash',EndTime:UINT,CurrentTime:UINT,TotalCount:INT,CurrentCount:INT}
16
- this.progressTrackers = {};
17
-
18
16
  this.serviceType = 'PhasedOperation';
19
17
 
20
18
  this.state = JSON.parse(_OperationStatePrototypeString);
@@ -29,113 +27,136 @@ class FableOperation extends libFableServiceBase
29
27
  this.state.Metadata.Name = (typeof(this.options.Name) == 'string') ? this.options.Name : `Unnamed Operation ${this.state.Metadata.UUID}`;
30
28
  this.name = this.state.Metadata.Name;
31
29
 
30
+ this.progressTrackers = this.fable.instantiateServiceProviderWithoutRegistration('ProgressTracker');
31
+
32
+ this.state.OverallProgressTracker = this.progressTrackers.createProgressTracker(`Overall-${this.state.Metadata.UUID}`);
33
+
34
+ // This is here to use the pass-through logging functions in the operation itself.
32
35
  this.log = this;
33
36
  }
34
37
 
35
38
  execute(fExecutionCompleteCallback)
36
39
  {
37
40
  // TODO: Should the same operation be allowed to execute more than one time?
38
- if (this.state.Status.TimeStart)
41
+ if (this.state.OverallProgressTracker.StartTimeStamp > 0)
39
42
  {
40
43
  return fExecutionCompleteCallback(new Error(`Operation [${this.state.Metadata.UUID}] ${this.state.Metadata.Name} has already been executed!`));
41
44
  }
42
45
 
43
- this.state.Status.TimeStart = +new Date();
44
-
45
46
  let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate');
47
+
48
+ this.progressTrackers.setProgressTrackerTotalOperations(this.state.OverallProgressTracker.Hash, this.state.Status.StepCount);
49
+ this.progressTrackers.startProgressTracker(this.state.OverallProgressTracker.Hash);
50
+ this.info(`Operation [${this.state.Metadata.UUID}] ${this.state.Metadata.Name} starting...`);
46
51
 
47
- for (let i = 0; i < this.state.Steps; i++)
52
+ for (let i = 0; i < this.state.Steps.length; i++)
48
53
  {
49
- tmpAnticipate.anticipate(this.stepFunctions[this.state.Steps[i].GUIDStep].bind(this));
54
+ tmpAnticipate.anticipate(
55
+ function(fNext)
56
+ {
57
+ this.fable.log.info(`Step #${i} [${this.state.Steps[i].GUIDStep}] ${this.state.Steps[i].Name} starting...`);
58
+ this.progressTrackers.startProgressTracker(this.state.Steps[i].ProgressTracker.Hash);
59
+ return fNext();
60
+ }.bind(this));
61
+ // Steps are executed in a custom context with
62
+ tmpAnticipate.anticipate(this.stepFunctions[this.state.Steps[i].GUIDStep].bind(
63
+ {
64
+ log:this,
65
+ fable:this.fable,
66
+ options:this.state.Steps[i].Metadata,
67
+ metadata:this.state.Steps[i].Metadata,
68
+ ProgressTracker:this.progressTrackers.getProgressTracker(this.state.Steps[i].ProgressTracker.Hash),
69
+ updateProgressTracker: function(pProgressAmount)
70
+ {
71
+ return this.progressTrackers.updateProgressTracker(this.state.Steps[i].ProgressTracker.Hash, pProgressAmount);
72
+ }.bind(this),
73
+ incrementProgressTracker: function(pProgressIncrementAmount)
74
+ {
75
+ return this.progressTrackers.incrementProgressTracker(this.state.Steps[i].ProgressTracker.Hash, pProgressIncrementAmount);
76
+ }.bind(this),
77
+ setProgressTrackerTotalOperations: function(pTotalOperationCount)
78
+ {
79
+ return this.progressTrackers.setProgressTrackerTotalOperations(this.state.Steps[i].ProgressTracker.Hash, pTotalOperationCount);
80
+ }.bind(this),
81
+ getProgressTrackerStatusString: function()
82
+ {
83
+ return this.progressTrackers.getProgressTrackerStatusString(this.state.Steps[i].ProgressTracker.Hash);
84
+ }.bind(this),
85
+ logProgressTrackerStatus: function()
86
+ {
87
+ return this.log.info(`Step #${i} [${this.state.Steps[i].GUIDStep}]: ${this.progressTrackers.getProgressTrackerStatusString(this.state.Steps[i].ProgressTracker.Hash)}`);
88
+ }.bind(this),
89
+ OperationState:this.state,
90
+ StepState:this.state.Steps[i]
91
+ }));
92
+ tmpAnticipate.anticipate(
93
+ function(fNext)
94
+ {
95
+ this.progressTrackers.endProgressTracker(this.state.Steps[i].ProgressTracker.Hash);
96
+ let tmpStepTimingMessage = this.progressTrackers.getProgressTrackerStatusString(this.state.Steps[i].ProgressTracker.Hash);
97
+ this.fable.log.info(`Step #${i} [${this.state.Steps[i].GUIDStep}] ${this.state.Steps[i].Name} complete.`);
98
+ this.fable.log.info(`Step #${i} [${this.state.Steps[i].GUIDStep}] ${this.state.Steps[i].Name} ${tmpStepTimingMessage}.`);
99
+
100
+ this.progressTrackers.incrementProgressTracker(this.state.OverallProgressTracker.Hash, 1);
101
+ let tmpOperationTimingMessage = this.progressTrackers.getProgressTrackerStatusString(this.state.OverallProgressTracker.Hash);
102
+ this.fable.log.info(`Operation [${this.state.Metadata.UUID}] ${tmpOperationTimingMessage}.`);
103
+ return fNext();
104
+ }.bind(this));
50
105
  }
51
106
 
52
107
  // Wait for the anticipation to complete
53
108
  tmpAnticipate.wait(
54
109
  (pError) =>
55
110
  {
56
- this.state.Status.TimeEnd = +new Date();
111
+ if (pError)
112
+ {
113
+ this.fable.log.error(`Operation [${this.state.Metadata.UUID}] ${this.state.Metadata.Name} had an error: ${pError}`, pError);
114
+ return fExecutionCompleteCallback(pError);
115
+ }
116
+ this.info(`Operation [${this.state.Metadata.UUID}] ${this.state.Metadata.Name} complete.`);
117
+ let tmpOperationTimingMessage = this.progressTrackers.getProgressTrackerStatusString(this.state.OverallProgressTracker.Hash);
118
+ this.progressTrackers.endProgressTracker(this.state.OverallProgressTracker.Hash);
119
+ this.fable.log.info(`Operation [${this.state.Metadata.UUID}] ${tmpOperationTimingMessage}.`);
57
120
  return fExecutionCompleteCallback();
58
121
  });
59
122
  }
60
123
 
61
- /*
62
- TODO: I've gone back and forth on whether this should be an object, JSON
63
- object prototype, or set of functions here. Discuss with colleagues!
64
- */
65
- addStep(fStepFunction, pStepName, pStepDescription, pStepMetadata, pGUIDStep)
124
+ addStep(fStepFunction, pStepMetadata, pStepName, pStepDescription, pGUIDStep)
66
125
  {
67
126
  let tmpStep = {};
68
127
 
69
128
  // GUID is optional
70
129
  tmpStep.GUIDStep = (typeof(pGUIDStep) !== 'undefined') ? pGUIDStep : `STEP-${this.state.Steps.length}-${this.fable.DataGeneration.randomNumericString()}`;
71
130
 
131
+
72
132
  // Name is optional
73
133
  tmpStep.Name = (typeof(pStepName) !== 'undefined') ? pStepName : `Step [${tmpStep.GUIDStep}]`;
74
134
  tmpStep.Description = (typeof(pStepDescription) !== 'undefined') ? pStepDescription : `Step execution of ${tmpStep.Name}.`;
75
135
 
76
- tmpStep.Metadata = (typeof(pStepMetadata) === 'object') ? pStepMetadata : {};
136
+ tmpStep.ProgressTracker = this.progressTrackers.createProgressTracker(tmpStep.GUIDStep);
77
137
 
78
- tmpStep.TimeStart = false;
79
- tmpStep.TimeEnd = false;
138
+ tmpStep.Metadata = (typeof(pStepMetadata) === 'object') ? pStepMetadata : {};
80
139
 
81
140
  // There is an array of steps, in the Operation State itself ... push a step there
82
141
  this.state.Steps.push(tmpStep);
83
142
 
84
- this.stepFunctions[tmpStep.GUIDStep] = fStepFunction;;
143
+ this.stepMap[tmpStep.GUIDStep] = tmpStep;
85
144
 
86
- this.stepMap[tmpStep.GUIDStep];
145
+ this.stepFunctions[tmpStep.GUIDStep] = typeof(fStepFunction) == 'function' ? fStepFunction : function (fDone) { return fDone(); };
87
146
 
88
147
  this.state.Status.StepCount++;
89
148
 
90
149
  return tmpStep;
91
150
  }
92
151
 
93
- /**
94
- * Retrieves a step from the step map based on the provided GUID.
95
- * @param {string} pGUIDStep - The GUID of the step to retrieve.
96
- * @returns {object|boolean} - The step object if found, otherwise false.
97
- */
98
- getStep(pGUIDStep)
152
+ setStepTotalOperations(pGUIDStep, pTotalOperationCount)
99
153
  {
100
- if (this.stepMap.hasOwnProperty(pGUIDStep))
154
+ if (!this.stepMap.hasOwnProperty(pGUIDStep))
101
155
  {
102
- return this.stepMap[pGUIDStep];
156
+ return new Error(`Step [${pGUIDStep}] does not exist in operation [${this.state.Metadata.UUID}] ${this.state.Metadata.Name} when attempting to set total operations to ${pTotalOperationCount}.`);
103
157
  }
104
158
 
105
- return false;
106
- }
107
-
108
- /**
109
- * Begins a step in the Fable service operation.
110
- * @param {string} pGUIDStep - The GUID of the step to begin.
111
- * @returns {object|boolean} - The step object if found, or `false` if not found.
112
- */
113
- beginStep(pGUIDStep)
114
- {
115
- let tmpStep = this.getStep(pGUIDStep);
116
-
117
- if (tmpStep === false)
118
- {
119
- return false;
120
- }
121
-
122
- tmpStep.TimeStart = +new Date();
123
-
124
- return tmpStep;
125
- }
126
-
127
- endStep(pGUIDStep)
128
- {
129
- let tmpStep = this.getStep(pGUIDStep);
130
-
131
- if (tmpStep === false)
132
- {
133
- return false;
134
- }
135
-
136
- tmpStep.TimeEnd = +new Date();
137
-
138
- return tmpStep;
159
+ this.progressTrackers.setProgressTrackerTotalOperations(this.stepMap[pGUIDStep].ProgressTracker.Hash, pTotalOperationCount);
139
160
  }
140
161
 
141
162
  writeOperationLog(pLogLevel, pLogText, pLogObject)
@@ -15,6 +15,11 @@ class FableServiceProgressTime extends libFableServiceBase
15
15
  {
16
16
  let tmpTimeDuration = typeof(pTimeDurationInMilliseconds) == 'number' ? pTimeDurationInMilliseconds : 0;
17
17
 
18
+ if (pTimeDurationInMilliseconds < 0)
19
+ {
20
+ return 'unknown';
21
+ }
22
+
18
23
  let tmpTimeDurationString = '';
19
24
  if (tmpTimeDuration > 3600000)
20
25
  {
@@ -23,7 +23,7 @@ class FableServiceProgressTracker extends libFableServiceBase
23
23
 
24
24
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
25
25
  {
26
- this.fable.log.warn(`Progress Tracker ${tmpProgressTrackerHash} does not exist! Creating a new tracker...`);
26
+ this.fable.log.warn(`ProgressTracker ${tmpProgressTrackerHash} does not exist! Creating a new tracker...`);
27
27
  this.createProgressTracker(tmpProgressTrackerHash, 100);
28
28
  }
29
29
 
@@ -46,6 +46,8 @@ class FableServiceProgressTracker extends libFableServiceBase
46
46
  EndTimeStamp: -1,
47
47
 
48
48
  PercentComplete: -1,
49
+ // If this is set to true, PercentComplete will be calculated as CurrentCount / TotalCount even if it goes over 100%
50
+ AllowTruePercentComplete: false,
49
51
 
50
52
  ElapsedTime: -1,
51
53
  AverageOperationTime: -1,
@@ -58,7 +60,7 @@ class FableServiceProgressTracker extends libFableServiceBase
58
60
 
59
61
  if (this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
60
62
  {
61
- this.fable.log.warn(`Progress Tracker ${tmpProgressTrackerHash} already exists! Overwriting with a new tracker...`);
63
+ this.fable.log.warn(`ProgressTracker ${tmpProgressTrackerHash} already exists! Overwriting with a new tracker...`);
62
64
  this.progressTimes.removeTimeStamp(tmpProgressTracker.StartTimeHash);
63
65
  this.progressTimes.removeTimeStamp(tmpProgressTracker.EndTimeHash);
64
66
  }
@@ -68,11 +70,27 @@ class FableServiceProgressTracker extends libFableServiceBase
68
70
  return tmpProgressTracker;
69
71
  }
70
72
 
73
+ setProgressTrackerTotalOperations(pProgressTrackerHash, pTotalOperations)
74
+ {
75
+ let tmpProgressTrackerHash = (typeof(pProgressTrackerHash) == 'string') ? pProgressTrackerHash : 'Default';
76
+ let tmpTotalOperations = (typeof(pTotalOperations) == 'number') ? pTotalOperations : 100;
77
+
78
+ if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
79
+ {
80
+ this.fable.log.warn(`Attempted to set the total operations of ProgressTracker ${tmpProgressTrackerHash} but it does not exist! Creating a new tracker...`);
81
+ this.createProgressTracker(tmpProgressTrackerHash, tmpTotalOperations);
82
+ }
83
+
84
+ this.progressTrackers[tmpProgressTrackerHash].TotalCount = tmpTotalOperations;
85
+
86
+ return this.progressTrackers[tmpProgressTrackerHash];
87
+ }
88
+
71
89
  startProgressTracker(pProgressTrackerHash)
72
90
  {
73
91
  let tmpProgressTrackerHash = (typeof(pProgressTrackerHash) == 'string') ? pProgressTrackerHash : 'Default';
74
92
 
75
- // This is the only method to lazily create Progress Trackers now
93
+ // This is the only method to lazily create ProgressTrackers now
76
94
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
77
95
  {
78
96
  this.createProgressTracker(tmpProgressTrackerHash, 100);
@@ -82,6 +100,10 @@ class FableServiceProgressTracker extends libFableServiceBase
82
100
 
83
101
  this.progressTimes.createTimeStamp(this.progressTrackers[tmpProgressTrackerHash].StartTimeHash);
84
102
  tmpProgressTracker.StartTimeStamp = this.progressTimes.getTimeStampValue(this.progressTrackers[tmpProgressTrackerHash].StartTimeHash);
103
+ if (tmpProgressTracker.CurrentCount < 0)
104
+ {
105
+ tmpProgressTracker.CurrentCount = 0;
106
+ }
85
107
 
86
108
  return this.solveProgressTrackerStatus(tmpProgressTrackerHash);
87
109
  }
@@ -92,10 +114,12 @@ class FableServiceProgressTracker extends libFableServiceBase
92
114
 
93
115
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
94
116
  {
95
- this.fable.log.error(`Attempted to end Progress Tracker ${tmpProgressTrackerHash} that does not exist!`);
117
+ this.fable.log.error(`Attempted to end ProgressTracker ${tmpProgressTrackerHash} that does not exist!`);
96
118
  return false;
97
119
  }
98
120
 
121
+ let tmpProgressTracker = this.progressTrackers[tmpProgressTrackerHash];
122
+
99
123
  this.progressTimes.createTimeStamp(this.progressTrackers[tmpProgressTrackerHash].EndTimeHash);
100
124
  tmpProgressTracker.EndTimeStamp = this.progressTimes.getTimeStampValue(this.progressTrackers[tmpProgressTrackerHash].EndTimeHash);
101
125
 
@@ -108,7 +132,7 @@ class FableServiceProgressTracker extends libFableServiceBase
108
132
 
109
133
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
110
134
  {
111
- this.fable.log.error(`Attempted to solve Progress Tracker ${tmpProgressTrackerHash} that does not exist!`);
135
+ this.fable.log.error(`Attempted to solve ProgressTracker ${tmpProgressTrackerHash} that does not exist!`);
112
136
  return false;
113
137
  }
114
138
 
@@ -116,7 +140,7 @@ class FableServiceProgressTracker extends libFableServiceBase
116
140
 
117
141
  if ((tmpProgressTracker.TotalCount < 1) || isNaN(tmpProgressTracker.TotalCount))
118
142
  {
119
- this.fable.log.error(`Progress Tracker ${tmpProgressTracker.Hash} has an invalid total count of operations (${tmpProgressTracker.TotalCount}! Setting it to the default of 100...`);
143
+ this.fable.log.error(`ProgressTracker ${tmpProgressTracker.Hash} has an invalid total count of operations (${tmpProgressTracker.TotalCount}! Setting it to the default of 100...`);
120
144
  tmpProgressTracker.TotalCount = 100;
121
145
  }
122
146
 
@@ -124,18 +148,27 @@ class FableServiceProgressTracker extends libFableServiceBase
124
148
  if (tmpProgressTracker.CurrentCount < 1)
125
149
  {
126
150
  tmpProgressTracker.PercentComplete = 0;
127
- return tmpProgressTracker;
128
151
  }
129
152
  else
130
153
  {
131
154
  tmpProgressTracker.PercentComplete = (tmpProgressTracker.CurrentCount / tmpProgressTracker.TotalCount) * 100.0;
132
155
  }
133
156
 
157
+ if (!tmpProgressTracker.AllowTruePercentComplete && (tmpProgressTracker.PercentComplete > 100))
158
+ {
159
+ tmpProgressTracker.PercentComplete = 100;
160
+ }
161
+
134
162
  // Compute the average time per operation
135
163
  this.progressTimes.updateTimeStampValue('CurrentTime');
136
164
  tmpProgressTracker.CurrentTimeStamp = this.progressTimes.getTimeStampValue('CurrentTime');
137
165
  tmpProgressTracker.ElapsedTime = tmpProgressTracker.CurrentTimeStamp - tmpProgressTracker.StartTimeStamp;
138
166
 
167
+ if (tmpProgressTracker.EndTimeStamp > 0)
168
+ {
169
+ tmpProgressTracker.ElapsedTime = tmpProgressTracker.EndTimeStamp - tmpProgressTracker.StartTimeStamp;
170
+ }
171
+
139
172
  if (tmpProgressTracker.CurrentCount > 0)
140
173
  {
141
174
  tmpProgressTracker.AverageOperationTime = (tmpProgressTracker.CurrentTimeStamp-tmpProgressTracker.StartTimeStamp) / tmpProgressTracker.CurrentCount;
@@ -165,7 +198,7 @@ class FableServiceProgressTracker extends libFableServiceBase
165
198
 
166
199
  if (isNaN(tmpCurrentOperations))
167
200
  {
168
- this.fable.log.warn(`Attempted to update Progress Tracker ${tmpProgressTrackerHash} with an invalid number of operations!`)
201
+ this.fable.log.warn(`Attempted to update ProgressTracker ${tmpProgressTrackerHash} with an invalid number of operations!`)
169
202
  return false;
170
203
  }
171
204
 
@@ -191,13 +224,13 @@ class FableServiceProgressTracker extends libFableServiceBase
191
224
 
192
225
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
193
226
  {
194
- this.fable.log.warn(`Attempted to increment Progress Tracker ${tmpProgressTrackerHash} but it did not exist.`);
227
+ this.fable.log.warn(`Attempted to increment ProgressTracker ${tmpProgressTrackerHash} but it did not exist.`);
195
228
  return false;
196
229
  }
197
230
 
198
231
  if (this.progressTrackers[tmpProgressTrackerHash].StartTimeStamp < 1)
199
232
  {
200
- this.fable.log.warn(`Attempted to increment Progress Tracker ${tmpProgressTrackerHash} but it was not started.. starting now.`);
233
+ this.fable.log.warn(`Attempted to increment ProgressTracker ${tmpProgressTrackerHash} but it was not started.. starting now.`);
201
234
  this.startProgressTracker(tmpProgressTrackerHash);
202
235
  }
203
236
 
@@ -206,32 +239,113 @@ class FableServiceProgressTracker extends libFableServiceBase
206
239
  return this.solveProgressTrackerStatus(tmpProgressTrackerHash);
207
240
  }
208
241
 
209
- logProgressTrackerStatus(pProgressTrackerHash)
242
+ getProgressTrackerCompletedOperationCountString(pProgressTrackerHash)
243
+ {
244
+ let tmpProgressTrackerHash = (typeof(pProgressTrackerHash) == 'string') ? pProgressTrackerHash : 'Default';
245
+
246
+ // This call here can mean if we add operations and then immediately get the string, this function runs twice.
247
+ const tmpProgressTracker = this.progressTrackers[tmpProgressTrackerHash];
248
+
249
+ // The states of a progress tracker:
250
+ if (tmpProgressTracker.CurrentCount < 0)
251
+ {
252
+ return `none`;
253
+ }
254
+ else if (tmpProgressTracker.CurrentCount < 1)
255
+ {
256
+ return `0`;
257
+ }
258
+ else
259
+ {
260
+ return `${tmpProgressTracker.CurrentCount}`;
261
+ }
262
+ }
263
+
264
+ getProgressTrackerPercentCompleteString(pProgressTrackerHash)
210
265
  {
211
266
  let tmpProgressTrackerHash = (typeof(pProgressTrackerHash) == 'string') ? pProgressTrackerHash : 'Default';
212
267
 
268
+ // This call here can mean if we add operations and then immediately get the string, this function runs twice.
269
+ // TODO: Is there a pattern to avoid this double call that's worth putting in?
270
+ this.solveProgressTrackerStatus(tmpProgressTrackerHash);
271
+
213
272
  if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
214
273
  {
215
- this.fable.log.info(`>> Progress Tracker ${tmpProgressTrackerHash} does not exist! No stats to display.`);
274
+ return `ProgressTracker ${tmpProgressTrackerHash} does not exist! No stats to display.`;
216
275
  }
217
276
  else
218
277
  {
219
278
  const tmpProgressTracker = this.progressTrackers[tmpProgressTrackerHash];
220
279
 
280
+ // The states of a progress tracker:
281
+ // 1. Not started
282
+ if (tmpProgressTracker.StartTimeStamp < 1)
283
+ {
284
+ return `0%`;
285
+ }
286
+ // 2. Started, but no operations completed
287
+
221
288
  if (tmpProgressTracker.CurrentCount < 1)
222
289
  {
223
- this.fable.log.info(`>> Progress Tracker ${tmpProgressTracker.Hash} has no completed operations. ${tmpProgressTracker.CurrentTime}ms have elapsed since it was started.`);
290
+ return `0%`;
224
291
  }
292
+ // 3. Started, some operations completed
225
293
  else if (tmpProgressTracker.EndTimeStamp < 1)
226
294
  {
227
- this.fable.log.info(`>> Progress Tracker ${tmpProgressTracker.Hash} is ${tmpProgressTracker.PercentComplete.toFixed(3)}% completed - ${tmpProgressTracker.CurrentCount} / ${tmpProgressTracker.TotalCount} operations over ${this.progressTimes.formatTimeDuration(tmpProgressTracker.ElapsedTime)} (median ${this.progressTimes.formatTimeDuration(tmpProgressTracker.AverageOperationTime)} per). Estimated completion in ${this.progressTimes.formatTimeDuration(tmpProgressTracker.EstimatedCompletionTime)}`)
295
+ return `${tmpProgressTracker.PercentComplete.toFixed(3)}%`;
296
+ }
297
+ // 4. Done
298
+ else
299
+ {
300
+ return `${tmpProgressTracker.PercentComplete.toFixed(3)}%`;
301
+ }
302
+ }
303
+ }
304
+
305
+ getProgressTrackerStatusString(pProgressTrackerHash)
306
+ {
307
+ let tmpProgressTrackerHash = (typeof(pProgressTrackerHash) == 'string') ? pProgressTrackerHash : 'Default';
308
+
309
+ // This call here can mean if we add operations and then immediately get the string, this function runs twice.
310
+ // TODO: Is there a pattern to avoid this double call that's worth putting in?
311
+ this.solveProgressTrackerStatus(tmpProgressTrackerHash);
312
+
313
+ if (!this.progressTrackers.hasOwnProperty(tmpProgressTrackerHash))
314
+ {
315
+ return `ProgressTracker ${tmpProgressTrackerHash} does not exist! No stats to display.`;
316
+ }
317
+ else
318
+ {
319
+ const tmpProgressTracker = this.progressTrackers[tmpProgressTrackerHash];
320
+
321
+ // The states of a progress tracker:
322
+ // 1. Not started
323
+ if (tmpProgressTracker.StartTimeStamp < 1)
324
+ {
325
+ return `ProgressTracker ${tmpProgressTracker.Hash} has not been started yet.`;
228
326
  }
327
+ // 2. Started, but no operations completed
328
+
329
+ if ((tmpProgressTracker.CurrentCount < 1) && (tmpProgressTracker.EndTimeStamp < 1))
330
+ {
331
+ return `ProgressTracker ${tmpProgressTracker.Hash} has no completed operations. ${this.progressTimes.formatTimeDuration(tmpProgressTracker.ElapsedTime)} have elapsed since it was started.`;
332
+ }
333
+ // 3. Started, some operations completed
334
+ else if (tmpProgressTracker.EndTimeStamp < 1)
335
+ {
336
+ return `ProgressTracker ${tmpProgressTracker.Hash} is ${tmpProgressTracker.PercentComplete.toFixed(3)}% completed - ${tmpProgressTracker.CurrentCount} / ${tmpProgressTracker.TotalCount} operations over ${this.progressTimes.formatTimeDuration(tmpProgressTracker.ElapsedTime)} (median ${this.progressTimes.formatTimeDuration(tmpProgressTracker.AverageOperationTime)} per). Estimated completion: ${this.progressTimes.formatTimeDuration(tmpProgressTracker.EstimatedCompletionTime)}`; }
337
+ // 4. Done
229
338
  else
230
339
  {
231
- this.fable.log.info(`>> Progress Tracker ${tmpProgressTracker.Hash} is done and completed ${tmpProgressTracker.CurrentCount} / ${tmpProgressTracker.TotalCount} operations in ${tmpProgressTracker.EndTime}ms.`)
340
+ return `ProgressTracker ${tmpProgressTracker.Hash} is done. It completed ${tmpProgressTracker.CurrentCount} / ${tmpProgressTracker.TotalCount} operations in ${this.progressTimes.formatTimeDuration(tmpProgressTracker.ElapsedTime)} (median ${this.progressTimes.formatTimeDuration(tmpProgressTracker.AverageOperationTime)} per).`;
232
341
  }
233
342
  }
234
343
  }
344
+
345
+ logProgressTrackerStatus(pProgressTrackerHash)
346
+ {
347
+ this.fable.log.info(this.getProgressTrackerStatusString(pProgressTrackerHash));
348
+ }
235
349
  }
236
350
 
237
351
  module.exports = FableServiceProgressTracker;
@@ -86,26 +86,76 @@ suite
86
86
  function(fDone)
87
87
  {
88
88
  let testFable = new libFable();
89
- let tmpOperation = testFable.instantiateServiceProvider('Operation', {Name:'The last operation in town.'});
89
+ let tmpOperation = testFable.instantiateServiceProvider('Operation', {Name:'MTO - The Mega Test Operation'});
90
90
  Expect(tmpOperation).to.be.an('object');
91
91
  Expect(testFable.servicesMap.Operation.hasOwnProperty(tmpOperation.Hash)).to.equal(true);
92
92
  Expect(tmpOperation.state.Log.length).to.equal(0);
93
- let tmpText = `Operation ${tmpOperation.Hash} starting up...`;
93
+
94
+ let tmpText = `Created operation ${tmpOperation.Hash}; ready to add a step and start execution...`;
94
95
  tmpOperation.log.info(tmpText);
95
96
  Expect(tmpOperation.state.Log.length).to.equal(1);
96
97
  Expect(tmpOperation.state.Log[0]).to.contain(tmpText);
97
98
 
98
- tmpOperation.addStep('001-Login',
99
- (fStepComplete) =>
99
+ let tmpOperationCount = 10;
100
+
101
+ tmpOperation.addStep(
102
+ function (fStepComplete)
100
103
  {
101
- setTimeout(
102
- () =>
103
- {
104
- tmpOperation.log.trace('Login thingy complete!');
105
- return fStepComplete();
106
- }, 150);
107
- }, 'Example step 1!');
104
+ this.logProgressTrackerStatus();
105
+
106
+ let tmpAnticipate = testFable.newAnticipate();
107
+
108
+ for (let i = 1; i <= tmpOperationCount; i++)
109
+ {
110
+ tmpAnticipate.anticipate((fWorkComplete)=>
111
+ {
112
+ let tmpDelay = Math.floor(Math.random() * 100) + 100;
113
+ this.log.info(`Doing some big work for ${tmpDelay}ms on iteration ${i}...`);
114
+ setTimeout(
115
+ () =>
116
+ {
117
+ this.log.info(`Work done for iteration ${i}.`);
118
+ this.incrementProgressTracker(1);
119
+ this.logProgressTrackerStatus();
120
+ return fWorkComplete();
121
+ }, tmpDelay);
122
+ });
123
+ }
124
+
125
+ tmpAnticipate.wait(fStepComplete);
126
+ }, {}, 'Example Step 1', 'This is the first step of the mega test.', 'STEP1');
127
+ tmpOperation.setStepTotalOperations('STEP1', tmpOperationCount);
128
+
129
+ tmpOperation.addStep(
130
+ function (fStepComplete)
131
+ {
132
+ let tmpShortOperationCount = 300;
133
+
134
+ this.setProgressTrackerTotalOperations(tmpShortOperationCount);
135
+ this.logProgressTrackerStatus();
136
+
137
+ let tmpAnticipate = testFable.newAnticipate();
138
+
139
+ for (let i = 1; i <= tmpShortOperationCount; i++)
140
+ {
141
+ tmpAnticipate.anticipate((fWorkComplete)=>
142
+ {
143
+ let tmpDelay = Math.floor(Math.random() * 3) + 3;
144
+ this.log.info(`Doing a little work for ${tmpDelay}ms on iteration ${i}...`);
145
+ setTimeout(
146
+ () =>
147
+ {
148
+ this.log.info(`Leetle work done for iteration ${i}.`);
149
+ this.incrementProgressTracker(1);
150
+ this.logProgressTrackerStatus();
151
+ return fWorkComplete();
152
+ }, tmpDelay);
153
+ });
154
+ }
108
155
 
156
+ tmpAnticipate.wait(fStepComplete);
157
+ }, {}, 'Example Step 2', 'This is the second step of the mega test.', 'STEP2');
158
+
109
159
  tmpOperation.execute(fDone);
110
160
  }
111
161
  );