I'm fairly new to the Rally API and JS, and Stackoverflow for that matter. I have been using Stackoverflow to answer all of my questions so far, but I can't seem to find anything about adding new TimeEntryValues.
I am building an app that allows to add new TimeEntryValues. I can add or load TimeEntryItems but for TimeEntryValues, I ever only seem to post the Hours field when looking at the trace in the browser.
Here is a simplified code that exhibits the same problem.
launch: function(){
//For this example, pre-define Time Entry Reference, Date, and Hour value
var myTimeEntryItem = "/timeentryitem/1234";
var myDateValue = "2016-05-20T00:00:00.000Z";
var myHours = 2.5;
//Check if Time Entry Value (TEV) already exists
var TEVstore = Ext.create('Rally.data.WsapiDataStore', {
model: 'TimeEntryValue',
fetch: ['ObjectID','TimeEntryItem','Hours','DateVal'],
filters: [{
property: 'TimeEntryItem',
operator: '=',
value: myTimeEntryItem
},
{
property: 'DateVal',
operator: '=',
value: myDateValue
}],
autoLoad: true,
listeners: {
load: function(TEVstore, tevrecords, success) {
//No record found - TEV does not exist
if (tevrecords.length === 0) {
console.log("Creating new TEV record");
Rally.data.ModelFactory.getModel({
type: 'TimeEntryValue',
success: function(tevModel) {
var newTEV = Ext.create(tevModel, {
DateVal: myDateValue,
Hours: myHours,
TimeEntryItem: myTimeEntryItem
});
newTEV.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log("Succesful Save");
//Do something here
}
}
});
}
});
} else {
console.log("TEV Record exists.");
//Do something useful here
}
}
},
scope: this
});
}
Any hints what I am doing wrong are greatly appreciated.
Thanks
This is actually a longstanding defect in App SDK caused by a mismatch in the WSAPI attribute metadata and the client side models used for persisting data to the server.
Basically what's happening is the DateVal and TimeEntryItem fields are marked required and readonly, which doesn't make sense. Really, they need to be writable on create and then readonly after that.
So all you need to do in your app is before you try to save your new TimeEntryValue just mark the DateVal and TimeEntryItem fields as persistable and you should be good to go.
//workaround
tevModel.getField('DateVal').persist = true;
tevModel.getField('TimeEntryItem').persist = true;
//proceed as usual
var newTEV = Ext.create(tevModel, {
DateVal: myDateValue,
Hours: myHours,
TimeEntryItem: myTimeEntryItem
});
// ...
Related
I have been trying to query Rally just to get a certain object by its ObjectID, but then I end up needing its parent in many cases. For example, for a task, I need its associated User Story, and that Story's Feature. It ended up being quite the cascade of callbacks (fair warning, it's ugly) - can anyone recommend a more efficient solution? The ability to query by OID is nice, but its too bad I need more than just information about that OID. (Note - solution must utilize WSAPI, not LBAPI).
Rally.data.WsapiModelFactory.getModel({
type: 'Task',
context: {
workspace: Rally.util.Ref.getRelativeUri()
},
success: function(taskModel) {
taskModel.load(oid, {
scope: this,
callback: function(taskRecord, op, success) {
if (taskRecord && taskRecord.data.WorkProduct && taskRecord.data.WorkProduct._type == "HierarchicalRequirement") {
// get User Story
Rally.data.WsapiModelFactory.getModel({
type: 'User Story',
context: {
workspace: Rally.util.Ref.getRelativeUri()
},
success: function(userStoryModel) {
userStoryModel.load(taskRecord.data.WorkProduct._ref, {
scope: this,
callback: function(storyRecord, op, success) {
if (storyRecord && storyRecord.data && storyRecord.data.Feature) {
// Get Feature
Rally.data.WsapiModelFactory.getModel({
type: 'PortfolioItem/Feature',
context: {
workspace: Rally.util.Ref.getRelativeUri()
},
success: function(featureModel) {
featureModel.load(storyRecord.data.Feature._ref, {
scope: this,
callback: function(featureRecord) {
displayTask(oid, taskRecord, storyRecord, featureRecord);
}
});
}
});
}
}
});
}
});
}
}
});
}
});
You can pull in the Work Product parent and its associated Feature directly in a single query. Try this:
Ext.create('Rally.data.WsapiDataStore', {
model : 'Task',
fetch : ['WorkProduct','Name','Feature'],
filters : [{
property : 'ObjectID',
value : OID
}]
}).load({
callback : function(records, operation, success) {
var task = records[0];
var userStory = task.get('WorkProduct');
var feature = userStory.Feature;
}
});
I have a rally grid that shows defects. I want do add a column that shows the number of days a defect has been open.
I know can do that by adding a custom renderer in the column configs, but I would also like to sort on this column. Unfortunately, the renderer does not change the sorting of the column.
I think I might be able to use the convert() function on the store instead to create a new virtual column (in this case openAgeDays), but I'm not sure how to do this from the constructor--I presume I make some changes to storeConfig?
Does anyone have an example of how to use a convert function (assuming that this is the right way to do it) to add a new virtual, sortable column to a rally grid?
this.grid = this.add({
xtype: 'rallygrid',
model: model,
disableColumnMenus: false,
storeConfig: [...]
As is the answer in the duplicate, you can add a doSort to the column:
{dataIndex: 'Parent', name: 'Parent',
doSort: function(state) {
var ds = this.up('grid').getStore();
var field = this.getSortParam();
console.log('field',field);
ds.sort({
property: field,
direction: state,
sorterFn: function(v1, v2){
console.log('v1',v1);
console.log('v2',v2);
if (v1.raw.Parent) {
v1 = v1.raw.Parent.Name;
} else {
v1 = v1.data.Name;
}
if (v2.raw.Parent) {
v2 = v2.raw.Parent.Name;
} else {
v2 = v2.data.Name;
}
return v1.localeCompare(v2);
}
});
},
renderer: function(value, meta, record) {
var ret = record.raw.Parent;
if (ret) {
return ret.Name;
} else {
meta.tdCls = 'invisible';
return record.data.Name;
}
}
},
I'm new to breezejs. I am trying to define my entity type in the client without getting metadata from the server. I have a property called ID in the server entity.
I've defaulted the naming convention in the client side to camel case using the following code.
breeze.NamingConvention.camelCase.setAsDefault();
so, I started to map the entity as follows
store.addEntityType({
shortName: "Photo",
namespace: "MyProj.Models",
dataProperties: {
id: {
dataType: DataType.Guid,
isNullable: false,
isPartOfKey: true
},
title: {
dataType: DataType.String
},
description: {
dataType: DataType.String
},
createdDate: {
dataType: DataType.DateTime
},
}
});
This worked all fine, except the id field is not getting the proper value. instead, it has the default value set by the breeze datatype ctor which is equals to Guid.Empty.
by stepping through breezejs debug script, I found out that it looks for a property name called Id in the data that comes from the ajax request. But it can't find it as the property is ID so it initialize it to empty guid string. I assumed that by setting nameOnServer property of the dataProperty id, I will be able to fix it.
store.addEntityType({
shortName: "Photo",
namespace: "MyProj.Models",
dataProperties: {
id: {
dataType: DataType.Guid,
isNullable: false,
nameOnServer: 'ID',
isPartOfKey: true
},
title: {
dataType: DataType.String
},
description: {
dataType: DataType.String
},
createdDate: {
dataType: DataType.DateTime
},
}
});
But it didn't work.
Further digging through the breez.debug.js code, in the method updateClientServerNames on line 7154, it seems it ignores the nameOnServer that I have defined.
Am I missing something here?
Okay, Feel like I spent my whole life through breeze documentation. Anyways, Finally solved the issue. To be honest, this wasn't a problem in breeze (but I wonder why it doesn't override the actual nameOnServer when I provide one). It's an error made by one of the developers in the early stage of the database implementation (probably 6 years ago). If the database adhered to Pascal Case naming convention, things would have worked perfectly fine.
As a solution I wrote a custom naming convention which corrects the naming convention error when it has ID in the name and combines it with camelCase naming convention.
var createInconsistenIDConvention = function () {
var serverPropertyNameToClient = function (serverPropertyName, prop) {
if (prop && prop.isDataProperty && (prop.nameOnServer && prop.nameOnServer === "ID")) {
return "id";
} else {
var firstSection = serverPropertyName.substr(0, 1).toLowerCase();
var idSection = "";
if (serverPropertyName.substr(1).indexOf("ID") != -1) {
firstSection += serverPropertyName.substr(1, serverPropertyName.substr(1).indexOf("ID")).toLowerCase() + "Id";
} else {
firstSection += serverPropertyName.substr(1);
}
return firstSection;
}
}
var clientPropertyNameToServer = function (clientPropertyName, prop) {
if (prop && prop.isDataProperty && (prop.nameOnServer && prop.nameOnServer.indexOf("ID") != -1)) {
return prop.nameOnServer;
} else {
return clientPropertyName.substr(0, 1).toUpperCase() + clientPropertyName.substr(1);
}
}
return new breeze.NamingConvention({
name: "inconsistenID",
serverPropertyNameToClient: serverPropertyNameToClient,
clientPropertyNameToServer: clientPropertyNameToServer
});
};
Not sure if the way I've used nameOnServer property is not correct. I couldn't find any documentation on that in breeze website.
please note that the above code only consider situations like ID, CountryID, GameID, PersonID etc.
Problem solved for now.
In Rally SDK 2, how do I update a hash field, like the Author field for a changeset? I read how to update the Message field, but I can't figure out how to update Author["DisplayName"] hash.
var new_message = settings.message;
Rally.data.ModelFactory.getModel({
type: 'Changeset',
success: function(model) {
model.load( '1234', {
fetch: [ 'Artifacts' ],
callback: function(result, operation) {
if ( operation.wasSuccessful() ){
var message = new_message;
record.set( 'Message', message);
record.save( {
callback: function( resultset, operation ) {
console.log( "After saving:", resultset );
if ( operation.wasSuccessful() ) {
var that = tree.ownerCt.ownerCt.ownerCt.ownerCt;
that._getChangesets();
}
}
} );
}
}
})
}
});
The Author property on Changeset is of type User. Like any other object associations on Rally's WSAPI you just set this property to the ref of the object you'd like to link. You set this the same way as you're currently setting Message in your above code snippet. (Assuming author is writable after the changeset has already been created).
record.set('Author', '/user/123456');
You can probably also avoid the deeply nested structure of your code a little bit by specifying scope on your callbacks and using member functions in your app definition:
_loadChangesetModel: function() {
//If you already have a changeset record you can get the model
//via record.self. Otherwise, load it fresh.
Rally.data.ModelFactory.getModel({
type: 'Changeset',
success: this._onChangesetModelLoaded,
scope: this
});
},
_onChangesetModelLoaded: function(model) {
model.load( '1234', {
fetch: [ 'Artifacts' ],
callback: this._onChangesetLoaded,
scope: this
});
},
_onChangesetLoaded: function(record, operation) {
if ( operation.wasSuccessful() ){
var message = settings.message;
record.set( 'Message', message);
record.save( {
callback: this._onChangesetSaved,
scope: this
} );
}
},
_onChangesetSaved: function( resultset, operation ) {
console.log( "After saving:", resultset );
if ( operation.wasSuccessful() ) {
//You shouldn't need to do this now that the scope is correct.
//I'm guessing 'that' was referring to the app itself?
//var that = tree.ownerCt.ownerCt.ownerCt.ownerCt;
this._getChangesets();
}
},
_getChangesets: function() {
//refresh
}
I am getting the hang of 2.0, but a bit stuck on something that seems simple.
Basically, I have created a new app for my team to use (thanks all for the help). I thought it would be cool to have a way I could add messages to the dashboard.
I decided the easiest way to accomplish this, is to create a story and in my code simply query that one story, grab the description and show it in the app. Sounds easy enough right?
I am having a bit of a time simple running out grabbing the Description field and showing it. I know it sounds odd, but it seems so complicated. I have tried this way
showMessage: function (message) {
debugger;
this.add({
xtype: 'label',
html: message
});
},
getMessage: function () {
var defectStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['Description'],
filters: [{
property: 'FormattedID',
operator: '=',
value: 'US13258'
}],
autoLoad: true,
listeners: {
load: function (store, records) {
debugger;
if (records)
return records[0].get("Description");
}
}
});
},
But seems to get caught up in event spaghetti. Surely there is an easier way :)
Just want to go grab a specific stories description field...
You can use a model's load method to do this:
var storyOid = 12345;
//Get the story model
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: function(model) {
//Load the specific story
model.load(storyOid, {
fetch: ['Description']
success: function(record) {
//success!
var description = record.get('Description');
}
});
}
});
I'm not sure why you're trying to do it with a listener, but I would just call load and get the result upon success, like so:
getMessage: function (storyID) {
var defectStore = Ext.create('Rally.data.WsapiDataStore', {
model: 'UserStory',
fetch: ['Description'],
filters: [{
property: 'FormattedID',
operator: '=',
value: storyID
}],
autoLoad: true
});
defectStore.load({scope: this, callback: function(records, operation, success) {
if(success){
console.log(records[0].get('Description')); // additional logic here
} else {
console.log('You ruined the store. Jerk.');
}
}});
}
I'm thinking you might have some issues, though, unless call showMessage after you check for success, as extJS operates asynchronously.