Rally Software

  • 1.  WEBAPI Filter using Description Contains

    Posted Sep 08, 2017 03:11 PM

    I'm trying to filter a report in an App and when I try to add a filter on the Description attribute it fails. Any idea what the trick is?  I suspect the Description attribute is a collection and not a simple string attribute.

     

    var artifacts = Ext.create('Rally.data.wsapi.Store', {
    model: 'UserStory',
    fetch: ['ObjectID', 'FormattedID','Name','Iteration','RevisionHistory','Revisions','Description','User'],
    autoLoad: true,
    limit: 10000,
    pageSize: 1000,
    filters: [
    {
    property: 'Iteration.StartDate',
    operator: '>=',
    value: iterationStart
    },
    {
    property: 'Iteration.EndDate',
    operator: '<=',
    value: iterationFinish
    }

    ,
    {
    property: 'Description',
    operator: 'contains',
    value: "DESCRIPTION"
    }
    ]
    });



  • 2.  Re: WEBAPI Filter using Description Contains

    Posted Sep 08, 2017 04:56 PM

    Hi trevor.lowing ,

     

    I created a very simple AppSDK2.1 app using a filter just like yours on Description and it worked. Can you post the error message that you get?

     

    -Sean Davis



  • 3.  Re: WEBAPI Filter using Description Contains

    Posted Sep 09, 2017 12:23 AM

    I think I figured out the issue but not he solution.  I need to OR two contains filters and AND two date filters.  i.e. (Description contains "Acceptance Criteria" or Description Contains "DESCRIPTION" ) and Iteration.StartDate >= YYYY-MM-DD and Iteration.EndDate <= YYYY-MM-DD



  • 4.  Re: WEBAPI Filter using Description Contains

    Posted Sep 12, 2017 06:19 AM

    trevor.lowing,

     

    You can do this by creating the two filters and then 'and' them together:

     

    var iteration = Rally.data.wsapi.Filter.and([
       {property: 'Iteration.StartDate',operator: '>=',value: '2017-08-01'},
       {property: 'Iteration.EndDate',operator: '<=',value: '2017-08-31'}

    ]);

     

    var description = Rally.data.wsapi.Filter.or([
       {property: 'Description',operator: 'contains',value: 'Acceptance Criteria'},
       {property: 'Description',operator: 'contains',value: 'DESCRIPTION'}

    ]);

     

    Then in the Filters section of the store:

     

    filters: [iteration.and(description)]

     

    Some more information is available here:

    Agile Central App SDK 2.1 Docs 

     

    An example, not exactly the same, but I pieced the above together from this post, is here:

     

    Rally SDK 2.0 - Multi-type artifact filtering - Stack Overflow 

     

    Hope that helps!

     

    Regards,

     

    Michael



  • 5.  Re: WEBAPI Filter using Description Contains

    Posted Sep 12, 2017 09:56 AM

    Michael, it doesn't seem to be filtering the Description:

     

    a <!DOCTYPE html>
    <html>
    <head>
    <title>Revisions by User</title>

    <script type="text/javascript" src="/apps/2.0rc3/sdk.js"></script>

    <script type="text/javascript">
    //YYYY-MM-DD 6-29 to 8-30

    Rally.onReady(function() {
    Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    _artifacts: [],
    launch: function() {
    var today = new Date().toISOString();
    var iterationStart = new Date('29 June 2017').toISOString();
    var iterationFinish = new Date('30 August 2017').toISOString();
    var iteration = Rally.data.wsapi.Filter.and([
    {property: 'Iteration.StartDate',operator: '>=',value: iterationStart },
    {property: 'Iteration.EndDate',operator: '<=',value: iterationFinish }
    ]);

    var description = Rally.data.wsapi.Filter.or([
    {property: 'Description',operator: 'contains',value: 'ACCEPTANCE CRITERIA'},
    {property: 'Description',operator: 'contains',value: 'DESCRIPTION'}
    ]);


    var that = this;
    var widgetPanel = Ext.create("Ext.Panel", {
    itemId: "widget",
    layout: {
    type: "hbox",
    align: "middle"
    },
    items: [{
    xtype: "rallyprojectpicker",
    itemId: "projectPicker",
    fieldLabel: "Select Project",
    workspace: this.getContext().getWorkspace()._ref,
    value: this.getContext().getProject()._ref,
    listeners: {
    change: this.onProjectSelected,
    scope: this
    },
    width: 300,
    mqrgin: 20
    }, {
    xtype: "rallyiterationcombobox",
    itemId: "iterationPicker",
    fieldLabel: "Select Iteration:",
    listeners: {
    ready: this.onIterationSelected,
    select: this.onIterationSelected,
    scope: this
    },
    width: 300,
    margin: 20
    }]
    });
    this.add(widgetPanel), this.add({
    xtype: "container",
    itemId: "reportContainer"
    })
    var artifacts = Ext.create('Rally.data.wsapi.Store', {
    model: 'UserStory',
    fetch: ['ObjectID', 'FormattedID', 'Name', 'Iteration', 'RevisionHistory', 'Revisions', 'Description', 'User'],
    autoLoad: true,
    limit: 10000,
    pageSize: 1000,
    filters: [iteration.and(description)]
    });
    artifacts.load().then({
    success: this._getRevHistoryModel,
    scope: this
    }).then({
    success: this._onRevHistoryModelCreated,
    scope: this
    }).then({
    success: this._onModelLoaded,
    scope: this
    }).then({
    success: this._stitchDataTogether,
    scope: this
    }).then({
    success: function(results) {
    that._makeGrid(results);
    },
    failure: function() {
    console.log("oh noes!");
    }
    });
    },
    onProjectSelected: function(combobox) {
    console.log("project", combobox.getSelectedRecord().get("_ref")), Ext.ComponentQuery.query("#iterationPicker")[0].setProject(combobox.getSelectedRecord().get("_ref"))
    },
    onIterationSelected: function(combobox) {
    console.log("iteration", combobox.getValue())
    },
    _getRevHistoryModel: function(artifacts) {
    this._artifacts = artifacts;
    return Rally.data.ModelFactory.getModel({
    type: 'RevisionHistory'
    });
    },
    _onRevHistoryModelCreated: function(model) {
    var that = this;
    var promises = [];
    _.each(this._artifacts, function(artifact) {
    var ref = artifact.get('RevisionHistory')._ref;
    console.log(artifact.get('FormattedID'), ref);
    promises.push(model.load(Rally.util.Ref.getOidFromRef(ref)));
    });
    return Deft.Promise.all(promises);
    },

    _onModelLoaded: function(histories) {
    var promises = [];
    _.each(histories, function(history) {
    var revisions = history.get('Revisions');
    revisions.store = history.getCollection('Revisions', {
    fetch: ['User', 'Description', 'CreationDate', 'RevisionNumber']
    });
    promises.push(revisions.store.load());
    });
    return Deft.Promise.all(promises);
    },
    _stitchDataTogether: function(revhistories) {
    var that = this;
    var artifactsWithRevs = [];
    _.each(that._artifacts, function(artifact) {
    artifactsWithRevs.push({
    artifact: artifact.data
    });
    });
    var i = 0;
    _.each(revhistories, function(revisions) {
    artifactsWithRevs[i].revisions = revisions;
    i++;
    });
    return artifactsWithRevs;

    },

    _makeGrid: function(artifactsWithRevs) {
    console.log(artifactsWithRevs);

    this.add({
    xtype: 'rallygrid',
    showPagingToolbar: true,
    showRowActionsColumn: false,
    editable: false,
    store: Ext.create('Rally.data.custom.Store', {
    data: artifactsWithRevs,
    pageSize: 1000
    }),
    columnCfgs: [{
    text: 'FormattedID',
    dataIndex: 'artifact',
    renderer: function(value) {
    return '<a href="https://rally1.rallydev.com/#/detail/userstory/' + value.ObjectID + '" target="_blank">' + value.FormattedID + '</a>';
    }
    }, {
    text: 'Name',
    dataIndex: 'artifact',
    renderer: function(value) {
    return value.Name;
    }
    }, {
    text: 'Iteration',
    dataIndex: 'artifact',
    renderer: function(value) {
    return value.Iteration.Name;
    }
    }, {
    text: 'Revision author',
    dataIndex: 'revisions',
    renderer: function(value) {
    var html = [];
    _.each(value, function(rev) {
    html.push(rev.data.User._refObjectName);
    });
    return html.join('</br></br>');
    }
    }, {
    text: 'Revision # and description',
    dataIndex: 'revisions',
    flex: 1,
    renderer: function(value) {
    var html = [];
    _.each(value, function(rev) {
    html.push("REV# " + rev.data.RevisionNumber + ": " + rev.data.Description.replace('changed from [', 'changed from :<blockquote class="textBefore">').replace('] to', '</blockquote>to:<br/><blockquote class="textAfter">') + '</blockquote>');
    });
    return html.join('</br>'); //</br></br>
    }
    }]
    });

    }

    });

     

    Rally.launchApp('CustomApp', {
    name: "Revisions by User",
    parentRepos: ""
    });

    });
    </script>


    <style type="text/css">
    .textBofore{margin-left: 50px;text-decoration: line-through;}
    .textAfter{margin-left: 50px;}
    blockquote {
    background: #f9f9f9;
    border-left: 10px solid #ccc;
    margin: 1.5em 10px;
    padding: 0.5em 10px;
    quotes: "\201C""\201D""\2018""\2019";
    }
    blockquote:before {
    color: #ccc;
    content: open-quote;
    font-size: 4em;
    line-height: 0.1em;
    margin-right: 0.25em;
    vertical-align: -0.4em;
    }
    blockquote p {
    display: inline;
    }
    </style>

    </head>
    <body>
    </body>
    </html>



  • 6.  Re: WEBAPI Filter using Description Contains

    Posted Sep 12, 2017 04:54 PM

    Hi trevor.lowing ,

     

    I took your code and got it to work by changing the following:

     

    var iterationStart = new Date('29 June 2017').toISOString();
    var iterationFinish = new Date('30 August 2017').toISOString();

     

    To:

    var iterationStart = Rally.util.DateTime.format(new Date('05 June 2017'), 'Y-m-d');
    var iterationFinish = Rally.util.DateTime.format(new Date('12 July 2017'), 'Y-m-d');

    (Or just hardcode the dates)

    Not sure exactly why the toISOString() is not working. Another option would be to update your code to work with timebox filtering.

     

    Let me know if that helps,

    Sean Davis



  • 7.  Re: WEBAPI Filter using Description Contains

    Posted Sep 12, 2017 09:08 PM

    Thanks, the problem is that the filter on Description is not working. It's grabbing ALL the changes. Not just the ones with the filter words.



  • 8.  Re: WEBAPI Filter using Description Contains

    Posted Sep 13, 2017 04:45 AM

    trevor.lowing,

     

    I think we need to clarify what you are trying to do here.  This query will return all the User Stories that are in the Iteration provided and currently contain "DESCRIPTION" or "ACCEPTANCE CRITERIA" in the Description Field.

     

    Are you wanting only the Revisions on the User Stories where the Description was changed and that change included either adding or removing "DESCRIPTION" or "ACCEPTANCE CRITERIA"?

     

    Thank you.


    Michael



  • 9.  Re: WEBAPI Filter using Description Contains

    Posted Sep 13, 2017 10:35 AM

    Good point, the "rev.data.Description" is what I'm trying to filter. So I only pull back User Stories where they have revisions to Acceptance Criteria or  Description have changed. And only display the revisions where those fields were altered.



  • 10.  Re: WEBAPI Filter using Description Contains
    Best Answer

    Posted Sep 14, 2017 12:04 PM

    HI trevor.lowing,

     

    I understand now. In your code you are running the filter on the User Story's Description not the Description of the Revisions for the User Story. This would be a bit complicated as you would have to get the RevisionHistory reference for every User Story, then run a filter on the Revision.Description.  It would be nice if you could do something like:

     

    ( RevisionHistory.Revisions.Description contains "DESCRIPTION") 

     

    but that is not possible since RevisionHistory cannot be used in queries. Maybe this older example will help to show how to loop through User Stories?

    -Sean



  • 11.  Re: WEBAPI Filter using Description Contains

    Posted Sep 21, 2017 09:59 AM

    Thank you.