Rally Software

  • 1.  Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 13, 2018 12:25 PM

    Our agile teams would like to display their sprint goal (the short description (one or two sentences) of what the team plans to achieve during the sprint) within Rally, preferably on our custom kanban board.  Any ideas or has anyone done this before.  I was thinking of a custom app that would display the description along with the ability to filter it by sprint. 



  • 2.  Re: Our teams would like to display their sprint goal in CA Agile.
    Best Answer

    Broadcom Employee
    Posted Sep 13, 2018 01:45 PM

    Hi Rob,

     

    Would an HTML app work for you here?

     



  • 3.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 17, 2018 03:36 PM

    David,

     

    That’s what I was thinking.  Ideally I would like the HTML app to be filtered by the current sprint.  Problem is I am not sure how to filter it based on the sprint within the html app.

     

    Thanks

    Rob



  • 4.  Re: Our teams would like to display their sprint goal in CA Agile.

    Broadcom Employee
    Posted Sep 17, 2018 04:20 PM

    Hi Rob,

     

    That definitely ups the ante a bit.  It should be doable, however it's going to involve a bit of coding to get there.

     

    The 50,000 ft view would be that you had an iteration filtered page.

    The HTML page then contains code in it that is subscribed to the iteration change event.

    Then you'd display the text for that iteration (probably referenced from an array)

     

    This is a good page for getting started with apps in an HTML box.  It shows you how to drop a simple app into an HTML page and let's you drop in your own code in the appropriate section.  It's a good way to get a quick "Hello World" going.

     

    This is the page you're looking for for timebox events.  It shows you how to subscribe to the onChange events and I would imagine you'd use some property from the iteration drop down as a key for a string in your array.



  • 5.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 20, 2018 11:08 AM

    Is there a simple Rich text app; like the "Hello World" listed above.

     

    thanks

    paul



  • 6.  Re: Our teams would like to display their sprint goal in CA Agile.

     
    Posted Sep 20, 2018 05:18 PM

    Paul, you could add a Custom HTML app, then paste the code of this unsupported app in it, and there ya go! From there, you should just be able to edit the text as you need. 

     

    Here's the app: GitHub - RallyTechServices/rich-text-app: Basic HTML app that has a rich text editor in the app settings for quickly cre… 

     

    And here's the code to paste into your Custom HTML app:

    <!DOCTYPE html>
    <html>
    <head>
        <title>CATS-Rich Text App-0.1</title>
        <!--  (c) 2017 CA Technologies.  All Rights Reserved. -->
        <!--  Build Date: Thu Nov 02 2017 08:40:04 GMT-0600 (MDT) -->

        <script type="text/javascript">
            var APP_BUILD_DATE = "Thu Nov 02 2017 08:40:04 GMT-0600 (MDT)";
            var ARTIFACT = "F387";
            var BUILDER  = "corkr03";
            var CHECKSUM = 1932722076;
        </script>


        <script type="text/javascript" src="/apps/2.1/sdk.js"></script>
        <!-- our highcharts (needed so that we can add patterns)
        <script type="text/javascript" src="/apps/2.1/lib/analytics/analytics-all.js"></script>
        -->



        <script type="text/javascript">
            Rally.onReady(function() {
                Ext.define("Rally.technicalservices.InfoLink",{extend:"Rally.ui.dialog.Dialog",alias:"widget.tsinfolink",informationHtml:null,title:"Build Information",defaults:{padding:5,margin:5},closable:!0,draggable:!0,autoShow:!0,width:350,informationalConfig:null,showLog:!1,logger:null,items:[{xtype:"container",itemId:"information"},{xtype:"container",itemId:"button_box"}],initComponent:function(){Ext.id(this);this.title="<span class='icon-help'> </span>"+this.title,this.callParent(arguments)},_generateChecksum:function(a){var b,c=305419896;for(a=a.replace(/var CHECKSUM = .*;/,""),a=a.replace(/var BUILDER  = .*;/,""),a=a.replace(/\s/g,""),b=0;b<a.length;b++)c+=a.charCodeAt(b)*b;return c},_checkChecksum:function(a){var b=Ext.create("Deft.Deferred"),c=this;return Ext.Ajax.request({url:document.URL,params:{id:1},success:function(a){if(text=a.responseText,CHECKSUM){var d=c._generateChecksum(text);if(CHECKSUM!==d)return void b.resolve(!1)}b.resolve(!0)}}),b.promise},_addToContainer:function(a){var b=Ext.apply({xtype:"container",height:200,overflowY:!0},this.informationalConfig);a.add(b)},afterRender:function(){var a=Rally.getApp();if(!Ext.isEmpty(this.informationalConfig)){var b=this.down("#information");this._addToContainer(b)}this.showLog&&this.logger&&this.down("#button_box").add({xtype:"rallybutton",text:"Show Log",listeners:{scope:this,click:function(){this.logger.displayLog()}}}),a.isExternal()?this.addDocked({xtype:"container",cls:"build-info",padding:2,dock:"bottom",html:"... Running externally"}):this._checkChecksum(a).then({scope:this,success:function(a){a||this.addDocked({xtype:"container",cls:"build-info",dock:"bottom",padding:2,html:'<span class="icon-warning"> </span>Checksums do not match'})},failure:function(a){console.log("oops:",a)}}),this.callParent(arguments)},beforeRender:function(){if(this.callParent(arguments),this.informationHtml&&this.addDocked({xtype:"component",componentCls:"intro-panel",padding:2,html:this.informationHtml,dock:"bottom"}),this.addDocked({xtype:"container",cls:"build-info",padding:2,dock:"bottom",html:"This app was created by the CA AC Technical Services Team."}),APP_BUILD_DATE){var a=Ext.String.format("Built on: {0} <br/>Built by: {1}",APP_BUILD_DATE,BUILDER);ARTIFACT&&(a=a+"<br/>Source artifact: "+ARTIFACT),this.addDocked({xtype:"container",cls:"build-info",padding:2,dock:"top",html:a})}}}),Ext.define("CArABU.technicalservices.Logger",{saveForLater:!1,saveLines:100,logArray:[],constructor:function(a){Ext.apply(this,a)},setSaveForLater:function(a){this.saveForLater=a},log:function(a){var b="[ "+Ext.util.Format.date(new Date,"Y-m-d H:i:s.u")+" ]",c=[];c=Ext.Array.push(c,[b]),c=Ext.Array.push(c,Ext.Array.slice(arguments,0)),this.saveForLater&&(this.logArray||(this.logArray=[]),this.logArray.push(c.join(" ")),this.logArray.length>this.saveLines&&this.logArray.shift()),window.console&&console.log.apply(console,c)},getLogText:function(){return this.logArray&&0!==this.logArray.length?this.logArray.join("<br/>"):"-- no log --"},displayLog:function(){var a=this.getLogText();this.popup=Ext.create("Rally.ui.dialog.Dialog",{width:Ext.getBody().getWidth()-20,height:Ext.getBody().getHeight()-20,closable:!0,title:"Log",autoShow:!0,layout:"border",defaults:{layout:"fit",width:"50%",border:!1},items:[{region:"center",xtype:"container",html:a,autoScroll:!0}]})}}),Ext.define("TSUtilities",{singleton:!0,loadWsapiRecords:function(a){var b=Ext.create("Deft.Deferred"),c={model:"Defect",fetch:["ObjectID"]};return Ext.create("Rally.data.wsapi.Store",Ext.Object.merge(c,a)).load({callback:function(a,c,d){d?b.resolve(a):(console.error("Failed: ",c),b.reject("Problem loading: "+c.error.errors.join(". ")))}}),b.promise},loadAStoreWithAPromise:function(a,b){var c=Ext.create("Deft.Deferred");return Ext.create("Rally.data.wsapi.Store",{model:a,fetch:b}).load({callback:function(a,b,d){d?c.resolve(this):(console.error("Failed: ",b),c.reject("Problem loading: "+b.error.errors.join(". ")))}}),c.promise}}),Ext.define("CArABU.app.RichTextApp",{extend:"Rally.app.App",componentCls:"app",defaults:{margin:10},layout:"fit",config:{defaultSettings:{html:"<em>Use the gear to change display text...</em>"}},integrationHeaders:{name:"CArABU.app.TSApp"},launch:function(){this.removeAll();var a=this.getSetting("html");this.add({xtype:"container",html:a,cls:"default-counter"})},getSettingsFields:function(){return[{xtype:"container",margin:"10 70 0 60",html:'<div class="variable-label">Display Text</div>'},{name:"html",flex:1,xtype:"rallyrichtexteditor",margin:"10 70 0 60",fieldLabel:"Informational Text",_createResizer:function(){},resizeable:!1}]},getOptions:function(){var a=[{text:"About...",handler:this._launchInfo,scope:this}];return a},_launchInfo:function(){this.about_dialog&&this.about_dialog.destroy(),this.about_dialog=Ext.create("Rally.technicalservices.InfoLink",{showLog:this.getSetting("saveLog"),logger:this.logger})},isExternal:function(){return"undefined"==typeof this.getAppId()}});

                   Rally.launchApp('CArABU.app.RichTextApp', {
                       name: 'Rich Text App'
                   });
            });
        </script>


        <style type="text/css">

    .app {
    }
    .tsinfolink {
        position:absolute;
        right:0px;
        width: 14px;
        height: 14px;
        border-radius: 7px;
        text-align: center;
        color: white;
        background: #C0C0C0;
        border-style: solid;
        border-width: 1px;
        margin-top: 25px;
        margin-right: 5px;
        cursor: pointer;
    }

    .variable-label {
      font-family: ProximaNovaSemiBold, Helvetica, Arial;
      text-transform: uppercase;
      font-size:11px;
    }
    .default-counter {
       font-family: ProximaNova, Helvetica, Arial;
       font-size: 14px;
    }

        </style>


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

     

     

    Once you enter that, just click the gear at top-right and Edit App Settings to add your text. 



  • 7.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 21, 2018 09:37 AM

    I was able to modify the retrospective application.  This gives me the ability to allow each team to have their own sprint goal by iteration. Using just the rich text editor did not allow for the filtering.  This version has both the rich text editor and the ability to track sprint goals by team and sprint.  You need to add a new field at the iteration level called "c_SprintGoals" in order to save it.



  • 8.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 21, 2018 10:19 AM

    The user must be an admin to "click the gear at top-right and Edit App Settings to add your text"; so this won't work for us.

    Rob, can you paste the updated html instead of a image, I would like to take a look at that option?



  • 9.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 21, 2018 10:41 AM

    Here is the code.  You do not need to be an admin to use it. Our Scrum Masters are using it now and they are not admins. 

     

    <!DOCTYPE html>
    <html>
    <head>
        <script type="text/javascript" src="https://rally1.rallydev.com/apps/2.1/sdk.js"></script>

     

        <script type="text/javascript">
            Rally.onReady(function () {
                    var REQUIRED_FIELDS={c_SprintGoals:"Sprint Goals"};Ext.define("SprintGoalApp",{extend:"Rally.app.TimeboxScopedApp",componentCls:"app",scopeType:"iteration",supportsUnscheduled:!1,initComponent:function(){this.callParent(arguments),this.add({xtype:"container",itemId:"content"})},onScopeChange:function(){this.callParent(arguments),this.models?this._loadIteration():Rally.data.wsapi.ModelFactory.getModels({context:this.getContext(),types:["Iteration","AttributeDefinition"],success:function(models){this.models=models,this._loadIteration()},scope:this})},onNoAvailableTimeboxes:function(){this.callParent(arguments);var contentContainer=this.down("#content");contentContainer&&contentContainer.removeAll()},_loadIteration:function(){var id=this.getContext().getTimeboxScope().getRecord().getId();this.models.Iteration.load(id,{fetch:_.keys(REQUIRED_FIELDS)}).then({success:function(iteration){this.iteration=iteration,this._checkForFields()},scope:this})},_checkForFields:function(){var contentContainer=this.down("#content");if(contentContainer.removeAll(),_.every(_.keys(REQUIRED_FIELDS),this._fieldExists,this))contentContainer.add([{xtype:"panel",cls:"actions-panel",items:[this._buildEditorFor("c_SprintGoals")]}]);else{var isWorkspaceAdmin=this.getContext().getPermissions().isWorkspaceOrSubscriptionAdmin(this.getContext().getWorkspace());contentContainer.add({itemId:"missingFieldsBlankSlate",xtype:"container",cls:"no-data-container",items:[{xtype:"component",cls:"primary-message",html:"One or more required Iteration custom fields are missing."},{xtype:"component",cls:"secondary-message",html:"This app uses custom fields on Iteration to store sprint goals."},{xtype:"component",cls:"secondary-message",html:isWorkspaceAdmin?'Click <a href="#" class="create-fields-link">here</a> to create these fields.':"Please contact a workspace administrator to set up these fields using this app.",listeners:{afterrender:function(cmp){var linkEl=cmp.getEl().down(".create-fields-link");this.mon(linkEl,"click",this._createRequiredFields,this)},scope:this}}]})}},_buildEditorFor:function(fieldName){return{xtype:"rallyrichtexteditor",itemId:fieldName.replace("c_R","r"),fieldName:fieldName,showUndoButton:!0,margin:"0 20px",height:130,value:this.iteration.get(fieldName),listeners:{blur:this._onEditorChange,scope:this}}},_fieldExists:function(name){return!!_.find(this.models.Iteration.getFields(),function(field){return field.isCustom()&&"text"===field.getType()&&field.name===name})},_createRequiredFields:function(e){e.preventDefault(),this.setLoading({msg:"Creating fields..."});var missingFields=_.reject(_.keys(REQUIRED_FIELDS),this._fieldExists,this);Deft.Promise.all(_.map(missingFields,this._createRequiredField,this)).then({success:this._requiredFieldsCreated,scope:this})},_createRequiredField:function(name){_.each(["RealAttributeType","Constrained","TypeDefinition"],function(fieldName){this.models.AttributeDefinition.getField(fieldName).persist=!0},this);var newField=Ext.create(this.models.AttributeDefinition,{RealAttributeType:"TEXT",Constrained:!1,Custom:!0,Filterable:!0,Sortable:!1,Name:REQUIRED_FIELDS[name],TypeDefinition:Rally.util.Ref.getRelativeUri(this.models.Iteration.typeDefinition)});return newField.save()},_requiredFieldsCreated:function(){Rally.data.wsapi.ModelFactory.clearModels(),Rally.data.wsapi.ModelFactory.getModel({context:this.getContext(),type:"Iteration"}).then({success:function(model){this.models.Iteration=model,this.setLoading(!1),this.onScopeChange()},scope:this})},_onEditorChange:function(editor){this.iteration.set(editor.fieldName,editor.getValue()),this.iteration.save({fetch:_.keys(REQUIRED_FIELDS)}).then({success:function(iteration){this.iteration=iteration,Ext.create("Rally.ui.detail.view.SavedIndicator",{target:editor})},scope:this})}});
                Rally.launchApp('SprintGoalApp', {
                    name:"Sprint Goal",
                    parentRepos:""
                });
            });
        </script>

     


        <style type="text/css">
            .x-panel-body{border:none}.x-panel-header-default{box-shadow:none}.x-panel-header-text-container-default{line-height:48px;color:#ccc}.plus-panel{padding-bottom:10px}.delta-panel{padding-bottom:10px}.plus-panel .x-panel-header-text-container-default{font-size:72px}.delta-panel .x-panel-header-text-container-default{font-size:48px}.actions-panel .x-panel-header-text-container-default{font-size:36px;text-transform:none}.rallyRichTextEditor.pre-save{transition:border-color 2s}.rallyRichTextEditor.pre-save.is-saving{transition:border-color 0s;border-color:#8DC63F} .rallyRichTextEditor { border: none; }
        </style>
    </head>
    <body>
    </body>
    </html>



  • 10.  Re: Our teams would like to display their sprint goal in CA Agile.

    Posted Sep 21, 2018 11:11 AM

    This works well;

    I think this belongs in the GIthub community space with and without the iteration filter.

     

    - thxs

    paul