I have a rally poker app which is custom html where there is a screen for Team member and a screen for Scrum Master. What I need is how to control concurrent updates from team members when they use this custom html app. As of today, the app only takes the latest updates from the user who submitted last in concurrent session. It overrides everyone else data. It would be great help if someone can post the sample code of how to check for concurrent session and stop overriding of user inputs in concurrent sessions. Thanks for your help.
Click here for Scrum Master View Screenshot
@kyle - I tried to add Version ID as mentioned by you
Thanks Kyle. I did tried a cumbersome way of checking the versionID by repeating the fetch code however in my testing, it proved that versionID wasnt setting properly for concurrent sessions. I have pasted the code snippet which has the versionID check. Can you please review and let me know if anything needs to be added. Appreciate your help
Ext.create('Ext.window.Window', {
title: 'Planning Poker-Team Member view UserStoryNumber : '+userstorynumber, height: 200, width: 400, layout: 'fit',
items: [
//*************** START PANEL FORM 1 *******************
Ext.create('Ext.form.Panel', {
bodyPadding: 5, width: 350, url: 'save-form.php',
layout: { type: 'vbox', align: 'stretch', pack: 'start' },
defaults: { anchor: '100%' },
//Fields
defaultType: 'textfield',
items: [
{ fieldLabel: 'Estimate', name: 'Estimate', allowBlank: false, id: 'estimate' },
{ fieldLabel: 'Justification', name: 'Justification', allowBlank: false, id: 'justification' }
],
// Reset and Submit buttons
buttons: [
//{ text: 'Reset', handler: function () { this.up('form').getForm().reset(); } },
{
text: 'Submit', formBind: true, disabled: true,
handler: function () {
//alert(f_newEstimate('name','points','role','justification'));
var EstimateObject = getExistingEstimates(result);
if (EstimateObject)
console.log('Notes ************ ' + result.get('Notes'));
console.log('Stringfy ************ ' + JSON.stringify(EstimateObject));
//rec.set('PlanEstimate', estFinal);
var jsonobj = f_newEstimate(name, Ext.getCmp('estimate').getValue(), role, Ext.getCmp('justification').getValue());
console.log("json raw is", jsonobj);
//console.log("json justi is", jsonobj.justification);
console.log("PO note is", result.get('c_PONote'));
//var existingPONote = result.get('c_PONote');
//var newponote = existingPONote + ',' + jsonobj;
var CurrentVersionID;
var newVersionID;
//Exp Karthik Starts
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
CurrentVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
var existingPONote = result.get('c_PONote');
var newponote = '';
if (existingPONote.length == 0) {
newponote = '[' + jsonobj + ']';
}
else {
replacestr = ',' + jsonobj + ']';
newponote = existingPONote.replace(']', replacestr);
}
rec.set('c_PONote', newponote);
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
newVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
if (CurrentVersionID == newVersionID) {
rec.save();
}
else
{
var myStore = Ext.create('Rally.data.wsapi.Store', {
model: 'User Story',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: [
{
property: 'ObjectID',
operator: '=',
value: objectId
}
],
listeners: {
load: function (myStore, myData, success) {
newVersionID = myStore.data.items["0"].data.VersionId;
console.log("Version ID is", myStore.data.items["0"].data.VersionId);
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['ObjectID', 'c_PONote', 'VersionId'] // Look in the WSAPI docs online to see all fields available!
});
var existingPONote = myStore.data.items["0"].data.c_PONote;
var newponote = '';
if (existingPONote.length == 0) {
newponote = '[' + jsonobj + ']';
}
else {
replacestr = ',' + jsonobj + ']';
newponote = existingPONote.replace(']', replacestr);
}
rec.set('c_PONote', newponote);
console.log("Concurrent versions saved", newponote);
rec.save();
}
console.log('Submit Success1');
this.up('window').close();
}
}]
All objects in WSAPI include a VersionId field which is automatically incremented on the server every time the object is updated. The way the product deals with concurrency issues is to always fetch this VersionId field. Then users can make updates locally. When users save, first the story is read again. If the VersionId is the same as it was when it was first read, the update is safe to send. If that VersionId is higher though, that means another user has updated the story in the meantime.
In the case of a conflict you can just merge your local changes into that newly read object and then try the save again. You can repeat this as needed until the user is able to perform a clean save.