2013-09-25 1 views
0

자바 스크립트에서 REST API를 사용하여 표보기 및 차트보기에 맞춤 (매우 세부적인)주기/리드 타임 데이터를 생성하려고합니다.룩백 API를 통해 순환 맞춤/리드 타임 데이터

내 이야기에 사용자 지정 (필수 항목이 아님) 필드가 & 개이며, c_KanbanStatus의 값은 다음과 같습니다. null, Kickoff, PO, Creative, Team Backlog, Coding, "수락 테스트", "수락 됨".

저는이 사용자 정의 필드를 최근에 추가 했으므로 많은 이야기에 해당 필드가 없거나 그 필드가 없습니다. 다음과 같이

내 생각은 간다 :

  • 는, 각각의 상태에 대해, 각 OBJECTID를 들어 OBJECTID
  • 각 칸반 상태 전환을
  • 집계를 전환 확인 쿼리를 수행 사이의 시간 - 델타을 계산할 때 객체는 그 상태로 들어가고 객체가 다음 상태로 들어갈 때.

이 내 코드의 추출물 :

var kanbanStates = 
    [ 
     "Kickoff", 
     "PO", 
     "Creative", 
     "Team Backlog", 
     "Coding", 
     "Acceptance Testing" 
    ]; 

    var username = "**************"; 
    var password = "**************"; 

    var deferreds = []; 
    for(var i = 0; i < kanbanStates.length; i++) 
    { 
     var find = 
     { 
      _ProjectHierarchy: ***************, 
      "_PreviousValues.c_KanbanStatus": { $lt: kanbanStates[i] }, 
      c_KanbanStatus: kanbanStates[i] 
     }; 

     var config = 
     { 
      url:"https://rally1.rallydev.com/analytics/v2.0/service/rally/workspace/********/artifact/snapshot/query.js?find=" + JSON.stringify(find) + "&fields=true&pagesize=999999", 
      dataType: "jsonp", 
      jsonp: "jsonp", 
      contentType: "application/json", 
      beforeSend: function(xhr) 
      { 
       xhr.setRequestHeader("Authorization", "Basic " + btoa(username + ":" + password)); 
      } 
     }; 

     deferreds.push($.ajax(config)); 
    } 

    var aggregateResultsByObjectID = function(results) 
    { 
     var resultsByItemID = {}; 
     for(var i = 0; i < results.length; i++) 
     { 
      if(!results[i][0].Results.c_KanbanStatus === kanbanStates[i]) 
       throw "States don't match!"; 

      for(var j = 0; j < results[i][0].Results.length; j++) 
      { 
       var itemID = results[i][0].Results[j].ObjectID; 

       if(!resultsByItemID.hasOwnProperty(itemID)) 
       { 
        resultsByItemID[itemID] = 
        { 
         creationDate: results[i][0].Results[j].CreationDate, 
         name: results[i][0].Results[j].Name, 
         states: [], 
         results: [] 
        }; 
       } 

       resultsByItemID[itemID].results.push(results[i][0].Results[j]); 
       resultsByItemID[itemID].states.push(results[i][0].Results[j].c_KanbanStatus); 
      } 
     } 
     return resultsByItemID; 
    }; 

    $.when.apply($, deferreds).done(function() 
    { 
     var resultsByItemID = aggregateResultsByObjectID(arguments); 
     console.log(resultsByItemID); 
    }); 

이 쿼리의 문제는 사용자가 지정한 비록 내가, 각 상태에 대해 다시 각 OBJECTID에 대해 여러 결과를 얻을 수 있다는 것이다 나는 단지 그 스냅 샷을 원하는 다른 c_KanbanStatus 필드가 있습니다. 결과를 확인하면 c_KanbanStatus 및 _PreviousValues.c_KanbanStatus와 동일한 ObjectID와 동일한 조합에 대해 스냅 샷을 다시 얻습니다. 각 스냅 샷은 다른 일부 필드 편집과 함께 다시 나타납니다. 나는 (그것이 c_KanbanStatus 필드를 가지고 있지로부터 갔을 때에 대한 스냅 샷을 첫 번째 결과를 기대

_PreviousValues: 
{ 
    _User: 10301773174 
    c_KanbanStatus: null 
}, 
c_KanbanStatus: "Coding" 

_PreviousValues: 
{ 
    ScheduleState: 10148772688 
    _User: 10148977759 
}, 
c_KanbanStatus: "Coding" 

:

예를 들어, 같은 OBJECTID, 나는이 두 가지 결과를 얻을 수 "코딩"으로 설정). 두 번째 결과는 c_KanbanStatus 필드가 없다는 것에서 "코딩"으로 넘어 갔다는 것을 암시하는 것처럼 보입니다. 그러나 그 이유는 무엇입니까?

