Our state of the art Gantt chart


Post by devcat »

Hello,

We are implementing Live Updates in a web application with Angular 14 and .NET 8, Gantt version 6.0.4. We have 2 clients open and we make changes to the name of a Task.

The first revision that receives the second client is done correctly:

{
	"project":4,
	"revisions":[
		{
			"revisionId": "server-269",
			"localRevisionId":"local-1",
			"clientId":"_generatedProjectModel_fb1cbcc3-9dc1-464d-878e-9b192213b7fa",
			"changes":{
				"tasks":{
					"updated":[{"Name":"Task test A","id":103194}]
				}
			}
		}
	]
}

But the next revision generates a warning and error:

{
	"project":4,
	"revisions":[
		{
			"revisionId": "server-270",
			"localRevisionId":"local-2",
			"clientId":"_generatedProjectModel_fb1cbcc3-9dc1-464d-878e-9b192213b7fa",
			"changes":{
				"tasks":{
					"updated":[{"Name":"Task test B","id":103194}]
				}
			}
		}
	]
}

Warning:

gantt.module.js:30650 Revision is not found
canCheckoutTo	@	gantt.module.js:30650
canCheckoutTo	@	gantt.module.js:32054
checkoutTo	@	gantt.module.js:32210
checkoutToLastCommittedRevision	@	gantt.module.js:32216
(anonymous)	@	gantt.module.js:193626
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
(anonymous)	@	gantt.module.js:193025
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
runQueueStep	@	gantt.module.js:193024
(anonymous)	@	gantt.module.js:191923
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
invoke	@	zone.js:372
onInvoke	@	core.mjs:26231
invoke	@	zone.js:371
run	@	zone.js:134
(anonymous)	@	zone.js:1275
invokeTask	@	zone.js:406
onInvokeTask	@	core.mjs:26218
invokeTask	@	zone.js:405
runTask	@	zone.js:178
drainMicroTaskQueue	@	zone.js:585
invokeTask	@	zone.js:491
invokeTask	@	zone.js:1661
globalCallback	@	zone.js:1692
globalZoneAwareCallback	@	zone.js:1725

Error:

core.mjs:7635 ERROR 
Error: Uncaught (in promise): Error: Abstract method call!
Error: Abstract method call!
    at throwAbstractMethodCall (gantt.module.js:29367:9)
    at AutoReadyStateClass.onStartRevision (gantt.module.js:29395:5)
    at stateTransition (gantt.module.js:31349:48)
    at StateTrackingManager2.startRevision (gantt.module.js:32230:5)
    at gantt.module.js:193671:9
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at asyncToGenerator.js:32:1
    at new ZoneAwarePromise (zone.js:1429:21)
    at resolvePromise (zone.js:1211:31)
    at zone.js:1118:17
    at zone.js:1134:33
    at asyncGeneratorStep (asyncToGenerator.js:6:1)
    at _throw (asyncToGenerator.js:29:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26231:33)
    at _ZoneDelegate.invoke (zone.js:371:52)
    at Zone.run (zone.js:134:43)
    at zone.js:1275:36

Post by Maxim Gorkovsky »

Hello.
The warning means that client tried to checkout to the last local committed revision and could not found one. It might happen if you reset the STM queue, reset code currently has a bug when it sets revision queue in a wrong state after reset. It might happen also if you initialize revisions before load. Try re-initializing them after load or after you reset the queue (if you do so):

gantt.project.on({ load() { gantt.project.initRevisions(clientId); } })

That should do the trick.

Exception suggests smth has altered the state of the state tracking manager in the middle of the transaction. That can happen if you call API programmatically and do not use the queue. Or maybe it is a result of the warning.


Post by devcat »

Thanks for the reply. Now if I have the task editing dialog open, a change revision comes from the second client, even if it is from a different task than the one I am editing, and I click save, then the error is generated:

ERROR Error: Uncaught (in promise): TypeError: Cannot read properties of null (reading 'mergeUpdateModelActions')
TypeError: Cannot read properties of null (reading 'mergeUpdateModelActions')
    at StateTrackingManager2.mergeTransactionUpdateActions (gantt.module.js:32047:17)
    at StateTrackingManager2.stopTransaction (gantt.module.js:31712:10)
    at TaskEdit2.commitStmTransaction (gantt.module.js:149595:13)
    at TaskEdit2.commitStmTransaction (gantt.module.js:149680:13)
    at gantt.module.js:149634:22
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at asyncToGenerator.js:32:1
    at ZoneAwarePromise (zone.js:1429:21)
    at resolvePromise (zone.js:1211:31)
    at resolvePromise (zone.js:1165:17)
    at ZoneAwarePromise.resolve (zone.js:1294:20)
    at _ObjectHelper.isPromise (gantt.module.js:727:22)
    at gantt.module.js:189932:24
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26231:33)

