Rally - More efficient way to get item by ID - rally

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;
}
});

Related

GraphQL queries with tables join using Node.js

I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
The relations are one-to-many, where a user can have many comments.
I have built the schema like this:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
And the query:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
Executing the query for a user and his comments is done with 1 request, like so:
{
User(id:"1"){
Comments{
content
}
}
}
As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?
The concept you are refering to is called batching. There are several libraries out there that offer this. For example:
Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"
join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."
To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>()
.Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
q.Enqueue(new Tuple<string, Field>
(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
else
{
yield return node.Item1;
}
}}}
Lets say we have the following query:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
We can create a variable in "resolve" method of the field:
var include = context.GetIncludeString();
which generates the following string:
"Product.Category.SubCategory,Product.Category.SubAnything"
and pass it to Entity Framework:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}

How to create new TimeEntryValue in Rally

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
});
// ...

Sencha Touch sessionstorage, save and retrieve

Hi i am trying to use javascript session storage on my app in sencha touch with model, after a long search on the internet i am not getting lucky at all, please help if you can, Thanks.
This is my code so far.
My Controller the onStorage function works, getStorage usession.load fails to load, thats were i'm stuck
Ext.define('SideMenu.controller.Menu', {
extend: 'Ext.app.Controller',
currView:'home',
requires: ['SideMenu.model.Mymdl'],
config: {
refs: {
btn_localstore:'#btn_localstore',
btn_getlocalstore:'#btn_getlocalstore'
}
control: {
btn_localstore:{
tap: 'onStorage'
},
btn_getlocalstore:{
tap: 'getStorage'
},
},
onStorage:function(){
//create model and store data
var myMod = Ext.create('SideMenu.model.Mymdl',{
brandName:'Nike Jordan',
brandCat:'Sneakers'
});
myMod.save({
success:function(res){
console.log('saved to model : '+res.get('brandName'));
}
});
},
getStorage:function(){
var usession = Ext.ModelMgr.getModel('SideMenu.model.Mymdl');
console.log('model is :'+usession);
usession.load(0, {
scope: this,
success: function(model, opp) {
console.log('passed '+model.get('brandCat'));
},
failure: function(record, operation) {
console.log('failed : '+operation);
// Ext.Viewport.setMasked(false);
//====================================================
// alert('could not get session details');
//====================================================
}
});
}
}
My Model
Ext.define('SideMenu.model.Mymdl', {
extend : 'Ext.data.Model',
xtype : 'Mymdl',
requires:['Ext.data.proxy.SessionStorage'],
id : 'Mymdl',
config: {
fields: [
'brandName',
'brandCat'
]
,
proxy : {
type: 'localstorage',
id : 'mymdl'
}
}
});
My app.js i excluded the other stuff dts not needed in this case
models: ['Mymdl'],
views: ['Main', 'Home'],
controllers: ['Menu'],
launch:function()
{
Ext.create('SideMenu.model.Mymdl');
}
Your answer would be appreciated, thanks
You will need to call the model load method using the id of the model data you want to retrieve from local storage.
You can get the id from the model save callback function
var modelId;
myMod.save({success:function(res){
modelId = res.getId();
console.log('saved to model : '+res.get('brandName'));
}
});
Then use this id when you load the model:
SideMenu.model.Mymdl.load(modelId, function(record) {
console.log('Brand: ' + record.get('brandName'));
}
You can set the id value directly when you save the model. This would save you from having to retrieve and save the auto-generated id on each save.

In Rally SDK 2, how do I update a hash field?

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
}

Rally App2.0 - Retrieve a specific story

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.