https://bugs.webkit.org/show_bug.cgi?id=155529
Reviewed by Chris Dumez.
Added the capability to associate revisions that caused or fixed a progression or a regression for which
an analysis task was created. Added task_commits that stores this relationship and added the backend
support to retrieve this table in /api/analysis-tasks and an privileged API to update this table at
/privileged-api/associate-commit.
Also extracted a new component, MutableListView, out of AnalysisTaskPage to render and manipulate a list
of mutable items, and used it to render the list of associated bugs and commits. The view takes a list of
kinds (e.g. repositories or bug trackers), and accepts a pair of a kind and arbitrary text as a new item
value.
* init-database.sql: Added task_commits table.
* public/api/analysis-tasks.php:
(main):
(fetch_associated_data_for_tasks): Renamed from fetch_and_push_bugs_to_tasks now that it also fetches
the list of commits associated with each analysis task by calling CommitLogFetcher::fetch_for_tasks.
Also fixe the bug that we were not taking
(format_task): No longer sets 'category' since the computation of category now depends on the list of
commits associated with this analysis task which aren't available until fetch_associated_data_for_tasks.
(determine_category): Added. Categorize any analysis tasks with "fixes" commits as "closed" and "causes"
commits as "identified".
* public/include/commit-log-fetcher.php:
(CommitLogFetcher::__construct): Remove the unused instance variable.
(CommitLogFetcher::fetch_for_tasks): Added. Fetches all commits associated with a list of analysis tasks.
Assumes the caller (fetch_associated_data_for_tasks) had setup "fixes" and "causes" fields on each task.
* public/privileged-api/associate-commit.php: Added. Updates task_commits table to associate or disassociate
a commit with an analysis task. When the specified analysis task and the specified commit are already
associated, we simply update the table instead of adding a duplicating entry or error. For dissociation,
the front-end specifies the commit ID.
(main): Added.
* public/v3/index.html:
* public/v3/components/mutable-list-view.js: Added. Used by the list associated bugs and commits.
(MutableListView): Added.
(MutableListView.prototype.setList): Added.
(MutableListView.prototype.setKindList): Added.
(MutableListView.prototype.setAddCallback): Added. This callback is invoked when the user tries to add
a new item to the list.
(MutableListView.prototype.render): Added.
(MutableListView.prototype._submitted): Added.
(MutableListView.cssTemplate):
(MutableListView.htmlTemplate):
(MutableListItem): Added. RemovalLink could be a hyperlink or a callback and gets involved when the user
tries to delete this item.
(MutableListItem.prototype.content):
* public/v3/models/analysis-task.js:
(AnalysisTask): Added the support of the list of commits that fixed and caused changes.
(AnalysisTask.prototype.updateSingleton): Ditto.
(AnalysisTask.prototype.causes): Added.
(AnalysisTask.prototype.fixes): Added.
(AnalysisTask.prototype.associateCommit): Added. Use the API added at /privileged-api/associate-commit
to associate a new commit with this analysis task. Each commit has either caused or fixed the change.
(AnalysisTask.prototype.dissociateCommit): Added. Use the same API to disassociate each commit.
(AnalysisTask._constructAnalysisTasksFromRawData): Find all commits associated with each analysis task.
Because commit log objects use a fake ID fdue to /api/measurement-set not providing commit IDs, we must
use CommitLog.findByRemoteId to find each commit instead of usual CommitLog.findById.
(AnalysisTask._constructAnalysisTasksFromRawData.resolveCommits): Added.
* public/v3/models/build-request.js:
(BuildRequest.prototype.hasFinished): Renamed from hasCompleted since it was confusing for this._status
being "completed" wasn't a necessary condition for this function to return true.
* public/v3/models/commit-log.js:
(CommitLog): Added the static map for actual commit ID instead of a fake ID created in ensureSingleton.
(CommitLog.prototype.remoteId): Added. Returns the real commit ID.
(CommitLog.findByRemoteId): Added. Finds an CommitLog object using the real ID.
* public/v3/models/test-group.js:
(TestGroup.prototype.hasFinished): Renamed from hasCompleted to match the rename in BuildRequest.
* public/v3/pages/analysis-task-page.js:
(AnalysisTaskPage): Added lists for the commits that fixed and caused the change using MutableListView.
Also adopted MutableListView for the list of associated bugs.
(AnalysisTaskPage.prototype.render): Added the code to populate the newly added lists.
(AnalysisTaskPage.prototype._makeCommitListItem): Added.
(AnalysisTaskPage.prototype._associateBug): Now this is a callback from MutableListView.
(AnalysisTaskPage.prototype._associateCommit): Added.
(AnalysisTaskPage.prototype._dissociateCommit): Added.
(AnalysisTaskPage.htmlTemplate):
(AnalysisTaskPage.cssTemplate):
* public/v3/remote.js:
(getJSON): Spit out the entire responseText when JSON failed to parse to make debugging easier.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@198265
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2016-03-16 Ryosuke Niwa <rniwa@webkit.org>
+
+ Analysis task page should allow specifying commits that caused or fixed a regression or a progression
+ https://bugs.webkit.org/show_bug.cgi?id=155529
+
+ Reviewed by Chris Dumez.
+
+ Added the capability to associate revisions that caused or fixed a progression or a regression for which
+ an analysis task was created. Added task_commits that stores this relationship and added the backend
+ support to retrieve this table in /api/analysis-tasks and an privileged API to update this table at
+ /privileged-api/associate-commit.
+
+ Also extracted a new component, MutableListView, out of AnalysisTaskPage to render and manipulate a list
+ of mutable items, and used it to render the list of associated bugs and commits. The view takes a list of
+ kinds (e.g. repositories or bug trackers), and accepts a pair of a kind and arbitrary text as a new item
+ value.
+
+ * init-database.sql: Added task_commits table.
+
+ * public/api/analysis-tasks.php:
+ (main):
+ (fetch_associated_data_for_tasks): Renamed from fetch_and_push_bugs_to_tasks now that it also fetches
+ the list of commits associated with each analysis task by calling CommitLogFetcher::fetch_for_tasks.
+ Also fixe the bug that we were not taking
+ (format_task): No longer sets 'category' since the computation of category now depends on the list of
+ commits associated with this analysis task which aren't available until fetch_associated_data_for_tasks.
+ (determine_category): Added. Categorize any analysis tasks with "fixes" commits as "closed" and "causes"
+ commits as "identified".
+
+ * public/include/commit-log-fetcher.php:
+ (CommitLogFetcher::__construct): Remove the unused instance variable.
+ (CommitLogFetcher::fetch_for_tasks): Added. Fetches all commits associated with a list of analysis tasks.
+ Assumes the caller (fetch_associated_data_for_tasks) had setup "fixes" and "causes" fields on each task.
+
+ * public/privileged-api/associate-commit.php: Added. Updates task_commits table to associate or disassociate
+ a commit with an analysis task. When the specified analysis task and the specified commit are already
+ associated, we simply update the table instead of adding a duplicating entry or error. For dissociation,
+ the front-end specifies the commit ID.
+ (main): Added.
+
+ * public/v3/index.html:
+ * public/v3/components/mutable-list-view.js: Added. Used by the list associated bugs and commits.
+ (MutableListView): Added.
+ (MutableListView.prototype.setList): Added.
+ (MutableListView.prototype.setKindList): Added.
+ (MutableListView.prototype.setAddCallback): Added. This callback is invoked when the user tries to add
+ a new item to the list.
+ (MutableListView.prototype.render): Added.
+ (MutableListView.prototype._submitted): Added.
+ (MutableListView.cssTemplate):
+ (MutableListView.htmlTemplate):
+ (MutableListItem): Added. RemovalLink could be a hyperlink or a callback and gets involved when the user
+ tries to delete this item.
+ (MutableListItem.prototype.content):
+
+ * public/v3/models/analysis-task.js:
+ (AnalysisTask): Added the support of the list of commits that fixed and caused changes.
+ (AnalysisTask.prototype.updateSingleton): Ditto.
+ (AnalysisTask.prototype.causes): Added.
+ (AnalysisTask.prototype.fixes): Added.
+ (AnalysisTask.prototype.associateCommit): Added. Use the API added at /privileged-api/associate-commit
+ to associate a new commit with this analysis task. Each commit has either caused or fixed the change.
+ (AnalysisTask.prototype.dissociateCommit): Added. Use the same API to disassociate each commit.
+ (AnalysisTask._constructAnalysisTasksFromRawData): Find all commits associated with each analysis task.
+ Because commit log objects use a fake ID fdue to /api/measurement-set not providing commit IDs, we must
+ use CommitLog.findByRemoteId to find each commit instead of usual CommitLog.findById.
+ (AnalysisTask._constructAnalysisTasksFromRawData.resolveCommits): Added.
+
+ * public/v3/models/build-request.js:
+ (BuildRequest.prototype.hasFinished): Renamed from hasCompleted since it was confusing for this._status
+ being "completed" wasn't a necessary condition for this function to return true.
+
+ * public/v3/models/commit-log.js:
+ (CommitLog): Added the static map for actual commit ID instead of a fake ID created in ensureSingleton.
+ (CommitLog.prototype.remoteId): Added. Returns the real commit ID.
+ (CommitLog.findByRemoteId): Added. Finds an CommitLog object using the real ID.
+
+ * public/v3/models/test-group.js:
+ (TestGroup.prototype.hasFinished): Renamed from hasCompleted to match the rename in BuildRequest.
+
+ * public/v3/pages/analysis-task-page.js:
+ (AnalysisTaskPage): Added lists for the commits that fixed and caused the change using MutableListView.
+ Also adopted MutableListView for the list of associated bugs.
+ (AnalysisTaskPage.prototype.render): Added the code to populate the newly added lists.
+ (AnalysisTaskPage.prototype._makeCommitListItem): Added.
+ (AnalysisTaskPage.prototype._associateBug): Now this is a callback from MutableListView.
+ (AnalysisTaskPage.prototype._associateCommit): Added.
+ (AnalysisTaskPage.prototype._dissociateCommit): Added.
+ (AnalysisTaskPage.htmlTemplate):
+ (AnalysisTaskPage.cssTemplate):
+
+ * public/v3/remote.js:
+ (getJSON): Spit out the entire responseText when JSON failed to parse to make debugging easier.
+
2016-03-15 Ryosuke Niwa <rniwa@webkit.org>
Extract the code to format commit logs into its own PHP file
DROP TABLE reports CASCADE;
DROP TABLE tracker_repositories CASCADE;
DROP TABLE bug_trackers CASCADE;
+DROP TABLE task_commits CASCADE;
DROP TABLE analysis_tasks CASCADE;
DROP TABLE analysis_strategies CASCADE;
DROP TYPE analysis_task_result_type CASCADE;
CONSTRAINT analysis_task_should_not_be_associated_with_single_run
CHECK ((task_start_run IS NULL AND task_end_run IS NULL) OR (task_start_run IS NOT NULL AND task_end_run IS NOT NULL)));
+CREATE TABLE task_commits (
+ taskcommit_task integer NOT NULL REFERENCES analysis_tasks ON DELETE CASCADE,
+ taskcommit_commit integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+ taskcommit_is_fix boolean NOT NULL
+ CONSTRAINT task_commit_must_be_unique UNIQUE(taskcommit_task, taskcommit_commit));
+
CREATE TABLE bugs (
bug_id serial PRIMARY KEY,
bug_task integer REFERENCES analysis_tasks NOT NULL,
<?php
require('../include/json-header.php');
+require('../include/commit-log-fetcher.php');
function main($path) {
$db = new Database;
}
$tasks = array_map("format_task", $tasks);
- $bugs = fetch_and_push_bugs_to_tasks($db, $tasks);
-
- exit_with_success(array('analysisTasks' => $tasks, 'bugs' => $bugs));
+ exit_with_success(fetch_associated_data_for_tasks($db, $tasks));
}
-function fetch_and_push_bugs_to_tasks($db, &$tasks) {
+function fetch_associated_data_for_tasks($db, &$tasks) {
$task_ids = array();
$task_by_id = array();
foreach ($tasks as &$task) {
array_push($associated_task['bugs'], $bug['id']);
}
+ $commit_log_fetcher = new CommitLogFetcher($db);
+ $commits = $commit_log_fetcher->fetch_for_tasks($task_ids, $task_by_id);
+ if (!is_array($commits))
+ exit_with_error('FailedToFetchCommits');
+
$task_build_counts = $db->query_and_fetch_all('SELECT
testgroup_task AS "task",
count(testgroup_id) as "total",
- sum(case when request_status = \'failed\' or request_status = \'completed\' then 1 else 0 end) as "finished"
+ sum(case when request_status = \'failed\' or request_status = \'completed\' or request_status = \'canceled\' then 1 else 0 end) as "finished"
FROM analysis_test_groups, build_requests
WHERE request_group = testgroup_id AND testgroup_task = ANY($1) GROUP BY testgroup_task',
array('{' . implode(', ', $task_ids) . '}'));
$task = &$task_by_id[$build_count['task']];
$task['buildRequestCount'] = $build_count['total'];
$task['finishedBuildRequestCount'] = $build_count['finished'];
+ $task['category'] = determine_category($task);
}
- return $bugs;
+ return array('analysisTasks' => $tasks, 'bugs' => $bugs, 'commits' => $commits);
}
function format_task($task_row) {
- $category = 'unconfirmed';
- $result = $task_row['task_result'];
- if ($result == 'unchanged' || $result == 'inconclusive')
- $category = 'closed';
- else if ($result)
- $category = 'bisecting';
-
return array(
'id' => $task_row['task_id'],
'name' => $task_row['task_name'],
'startRunTime' => Database::to_js_time($task_row['task_start_run_time']),
'endRun' => $task_row['task_end_run'],
'endRunTime' => Database::to_js_time($task_row['task_end_run_time']),
- 'category' => $category,
- 'result' => $result,
+ 'category' => null,
+ 'result' => $task_row['task_result'],
'needed' => $task_row['task_needed'] ? Database::is_true($task_row['task_needed']) : null,
'bugs' => array(),
+ 'causes' => array(),
+ 'fixes' => array(),
);
}
+function determine_category($task) {
+ $category = 'unconfirmed';
+
+ $result = $task['result'];
+ if ($result == 'unchanged' || $result == 'inconclusive' || $task['fixes'])
+ $category = 'closed';
+ else if ($task['causes'])
+ $category = 'identified';
+ else if ($result)
+ $category = 'bisecting';
+
+ return $category;
+}
+
main(array_key_exists('PATH_INFO', $_SERVER) ? explode('/', trim($_SERVER['PATH_INFO'], '/')) : array());
?>
function __construct($db) {
$this->db = $db;
- $this->commits = array();
+ }
+
+ function fetch_for_tasks($task_id_list, $task_by_id)
+ {
+ $commit_rows = $this->db->query_and_fetch_all('SELECT task_commits.*, commits.*, committers.*
+ FROM task_commits, commits LEFT OUTER JOIN committers ON commit_committer = committer_id
+ WHERE taskcommit_commit = commit_id AND taskcommit_task = ANY ($1)', array('{' . implode(', ', $task_id_list) . '}'));
+ if (!is_array($commit_rows))
+ return NULL;
+
+ $commits = array();
+ foreach ($commit_rows as &$commit_row) {
+ $associated_task = &$task_by_id[$commit_row['taskcommit_task']];
+ $commit = $this->format_commit($commit_row, $commit_row);
+ $commit['repository'] = $commit_row['commit_repository'];
+ array_push($commits, $commit);
+ array_push($associated_task[Database::is_true($commit_row['taskcommit_is_fix']) ? 'fixes' : 'causes'], $commit_row['commit_id']);
+ }
+ return $commits;
}
function repository_id_from_name($name)
--- /dev/null
+<?php
+
+require_once('../include/json-header.php');
+
+function main() {
+ $data = ensure_privileged_api_data_and_token();
+
+ $analysis_task_id = array_get($data, 'task');
+ $repository_id = array_get($data, 'repository');
+ $revision = array_get($data, 'revision');
+ $kind = array_get($data, 'kind');
+ $commit_id_to_diassociate = array_get($data, 'commit');
+
+ $db = connect();
+ $db->begin_transaction();
+
+ require_format('AnalysisTask', $analysis_task_id, '/^\d+$/');
+ if ($commit_id_to_diassociate) {
+ require_format('Commit', $commit_id_to_diassociate, '/^\d*$/');
+
+ $count = $db->query_and_get_affected_rows("DELETE FROM task_commits WHERE taskcommit_task = $1 AND taskcommit_commit = $2",
+ array($analysis_task_id, $commit_id_to_diassociate));
+ if ($count != 1) {
+ $db->rollback_transaction();
+ exit_with_error('UnexpectedNumberOfAffectedRows', array('affectedRows' => $count));
+ }
+ } else {
+ require_format('Repository', $repository_id, '/^\d+$/');
+ require_format('Kind', $kind, '/^(cause|fix)$/');
+
+ $commit_info = array('repository' => $repository_id, 'revision' => $revision);
+ $commit_row = $db->select_first_row('commits', 'commit', $commit_info);
+ if (!$commit_row) {
+ $db->rollback_transaction();
+ exit_with_error('CommitNotFound', $commit_info);
+ }
+ $commit_id = $commit_row['commit_id'];
+
+ $association = array('task' => $analysis_task_id, 'commit' => $commit_id, 'is_fix' => Database::to_database_boolean($kind == 'fix'));
+ $commit_id = $db->update_or_insert_row('task_commits', 'taskcommit',
+ array('task' => $analysis_task_id, 'commit' => $commit_id), $association, 'commit');
+ if (!$commit_id) {
+ $db->rollback_transaction();
+ exit_with_error('FailedToAssociateCommit', $association);
+ }
+ }
+
+ $db->commit_transaction();
+
+ exit_with_success();
+}
+
+main();
+
+?>
--- /dev/null
+
+
+class MutableListView extends ComponentBase {
+
+ constructor()
+ {
+ super('mutable-list-view');
+ this._list = [];
+ this._kindList = [];
+ this._addCallback = null;
+ this._kindMap = new Map;
+ this.content().querySelector('form').onsubmit = this._submitted.bind(this);
+ }
+
+ setList(list) { this._list = list; }
+ setKindList(list) { this._kindList = list; }
+ setAddCallback(callback) { this._addCallback = callback; }
+
+ render()
+ {
+ this.renderReplace(this.content().querySelector('.mutable-list'),
+ this._list.map(function (item) {
+ console.assert(item instanceof MutableListItem);
+ return item.content();
+ }));
+
+ var element = ComponentBase.createElement;
+ var kindMap = this._kindMap;
+ kindMap.clear();
+ this.renderReplace(this.content().querySelector('.kind'),
+ this._kindList.map(function (kind) {
+ kindMap.set(kind.id(), kind);
+ return element('option', {value: kind.id()}, kind.label());
+ }));
+ }
+
+ _submitted(event)
+ {
+ event.preventDefault();
+ if (this._addCallback)
+ this._addCallback(this._kindMap.get(this.content().querySelector('.kind').value), this.content().querySelector('.value').value);
+ }
+
+ static cssTemplate()
+ {
+ return `
+ .mutable-list,
+ .mutable-list li {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ .mutable-list:not(:empty) {
+ margin-bottom: 1rem;
+ }
+
+ .mutable-list {
+ margin-bottom: 1rem;
+ }
+
+ .new-list-item-form {
+ white-space: nowrap;
+ }
+ `;
+ }
+
+ static htmlTemplate()
+ {
+ return `
+ <ul class="mutable-list"></ul>
+ <form class="new-list-item-form">
+ <select class="kind"></select>
+ <input class="value">
+ <button type="submit">Add</button>
+ </form>`;
+ }
+
+}
+
+class MutableListItem {
+ constructor(kind, value, valueTitle, valueLink, removalTitle, removalLink)
+ {
+ this._kind = kind;
+ this._value = value;
+ this._valueTitle = valueTitle;
+ this._valueLink = valueLink;
+ this._removalTitle = removalTitle;
+ this._removalLink = removalLink;
+ }
+
+ content()
+ {
+ var link = ComponentBase.createLink;
+ return ComponentBase.createElement('li', [
+ this._kind.label(),
+ ' ',
+ link(this._value, this._valueTitle, this._valueLink),
+ ' ',
+ link(new CloseButton, this._removalTitle, this._removalLink)]);
+ }
+}
+
+ComponentBase.defineElement('mutable-list-view', MutableListView);
<script src="components/customizable-test-group-form.js"></script>
<script src="components/chart-styles.js"></script>
<script src="components/chart-pane-base.js"></script>
+ <script src="components/mutable-list-view.js"></script>
<script src="pages/page.js"></script>
<script src="pages/page-router.js"></script>
<script src="pages/heading.js"></script>
this._changeType = object.result; // Can't change due to v2 compatibility.
this._needed = object.needed;
this._bugs = object.bugs || [];
+ this._causes = object.causes || [];
+ this._fixes = object.fixes || [];
this._buildRequestCount = object.buildRequestCount;
this._finishedBuildRequestCount = object.finishedBuildRequestCount;
}
this._changeType = object.result; // Can't change due to v2 compatibility.
this._needed = object.needed;
this._bugs = object.bugs || [];
+ this._causes = object.causes || [];
+ this._fixes = object.fixes || [];
this._buildRequestCount = object.buildRequestCount;
this._finishedBuildRequestCount = object.finishedBuildRequestCount;
}
author() { return this._author || ''; }
createdAt() { return this._createdAt; }
bugs() { return this._bugs; }
+ causes() { return this._causes; }
+ fixes() { return this._fixes; }
platform() { return this._platform; }
metric() { return this._metric; }
category() { return this._category; }
});
}
+ associateCommit(kind, repository, revision)
+ {
+ console.assert(kind == 'cause' || kind == 'fix');
+ console.assert(repository instanceof Repository);
+ var id = this.id();
+ return PrivilegedAPI.sendRequest('associate-commit', {
+ task: id,
+ repository: repository.id(),
+ revision: revision,
+ kind: kind,
+ }).then(function (data) {
+ return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
+ .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
+ });
+ }
+
+ dissociateCommit(commit)
+ {
+ console.assert(commit instanceof CommitLog);
+ var id = this.id();
+ return PrivilegedAPI.sendRequest('associate-commit', {
+ task: id,
+ commit: commit.remoteId(),
+ }).then(function (data) {
+ return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
+ .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
+ });
+ }
+
static categories()
{
return [
taskToBug[rawData.task].push(bug);
}
+ for (var rawData of data.commits) {
+ rawData.repository = Repository.findById(rawData.repository);
+ if (!rawData.repository)
+ continue;
+ CommitLog.ensureSingleton(rawData.repository, rawData);
+ }
+
+ function resolveCommits(commits) {
+ return commits.map(function (id) { return CommitLog.findByRemoteId(id); }).filter(function (commit) { return !!commit; });
+ }
+
var results = [];
for (var rawData of data.analysisTasks) {
rawData.platform = Platform.findById(rawData.platform);
continue;
rawData.bugs = taskToBug[rawData.id];
+ rawData.causes = resolveCommits(rawData.causes);
+ rawData.fixes = resolveCommits(rawData.fixes);
results.push(AnalysisTask.ensureSingleton(rawData.id, rawData));
}
order() { return this._order; }
rootSet() { return this._rootSet; }
- hasCompleted() { return this._status == 'failed' || this._status == 'completed' || this._status == 'canceled'; }
+ hasFinished() { return this._status == 'failed' || this._status == 'completed' || this._status == 'canceled'; }
hasStarted() { return this._status != 'pending'; }
hasPending() { return this._status == 'pending'; }
statusLabel()
super(id);
this._repository = rawData.repository;
this._rawData = rawData;
+ this._remoteId = rawData.id;
+ if (this._remoteId)
+ this.ensureNamedStaticMap('remoteId')[this._remoteId] = this;
+ }
+
+ // FIXME: All this non-sense should go away once measurement-set start returning real commit id.
+ remoteId() { return this._remoteId; }
+ static findByRemoteId(id)
+ {
+ var remoteIdMap = super.namedStaticMap('remoteId');
+ return remoteIdMap ? remoteIdMap[id] : null;
}
static ensureSingleton(repository, rawData)
this._allRootSets = null;
}
- hasCompleted()
+ hasFinished()
{
- return this._buildRequests.every(function (request) { return request.hasCompleted(); });
+ return this._buildRequests.every(function (request) { return request.hasFinished(); });
}
hasStarted()
var result = {changeType: null, status: 'failed', label: 'Failed', fullLabel: 'Failed', isStatisticallySignificant: false};
- var hasCompleted = this.hasCompleted();
+ var hasCompleted = this.hasFinished();
if (!hasCompleted) {
if (this.hasStarted()) {
result.status = 'running';
this.content().querySelector('.change-type-form').onsubmit = this._updateChangeType.bind(this);
this._taskStatusControl = this.content().querySelector('.change-type-form select');
- this.content().querySelector('.associate-bug-form').onsubmit = this._associateBug.bind(this);
- this._bugTrackerControl = this.content().querySelector('.bug-tracker-control');
- this._bugNumberControl = this.content().querySelector('.bug-number-control');
+ this._bugList = this.content().querySelector('.associated-bugs mutable-list-view').component();
+ this._bugList.setKindList(BugTracker.all());
+ this._bugList.setAddCallback(this._associateBug.bind(this));
+
+ this._causeList = this.content().querySelector('.cause-list mutable-list-view').component();
+ this._causeList.setAddCallback(this._associateCommit.bind(this, 'cause'));
+
+ this._fixList = this.content().querySelector('.fix-list mutable-list-view').component();
+ this._fixList.setAddCallback(this._associateCommit.bind(this, 'fix'));
this._newTestGroupFormForChart = this.content().querySelector('.overview-chart customizable-test-group-form').component();
this._newTestGroupFormForChart.setStartCallback(this._createNewTestGroupFromChart.bind(this));
this.renderReplace(anchor, metric.fullName() + ' on ' + platform.label());
anchor.href = this.router().url('charts', ChartsPage.createStateForAnalysisTask(this._task));
- var bugs = [];
- for (var bug of this._task.bugs()) {
- bugs.push(element('li', [
- bug.bugTracker().label() + ' ',
- link(bug.label(), bug.title(), bug.url()),
- ' ',
- link(new CloseButton, 'Disassociate this bug', this._disassociateBug.bind(this, bug))]));
- }
- this.renderReplace(this.content().querySelector('.associated-bugs'), bugs);
+ var self = this;
+ this._bugList.setList(this._task.bugs().map(function (bug) {
+ return new MutableListItem(bug.bugTracker(), bug.label(), bug.title(), bug.url(),
+ 'Disassociate this bug', self._disassociateBug.bind(self, bug));
+ }));
+
+ this._causeList.setList(this._task.causes().map(this._makeCommitListItem.bind(this)));
+ this._fixList.setList(this._task.fixes().map(this._makeCommitListItem.bind(this)));
this._taskStatusControl.value = this._task.changeType() || 'unconfirmed';
}
- var element = ComponentBase.createElement;
- this.renderReplace(this._bugTrackerControl,
- BugTracker.all().map(function (tracker) {
- return element('option', {value: tracker.id()}, tracker.label());
- }));
+ var repositoryList;
+ if (this._startPoint) {
+ var rootSet = this._startPoint.rootSet();
+ repositoryList = Repository.sortByNamePreferringOnesWithURL(rootSet.repositories());
+ } else
+ repositoryList = Repository.sortByNamePreferringOnesWithURL(Repository.all());
+
+ this._bugList.render();
+
+ this._causeList.setKindList(repositoryList);
+ this._causeList.render();
+
+ this._fixList.setKindList(repositoryList);
+ this._fixList.render();
this.content().querySelector('.analysis-task-status').style.display = this._task ? null : 'none';
this.content().querySelector('.overview-chart').style.display = this._task ? null : 'none';
Instrumentation.endMeasuringTime('AnalysisTaskPage', 'render');
}
+ _makeCommitListItem(commit)
+ {
+ return new MutableListItem(commit.repository(), commit.label(), commit.title(), commit.url(),
+ 'Disassociate this commit', this._dissociateCommit.bind(this, commit));
+ }
+
_renderTestGroupList()
{
var element = ComponentBase.createElement;
});
}
- _associateBug(event)
+ _associateBug(tracker, bugNumber)
{
- event.preventDefault();
- console.assert(this._task);
-
- var tracker = BugTracker.findById(this._bugTrackerControl.value);
- console.assert(tracker);
- var bugNumber = parseInt(this._bugNumberControl.value);
+ console.assert(tracker instanceof BugTracker);
+ bugNumber = parseInt(bugNumber);
var render = this.render.bind(this);
return this._task.associateBug(tracker, bugNumber).then(render, function (error) {
});
}
+ _associateCommit(kind, repository, revision)
+ {
+ var render = this.render.bind(this);
+ return this._task.associateCommit(kind, repository, revision).then(render, function (error) {
+ render();
+ alert('Failed to associate the commit: ' + error);
+ });
+ }
+
+ _dissociateCommit(commit)
+ {
+ var render = this.render.bind(this);
+ return this._task.dissociateCommit(commit).then(render, function (error) {
+ render();
+ alert('Failed to disassociate the commit: ' + error);
+ });
+ }
+
_retryCurrentTestGroup(repetitionCount)
{
console.assert(this._currentTestGroup);
<button type="submit">Save</button>
</form>
</section>
- <section>
+ <section class="associated-bugs">
<h3>Associated Bugs</h3>
- <ul class="associated-bugs"></ul>
- <form class="associate-bug-form">
- <select class="bug-tracker-control"></select>
- <input type="number" class="bug-number-control">
- <button type="submit">Add</button>
- </form>
+ <mutable-list-view></mutable-list-view>
+ </section>
+ <section class="cause-fix">
+ <h3>Caused by</h3>
+ <span class="cause-list"><mutable-list-view></mutable-list-view></span>
+ <h3>Fixed by</h3>
+ <span class="fix-list"><mutable-list-view></mutable-list-view></span>
</section>
<section class="related-tasks">
<h3>Related Tasks</h3>
.analysis-task-status > section {
flex-grow: 1;
+ flex-shrink: 0;
border-left: solid 1px #eee;
padding-left: 1rem;
+ padding-right: 1rem;
}
- .analysis-task-status > section:first-child {
- border-left: none;
+ .analysis-task-status > section.related-tasks {
+ flex-shrink: 1;
}
- .associated-bugs:not(:empty) {
- margin-bottom: 1rem;
+ .analysis-task-status > section:first-child {
+ border-left: none;
}
.analysis-task-status h3 {
var parsed = JSON.parse(xhr.responseText);
resolve(parsed);
} catch (error) {
+ console.error(xhr.responseText);
reject(xhr.status + ', ' + error);
}
};