Post by Maxim Gorkovsky »

This call stack does not look right. Instead of second commitStmTransaction there should be finishFeatureTransaction. Have you enabled enableTransactionalFeatures flag? https://bryntum.com/products/gantt/docs/api/Scheduler/view/mixin/TransactionalFeatureMixin#config-enableTransactionalFeatures


Post by devcat »

Thanks, I've added the property. Now, another error is generated when deleting a dependency.

I edit a task and delete the element from the 'predecessors' tab, when I click on the save button in the dialog a revision is generated, with the dependency element deleted and the task's update.

When receiving the message from the backend and the client executes applyRevisions() a new revision is immediately generated with only update of the task's EndDate field. When receiving the message back from the backend the error is generated in the client:

Warning:

gantt.module.js:30650 Revision is not found
canCheckoutTo	@	gantt.module.js:30650
canCheckoutTo	@	gantt.module.js:32054
checkoutTo	@	gantt.module.js:32210
(anonymous)	@	gantt.module.js:193650
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
(anonymous)	@	asyncToGenerator.js:32
ZoneAwarePromise	@	zone.js:1429
(anonymous)	@	asyncToGenerator.js:21
confirmLocalRevisionFromRemote	@	gantt.module.js:193648
(anonymous)	@	gantt.module.js:193631
asyncGeneratorStep	@	asyncToGenerator.js:3
_next	@	asyncToGenerator.js:25
invoke	@	zone.js:372
onInvoke	@	core.mjs:26231
invoke	@	zone.js:371
run	@	zone.js:134
(anonymous)	@	zone.js:1275
invokeTask	@	zone.js:406
onInvokeTask	@	core.mjs:26218
invokeTask	@	zone.js:405
runTask	@	zone.js:178
drainMicroTaskQueue	@	zone.js:585
invokeTask	@	zone.js:491
invokeTask	@	zone.js:1661
globalCallback	@	zone.js:1692
globalZoneAwareCallback	@	zone.js:1725

Error:

core.mjs:7635 ERROR 
Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading 'title')
TypeError: Cannot read properties of undefined (reading 'title')
    at StateTrackingManager2.commitRevision (gantt.module.js:32259:67)
    at gantt.module.js:193665:9
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26231:33)
    at _ZoneDelegate.invoke (zone.js:371:52)
    at Zone.run (zone.js:134:43)
    at zone.js:1275:36
    at resolvePromise (zone.js:1211:31)
    at zone.js:1118:17
    at zone.js:1134:33
    at asyncGeneratorStep (asyncToGenerator.js:6:1)
    at _throw (asyncToGenerator.js:29:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26231:33)
    at _ZoneDelegate.invoke (zone.js:371:52)
    at Zone.run (zone.js:134:43)
    at zone.js:1275:36

Part of our implementation:

this.signalSubscription = this.signalService.observableProjectChange.subscribe(async (model: RevisionResponse) => {

    await this.gantt.project.applyRevisions(model.revisions.map(r => ({
      revisionId            : r.revisionId,
      localRevisionId       : r.localRevisionId,
      conflictResolutionFor : r.conflictResolutionFor,
      clientId              : r.clientId,
      changes               : r.changes
    } as RevisionInfo)));

    // We don't let them do "undo"
    this.gantt.project.stm.resetQueue();
    this.gantt.project.stm.enable();
  
    // Bugfix: Try re-initializing them after load or after you reset the queue
    const id = this.gantt.project.id;
    this.gantt.project.initRevisions(id.toString());
  });

Post by Maxim Gorkovsky »

This call stack suggests that you're passing a revision to a client and client does not have any revisions, because you're reset the revision queue. For example, assume your client has id client-1 and you're applying following revision:

{
  revisionId: 'remote-1',
  localRevisionId: 'local-1',
  clientId: 'client-1',
  changes: ...
  }
  

If client local revision queue is empty exception will be raised because local revision is missing. I've opened a ticket to allow applying revision after resetting the queue: https://github.com/bryntum/support/issues/9826

However, it is not so obvious what to do in this case. If we reset the queue and revision queue becomes empty, we cannot be sure we're applying revision to the correct state. I think this exception is valid. Maybe STM can be reset without resetting the revision queue.
 
Speaking of which: why do you need to reset the queue? Does the user see undo/redo button? It seems that in the scenario with live updates user will be seeing undo/redo buttons getting enabled and disabled constantly.


Post Reply