Lookback API에서 뭔가 빠져 있다는 느낌이 들었습니다. 이해 좀 도와주세요!

+0

이 게시물 http://stackoverflow.com/questions/18971688/custom-cycle-time를 참조하십시오. 조건을 만족하는 레코드는 다음과 같이 반환됩니다. 'Ext.Array.filter (records, function (record) { return record.get ('ScheduleState ') ==='Accepted'' 그리고'Rally.util.Array.last ' – nickm

답변

1

각 상태마다 여러 개의 결과가 반환 될 것으로 예상됩니다. 스토리가 수정 될 때마다 스냅 샷이 생성됩니다. c_Kanban 상태가 네 가지 있다고 가정 해 보겠습니다. backlog, in-progress, donereleased 이 예제를 계속 진행하려면 스토리를 진행중인 컬럼으로 이동하고 스토리의 PlanEstimate을 설정 한 다음 해당 스토리 아래의 태스크를 차단하고 태스크를 차단 해제하고 태스크의 상태를 Completed로 설정 한 다음 Kanban의 완료 컬럼으로 스토리를 이동하십시오. 판. 이 모든 변경 사항에는 고유 한 스냅 샷이 생성됩니다. c_Kanban === 'in-progress'

github repo에있는 앱에는 각 c_Kanban 상태에서 스토리가 소비 된 시간에 대한 열이있는 눈금이 있습니다. c_Kanban: "in-progress" 동안 다른 하나의 스냅 샷 및 Blocked: falseBlocked: true 공지 사항 :이 문제에 대한 내 자신의 솔루션을 장난 한

enter image description here

+0

당신의 의견을 많이 주셔서 감사합니다. 많이 받아 주셔서 감사합니다!하지만, 내 설명의 일부를 놓쳤다 고 생각합니다. 당신이 보여주는 내용을 이해하고 c_Kanban에 대해서만 질의하기 때문에 예제가 여러 스냅 샷을 반환 할 것으로 예상됩니다. === "진행 중"이므로 항목이 여전히 "진행 중"인 동안 변경된 모든 항목 (차단됨, 이름, 설명 등)은 그러한 스냅 샷을 트리거합니다. 그러나 특정 경우에는 _PreviousValue 개체를 쿼리했습니다. ("진행중"에서 "테스트"로의 전환은 오직 한 번만 일어날 수 있습니다.) – Bernardo

+0

이 필터를 추가하십시오 :'{ 속성 : '_PreviousValues.c_Kanban', operator : 'exists', value : true } '필터 배열에이 필터를 추가하면 상태 당 하나의 스냅 샷 만 표시됩니다. – nickm

+0

감사! 나는 다른 접근 방식을 시도하고 있지만 그 필터를 사용해 볼 것입니다. – Bernardo

0

이 내가 함께 온 것입니다.그것은 코너 케이스를 처리하기 위해 좀 더 튜닝을 필요로하지만, 내가 필요한 것에 가깝습니다. 나는 이것을 계속 사용할 것이며 GitHub 레포를 만들 것입니다.

편집 : GitHub의의의 repo : https://github.com/bfanti/RallyCustomCycleTimeApp

