| <!-- |
| The res/js/status.js file must be included before this file. |
| |
| This in an HTML Import-able file this contains the definition |
| of the following elements: |
| |
| <commits-data-sk> |
| |
| This element performs an ajax request to the status backend, parses the json response and |
| returns bindable properties to be used to render the various components of the status page. |
| This element takes some filter inputs (i.e. filter and search) and if |
| either changes, the data will be re-filtered to reflect that. |
| |
| To use this file import it: |
| |
| <link href="/res/imp/commits-data-sk.html" rel="import" /> |
| |
| Usage: |
| |
| <commits-data-sk></commits-data-sk> |
| |
| Properties: |
| // inputs |
| roll_statuses: Array, Status information about the autorollers, as defined in autoroll-widget-sk.html. |
| commits_to_load: Number, the number of commits to load from the backend |
| filter: String, the task spec filter to be used. |
| reload: How often (in seconds) to reload the data. |
| search: String, the string to be used if filter is "search". |
| |
| // outputs |
| branch_heads: Array<Object>, an array of hashes and branch names of the commits. |
| task_details: Object, a map of commit hash to an object that has the task results by task spec. |
| task_specs: Object, a map of the task spec names to an object that has, among other things, category, subcategory, comments and master. |
| tasks: Object, a map of the task spec names to an object that maps task IDs to task results. |
| categories: Object, a map of the task spec categories to an object that has the subcategories and the colspan (total number of included task specs). |
| category_list: Array<String>, an array of the task spec category names. |
| commits: Array<Object>, the commit objects, in chronological order. |
| commits_map: Object, a map of commit hash to commit objects. |
| loading: Boolean, if the data is being fetched from the server or parsed. |
| relanded_map: Object, a map of a commit hash that was relanded to the commit object that relands it |
| reverted_map: Object, a map of a commit hash that was reverted to the commit object that reverts it |
| swarming_url: String, the URL of the Swarming server. |
| task_scheduler_url: String, the URL of the Task Scheduler. |
| |
| Methods: |
| forceUpdate: Force the commits-data-sk to reload its data from the server. |
| forceReProcess: Force the commits-data-sk to re-evaluate its data. Useful for |
| when Polymer does not automatically trigger updates, eg. for complex objects. |
| |
| Events: |
| None. |
| --> |
| |
| <link rel="import" href="/res/imp/bower_components/iron-ajax/iron-ajax.html"> |
| |
| <link rel="import" href="/res/common/imp/timer-sk.html"> |
| |
| <dom-module id="commits-data-sk"> |
| <template> |
| <timer-sk period="[[reload]]" on-trigger="_startUpdate"></timer-sk> |
| </template> |
| |
| <script> |
| (function() { |
| var VALID_TASK_SPEC_CATEGORIES = ["Build", "Housekeeper", "Infra", "Perf", "Test", "Upload"]; |
| |
| var FILTER_ALL = "all"; |
| var FILTER_DEFAULT = "default"; |
| var FILTER_INTERESTING = "interesting"; |
| var FILTER_FAILURES = "failures"; |
| var FILTER_FAIL_NO_COMMENT = "nocomment"; |
| var FILTER_COMMENTS = "comments"; |
| var FILTER_SEARCH = "search"; |
| |
| var TASK_STATUS_PENDING = ""; |
| var TASK_STATUS_RUNNING = "RUNNING"; |
| var TASK_STATUS_SUCCESS = "SUCCESS"; |
| var TASK_STATUS_FAILURE = "FAILURE"; |
| var TASK_STATUS_MISHAP = "MISHAP"; |
| |
| var TIME_POINTS = [ |
| { |
| label:"-1h", |
| offset: 60 * 60 * 1000, |
| }, |
| { |
| label:"-3h", |
| offset: 3 * 60 * 60 * 1000, |
| }, |
| { |
| label:"-1d", |
| offset: 24* 60 * 60 * 1000, |
| }, |
| ]; |
| |
| // shortCommit returns the first 7 characters of a commit hash. |
| function shortCommit(commit) { |
| return commit.substring(0, 7); |
| } |
| |
| // shortAuthor shortens the commit author field by returning the |
| // parenthesized email address if it exists. If it does not exist, the |
| // entire author field is used. |
| function shortAuthor(author) { |
| var re = /.*\((.+)\)/; |
| var match = re.exec(author); |
| var res = author; |
| if (match) { |
| res = match[1]; |
| } |
| return res.split("@")[0]; |
| } |
| |
| // shortSubject truncates a commit subject line to 72 characters if needed. |
| // If the text was shortened, the last three characters are replaced by |
| // ellipsis. |
| function shortSubject(subject) { |
| return sk.truncate(subject, 72); |
| } |
| |
| // findIssueAndReviewTool returns an object literal of the form |
| // {issue, patchStorage}. patchStorage will be either Gerrit or empty, and |
| // issue will be the CL number or empty. |
| // If an issue cannot be determined then an empty string is returned for |
| // both issue and patchStorage. |
| function findIssueAndReviewTool(commit) { |
| // See if it is a Gerrit CL. |
| var gerritRE = /(.|[\r\n])*Reviewed-on:.*\/([0-9]*)/g; |
| var gerritTokens = gerritRE.exec(commit.body); |
| if (gerritTokens) { |
| return { |
| issue: gerritTokens[gerritTokens.length - 1], |
| patchStorage: 'gerrit' |
| }; |
| } |
| // Could not find a CL number return an empty string. |
| return { |
| issue: '', |
| patchStorage: '' |
| }; |
| } |
| |
| function isGerritIssue(commit) { |
| return commit.patchStorage === 'gerrit'; |
| } |
| |
| // Find and return the commit which was reverted by the given commit. |
| function findRevertedCommit(commits, commit) { |
| patt = new RegExp("^This reverts commit ([a-f0-9]+)"); |
| var tokens = patt.exec(commit.body); |
| if (tokens) { |
| return commits[tokens[tokens.length - 1]]; |
| } |
| return null; |
| } |
| |
| // Find and return the commit which was relanded by the given commit. |
| function findRelandedCommit(commits, commit) { |
| // Relands can take one of two formats. The first is a "direct" reland. |
| patt = new RegExp("^This is a reland of ([a-f0-9]+)"); |
| var tokens = patt.exec(commit.body); |
| if (tokens) { |
| return commits[tokens[tokens.length - 1]]; |
| } |
| |
| // The second is a revert of a revert. |
| var revert = findRevertedCommit(commits, commit); |
| if (revert) { |
| return findRevertedCommit(commits, revert); |
| } |
| return null; |
| } |
| |
| function getTaskColorClass(task) { |
| // These styles are from the styles-sk module. |
| if (task.status == TASK_STATUS_PENDING || task.status == TASK_STATUS_RUNNING) { |
| return "bg-inprogress"; |
| } else if (task.status == TASK_STATUS_SUCCESS) { |
| return "bg-success"; |
| } else if (task.status == TASK_STATUS_FAILURE) { |
| return "bg-failure"; |
| } else if (task.status == TASK_STATUS_MISHAP) { |
| return "bg-exception"; |
| } |
| console.log("Unknown color for task "+task); |
| return "bg-inprogress"; |
| } |
| |
| Polymer({ |
| is: "commits-data-sk", |
| properties: { |
| // input only |
| roll_statuses: { |
| type: Array, |
| value: function() { |
| return []; |
| }, |
| }, |
| commits_to_load: { |
| type: Number, |
| value: 35, |
| observer: "_reloadFromScratch", |
| }, |
| filter: { |
| type: String, |
| observer:"_filterTaskSpecs", |
| }, |
| is_sheriff: { |
| type: Boolean, |
| }, |
| is_trooper: { |
| type: Boolean, |
| }, |
| reload: { |
| type: Number, |
| value: 60, |
| }, |
| repo: { |
| type: String, |
| observer: "_reloadFromScratch", |
| }, |
| search: { |
| type: String, |
| observer: "_searchChanged", |
| }, |
| |
| // output only |
| branch_heads: { |
| type: Array, |
| value: function() { |
| return []; |
| }, |
| notify:true, |
| }, |
| task_details: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| tasks: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| task_specs: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| categories: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| category_list: { |
| type: Array, |
| value: function() { |
| return []; |
| }, |
| notify:true, |
| }, |
| commits: { |
| type: Array, |
| notify:true, |
| }, |
| commits_map: { |
| type: Object, |
| notify:true, |
| }, |
| loading: { |
| type: Number, |
| notify: true, |
| computed: "_or(_activeAJAX, _filtering)", |
| }, |
| purple_tasks: { |
| type: Array, |
| notify: true, |
| }, |
| relanded_map: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| reverted_map: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| swarming_url: { |
| type: String, |
| notify: true, |
| }, |
| task_scheduler_url: { |
| type: String, |
| notify: true, |
| }, |
| time_points: { |
| type: Object, |
| value: function() { |
| return {}; |
| }, |
| notify:true, |
| }, |
| |
| //private |
| _activeAJAX: { |
| type: Boolean, |
| value: false, |
| }, |
| _filtering: { |
| type: Boolean, |
| value: false, |
| }, |
| _data: { |
| type: Object, |
| value: function() { |
| return { |
| branch_heads: [], |
| commitComments: {}, |
| commits: [], |
| tasks: {}, |
| tasksByCommit: {}, |
| taskComments: {}, |
| tasks: {}, |
| taskSpecComments: {}, |
| }; |
| }, |
| observer:"", |
| }, |
| _last_load_ts: { |
| type: Number, |
| value: 0, |
| }, |
| _server_pod_id: { |
| type: String, |
| value: "", |
| }, |
| _updateRequested: { |
| type: Boolean, |
| value: false, |
| }, |
| _updateFromScratch: { |
| type: Boolean, |
| value: false, |
| }, |
| }, |
| |
| observers: [ |
| "_processData(_data.*, roll_statuses.*)", |
| ], |
| |
| _or: function(a,b) { |
| return a || b; |
| }, |
| |
| _makeCommitsMap: function(arr) { |
| if (!arr || arr.length == 0) { |
| this.set("commits_map", {}); |
| return; |
| } |
| var m = {}; |
| arr.forEach(function(c){ |
| m[c.hash] = c; |
| }); |
| this.set("commits_map", m); |
| }, |
| |
| _clearData: function() { |
| this._data = { |
| branch_heads: [], |
| commitComments: {}, |
| commits: [], |
| taskComments: {}, |
| tasks: {}, |
| tasksByCommit: {}, |
| taskSpecComments: {}, |
| }; |
| }, |
| |
| _reloadFromScratch: function() { |
| this._startUpdate(true); |
| }, |
| |
| _startUpdate: function(fromScratch) { |
| console.log("_startUpdate(" + fromScratch + ")"); |
| if (this._activeAJAX) { |
| this._updateRequested = true; |
| if (fromScratch === true) { |
| this._updateFromScratch = true; |
| } |
| return; |
| } |
| this.set("_activeAJAX", true); |
| this._update(fromScratch === true); |
| }, |
| |
| _updateFinished: function() { |
| console.log("_updateFinished"); |
| if (this._updateRequested) { |
| var fromScratch = this._updateFromScratch; |
| this._updateRequested = false; |
| this._updateFromScratch = false; |
| this._update(fromScratch); |
| } else { |
| this._updateRequested = false; |
| this._updateFromScratch = false; |
| this.set("_activeAJAX", false); |
| } |
| }, |
| |
| _update: function(fromScratch) { |
| console.log("_update("+ fromScratch + ")"); |
| // Sanity check. |
| if (!this._activeAJAX) { |
| throw "Cannot call _update directly; must use _startUpdate!"; |
| } |
| if (!this.repo || !this.commits_to_load) { |
| this._updateFinished(); |
| return; |
| } |
| var ts = Date.now(); |
| var url = "/json/" + this.repo + "/incremental?n=" + this.commits_to_load; |
| if (this._last_load_ts && !fromScratch) { |
| url += "&from=" + this._last_load_ts; |
| } |
| if (this._server_pod_id) { |
| url += "&pod=" + this._server_pod_id; |
| } |
| sk.get(url).then(JSON.parse).then(function(json) { |
| // Clear out the existing data if necessary. |
| if (json.start_over || fromScratch) { |
| this._clearData(); |
| } |
| |
| // Keep track of the server pod ID. This ensures that we're in sync |
| // with the server. |
| this._server_pod_id = json.pod; |
| |
| // Mix the new data into the existing. |
| if (!json.commits) { |
| json.commits = []; |
| } |
| var sliceIdx = this._data.commits.length - json.commits.length; |
| var keep = this._data.commits.slice(0, sliceIdx); |
| var remove = this._data.commits.slice(sliceIdx, this._data.commits.length); |
| this._data.commits = json.commits.concat(keep); |
| |
| // Replace the branch heads if they've changed. |
| if (json.branch_heads) { |
| this._data.branch_heads = json.branch_heads; |
| } |
| |
| // Add new tasks, replace modified ones. |
| if (json.tasks) { |
| for (var i = 0; i < json.tasks.length; i++) { |
| var task = json.tasks[i]; |
| this._data.tasks[task.id] = task; |
| } |
| } |
| |
| // Remove too-old tasks. |
| for (var i = 0; i < remove.length; i++) { |
| var commit = remove[i]; |
| for (var id in this._data.tasks) { |
| if (this._data.tasks[id].revision == commit) { |
| delete this._data.tasks[id]; |
| } |
| } |
| } |
| |
| // Map commits to tasks. |
| for (var id in this._data.tasks) { |
| var task = this._data.tasks[id]; |
| if (task.commits) { |
| for (var j = 0; j < task.commits.length; j++) { |
| var commit = task.commits[j]; |
| var tasksForCommit = this._data.tasksByCommit[commit]; |
| if (!tasksForCommit) { |
| tasksForCommit = {}; |
| this._data.tasksByCommit[commit] = tasksForCommit; |
| } |
| tasksForCommit[task.name] = task; |
| } |
| } |
| } |
| |
| if (json.swarming_url) { |
| this.set("swarming_url", json.swarming_url); |
| } |
| if (json.task_scheduler_url) { |
| this.set("task_scheduler_url", json.task_scheduler_url); |
| } |
| if (json.commit_comments) { |
| this._data.commitComments = json.commit_comments; |
| } |
| if (json.task_comments) { |
| this._data.taskComments = json.task_comments; |
| } |
| if (json.task_spec_comments) { |
| this._data.taskSpecComments = json.task_spec_comments; |
| } |
| |
| // Update the last-load timestamp. |
| this._last_load_ts = ts; |
| this._updateFinished(); |
| this._processData(this._data); |
| }.bind(this)).catch(function(msg) { |
| this._updateFinished(); |
| sk.errorMessage("Failed to load new data: " + msg); |
| }.bind(this)); |
| }, |
| |
| _processData: function(data) { |
| if (!data || !data.commits || data.commits.length == 0) { |
| return; |
| } |
| this.set("_filtering", true); |
| console.time("_processData"); |
| for (var i = 0; i < data.commits.length; i++) { |
| data.commits[i].comments = data.commitComments[data.commits[i].hash] || []; |
| } |
| |
| var commits = data.commits; |
| var commitsMap = {}; |
| for (var i = 0; i < commits.length; i++) { |
| commitsMap[commits[i].hash] = commits[i]; |
| } |
| |
| // Prepare task data. |
| var tasks = {}; |
| var task_specs = {}; |
| var task_details = this.task_details; |
| for (var commit in data.tasksByCommit) { |
| task_details[commit] = data.tasksByCommit[commit]; |
| for (var taskSpec in task_details[commit]) { |
| var task = task_details[commit][taskSpec]; |
| task.comments = []; |
| if (data.taskComments[commit] && data.taskComments[commit][taskSpec]) { |
| task.comments = data.taskComments[commit][taskSpec]; |
| } |
| } |
| } |
| |
| for (var i = 0; i < commits.length; i++) { |
| var commit = commits[i]; |
| commit.shortAuthor = shortAuthor(commit.author); |
| commit.shortHash = shortCommit(commit.hash); |
| commit.shortSubject = shortSubject(commit.subject); |
| |
| var c = findIssueAndReviewTool(commit); |
| commit.issue = c.issue; |
| commit.patchStorage = c.patchStorage; |
| } |
| |
| for (var i = 0; i < commits.length; i++) { |
| var commit = commits[i]; |
| commit.isRevert = false; |
| var reverted = findRevertedCommit(commitsMap, commit); |
| if (reverted) { |
| commit.isRevert = true; |
| this.reverted_map[reverted.hash] = commit; |
| } |
| |
| commit.isReland = false; |
| var relanded = findRelandedCommit(commitsMap, commit); |
| if (relanded) { |
| commit.isReland = true; |
| this.relanded_map[relanded.hash] = commit; |
| } |
| } |
| |
| for (var i = 0; i < commits.length; i++) { |
| var commit = commits[i]; |
| commit.ignoreFailure = commit.comments && |
| commit.comments.length > 0 && |
| commit.comments[commit.comments.length-1].ignoreFailure; |
| if (this.reverted_map[commit.hash]) { |
| commit.ignoreFailure = true; |
| } |
| |
| commit.displayClass = {}; |
| if (!task_details[commit.hash]) { |
| task_details[commit.hash] = {}; |
| } |
| for (var taskSpec in task_details[commit.hash]) { |
| var task = task_details[commit.hash][taskSpec]; |
| task.colorClass = getTaskColorClass(task); |
| |
| if (!tasks[taskSpec]) { |
| // This is the first time we've seen this task spec. |
| tasks[taskSpec] = {}; |
| var taskSpecDetails = { |
| "comments": data.taskSpecComments[taskSpec] || [], |
| "name": taskSpec, |
| // We're traveling backward in time, so the first task we |
| // find for a given task spec is its most recent. |
| "colorClass": task.colorClass, |
| "flaky": false, |
| "ignoreFailure": false, |
| }; |
| var split = taskSpec.split("-"); |
| if (split.length >= 2 && VALID_TASK_SPEC_CATEGORIES.indexOf(split[0]) != -1) { |
| taskSpecDetails.category = split[0]; |
| taskSpecDetails.subcategory = split[1]; |
| } |
| if (taskSpecDetails.comments && taskSpecDetails.comments.length > 0) { |
| taskSpecDetails.flaky = !!taskSpecDetails.comments[taskSpecDetails.comments.length-1].flaky; |
| taskSpecDetails.ignoreFailure = !!taskSpecDetails.comments[taskSpecDetails.comments.length-1].ignoreFailure |
| } |
| task_specs[taskSpec] = taskSpecDetails; |
| } |
| tasks[taskSpec][task.id] = task; |
| // Figure out the display class to use. |
| var classes = [CLASS_TASK_SINGLE]; |
| if (i > 0) { |
| // We are drawing from most recent on back in time. prevCommit is really the "next" |
| // commit in a temporal timeline. But, it was the previously drawn commit, so the |
| // name sticks. |
| var prevCommit = commits[i-1]; |
| var prevDetails = task_details[prevCommit.hash] || {}; |
| if (prevCommit.parent.indexOf(commit.hash) === -1) { |
| // We skipped one or more commits. This is likely due to a branch. We need to find the last drawn commit whose parent is this one. |
| prevCommit = undefined; |
| for (var j = i-1; j>= 0; j--) { |
| if (commits[j].parent.indexOf(commit.hash) !== -1) { |
| prevCommit = commits[j]; |
| break; |
| } |
| } |
| if (prevCommit) { |
| // If the previously drawn commit does not exist, it basically means we are the |
| // head of the branch. If it does exist, we change it to have a dashed bottom |
| // and for this commit to have a dashed top. |
| prevDetails = task_details[prevCommit.hash] || {}; |
| var prevTask = prevDetails[taskSpec]; |
| // Only continue drawing if it's actually the same task |
| if (prevTask && prevTask.id == task.id) { |
| classes = [CLASS_TASK_BOTTOM, CLASS_DASHED_TOP]; |
| |
| if (prevCommit.displayClass[taskSpec].indexOf(CLASS_TASK_SINGLE) >= 0) { |
| prevCommit.displayClass[taskSpec] = [CLASS_TASK_TOP, CLASS_DASHED_BOTTOM]; |
| } else { |
| prevCommit.displayClass[taskSpec] = [CLASS_TASK_MIDDLE, CLASS_DASHED_BOTTOM]; |
| } |
| |
| } |
| } |
| } else if (prevDetails) { |
| var prevTask = prevDetails[taskSpec]; |
| // Only continue drawing if it's actually the same task |
| if (prevTask && prevTask.id == task.id) { |
| classes = [CLASS_TASK_BOTTOM]; |
| var prevClasses = prevCommit.displayClass[taskSpec]; |
| if (prevClasses.indexOf(CLASS_TASK_SINGLE) >= 0) { |
| prevCommit.displayClass[taskSpec] = [CLASS_TASK_TOP]; |
| } else if (prevClasses.indexOf(CLASS_TASK_BOTTOM) >= 0) { |
| var j = prevClasses.indexOf(CLASS_TASK_BOTTOM); |
| prevClasses[j] = CLASS_TASK_MIDDLE; |
| prevCommit.displayClass[taskSpec] = prevClasses; |
| } |
| } |
| } |
| } |
| commit.displayClass[taskSpec] = classes; |
| } |
| } |
| this._makeCommitsMap(commits); |
| this.set("tasks", tasks); |
| this.set("task_details", task_details); |
| this.set("task_specs", task_specs); |
| this._filterTaskSpecs(); |
| |
| // Add autoroll tags as branch heads. |
| var filteredBranchHeads = []; |
| for (var i = 0; i < data.branch_heads.length; i++) { |
| var b = data.branch_heads[i]; |
| filteredBranchHeads.push(b); |
| } |
| for (var i = 0; i < this.roll_statuses.length; i++) { |
| var roll = this.roll_statuses[i]; |
| if (roll.lastRollRev) { |
| filteredBranchHeads.push({ |
| name: roll.name + " rolled", |
| head: roll.lastRollRev, |
| }); |
| } |
| if (roll.currentRollRev) { |
| filteredBranchHeads.push({ |
| name: roll.name + " rolling", |
| head: roll.currentRollRev, |
| }); |
| } |
| } |
| |
| this.set("branch_heads", filteredBranchHeads); |
| |
| var timeIdx = 0; |
| var now = new Date(); |
| var time_points = {}; |
| |
| // If the first commit happened after our first time point cutoff, we advance past it. |
| while ((timeIdx < TIME_POINTS.length) && (now - TIME_POINTS[timeIdx].offset) > new Date(commits[0].timestamp)) { |
| timeIdx++; |
| } |
| |
| // Going backwards in time, we place a marker if the current commit happened before the time offset and the following commit happened after. Once we find a cutoff, start looking for the next time point. |
| var commitIdx = 0; |
| while (commitIdx < (commits.length - 1) && timeIdx < TIME_POINTS.length) { |
| var c = commits[commitIdx]; |
| var curr = new Date(c.timestamp); |
| var next = new Date(commits[commitIdx+1].timestamp); |
| |
| if ((now - TIME_POINTS[timeIdx].offset) <= curr && (now - TIME_POINTS[timeIdx].offset) > next) { |
| time_points[c.hash] = TIME_POINTS[timeIdx]; |
| timeIdx++; |
| // We don't increment commitIdx because we want to double check the current cutoff. |
| // Example: commit A happened 59 minutes ago and commit B happened 1.3 days ago. |
| // The time point between them should be the -1d one, not the -1h one. Since time_points |
| // is based off of commit, we can recheck and replace the shorter cutoffs if necessary. |
| } else { |
| commitIdx++; |
| } |
| } |
| |
| // Check for the last commit as well, except we don't compare it to the following commit. |
| var last = commits[commits.length - 1]; |
| if ((timeIdx < TIME_POINTS.length) && (now - TIME_POINTS[timeIdx].offset) <= last.timestamp) { |
| time_points[last.hash] = TIME_POINTS[timeIdx]; |
| } |
| |
| console.timeEnd("_processData"); |
| this.set("_filtering", false); |
| |
| // Actually draw the commits. |
| this.set("commits", commits); |
| this.set("time_points", time_points); |
| }, |
| |
| // Apply the desired filter to the tasks. |
| _filterTaskSpecs: function() { |
| console.time("filterTaskSpecs"); |
| var filteredTaskSpecs = []; |
| var selected = this.filter || FILTER_DEFAULT; |
| if (selected === FILTER_DEFAULT) { |
| if (this.is_sheriff || this.is_trooper) { |
| selected = FILTER_FAILURES; |
| } else { |
| selected = FILTER_INTERESTING; |
| } |
| } |
| if (selected == FILTER_ALL) { |
| for (var taskSpec in this.task_specs) { |
| filteredTaskSpecs.push(taskSpec); |
| } |
| } else if (selected == FILTER_INTERESTING || selected == FILTER_FAILURES || selected == FILTER_FAIL_NO_COMMENT) { |
| for (var taskSpec in this.task_specs) { |
| var failed = false; |
| var succeeded = false; |
| for (var taskId in this.tasks[taskSpec]) { |
| var task = this.tasks[taskSpec][taskId]; |
| if (task.status == TASK_STATUS_PENDING || task.status == TASK_STATUS_RUNNING) { |
| continue; |
| } |
| // If interesting or "failing w/o comment" is selected, compute ignoreFailure |
| // and skip this task if it belongs entirely to commits that have been ignored. |
| if (selected === FILTER_INTERESTING || selected === FILTER_FAIL_NO_COMMENT) { |
| var commits = task.commits || []; |
| var isIgnored = commits.length > 0; |
| commits.forEach(function(c){ |
| var o = this.commits_map[c]; |
| isIgnored = isIgnored && o && o.ignoreFailure; |
| }.bind(this)); |
| if (isIgnored) { |
| continue; |
| } |
| } |
| |
| if (task.status == TASK_STATUS_SUCCESS) { |
| succeeded = true; |
| } else { |
| failed = true; |
| } |
| if (selected == FILTER_INTERESTING) { |
| if (succeeded && failed && !this.task_specs[taskSpec].ignoreFailure) { |
| filteredTaskSpecs.push(taskSpec); |
| break; |
| } |
| } else if (selected == FILTER_FAILURES) { |
| if (failed) { |
| filteredTaskSpecs.push(taskSpec); |
| break; |
| } |
| } else if (selected == FILTER_FAIL_NO_COMMENT) { |
| if (task.status != TASK_STATUS_SUCCESS && (!this.task_specs[taskSpec].comments || this.task_specs[taskSpec].comments.length == 0)) { |
| if (!task.comments || task.comments.length == 0) { |
| filteredTaskSpecs.push(taskSpec); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } else if (selected == FILTER_COMMENTS) { |
| for (var taskSpec in this.task_specs) { |
| if (this.task_specs[taskSpec].comments && this.task_specs[taskSpec].comments.length > 0) { |
| filteredTaskSpecs.push(taskSpec); |
| continue; |
| } |
| for (var taskId in this.tasks[taskSpec]) { |
| var task = this.tasks[taskSpec][taskId]; |
| if (task.status == TASK_STATUS_PENDING || task.status == TASK_STATUS_RUNNING) { |
| continue; |
| } |
| if (task.comments && task.comments.length > 0) { |
| filteredTaskSpecs.push(taskSpec); |
| break; |
| } |
| } |
| } |
| } else if (selected == FILTER_SEARCH) { |
| var matchText = this.search; |
| for (var taskSpec in this.task_specs) { |
| if (taskSpec.toLowerCase().match(matchText.toLowerCase())) { |
| filteredTaskSpecs.push(taskSpec); |
| } |
| } |
| } else { |
| console.error("Invalid task spec filter selection: " + selected); |
| } |
| sk.sortStrings(filteredTaskSpecs); |
| |
| var categories = {}; |
| var categoryList = []; |
| var purpleTasks = []; |
| for (var i = 0; i < filteredTaskSpecs.length; i++) { |
| var taskSpecName = filteredTaskSpecs[i]; |
| var category = this.task_specs[taskSpecName].category; |
| if (!category) { |
| category = "Other"; |
| } |
| if (!categories[category]) { |
| categories[category] = { |
| colspan: 0, |
| subcategoryList: [], |
| subcategories: {}, |
| }; |
| categoryList.push(category); |
| } |
| var subcategory = this.task_specs[taskSpecName].subcategory; |
| if (!subcategory) { |
| subcategory = "Other"; |
| } |
| if (!categories[category].subcategories[subcategory]) { |
| categories[category].subcategories[subcategory] = { |
| task_specs: [], |
| }; |
| categories[category].subcategoryList.push(subcategory); |
| } |
| categories[category].subcategories[subcategory].task_specs.push(taskSpecName); |
| categories[category].colspan++; |
| |
| // Find any purple tasks for this task spec. |
| |
| // Exclude the Google3-Autoroller since it is not a real bot and |
| // therefore cannot be retried. |
| if (taskSpecName != "Google3-Autoroller") { |
| for (var taskId in this.tasks[taskSpecName]) { |
| var task = this.tasks[taskSpecName][taskId]; |
| if (task.status == TASK_STATUS_MISHAP) { |
| // TODO(borenet): This is a bit of a hack. |
| // Jobs are named after task, test, or perf tasks, but not |
| // uploads. If this is an upload, trim the prefix. |
| var jobName = taskSpecName; |
| if (jobName.startsWith("Upload-")) { |
| jobName = jobName.substring("Upload-".length); |
| } |
| purpleTasks.push({"name": jobName, "commit": task.revision}); |
| } |
| } |
| } |
| } |
| this.set("categories", categories); |
| this.set("category_list", categoryList); |
| this.set("purple_tasks", purpleTasks); |
| console.timeEnd("filterTaskSpecs"); |
| }, |
| |
| _searchChanged: function() { |
| // This callback fires every time the user presses a key inside the |
| // input box. Updating the display can be really slow when there are |
| // a lot of task specs, so we should wait until the user is done typing |
| // before re-filtering. |
| if (this.taskSpecSearchChangedTimeout) { |
| window.clearTimeout(this.taskSpecSearchChangedTimeout); |
| } |
| this.taskSpecSearchChangedTimeout = window.setTimeout(function() { |
| this.taskSpecSearchChangedTimeout = null; |
| |
| // If "search" is already selected, filter the task specs. |
| if (this.filter == "search") { |
| this._filterTaskSpecs(); |
| } |
| }.bind(this), 400); |
| }, |
| |
| forceUpdate: function() { |
| this._startUpdate(false); |
| }, |
| |
| forceReProcess: function() { |
| this._processData(this._data, this.roller_statuses); |
| }, |
| }); |
| })(); |
| </script> |
| </dom-module |