<!DOCTYPE html> 
<html> 
<head> 
    <title>My App</title> 

    <!--App information--> 
    <meta name="Name" content="App: Custom Cycle Time Table"/> 
    <meta name="Version" content="1.0"/> 
    <meta name="Vendor" content=""/> 

    <!--Include SDK--> 
    <script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script> 

    <!--App code--> 
    <script type="text/javascript"> 
     var kanbanStatuses = 
     [ 
      "New Feature", 
      "Kickoff", 
      "PO", 
      "Creative", 
      "Team Backlog", 
      "Coding", 
      "Acceptance Testing" 
     ]; 

     var shirtSizeLUT = 
     { 
      3: "S", 
      5: "M", 
      8: "L" 
     }; 

     Rally.onReady(function() 
     { 
      Ext.define("CustomApp", 
      { 
       extend: "Rally.app.App", 
       componentCls: "app", 

       launch: function() 
       { 
        var self = this; 

        Ext.create("Rally.data.lookback.SnapshotStore", 
        { 
         fetch : [ "_UnformattedID", "_TypeHierarchy", "Name", "PlanEstimate", "c_KanbanStatus", "ScheduleState" ], 
         hydrate : [ "c_KanbanStatus", "ScheduleState" ], 
         filters : 
         [ 
          { 
           property : "_ProjectHierarchy", 
           value : *********** 
          }, 
          { 
           property: "_TypeHierarchy", 
           value: { $nin: [ -51009, -51012, -51031, -51078 ] } 
          }, 
          { 
           property: "_ValidFrom", 
           value: { $gt: "2013-09-13" } 
          } 
         ], 
         sorters : 
         [ 
          { 
           property : "_ValidTo", 
           direction : "ASC" 
          } 
         ] 
        }).load(
        { 
         params: 
         { 
          compress: false, 
          removeUnauthorizedSnapshots: true 
         }, 
         callback : function(records, operation, success) 
         { 
          var aggregateCycleTimes = []; 

          var allObjectIDs = {}; 
          Ext.Array.each(records, function(record) { allObjectIDs[record.get("ObjectID")] = record.get("ObjectID"); }); 

          for(var storyIndex = 0; storyIndex < Object.keys(allObjectIDs).length; storyIndex++) 
          { 
           (function() 
           { 
            var currentObjectID = parseInt(Object.keys(allObjectIDs)[storyIndex], 10); 
            var recordsByStory = Ext.Array.filter(records, function(record) { return record.get("ObjectID") === currentObjectID; }); 

            var currentStateOfStory = ""; 
            var currentStateOfStorySnapshot = Ext.Array.filter(recordsByStory, function(record) { return record.get("_ValidTo").indexOf("9999") !== -1; })[0]; 
            if(currentStateOfStorySnapshot) 
             currentStateOfStory = currentStateOfStorySnapshot.get("c_KanbanStatus"); 

            var formattedID = (Ext.Array.contains(recordsByStory[0].get("_TypeHierarchy"), -51006) ? "DE" : "US") + recordsByStory[0].get("_UnformattedID"); 
            var cycleTimes = 
            { 
             id: formattedID, 
             name: recordsByStory[0].get("Name"), 
             planEstimate: shirtSizeLUT[Rally.util.Array.last(recordsByStory).get("PlanEstimate")], 
             currentStateOfStory: currentStateOfStory 
            }; 

            var allStatusesAreNull = true; 

            for(var i = 0; i < kanbanStatuses.length; i++) 
            { 
             var currentStatus = kanbanStatuses[i]; 

             var currentSnapshot = Ext.Array.filter(recordsByStory, function(record) { return record.get("c_KanbanStatus") === currentStatus; })[0]; 

             if(!currentSnapshot) 
             { 
              cycleTimes[currentStatus] = null; 
              continue; 
             } 

             var firstDate = new Date(currentSnapshot.get("_ValidFrom")); 

             var nextSnapshot = Rally.util.Array.last(Ext.Array.filter(recordsByStory, function(record) { return record.get("c_KanbanStatus") === currentStatus; })); 
             var secondDate = new Date(); 

             if(nextSnapshot && (new Date(nextSnapshot.get("_ValidTo"))).getFullYear() !== 9998) 
              secondDate = new Date(nextSnapshot.get("_ValidTo")); 

             var cycleTime = Rally.util.DateTime.getDifference(secondDate, firstDate, "day"); 
             cycleTimes[currentStatus] = cycleTime; 

             if(cycleTime !== null) 
              allStatusesAreNull = false; 
            } 

            if(!allStatusesAreNull) 
             aggregateCycleTimes.push(cycleTimes); 
           })(); 
          } 

          var myStore = Ext.create("Rally.data.custom.Store", 
          { 
           data: aggregateCycleTimes, 
           pageSize: 100, 
          }); 

          var columnConfig = 
          [ 
           { 
            text: "ID", 
            dataIndex: "id" 
           }, 
           { 
            text: "Name", 
            dataIndex: "name", 
            width: "280px" 
           }, 
           { 
            text: "Size", 
            dataIndex: "planEstimate" 
           }, 
           { 
            text: "Current State", 
            dataIndex: "currentStateOfStory" 
           } 
          ]; 

          for (var i = 0; i < kanbanStatuses.length; i++) 
          { 
           var columnConfigElement = {}; 
           columnConfigElement["text"] = kanbanStatuses[i]; 
           columnConfigElement["dataIndex"] = kanbanStatuses[i]; 
           columnConfig.push(columnConfigElement); 
          } 

          if (!self.grid) 
          { 
           self.grid = self.add(
           { 
            xtype: "rallygrid", 
            itemId: "mygrid", 
            store: myStore, 
            columnCfgs: columnConfig 
           }); 
          } 
         } 
        }); 
       } 
      }); 

      Rally.launchApp("CustomApp", { name: "My Custom App" }); 
     }); 
    </script> 
</head> 
<body> 
</body> 
</html>