зеркало из https://github.com/mozilla/treeherder.git
Bug 1647444 - My pushes landing page (#6870)
* change default route to my pushes and reconfigure other routes * move Push Health Navigation and notifications to push health App * modify health_summmary API to provide additional data * minor style changes to CommitHistory in push health directory * add new tests and fix existing tests
This commit is contained in:
Родитель
77870bc405
Коммит
be58c0d3d7
|
@ -33,8 +33,9 @@ def test_get_build_failures_with_parent(
|
|||
|
||||
store_job_data(test_repository, parent_jobs)
|
||||
|
||||
build_failures = get_build_failures(test_push, parent_push)
|
||||
result, build_failures = get_build_failures(test_push, parent_push)
|
||||
first_build_failure = build_failures[0]
|
||||
|
||||
assert len(build_failures) == 5
|
||||
assert result == 'fail'
|
||||
assert len(build_failures) == 2
|
||||
assert first_build_failure['failedInParent']
|
||||
|
|
|
@ -38,8 +38,9 @@ def test_get_linting_failures_with_parent(
|
|||
|
||||
store_job_data(test_repository, parent_jobs)
|
||||
|
||||
build_failures = get_lint_failures(test_push, parent_push)
|
||||
result, build_failures = get_lint_failures(test_push, parent_push)
|
||||
first_build_failure = build_failures[0]
|
||||
|
||||
assert result == 'fail'
|
||||
assert len(build_failures) == 2
|
||||
assert first_build_failure['failedInParent']
|
||||
|
|
|
@ -43,10 +43,11 @@ def test_get_test_failures_no_parent(
|
|||
test_job.result = 'testfailed'
|
||||
test_job.save()
|
||||
|
||||
jobs = get_test_failure_jobs(test_job.push)
|
||||
build_failures = get_test_failures(test_job.push, jobs)
|
||||
result_status, jobs = get_test_failure_jobs(test_job.push)
|
||||
result, build_failures = get_test_failures(test_job.push, jobs, result_status)
|
||||
need_investigation = build_failures['needInvestigation']
|
||||
|
||||
assert result == 'fail'
|
||||
assert len(need_investigation) == 1
|
||||
assert len(jobs[need_investigation[0]['jobName']]) == 1
|
||||
assert not need_investigation[0]['failedInParent']
|
||||
|
@ -73,10 +74,11 @@ def test_get_test_failures_with_parent(
|
|||
|
||||
create_lines(parent_job, [(test_line, {})])
|
||||
|
||||
jobs = get_test_failure_jobs(test_job.push)
|
||||
build_failures = get_test_failures(test_job.push, jobs, parent_push)
|
||||
result_status, jobs = get_test_failure_jobs(test_job.push)
|
||||
result, build_failures = get_test_failures(test_job.push, jobs, result_status, parent_push)
|
||||
need_investigation = build_failures['needInvestigation']
|
||||
|
||||
assert result == 'fail'
|
||||
assert len(need_investigation) == 1
|
||||
assert len(jobs[need_investigation[0]['jobName']]) == 1
|
||||
assert need_investigation[0]['failedInParent']
|
||||
|
|
|
@ -0,0 +1,336 @@
|
|||
[
|
||||
{
|
||||
"revision": "159f99ccca1d35c586b937ca1ab93769e768dd9f",
|
||||
"repository": "autoland",
|
||||
"testFailureCount": 0,
|
||||
"buildFailureCount": 6,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 6,
|
||||
"status": {
|
||||
"completed": 34,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"success": 33,
|
||||
"retry": 1
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 820930,
|
||||
"revision": "159f99ccca1d35c586b937ca1ab93769e768dd9f",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 820930,
|
||||
"repository_id": 77,
|
||||
"revision": "159f99ccca1d35c586b937ca1ab93769e768dd9f",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "Merge mozilla-central to autoland a=merge. CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820930,
|
||||
"repository_id": 77,
|
||||
"revision": "0d82c3f140ba2110d3d40512c9f04b4cadcb4883",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "Merge autoland to mozilla-central a=merge. CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820930,
|
||||
"repository_id": 77,
|
||||
"revision": "e22423381bcd757b7ab1e58b0f915cd2e9a6a729",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "Update configs. IGNORE BROKEN CHANGESETS CLOSED TREE NO BUG a=release ba=release"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820930,
|
||||
"repository_id": 77,
|
||||
"revision": "386f54d56647b17422d428dbc8cd9614d6dbcf35",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "No bug - tagging efceae9186cd456bb70240aa851855efb5afd316 with FIREFOX_NIGHTLY_84_END a=release DONTBUILD CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820930,
|
||||
"repository_id": 77,
|
||||
"revision": "efceae9186cd456bb70240aa851855efb5afd316",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "No bug - tagging b9dd8e54c8ae449638635758a6571285655a4d0c with FIREFOX_BETA_84_BASE a=release DONTBUILD CLOSED TREE"
|
||||
}
|
||||
],
|
||||
"revision_count": 5,
|
||||
"push_timestamp": 1605544643,
|
||||
"repository_id": 77
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "pass"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "fail"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"revision": "0d82c3f140ba2110d3d40512c9f04b4cadcb4883",
|
||||
"repository": "mozilla-central",
|
||||
"testFailureCount": 0,
|
||||
"buildFailureCount": 2,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 2,
|
||||
"status": {
|
||||
"completed": 3029,
|
||||
"pending": 50,
|
||||
"running": 76,
|
||||
"success": 2974,
|
||||
"retry": 52,
|
||||
"testfailed": 3
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 820926,
|
||||
"revision": "0d82c3f140ba2110d3d40512c9f04b4cadcb4883",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "0d82c3f140ba2110d3d40512c9f04b4cadcb4883",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "Merge autoland to mozilla-central a=merge. CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "4b17d0a193f708806508d981ac8a61b730024c13",
|
||||
"author": "Paul Adenot <paul@paul.cx>",
|
||||
"comments": "Bug 1677435 - Update libcubeb to df5fe42. r=cubeb-reviewers,kinetik\n\nDifferential Revision: https://phabricator.services.mozilla.com/D97128"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "9b02eeca2a5f002b8faaac8684d6163ce2d62434",
|
||||
"author": "Julian Descottes <jdescottes@mozilla.com>",
|
||||
"comments": "Bug 1676646 - [devtools] Remove unused Connected localized string in aboutdebugging r=ladybenko,fluent-reviewers\n\nDifferential Revision: https://phabricator.services.mozilla.com/D96714"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "c1478fe4a2a75467bfdf73d80060d8ccf2eecffa",
|
||||
"author": "Razvan Maries <rmaries@mozilla.com>",
|
||||
"comments": "Backed out changeset 8531203d7207 (bug 1677092) for build bustages on TestAlgorithm.cpp. CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "8531203d720778399e4317445b2d5d42b929f430",
|
||||
"author": "Mirko Brodesser <mbrodesser@mozilla.com>",
|
||||
"comments": "Bug 1677092: add `constexpr` `AnyOf`. r=sg\n\nThe STL supports it only with C++20.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D97123"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "6f9542c83bb8d433f26aa4b4d7cef667f1ae1fa6",
|
||||
"author": "Frederik Braun <fbraun@mozilla.com>",
|
||||
"comments": "Bug 1677047 - Testing to block ports 1720, 1723, 554- r=necko-reviewers,valentin\n\nDepends on D96979\n\nDifferential Revision: https://phabricator.services.mozilla.com/D96980"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "6d86c1f75c77c2fdc40b1288dbb0b7f89ab0bd8d",
|
||||
"author": "Frederik Braun <fbraun@mozilla.com>",
|
||||
"comments": "Bug 1677047 - Add the ports for the H323, PPTP, RTSP protocols (1720, 1723, 554) to the restricted ports list. - r=valentin,necko-reviewers\n\nDifferential Revision: https://phabricator.services.mozilla.com/D96979"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "2298e1a23c6e76d3240b0338b0c975f7c4877bb0",
|
||||
"author": "Matt Woodrow <mwoodrow@mozilla.com>",
|
||||
"comments": "Bug 1677211 - Add Read access flags to D3D11 staging textures to avoid slow accesses when reading our intermediate results. r=jrmuizel\n\nDifferential Revision: https://phabricator.services.mozilla.com/D97106"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "8712c4a5dae9cab95cdffd3995102755601c8dae",
|
||||
"author": "Maja Frydrychowicz <mjzffr@gmail.com>",
|
||||
"comments": "Bug 1534582 - [AWSY] Use spec-compliant Marionette actions r=perftest-reviewers,marionette-reviewers,AlexandruIonescu\n\nAWSY has been using Marionette's \"legacy\" actions implementation\nbecause spec-compliant actions didn't work yet in chrome scope.\n\nWith Marionette's new JSWindowActor-based architecture, which\nis now enabled on all builds, chrome scope is now supported by\nspec-compliant actions, so AWSY can now use them.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D97019"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "fe39b455f8d9a85426df723b651c82bd8006fe81",
|
||||
"author": "Rob Wu <rob@robwu.nl>",
|
||||
"comments": "Bug 1652925 - Fix memory leak of extension Port via conduits r=zombie\n\nExtension ports should be eligible for garbage collection when\ndisconnected. This did not happen because there was a strong reference\nfrom the context to the conduit, whose subject was the Port. As a\nresult, the Port instances were not GCd until the context was unloaded.\n\nThis results in significant memory leaks over time, because it is not\nuncommon for extensions to have a long-lived background page that\nreceives messages via Ports. The issue is made even worse by the fact\nthat ports contain metadata that can potentially be very large.\n\nThere are other callers of openConduit, but these are not affected\nbecause their lifetimes are similar to the BaseContext.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D96952"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "8636b9a39525a98d95b3eb95996c6fd2630a610a",
|
||||
"author": "Daisuke Akatsuka <daisuke@birchill.co.jp>",
|
||||
"comments": "Bug 1181081: Fix up typos for string that user would have intented as protocol. r=mak\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95982"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "13e7ec11d44934dcf4dc26a0ae3776108460dc4b",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 reject MediaDevices method promises with existing standard errors r=jib\n\nOverconstrainedError is not yet supported.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95971"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "c8dfeadab0960c480b78b26560c7a88d8b6ecd2e",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 remove unused MediaMgrErr::Name::NotSupportedError r=jib\n\nDepends on D95969\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95970"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "fedd5a65269a98c9e6742eecaff9e88824b826d3",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 use a DOMException for MaybeRejectWithNotReadableError() r=baku\n\nThis is consistent with\nhttps://w3c.github.io/FileAPI/#dfn-error-codes\n\nDepends on D95968\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95969"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "f10b1dba4d056573bfc7300b463e0459f9b8a077",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 remove unused NS_ERROR_DOM_FILE_ABORT_ERR r=baku\n\nDepends on D95967\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95968"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "992a979b72851ff311b722bf2e8f1c24f5cd93a2",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 use UTF8 for MediaMgrError and SpeechRecognitionError message r=jib\n\nfor consistency with ErrorResult and dom::Promise, which will mean no reverse\nconversion is required for rejecting Promises.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95967"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "e20da67e6306d73b2a55815cf970b508f90a0156",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 remove unused MediaStreamError constructor r=jib\n\nDepends on D95965\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95966"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "f9b1c03019a6ffa7a6adcf7bb1bbaeb3741ee67d",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 add a constraint so that mozGetUserMedia does not fail immediately r=jib\n\nhttps://bug802982.bmoattachments.org/attachment.cgi?id=672704 was a crash in\nEnumerateVideoDevices. Presumably the constraint was not required at that\ntime, but the intent of the test was to exercise video capture code.\n\nDepends on D95964\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95965"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "21aa4a24335f45101a774dd653a0399a909699d3",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 improve checks on InvalidStateError and TypeError getDisplayMedia() rejections r=jib\n\nDepends on D95963\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95964"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820926,
|
||||
"repository_id": 1,
|
||||
"revision": "38a5df003dcc850250527954e38b1d2581d4926a",
|
||||
"author": "Karl Tomlinson <karlt+@karlt.net>",
|
||||
"comments": "Bug 1213512 improve checks on NotAllowedError getUserMedia() rejection reason r=jib\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95963"
|
||||
}
|
||||
],
|
||||
"revision_count": 28,
|
||||
"push_timestamp": 1605544082,
|
||||
"repository_id": 1
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "pass"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "fail"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"revision": "cc937e3d332f3862a1da2950f13aab3bada64eb7",
|
||||
"repository": "autoland",
|
||||
"testFailureCount": 1,
|
||||
"buildFailureCount": 0,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 1,
|
||||
"status": {
|
||||
"completed": 123,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"success": 115,
|
||||
"retry": 8
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 820096,
|
||||
"revision": "cc937e3d332f3862a1da2950f13aab3bada64eb7",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 820096,
|
||||
"repository_id": 77,
|
||||
"revision": "cc937e3d332f3862a1da2950f13aab3bada64eb7",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "Merge mozilla-central to autoland a=merge. CLOSED TREE"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820096,
|
||||
"repository_id": 77,
|
||||
"revision": "7ddb9d55aa38a29e16431182057c329c4a0c90d0",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "Merge autoland to mozilla-central a=merge"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820096,
|
||||
"repository_id": 77,
|
||||
"revision": "7bc6104186054d5b1b5383d480446bd7fa4cdfc8",
|
||||
"author": "Dorel Luca <dluca@mozilla.com>",
|
||||
"comments": "Merge autoland to mozilla-central. a=merge"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820096,
|
||||
"repository_id": 77,
|
||||
"revision": "74442fd27dfa81129105fe2de0ebea5dbdd85c85",
|
||||
"author": "Mike Hommey <mh+mozilla@glandium.org>",
|
||||
"comments": "Bug 1675740 - Republish one more file to make beetmover happy. r=me,a=bustage\n"
|
||||
},
|
||||
{
|
||||
"result_set_id": 820096,
|
||||
"repository_id": 77,
|
||||
"revision": "84480987c9a7f832986085c71b0fd0a71955c9f2",
|
||||
"author": "Mike Hommey <mh+mozilla@glandium.org>",
|
||||
"comments": "Bug 1675740 - Republish more files to make beetmover happy. r=me,a=bustage\n"
|
||||
}
|
||||
],
|
||||
"revision_count": 5,
|
||||
"push_timestamp": 1605285998,
|
||||
"repository_id": 77
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "fail"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "pass"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,336 @@
|
|||
[
|
||||
{
|
||||
"revision": "8f538bc9c23b1bce55abfa3fd21d4239eee80e61",
|
||||
"repository": "try",
|
||||
"testFailureCount": 109,
|
||||
"buildFailureCount": 13,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 122,
|
||||
"status": {
|
||||
"completed": 1371,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"success": 1351,
|
||||
"retry": 19,
|
||||
"exception": 1
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 812348,
|
||||
"revision": "8f538bc9c23b1bce55abfa3fd21d4239eee80e61",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 812348,
|
||||
"repository_id": 4,
|
||||
"revision": "8f538bc9c23b1bce55abfa3fd21d4239eee80e61",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "staging release: 84.0b1\n\nPushed via `mach try release`"
|
||||
},
|
||||
{
|
||||
"result_set_id": 812348,
|
||||
"repository_id": 4,
|
||||
"revision": "030084e409466a63bbede17083bccfd6a5c42a4c",
|
||||
"author": "Nicolas B. Pierron <nicolas.b.pierron@nbp.name>",
|
||||
"comments": "Bug 1674436 - Share Prepare/Execute functions across JIT test cases.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D95384"
|
||||
},
|
||||
{
|
||||
"result_set_id": 812348,
|
||||
"repository_id": 4,
|
||||
"revision": "b1d72b69a8e6bbb434ca106f8ea9e28ad26ea9ea",
|
||||
"author": "Butkovits Atila <abutkovits@mozilla.com>",
|
||||
"comments": "Backed out 4 changesets (bug 1621454) for causing bustage src/swgl_ext.h. a=backout\n\nBacked out changeset d0d03d5a81a8 (bug 1621454)\nBacked out changeset 6675f76d6f11 (bug 1621454)\nBacked out changeset 29943d5348df (bug 1621454)\nBacked out changeset 80c0aaa81c2e (bug 1621454)"
|
||||
}
|
||||
],
|
||||
"revision_count": 3,
|
||||
"push_timestamp": 1604143587,
|
||||
"repository_id": 4
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "fail"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "fail"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"revision": "a140a5639e7b145066a3e6232217674294c9eeda",
|
||||
"repository": "try",
|
||||
"testFailureCount": 0,
|
||||
"buildFailureCount": 4,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 4,
|
||||
"status": {
|
||||
"completed": 1316,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"success": 1276,
|
||||
"retry": 40
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 800224,
|
||||
"revision": "a140a5639e7b145066a3e6232217674294c9eeda",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "a140a5639e7b145066a3e6232217674294c9eeda",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "staging release: 83.0b1\n\nPushed via `mach try release`"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "0a19938d6a2ab895075753ee2e9cbcb0d4d7ea18",
|
||||
"author": "Nicolas Chevobbe <nchevobbe@mozilla.com>",
|
||||
"comments": "Bug 1660609 - [devtool] Fix browser_console_devtools_loader_exception.js failure on Beta. r=ladybenko.\n\n\nThis test was failing on Beta because we are using the targetList to wait for\na given source to be available, in order to avoid pending connection to the\nserver (that were happening when the targetList was trying to attach the thread\nof the target), but on Beta, for the browser console, we don't listen for anything\nwith the targetList if the Browser Toolbox fission pref is disabled.\nTo fix the test, we check the pref and wait for the target only if it's enabled.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D92877\n\nDepends on D92876"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "37fcabf03c8326631a587343fd4a067573128ac8",
|
||||
"author": "Nicolas Chevobbe <nchevobbe@mozilla.com>",
|
||||
"comments": "Bug 1660609 - [devtools] Fix browser_jsterm_hide_when_devtools_chrome_enabled_false.js failures on Beta. r=ladybenko.\n\n\nThis test was using the browser console targetList to wait for a specific worker\ntarget to be available. This was failing on Beta because for the Browser Console,\nwe only listen for workers if the browser toolbox fission pref is enabled, which\nis the case on Nightly, but not on Beta.\n\nTo fix the test, we check the pref and wait for the target only if it's enabled.\n\nDifferential Revision: https://phabricator.services.mozilla.com/D92876"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "725180017480c46f17faddc71ee35dcca39db109",
|
||||
"author": "Timothy Nikkel <tnikkel@gmail.com>",
|
||||
"comments": "Bug 1670343. Call SetVisualViewportOffset after nsHTMLScrollFrame::Reflow so we can add a weak frame check around it. r=kats\n\nDifferential Revision: https://phabricator.services.mozilla.com/D93147"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "844c3d925e9e1b97449abbcf9a8cda9f0b139c51",
|
||||
"author": "Itiel <itiel_yn8@walla.com>",
|
||||
"comments": "Bug 1669756 - Align the labels in Add Engine to the textboxes, and force LTR the engine URL input r=ntim,preferences-reviewers\n\nDifferential Revision: https://phabricator.services.mozilla.com/D92783"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "2468b16a890c3a8091daf116ed82cc07b5cd7597",
|
||||
"author": "Tim Nguyen <ntim.bugs@gmail.com>",
|
||||
"comments": "Bug 1632351 - Enable CSS conic-gradient by default and let it ride the trains. r=emilio\n\nDifferential Revision: https://phabricator.services.mozilla.com/D93024"
|
||||
},
|
||||
{
|
||||
"result_set_id": 800224,
|
||||
"repository_id": 4,
|
||||
"revision": "4e4b334fecfdc052a3bbc891789764da298fb9d6",
|
||||
"author": "Dão Gottwald <dao@mozilla.com>",
|
||||
"comments": "Bug 1650580 - Fix menu-arrow.svg and stop misusing back-12.svg. r=Gijs\n\nDifferential Revision: https://phabricator.services.mozilla.com/D92933"
|
||||
}
|
||||
],
|
||||
"revision_count": 7,
|
||||
"push_timestamp": 1602410703,
|
||||
"repository_id": 4
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "pass"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "fail"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"revision": "35551ddfa195faf7818a9a92101523e588787449",
|
||||
"repository": "try",
|
||||
"testFailureCount": 27,
|
||||
"buildFailureCount": 0,
|
||||
"lintFailureCount": 0,
|
||||
"needInvestigation": 27,
|
||||
"status": {
|
||||
"completed": 945,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"success": 923,
|
||||
"retry": 22
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"id": 780752,
|
||||
"revision": "35551ddfa195faf7818a9a92101523e588787449",
|
||||
"author": "ccoroiu@mozilla.com",
|
||||
"revisions": [
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "35551ddfa195faf7818a9a92101523e588787449",
|
||||
"author": "Cristina Coroiu <ccoroiu@mozilla.com>",
|
||||
"comments": "staging release: 81.0\n\nPushed via `mach try release`"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "ca39475784ddc1b65a068ef20a2ba50e166da0b4",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nca -> 25848f33c39ef70b0fb29ff0e13383fcbcd5c056"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "9784454a4bea2c4643731538489c90225c5efc19",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nkab -> cf1e4948f7349730307d8551a87b0ea87022201d"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "8bd444ea5d738470f0f1578ac3d4a44c24b88ae8",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nlt -> c80aa422bedfed3e1ea021796acfefb5e44e97cf"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "29229cad6ee6f2d6925069282fe839c8900a7aaf",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nlt -> 9d7d763200270b4c6dc25114c3731bd657dbc40e\npl -> 99bbe58139d1582ac477290c5233c7a14032e533"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "0d16e1e1559d022b0071881591fa1b4ec1a97989",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nlt -> 242a344eef5b2d23fb65e73eaac7b52a2fda41aa\nnn-NO -> 2359ba5f7faf038c94c6b515240879ed1dc1661b\nsv-SE -> 524729f65b087b71b202c8443b4b2c489cea8a3b"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "0ac379f3be3cf846d2e3d0d18b9116ed273f2359",
|
||||
"author": "Sebastian Hengst <archaeopteryx@coole-files.de>",
|
||||
"comments": "No bug - unset EARLY_BETA_OR_EARLIER. a=me"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "3c6c10667f526bc6446ef9702aa649212e60f3e4",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nfi -> 1539431f356673f15d4daea63e29a3a77b12d902\nte -> da18351f87d42c6e16a8b4e2c4abf20f7f9a4f48"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "0561830a37e417168189130b971100e071370d38",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nja -> 84f958ddb051c8b8b9f3e737f240d2a7f390fb1f\nja-JP-mac -> b389f4ab93faebfa1cf30be9febf5825c117479c"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "17d9c896fd2e671d8ea778abbbbf6a61e0c8d10b",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\ncak -> fad0dc55d63285f52e624dd28311d8cf3f1bcd44"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "4463041f4d8f3b5ee34c4afff41dd11359997548",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\ncak -> 6a72db0680dfad559a1096fa17639c26cdc1f094"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "0d2cb79119758248fd9caf6f9c9185ecafdcf4d2",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\npt-PT -> 2c17252c21a5359504a268507e8bf40ed82fa1e6"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "09d70e0f21985b56aadf14d579750ccaa78b6370",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nast -> ea0aa635801d00d0acb195a36adda6100a455ce0\nrm -> ce68282481fa59ab68d55a7c43287287e3976a1f\nte -> e8076c3462eabbe902554e71bf0e6b9e5da5247c"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "f135ba9a315d01c1292e20ad59b54ac0e121ec85",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nes-CL -> 139f195e783bb9d428db742c905db0f9c0c04743\nru -> 9590744c862f334056687788289bcd435f201506"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "19657c6714ae8c409227fa7763505891a4a6df4f",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nja -> 9b2256c9c5752c4c735823cc8759e4b9daeb702e\nja-JP-mac -> eb5d58030f82c77ec5a44c017396dbf464cdd2b2"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "06f7e13ddbe120c096bfb24e10d93c3b992a6beb",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nhsb -> bee4c0e2d46ab1c59e4238905595598fc930a299"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "ccdda81e83316e12fe07499bc575ad48f52627d8",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nkk -> 5396faffb7db4fd304ba774f6ad897ccb3344e8e\nru -> f379323fa738077b8336a08564caea2903f07f0f"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "71a87ae46b480389a065a1a1f88852f73036579b",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nkk -> d51c7f964f84b6d0e18b42e823b2a95d4ed6bab1"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "5b9a6914f5b37236d347173ec88925727c3443ba",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nar -> 4b5ccc5861063f4aaa38f13361e5e9ee6f33cc32\nth -> 78e4ed87bedf340b03015a6b19b37692fb9d1934"
|
||||
},
|
||||
{
|
||||
"result_set_id": 780752,
|
||||
"repository_id": 4,
|
||||
"revision": "6d83ff24fc2d02b55918527d2ec5be6f426193ca",
|
||||
"author": "Mozilla Releng Treescript <release+treescript@mozilla.org>",
|
||||
"comments": "no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD\n\nar -> 44991e31ec99b12ba1cce8e9c7c1891d76bd2006"
|
||||
}
|
||||
],
|
||||
"revision_count": 58,
|
||||
"push_timestamp": 1599403190,
|
||||
"repository_id": 4
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"linting": {
|
||||
"name": "Linting",
|
||||
"result": "pass"
|
||||
},
|
||||
"tests": {
|
||||
"name": "Tests",
|
||||
"result": "fail"
|
||||
},
|
||||
"builds": {
|
||||
"name": "Builds",
|
||||
"result": "pass"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -11,7 +11,7 @@ import { toDateStr } from '../../../ui/helpers/display';
|
|||
beforeEach(() => {
|
||||
fetchMock.get(
|
||||
'/api/project/autoland/push/health_summary/?revision=eeb6fd68c0223a72d8714734a34d3e6da69995e1',
|
||||
{ needInvestigation: 87, unsupported: 8 },
|
||||
[{ needInvestigation: 87, unsupported: 8 }],
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -166,4 +166,12 @@ describe('Health', () => {
|
|||
queryAllByTestId(classificationGroups[1], 'test-grouping'),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('should go to the correct tab if query param exists', async () => {
|
||||
history.push(`/push-health?repo=${repo}&revision=${revision}&tab=builds`);
|
||||
const { getByText } = render(testHealth());
|
||||
|
||||
const buildsTab = await waitFor(() => getByText('Builds'));
|
||||
expect(buildsTab).toHaveAttribute('aria-selected', 'true');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import React from 'react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { render, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import MyPushes from '../../../ui/push-health/MyPushes';
|
||||
import pushHealthSummaryTryData from '../mock/push_health_summary_try';
|
||||
import pushHealthSummaryData from '../mock/push_health_summary_all';
|
||||
import reposFixture from '../mock/repositories';
|
||||
import { getApiUrl } from '../../../ui/helpers/url';
|
||||
import { getProjectUrl } from '../../../ui/helpers/location';
|
||||
import { configureStore } from '../../../ui/job-view/redux/configureStore';
|
||||
|
||||
const repo = 'try';
|
||||
const history = createBrowserHistory();
|
||||
const params = 'author=ccoroiu%40mozilla.com&count=5&with_history=true';
|
||||
const testUser = { email: 'ccoroiu@mozilla.com', isLoggedIn: true };
|
||||
|
||||
describe('My Pushes', () => {
|
||||
beforeAll(() => {
|
||||
fetchMock.get(getApiUrl('/repository/'), reposFixture);
|
||||
fetchMock.get(
|
||||
getProjectUrl(`/push/health_summary/?${params}`, repo),
|
||||
pushHealthSummaryTryData,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
const testMyPushes = () => {
|
||||
const store = configureStore(history);
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<MyPushes
|
||||
user={testUser}
|
||||
location={history.location}
|
||||
notify={() => {}}
|
||||
clearNotification={() => {}}
|
||||
/>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
test('should fetch the push health data only for the logged in user', async () => {
|
||||
const { getAllByText } = render(testMyPushes(true));
|
||||
|
||||
const pushes = await waitFor(() => getAllByText(testUser.email));
|
||||
expect(pushes).toHaveLength(3);
|
||||
});
|
||||
|
||||
test('should filter pushes by repos', async () => {
|
||||
const { getByText, getAllByTestId, queryByText } = render(testMyPushes());
|
||||
|
||||
const tryPushes = await waitFor(() => getAllByTestId('header-repo'));
|
||||
expect(tryPushes).toHaveLength(3);
|
||||
expect(tryPushes.map((node) => node.textContent)).toEqual([
|
||||
'try',
|
||||
'try',
|
||||
'try',
|
||||
]);
|
||||
|
||||
const dropdownButton = await waitFor(() => getByText('try pushes'));
|
||||
fireEvent.click(dropdownButton);
|
||||
|
||||
fetchMock.get(
|
||||
getProjectUrl(`/push/health_summary/?${params}&all_repos=true`, repo),
|
||||
pushHealthSummaryData,
|
||||
);
|
||||
|
||||
const allRepos = await waitFor(() => getByText('all'));
|
||||
fireEvent.click(allRepos);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(queryByText('loading page, please wait')).toBeNull(),
|
||||
);
|
||||
|
||||
const allPushes = await waitFor(() => getAllByTestId('header-repo'));
|
||||
expect(allPushes).toHaveLength(3);
|
||||
expect(allPushes.map((node) => node.textContent)).toEqual([
|
||||
'autoland',
|
||||
'mozilla-central',
|
||||
'autoland',
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,9 +1,13 @@
|
|||
import React from 'react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { render, cleanup, waitFor } from '@testing-library/react';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import Usage from '../../../ui/push-health/Usage';
|
||||
import { configureStore } from '../../../ui/job-view/redux/configureStore';
|
||||
import healthUsage from '../mock/health_usage';
|
||||
import Usage from '../../../ui/push-health/Usage';
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.get('/api/project/try/push/health_usage/', healthUsage);
|
||||
|
@ -14,18 +18,31 @@ afterEach(() => {
|
|||
fetchMock.reset();
|
||||
});
|
||||
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const testUsage = () => {
|
||||
const store = configureStore(history);
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<Usage location={history.location} />
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('Usage', () => {
|
||||
const revision = 'bdb000dbec165634372c03ad2a8692ed81bf98a1';
|
||||
|
||||
test('should show 10 facets', async () => {
|
||||
const { getAllByTestId } = render(<Usage />);
|
||||
const { getAllByTestId } = render(testUsage());
|
||||
const facets = await waitFor(() => getAllByTestId('facet-link'));
|
||||
|
||||
expect(facets).toHaveLength(10);
|
||||
});
|
||||
|
||||
test('should show details about each revision', async () => {
|
||||
const { getByTestId } = render(<Usage />);
|
||||
const { getByTestId } = render(testUsage());
|
||||
const facet = await waitFor(() => getByTestId(`facet-${revision}`));
|
||||
const { children } = facet;
|
||||
|
||||
|
|
|
@ -8,21 +8,25 @@ import { getProjectUrl } from '../../../ui/helpers/location';
|
|||
beforeEach(() => {
|
||||
fetchMock.get(
|
||||
getProjectUrl('/push/health_summary/?revision=failed', 'autoland'),
|
||||
{
|
||||
testFailureCount: 2,
|
||||
buildFailureCount: 1,
|
||||
lintFailureCount: 0,
|
||||
needInvestigation: 3,
|
||||
},
|
||||
[
|
||||
{
|
||||
testFailureCount: 2,
|
||||
buildFailureCount: 1,
|
||||
lintFailureCount: 0,
|
||||
needInvestigation: 3,
|
||||
},
|
||||
],
|
||||
);
|
||||
fetchMock.get(
|
||||
getProjectUrl('/push/health_summary/?revision=passed', 'autoland'),
|
||||
{
|
||||
testFailureCount: 0,
|
||||
buildFailureCount: 0,
|
||||
lintFailureCount: 0,
|
||||
needInvestigation: 0,
|
||||
},
|
||||
[
|
||||
{
|
||||
testFailureCount: 0,
|
||||
buildFailureCount: 0,
|
||||
lintFailureCount: 0,
|
||||
needInvestigation: 0,
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
from treeherder.model.models import Job
|
||||
from treeherder.push_health.utils import mark_failed_in_parent, job_to_dict
|
||||
from treeherder.model.models import Job, JobType
|
||||
from treeherder.push_health.utils import mark_failed_in_parent, get_job_results
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
def get_build_failures(push, parent_push=None):
|
||||
build_failures = Job.objects.filter(
|
||||
# icontains doesn't work with mysql unless collation settings are adjusted: https://code.djangoproject.com/ticket/9682
|
||||
build_types = JobType.objects.filter(Q(name__contains='Build') | Q(name__contains='build'))
|
||||
|
||||
build_results = Job.objects.filter(
|
||||
push=push,
|
||||
tier__lte=2,
|
||||
result='busted',
|
||||
job_type__in=build_types,
|
||||
).select_related('machine_platform', 'taskcluster_metadata')
|
||||
|
||||
failures = [job_to_dict(job) for job in build_failures]
|
||||
result, failures = get_job_results(build_results, 'busted')
|
||||
|
||||
if parent_push:
|
||||
mark_failed_in_parent(failures, get_build_failures(parent_push))
|
||||
mark_failed_in_parent(failures, get_build_failures(parent_push)[1])
|
||||
|
||||
return failures
|
||||
return (result, failures)
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
from django.db.models import Q
|
||||
|
||||
from treeherder.model.models import Job
|
||||
from treeherder.push_health.utils import mark_failed_in_parent, job_to_dict
|
||||
from treeherder.push_health.utils import mark_failed_in_parent, get_job_results
|
||||
|
||||
|
||||
def get_lint_failures(push, parent_push=None):
|
||||
lint_failures = Job.objects.filter(
|
||||
lint_results = Job.objects.filter(
|
||||
Q(machine_platform__platform='lint') | Q(job_type__symbol='mozlint'),
|
||||
push=push,
|
||||
tier__lte=2,
|
||||
result='testfailed',
|
||||
).select_related('machine_platform', 'taskcluster_metadata')
|
||||
|
||||
failures = [job_to_dict(job) for job in lint_failures]
|
||||
result, failures = get_job_results(lint_results, 'testfailed')
|
||||
|
||||
if parent_push:
|
||||
mark_failed_in_parent(failures, get_lint_failures(parent_push))
|
||||
mark_failed_in_parent(failures, get_lint_failures(parent_push)[1])
|
||||
|
||||
return failures
|
||||
return (result, failures)
|
||||
|
|
|
@ -183,19 +183,24 @@ def get_test_failure_jobs(push):
|
|||
result='testfailed',
|
||||
)
|
||||
.exclude(
|
||||
Q(machine_platform__platform='lint') | Q(job_type__symbol='mozlint'),
|
||||
Q(machine_platform__platform='lint')
|
||||
| Q(job_type__symbol='mozlint')
|
||||
| Q(job_type__name__contains='build'),
|
||||
)
|
||||
.select_related('job_type', 'machine_platform', 'taskcluster_metadata')
|
||||
)
|
||||
failed_job_types = [job.job_type.name for job in testfailed_jobs]
|
||||
|
||||
passing_jobs = Job.objects.filter(
|
||||
push=push, job_type__name__in=failed_job_types, result__in=['success', 'unknown']
|
||||
).select_related('job_type', 'machine_platform', 'taskcluster_metadata')
|
||||
|
||||
jobs = {}
|
||||
result_status = set()
|
||||
|
||||
def add_jobs(job_list):
|
||||
for job in job_list:
|
||||
result_status.add(job.result)
|
||||
if job.job_type.name in jobs:
|
||||
jobs[job.job_type.name].append(job_to_dict(job))
|
||||
else:
|
||||
|
@ -207,12 +212,13 @@ def get_test_failure_jobs(push):
|
|||
for job in jobs:
|
||||
(jobs[job]).sort(key=lambda x: x['start_time'])
|
||||
|
||||
return jobs
|
||||
return (result_status, jobs)
|
||||
|
||||
|
||||
def get_test_failures(
|
||||
push,
|
||||
jobs,
|
||||
result_status=set(),
|
||||
parent_push=None,
|
||||
):
|
||||
logger.debug('Getting test failures for push: {}'.format(push.id))
|
||||
|
@ -248,11 +254,18 @@ def get_test_failures(
|
|||
)
|
||||
failures = get_grouped(filtered_push_failures)
|
||||
|
||||
result = 'pass'
|
||||
|
||||
if len(failures['needInvestigation']):
|
||||
result = 'fail'
|
||||
elif 'unknown' in result_status:
|
||||
result = 'unknown'
|
||||
|
||||
if parent_push:
|
||||
# Since there is a parent_push, we want to mark all failures with whether or not they also
|
||||
# exist in the parent.
|
||||
parent_push_jobs = get_test_failure_jobs(parent_push)
|
||||
parent_test_failures = get_test_failures(parent_push, parent_push_jobs)
|
||||
parent_push_jobs = get_test_failure_jobs(parent_push)[1]
|
||||
parent_test_failures = get_test_failures(parent_push, parent_push_jobs)[1]
|
||||
for classification, failure_group in failures.items():
|
||||
parent_failure_group = parent_test_failures[classification]
|
||||
failure_keys = {fail['key'] for fail in failure_group}
|
||||
|
@ -262,4 +275,4 @@ def get_test_failures(
|
|||
for failure in failure_group:
|
||||
failure['failedInParent'] = failure['key'] in both
|
||||
|
||||
return failures
|
||||
return (result, failures)
|
||||
|
|
|
@ -143,3 +143,21 @@ def job_to_dict(job):
|
|||
}
|
||||
)
|
||||
return job_dict
|
||||
|
||||
|
||||
def get_job_results(results, failure_type):
|
||||
result_status = set()
|
||||
result = 'pass'
|
||||
failures = []
|
||||
|
||||
for job in results:
|
||||
result_status.add(job.result)
|
||||
if job.result == failure_type:
|
||||
failures.append(job_to_dict(job))
|
||||
|
||||
if len(failures):
|
||||
result = 'fail'
|
||||
elif 'unknown' in result_status:
|
||||
result = 'unknown'
|
||||
|
||||
return (result, failures)
|
||||
|
|
|
@ -53,14 +53,19 @@ class PushViewSet(viewsets.ViewSet):
|
|||
del filter_params[param]
|
||||
meta[param] = v
|
||||
|
||||
try:
|
||||
repository = Repository.objects.get(name=project)
|
||||
except Repository.DoesNotExist:
|
||||
return Response(
|
||||
{"detail": "No project with name {}".format(project)}, status=HTTP_404_NOT_FOUND
|
||||
)
|
||||
all_repos = request.query_params.get('all_repos')
|
||||
|
||||
pushes = Push.objects.filter(repository=repository).order_by('-time')
|
||||
pushes = Push.objects.order_by('-time')
|
||||
|
||||
if not all_repos:
|
||||
try:
|
||||
repository = Repository.objects.get(name=project)
|
||||
except Repository.DoesNotExist:
|
||||
return Response(
|
||||
{"detail": "No project with name {}".format(project)}, status=HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
pushes = pushes.filter(repository=repository)
|
||||
|
||||
for (param, value) in meta.items():
|
||||
if param == 'fromchange':
|
||||
|
@ -168,7 +173,7 @@ class PushViewSet(viewsets.ViewSet):
|
|||
serializer = PushSerializer(pushes, many=True)
|
||||
|
||||
meta['count'] = len(pushes)
|
||||
meta['repository'] = project
|
||||
meta['repository'] = 'all' if all_repos else project
|
||||
meta['filter_params'] = filter_params
|
||||
|
||||
resp = {'meta': meta, 'results': serializer.data}
|
||||
|
@ -204,31 +209,93 @@ class PushViewSet(viewsets.ViewSet):
|
|||
Return a calculated summary of the health of this push.
|
||||
"""
|
||||
revision = request.query_params.get('revision')
|
||||
author = request.query_params.get('author')
|
||||
count = request.query_params.get('count')
|
||||
all_repos = request.query_params.get('all_repos')
|
||||
with_history = request.query_params.get('with_history')
|
||||
|
||||
try:
|
||||
push = Push.objects.get(revision=revision, repository__name=project)
|
||||
except Push.DoesNotExist:
|
||||
return Response(
|
||||
"No push with revision: {0}".format(revision), status=HTTP_404_NOT_FOUND
|
||||
if revision:
|
||||
try:
|
||||
pushes = Push.objects.filter(
|
||||
revision__in=revision.split(','), repository__name=project
|
||||
)
|
||||
except Push.DoesNotExist:
|
||||
return Response(
|
||||
"No push with revision: {0}".format(revision), status=HTTP_404_NOT_FOUND
|
||||
)
|
||||
else:
|
||||
try:
|
||||
pushes = (
|
||||
Push.objects.filter(author=author)
|
||||
.select_related('repository')
|
||||
.prefetch_related('commits')
|
||||
.order_by('-time')
|
||||
)
|
||||
|
||||
if not all_repos:
|
||||
pushes = pushes.filter(repository__name=project)
|
||||
|
||||
pushes = pushes[: int(count)]
|
||||
|
||||
except Push.DoesNotExist:
|
||||
return Response(
|
||||
"No pushes found for author: {0}".format(author), status=HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
data = []
|
||||
commit_history = None
|
||||
|
||||
for push in list(pushes):
|
||||
result_status, jobs = get_test_failure_jobs(push)
|
||||
|
||||
test_result, push_health_test_failures = get_test_failures(
|
||||
push,
|
||||
jobs,
|
||||
result_status,
|
||||
)
|
||||
|
||||
jobs = get_test_failure_jobs(push)
|
||||
build_result, push_health_build_failures = get_build_failures(push)
|
||||
|
||||
push_health_test_failures = get_test_failures(push, jobs)
|
||||
push_health_lint_failures = get_lint_failures(push)
|
||||
push_health_build_failures = get_build_failures(push)
|
||||
test_failure_count = len(push_health_test_failures['needInvestigation'])
|
||||
build_failure_count = len(push_health_build_failures)
|
||||
lint_failure_count = len(push_health_lint_failures)
|
||||
lint_result, push_health_lint_failures = get_lint_failures(push)
|
||||
|
||||
return Response(
|
||||
{
|
||||
'testFailureCount': test_failure_count,
|
||||
'buildFailureCount': build_failure_count,
|
||||
'lintFailureCount': lint_failure_count,
|
||||
'needInvestigation': test_failure_count + build_failure_count + lint_failure_count,
|
||||
}
|
||||
)
|
||||
test_failure_count = len(push_health_test_failures['needInvestigation'])
|
||||
build_failure_count = len(push_health_build_failures)
|
||||
lint_failure_count = len(push_health_lint_failures)
|
||||
|
||||
if with_history:
|
||||
serializer = PushSerializer([push], many=True)
|
||||
commit_history = serializer.data
|
||||
|
||||
data.append(
|
||||
{
|
||||
'revision': push.revision,
|
||||
'repository': push.repository.name,
|
||||
'testFailureCount': test_failure_count,
|
||||
'buildFailureCount': build_failure_count,
|
||||
'lintFailureCount': lint_failure_count,
|
||||
'needInvestigation': test_failure_count
|
||||
+ build_failure_count
|
||||
+ lint_failure_count,
|
||||
'status': push.get_status(),
|
||||
'history': commit_history,
|
||||
'metrics': {
|
||||
'linting': {
|
||||
'name': 'Linting',
|
||||
'result': lint_result,
|
||||
},
|
||||
'tests': {
|
||||
'name': 'Tests',
|
||||
'result': test_result,
|
||||
},
|
||||
'builds': {
|
||||
'name': 'Builds',
|
||||
'result': build_result,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return Response(data)
|
||||
|
||||
@action(detail=False)
|
||||
def health_usage(self, request, project):
|
||||
|
@ -252,7 +319,7 @@ class PushViewSet(viewsets.ViewSet):
|
|||
|
||||
commit_history_details = None
|
||||
parent_push = None
|
||||
jobs = get_test_failure_jobs(push)
|
||||
result_status, jobs = get_test_failure_jobs(push)
|
||||
# Parent compare only supported for Hg at this time.
|
||||
# Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1612645
|
||||
if repository.dvcs_type == 'hg':
|
||||
|
@ -260,24 +327,24 @@ class PushViewSet(viewsets.ViewSet):
|
|||
if commit_history_details['exactMatch']:
|
||||
parent_push = commit_history_details.pop('parentPush')
|
||||
|
||||
push_health_test_failures = get_test_failures(
|
||||
test_result, push_health_test_failures = get_test_failures(
|
||||
push,
|
||||
jobs,
|
||||
result_status,
|
||||
parent_push,
|
||||
)
|
||||
test_result = 'pass'
|
||||
if len(push_health_test_failures['needInvestigation']):
|
||||
test_result = 'fail'
|
||||
|
||||
build_failures = get_build_failures(push, parent_push)
|
||||
build_result = 'fail' if len(build_failures) else 'pass'
|
||||
build_result, build_failures = get_build_failures(push, parent_push)
|
||||
|
||||
lint_failures = get_lint_failures(push)
|
||||
lint_result = 'fail' if len(lint_failures) else 'pass'
|
||||
lint_result, lint_failures = get_lint_failures(push)
|
||||
|
||||
push_result = 'pass'
|
||||
for metric_result in [test_result, lint_result, build_result]:
|
||||
if metric_result == 'indeterminate' and push_result != 'fail':
|
||||
if (
|
||||
metric_result == 'indeterminate'
|
||||
or metric_result == 'unknown'
|
||||
and push_result != 'fail'
|
||||
):
|
||||
push_result = metric_result
|
||||
elif metric_result == 'fail':
|
||||
push_result = metric_result
|
||||
|
|
|
@ -143,10 +143,7 @@ const App = () => {
|
|||
<Route
|
||||
path="/push-health"
|
||||
render={(props) =>
|
||||
withFavicon(
|
||||
<PushHealthApp {...props} />,
|
||||
props.location.pathname,
|
||||
)
|
||||
withFavicon(<PushHealthApp {...props} />, '/push-health')
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
|
|
|
@ -380,6 +380,17 @@
|
|||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.link-darker-secondary {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.link-darker-secondary:visited {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.link-darker-secondary:hover {
|
||||
color: #5a6268;
|
||||
}
|
||||
/*
|
||||
* Tables and panels
|
||||
*/
|
||||
|
|
|
@ -120,7 +120,7 @@ export const getJobsUrl = function getJobsUrl(params) {
|
|||
|
||||
// This takes a plain object, rather than a URLSearchParams object.
|
||||
export const getPushHealthUrl = function getPushHealthUrl(params) {
|
||||
return `${uiPushHealthBase}${createQueryParams(params)}`;
|
||||
return `${uiPushHealthBase}/push${createQueryParams(params)}`;
|
||||
};
|
||||
|
||||
export const getCompareChooserUrl = function getCompareChooserUrl(params) {
|
||||
|
|
|
@ -2,9 +2,19 @@ import React from 'react';
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
clearNotificationAtIndex,
|
||||
clearExpiredTransientNotifications,
|
||||
} from '../helpers/notifications';
|
||||
import NotificationList from '../shared/NotificationList';
|
||||
import ErrorBoundary from '../shared/ErrorBoundary';
|
||||
import { genericErrorMessage, errorMessageClass } from '../helpers/constants';
|
||||
|
||||
import NotFound from './NotFound';
|
||||
import Health from './Health';
|
||||
import Usage from './Usage';
|
||||
import MyPushes from './MyPushes';
|
||||
import Navigation from './Navigation';
|
||||
|
||||
import '../css/failure-summary.css';
|
||||
import '../css/lazylog-custom-styles.css';
|
||||
|
@ -19,26 +29,94 @@ function hasProps(search) {
|
|||
return params.get('repo') && params.get('revision');
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
user: { isLoggedIn: false },
|
||||
notifications: [],
|
||||
};
|
||||
}
|
||||
|
||||
notify = (message, severity, options = {}) => {
|
||||
const { notifications } = this.state;
|
||||
const notification = {
|
||||
...options,
|
||||
message,
|
||||
severity: severity || 'darker-info',
|
||||
created: Date.now(),
|
||||
};
|
||||
const newNotifications = [notification, ...notifications];
|
||||
|
||||
this.setState({
|
||||
notifications: newNotifications,
|
||||
});
|
||||
};
|
||||
|
||||
clearNotification = (index = null) => {
|
||||
const { notifications } = this.state;
|
||||
|
||||
if (index) {
|
||||
this.setState(clearNotificationAtIndex(notifications, index));
|
||||
} else {
|
||||
this.setState(clearExpiredTransientNotifications(notifications));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user, notifications } = this.state;
|
||||
const { path } = this.props.match;
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
errorClasses={errorMessageClass}
|
||||
message={genericErrorMessage}
|
||||
>
|
||||
<Navigation
|
||||
user={user}
|
||||
setUser={(user) => this.setState({ user })}
|
||||
notify={this.notify}
|
||||
/>
|
||||
<NotificationList
|
||||
notifications={notifications}
|
||||
clearNotification={this.clearNotification}
|
||||
/>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/"
|
||||
exact
|
||||
path={`${path}/`}
|
||||
render={(props) => (
|
||||
<MyPushes
|
||||
{...props}
|
||||
user={user}
|
||||
notify={this.notify}
|
||||
clearNotification={this.clearNotification}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${path}/push`}
|
||||
render={(props) =>
|
||||
hasProps(props.location.search) ? (
|
||||
<Health {...props} />
|
||||
<Health
|
||||
{...props}
|
||||
notify={this.notify}
|
||||
clearNotification={this.clearNotification}
|
||||
/>
|
||||
) : (
|
||||
<Usage {...props} />
|
||||
(<NotFound />)()
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route name="notfound" component={NotFound} />
|
||||
<Route
|
||||
path={`${path}/usage`}
|
||||
render={(props) => <Usage {...props} />}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default hot(App);
|
||||
|
|
|
@ -47,6 +47,7 @@ class CommitHistory extends React.PureComponent {
|
|||
},
|
||||
revision,
|
||||
currentRepo,
|
||||
showParent,
|
||||
} = this.props;
|
||||
const { clipboardVisible, isExpanded } = this.state;
|
||||
const parentRepoModel = new RepositoryModel(parentRepository);
|
||||
|
@ -71,8 +72,8 @@ class CommitHistory extends React.PureComponent {
|
|||
return (
|
||||
<React.Fragment>
|
||||
<div className="push-header" data-testid="push-header">
|
||||
<div className="push-bar">
|
||||
<div className="h3 text-capitalize" data-testid="headerText">
|
||||
<div>
|
||||
<div className="commit-header" data-testid="headerText">
|
||||
{headerText}
|
||||
</div>
|
||||
<div className="text-secondary" data-testid="authorTime">
|
||||
|
@ -93,7 +94,10 @@ class CommitHistory extends React.PureComponent {
|
|||
</a>
|
||||
</span>
|
||||
on
|
||||
<span className="d-inline-block text-capitalize font-weight-bold ml-1">
|
||||
<span
|
||||
data-testid="header-repo"
|
||||
className="d-inline-block text-capitalize font-weight-bold ml-1"
|
||||
>
|
||||
{currentRepo.name}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -120,48 +124,50 @@ class CommitHistory extends React.PureComponent {
|
|||
commentFont="h6"
|
||||
/>
|
||||
)}
|
||||
<div className="ml-3">
|
||||
Base commit:
|
||||
<span>
|
||||
{!exactMatch && (
|
||||
<div>
|
||||
<Alert color="warning" className="m-3 font-italics">
|
||||
Warning: Could not find an exact match parent Push in
|
||||
Treeherder.
|
||||
</Alert>
|
||||
{id && <div>Closest match: </div>}
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className="mb-2"
|
||||
onMouseEnter={() => this.showClipboard(true)}
|
||||
onMouseLeave={() => this.showClipboard(false)}
|
||||
>
|
||||
<a
|
||||
href={parentLinkUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Open this push"
|
||||
data-testid="parent-commit-sha"
|
||||
className="mr-1 ml-1 font-weight-bold text-secondary"
|
||||
>
|
||||
{parentPushRevision || parentSha}
|
||||
</a>
|
||||
{exactMatch && (
|
||||
<PushHealthStatus
|
||||
revision={parentPushRevision}
|
||||
repoName={parentRepository.name}
|
||||
jobCounts={jobCounts}
|
||||
/>
|
||||
{showParent && (
|
||||
<div className="ml-3">
|
||||
Base commit:
|
||||
<span>
|
||||
{!exactMatch && (
|
||||
<div>
|
||||
<Alert color="warning" className="m-3 font-italics">
|
||||
Warning: Could not find an exact match parent Push in
|
||||
Treeherder.
|
||||
</Alert>
|
||||
{id && <div>Closest match: </div>}
|
||||
</div>
|
||||
)}
|
||||
<Clipboard
|
||||
description="full hash"
|
||||
text={parentSha}
|
||||
visible={clipboardVisible}
|
||||
/>
|
||||
<span
|
||||
className="mb-2"
|
||||
onMouseEnter={() => this.showClipboard(true)}
|
||||
onMouseLeave={() => this.showClipboard(false)}
|
||||
>
|
||||
<a
|
||||
href={parentLinkUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Open this push"
|
||||
data-testid="parent-commit-sha"
|
||||
className="mr-1 ml-1 font-weight-bold text-secondary"
|
||||
>
|
||||
{parentPushRevision || parentSha}
|
||||
</a>
|
||||
{exactMatch && (
|
||||
<PushHealthStatus
|
||||
revision={parentPushRevision}
|
||||
repoName={parentRepository.name}
|
||||
jobCounts={jobCounts}
|
||||
/>
|
||||
)}
|
||||
<Clipboard
|
||||
description="full hash"
|
||||
text={parentSha}
|
||||
visible={clipboardVisible}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{revisions.length > 2 && (
|
||||
<span className="font-weight-bold">
|
||||
|
@ -190,7 +196,7 @@ class CommitHistory extends React.PureComponent {
|
|||
|
||||
CommitHistory.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
parentRepository: PropTypes.object.isRequired,
|
||||
parentRepository: PropTypes.object,
|
||||
revisionCount: PropTypes.number.isRequired,
|
||||
parentPushRevision: PropTypes.string,
|
||||
job_counts: PropTypes.shape({
|
||||
|
@ -202,6 +208,11 @@ CommitHistory.propTypes = {
|
|||
}).isRequired,
|
||||
revision: PropTypes.string.isRequired,
|
||||
currentRepo: PropTypes.shape({}).isRequired,
|
||||
showParent: PropTypes.bool,
|
||||
};
|
||||
|
||||
CommitHistory.defaultProps = {
|
||||
showParent: true,
|
||||
};
|
||||
|
||||
export default CommitHistory;
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Navbar, Nav, Container, Spinner } from 'reactstrap';
|
||||
import { Container, Spinner, Navbar, Button, Nav } from 'reactstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faClock,
|
||||
faExclamationTriangle,
|
||||
faCheck,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
@ -14,11 +9,6 @@ import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
|||
import faviconBroken from '../img/push-health-broken.png';
|
||||
import faviconOk from '../img/push-health-ok.png';
|
||||
import ErrorMessages from '../shared/ErrorMessages';
|
||||
import NotificationList from '../shared/NotificationList';
|
||||
import {
|
||||
clearNotificationAtIndex,
|
||||
clearExpiredTransientNotifications,
|
||||
} from '../helpers/notifications';
|
||||
import PushModel from '../models/push';
|
||||
import RepositoryModel from '../models/repository';
|
||||
import StatusProgress from '../shared/StatusProgress';
|
||||
|
@ -30,8 +20,7 @@ import {
|
|||
} from '../helpers/url';
|
||||
import InputFilter from '../shared/InputFilter';
|
||||
|
||||
import { resultColorMap } from './helpers';
|
||||
import Navigation from './Navigation';
|
||||
import { resultColorMap, getIcon } from './helpers';
|
||||
import TestMetric from './TestMetric';
|
||||
import JobListMetric from './JobListMetric';
|
||||
import CommitHistory from './CommitHistory';
|
||||
|
@ -43,7 +32,6 @@ export default class Health extends React.PureComponent {
|
|||
const params = new URLSearchParams(props.location.search);
|
||||
|
||||
this.state = {
|
||||
user: { isLoggedIn: false },
|
||||
revision: params.get('revision'),
|
||||
repo: params.get('repo'),
|
||||
currentRepo: null,
|
||||
|
@ -51,7 +39,6 @@ export default class Health extends React.PureComponent {
|
|||
jobs: null,
|
||||
result: null,
|
||||
failureMessage: null,
|
||||
notifications: [],
|
||||
defaultTabIndex: 0,
|
||||
showParentMatches: false,
|
||||
testGroup: params.get('testGroup') || '',
|
||||
|
@ -68,15 +55,26 @@ export default class Health extends React.PureComponent {
|
|||
|
||||
async componentDidMount() {
|
||||
const { repo, testGroup } = this.state;
|
||||
const { location } = this.props;
|
||||
const {
|
||||
metrics: { linting, builds, tests },
|
||||
} = await this.updatePushHealth();
|
||||
let defaultTabIndex = [linting, builds, tests].findIndex(
|
||||
(metric) => metric.result === 'fail',
|
||||
);
|
||||
if (testGroup) {
|
||||
|
||||
const params = parseQueryParams(location.search);
|
||||
let defaultTabIndex;
|
||||
|
||||
if (params.tab !== undefined) {
|
||||
defaultTabIndex = ['linting', 'builds', 'tests'].findIndex(
|
||||
(metric) => metric === params.tab,
|
||||
);
|
||||
} else if (testGroup) {
|
||||
defaultTabIndex = 2;
|
||||
} else {
|
||||
defaultTabIndex = [linting, builds, tests].findIndex(
|
||||
(metric) => metric.result === 'fail',
|
||||
);
|
||||
}
|
||||
|
||||
const repos = await RepositoryModel.getList();
|
||||
const currentRepo = repos.find((repoObj) => repoObj.name === repo);
|
||||
|
||||
|
@ -85,9 +83,7 @@ export default class Health extends React.PureComponent {
|
|||
// Update the tests every two minutes.
|
||||
this.testTimerId = setInterval(() => this.updatePushHealth(), 120000);
|
||||
this.notificationsId = setInterval(() => {
|
||||
const { notifications } = this.state;
|
||||
|
||||
this.setState(clearExpiredTransientNotifications(notifications));
|
||||
this.props.clearNotification();
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
|
@ -95,10 +91,6 @@ export default class Health extends React.PureComponent {
|
|||
clearInterval(this.testTimerId);
|
||||
}
|
||||
|
||||
setUser = (user) => {
|
||||
this.setState({ user });
|
||||
};
|
||||
|
||||
updateParamsAndState = (stateObj) => {
|
||||
const { location, history } = this.props;
|
||||
const newParams = {
|
||||
|
@ -120,27 +112,6 @@ export default class Health extends React.PureComponent {
|
|||
return newState;
|
||||
};
|
||||
|
||||
notify = (message, severity, options = {}) => {
|
||||
const { notifications } = this.state;
|
||||
const notification = {
|
||||
...options,
|
||||
message,
|
||||
severity: severity || 'darker-info',
|
||||
created: Date.now(),
|
||||
};
|
||||
const newNotifications = [notification, ...notifications];
|
||||
|
||||
this.setState({
|
||||
notifications: newNotifications,
|
||||
});
|
||||
};
|
||||
|
||||
clearNotification = (index) => {
|
||||
const { notifications } = this.state;
|
||||
|
||||
this.setState(clearNotificationAtIndex(notifications, index));
|
||||
};
|
||||
|
||||
setExpanded = (metricName, expanded) => {
|
||||
const root = camelCase(metricName);
|
||||
const key = `${root}Expanded`;
|
||||
|
@ -173,26 +144,14 @@ export default class Health extends React.PureComponent {
|
|||
this.setState({ searchStr });
|
||||
};
|
||||
|
||||
getIcon = (result) => {
|
||||
switch (result) {
|
||||
case 'pass':
|
||||
return faCheck;
|
||||
case 'fail':
|
||||
return faExclamationTriangle;
|
||||
}
|
||||
return faClock;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
metrics,
|
||||
result,
|
||||
user,
|
||||
repo,
|
||||
revision,
|
||||
jobs,
|
||||
failureMessage,
|
||||
notifications,
|
||||
status,
|
||||
searchStr,
|
||||
currentRepo,
|
||||
|
@ -212,56 +171,47 @@ export default class Health extends React.PureComponent {
|
|||
? tests.details.needInvestigation.length
|
||||
: 0;
|
||||
|
||||
const { notify } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Navbar color="light" light expand="sm" className="w-100">
|
||||
{!!tests && (
|
||||
<Nav className="mb-2 pt-2 pl-3 justify-content-between w-100">
|
||||
<span />
|
||||
<span className="mr-2 d-flex">
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-nowrap mr-1"
|
||||
title="Toggle failures that also failed in the parent"
|
||||
onClick={() =>
|
||||
this.setState({ showParentMatches: !showParentMatches })
|
||||
}
|
||||
>
|
||||
{showParentMatches ? 'Hide' : 'Show'} parent matches
|
||||
</Button>
|
||||
<InputFilter
|
||||
updateFilterText={this.filter}
|
||||
placeholder="filter path or platform"
|
||||
/>
|
||||
</span>
|
||||
</Nav>
|
||||
)}
|
||||
</Navbar>
|
||||
<Helmet>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href={result === 'fail' ? faviconBroken : faviconOk}
|
||||
/>
|
||||
<title>{`[${needInvestigationCount}] Push Health`}</title>
|
||||
<title>{`[${needInvestigationCount} failures] Push Health`}</title>
|
||||
</Helmet>
|
||||
<Navigation
|
||||
user={user}
|
||||
setUser={this.setUser}
|
||||
notify={this.notify}
|
||||
result={result}
|
||||
repo={repo}
|
||||
revision={revision}
|
||||
>
|
||||
<Navbar color="light" light expand="sm" className="w-100">
|
||||
{!!tests && (
|
||||
<Nav className="mb-2 pt-2 pl-3 justify-content-between w-100">
|
||||
<span />
|
||||
<span className="mr-2 d-flex">
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-nowrap mr-1"
|
||||
title="Toggle failures that also failed in the parent"
|
||||
onClick={() =>
|
||||
this.setState({ showParentMatches: !showParentMatches })
|
||||
}
|
||||
>
|
||||
{showParentMatches ? 'Hide' : 'Show'} parent matches
|
||||
</Button>
|
||||
<InputFilter
|
||||
updateFilterText={this.filter}
|
||||
placeholder="filter path or platform"
|
||||
/>
|
||||
</span>
|
||||
</Nav>
|
||||
)}
|
||||
</Navbar>
|
||||
</Navigation>
|
||||
<Container fluid className="mt-2 mb-5 max-width-default">
|
||||
<NotificationList
|
||||
notifications={notifications}
|
||||
clearNotification={this.clearNotification}
|
||||
/>
|
||||
{!!tests && !!currentRepo && (
|
||||
<React.Fragment>
|
||||
<div className="d-flex mb-5">
|
||||
<StatusProgress counts={status} />
|
||||
<div className="d-flex my-5">
|
||||
<StatusProgress
|
||||
counts={status}
|
||||
customStyle="progress-relative"
|
||||
/>
|
||||
<div className="mt-4 ml-2">
|
||||
{commitHistory.details && (
|
||||
<CommitHistory
|
||||
|
@ -283,7 +233,7 @@ export default class Health extends React.PureComponent {
|
|||
<Tab className="pb-2 list-inline-item ml-4 pointable">
|
||||
<span className="text-success">
|
||||
<FontAwesomeIcon
|
||||
icon={this.getIcon(linting.result)}
|
||||
icon={getIcon(linting.result)}
|
||||
className={`mr-1 text-${
|
||||
resultColorMap[linting.result]
|
||||
}`}
|
||||
|
@ -293,7 +243,7 @@ export default class Health extends React.PureComponent {
|
|||
</Tab>
|
||||
<Tab className="list-inline-item ml-4 pointable">
|
||||
<FontAwesomeIcon
|
||||
icon={this.getIcon(builds.result)}
|
||||
icon={getIcon(builds.result)}
|
||||
className={`mr-1 text-${resultColorMap[builds.result]}`}
|
||||
/>
|
||||
Builds
|
||||
|
@ -301,7 +251,7 @@ export default class Health extends React.PureComponent {
|
|||
<Tab className="list-inline-item ml-4 pointable">
|
||||
<FontAwesomeIcon
|
||||
fill={resultColorMap[tests.result]}
|
||||
icon={this.getIcon(tests.result)}
|
||||
icon={getIcon(tests.result)}
|
||||
className={`mr-1 text-${resultColorMap[tests.result]}`}
|
||||
/>
|
||||
Tests
|
||||
|
@ -333,7 +283,7 @@ export default class Health extends React.PureComponent {
|
|||
repo={repo}
|
||||
currentRepo={currentRepo}
|
||||
revision={revision}
|
||||
notify={this.notify}
|
||||
notify={notify}
|
||||
setExpanded={this.setExpanded}
|
||||
searchStr={searchStr}
|
||||
testGroup={testGroup}
|
||||
|
|
|
@ -8,12 +8,17 @@ import { filterJobs } from './helpers';
|
|||
export default class JobListMetric extends React.PureComponent {
|
||||
render() {
|
||||
const { data, repo, revision, showParentMatches } = this.props;
|
||||
const { name, details } = data;
|
||||
const { name, details, result } = data;
|
||||
|
||||
const jobs = filterJobs(details, showParentMatches);
|
||||
const msgForZeroJobs =
|
||||
details.length && !jobs.length
|
||||
? `All failed ${name} also failed in Parent Push`
|
||||
: `All ${name} passed`;
|
||||
let msgForZeroJobs = `All ${name} passed`;
|
||||
const correctGrammer = name.slice(-1) === 's' ? 'are' : 'is';
|
||||
|
||||
if (result === 'unknown') {
|
||||
msgForZeroJobs = `${name} ${correctGrammer} in progress. No failures detected.`;
|
||||
} else if (details.length && !jobs.length) {
|
||||
msgForZeroJobs = `All failed ${name} also failed in Parent Push`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
Row,
|
||||
Col,
|
||||
UncontrolledButtonDropdown,
|
||||
DropdownToggle,
|
||||
Navbar,
|
||||
Nav,
|
||||
} from 'reactstrap';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import faviconBroken from '../img/push-health-broken.png';
|
||||
import faviconOk from '../img/push-health-ok.png';
|
||||
import { getData } from '../helpers/http';
|
||||
import { getProjectUrl } from '../helpers/location';
|
||||
import { createQueryParams, pushEndpoint } from '../helpers/url';
|
||||
import RepositoryModel from '../models/repository';
|
||||
import StatusProgress from '../shared/StatusProgress';
|
||||
import LoadingSpinner from '../shared/LoadingSpinner';
|
||||
import ErrorMessages from '../shared/ErrorMessages';
|
||||
import DropdownMenuItems from '../shared/DropdownMenuItems';
|
||||
|
||||
import StatusButton from './StatusButton';
|
||||
import CommitHistory from './CommitHistory';
|
||||
|
||||
const defaultRepo = 'try';
|
||||
|
||||
class MyPushes extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pushMetrics: [],
|
||||
repos: [],
|
||||
loading: false,
|
||||
selectedRepo: defaultRepo,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.fetchRepos();
|
||||
if (this.props.user.isLoggedIn) {
|
||||
this.fetchMetrics(true);
|
||||
// Update the tests every two minutes.
|
||||
this.testTimerId = setInterval(() => this.fetchMetrics(), 120000);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.user.isLoggedIn && this.props.user.isLoggedIn) {
|
||||
this.fetchMetrics(true);
|
||||
// Update the tests every two minutes.
|
||||
this.testTimerId = setInterval(() => this.fetchMetrics(), 120000);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.testTimerId);
|
||||
}
|
||||
|
||||
formatRevisionHistory = (push) => ({
|
||||
parentSha: push.revision,
|
||||
id: push.id,
|
||||
revisions: push.revisions,
|
||||
revisionCount: push.revisions.length,
|
||||
currentPush: { author: push.author, push_timestamp: push.push_timestamp },
|
||||
});
|
||||
|
||||
async fetchMetrics(loading = false) {
|
||||
const { selectedRepo } = this.state;
|
||||
const { user, notify, clearNotification } = this.props;
|
||||
|
||||
this.setState({ loading });
|
||||
|
||||
const options = {
|
||||
author: user.email,
|
||||
count: 5,
|
||||
with_history: true,
|
||||
};
|
||||
const stateChanges = { loading: false };
|
||||
|
||||
if (selectedRepo === 'all') {
|
||||
options.all_repos = true;
|
||||
}
|
||||
|
||||
const { data, failureStatus } = await getData(
|
||||
getProjectUrl(
|
||||
`${pushEndpoint}health_summary/${createQueryParams(options)}`,
|
||||
defaultRepo,
|
||||
),
|
||||
);
|
||||
|
||||
// in case this request fails during polling
|
||||
clearNotification();
|
||||
|
||||
if (!failureStatus && data.length) {
|
||||
stateChanges.pushMetrics = data;
|
||||
} else {
|
||||
notify(`There was a problem retrieving push metrics: ${data}`, 'danger');
|
||||
}
|
||||
|
||||
this.setState(stateChanges);
|
||||
}
|
||||
|
||||
async fetchRepos() {
|
||||
const repos = await RepositoryModel.getList();
|
||||
this.setState({ repos });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const {
|
||||
repos,
|
||||
pushMetrics,
|
||||
loading,
|
||||
failureMessage,
|
||||
selectedRepo,
|
||||
} = this.state;
|
||||
|
||||
const totalNeedInvestigation = pushMetrics.length
|
||||
? pushMetrics
|
||||
.map((push) => push.needInvestigation)
|
||||
.reduce((total, count) => total + count)
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Navbar color="light" light expand="sm" className="w-100">
|
||||
<Nav className="mb-2 pt-2 pl-3 justify-content-between w-100">
|
||||
<span />
|
||||
<span className="mr-3 d-flex">
|
||||
<UncontrolledButtonDropdown>
|
||||
<DropdownToggle
|
||||
caret
|
||||
size="sm"
|
||||
>{`${selectedRepo} pushes`}</DropdownToggle>
|
||||
<DropdownMenuItems
|
||||
updateData={(selectedRepo) =>
|
||||
this.setState({ selectedRepo, loading: true }, () =>
|
||||
this.fetchMetrics(true),
|
||||
)
|
||||
}
|
||||
selectedItem={selectedRepo}
|
||||
options={['try', 'all']}
|
||||
/>
|
||||
</UncontrolledButtonDropdown>
|
||||
</span>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
<Helmet>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href={totalNeedInvestigation > 0 ? faviconBroken : faviconOk}
|
||||
/>
|
||||
<title>{`[${totalNeedInvestigation} failures] Push Health`}</title>
|
||||
</Helmet>
|
||||
<Container className="mt-2 mb-5 max-width-default">
|
||||
{!user.isLoggedIn && (
|
||||
<h2 className="pt-5 text-center">
|
||||
Please log in to see your pushes
|
||||
</h2>
|
||||
)}
|
||||
|
||||
{failureMessage && <ErrorMessages failureMessage={failureMessage} />}
|
||||
{loading && <LoadingSpinner />}
|
||||
{repos.length > 0 &&
|
||||
pushMetrics.length > 0 &&
|
||||
pushMetrics.map((push) => (
|
||||
<Row
|
||||
className="mt-5 flex-nowrap justify-content-center"
|
||||
key={push.revision}
|
||||
>
|
||||
<Col sm="2" className="ml-5">
|
||||
<StatusProgress counts={push.status} />
|
||||
</Col>
|
||||
<Col sm="6" className="mt-4 ml-2">
|
||||
<CommitHistory
|
||||
history={this.formatRevisionHistory(push.history[0])}
|
||||
revision={push.revision}
|
||||
currentRepo={repos.find(
|
||||
(repo) => repo.name === push.repository,
|
||||
)}
|
||||
showParent={false}
|
||||
/>
|
||||
</Col>
|
||||
<Col lg="1" xl="2" className="align-self-center ml-5 px-0 pb-4">
|
||||
<StatusButton
|
||||
title="Linting"
|
||||
result={push.metrics.linting.result}
|
||||
count={push.lintFailureCount}
|
||||
repo={push.repository}
|
||||
revision={push.revision}
|
||||
/>
|
||||
</Col>
|
||||
<Col lg="1" xl="2" className="align-self-center mr-2 px-0 pb-4">
|
||||
<StatusButton
|
||||
title="Builds"
|
||||
result={push.metrics.builds.result}
|
||||
count={push.buildFailureCount}
|
||||
repo={push.repository}
|
||||
revision={push.revision}
|
||||
/>
|
||||
</Col>
|
||||
<Col lg="1" xl="2" className="align-self-center px-0 pb-4">
|
||||
<StatusButton
|
||||
title="Tests"
|
||||
result={push.metrics.tests.result}
|
||||
count={push.testFailureCount}
|
||||
repo={push.repository}
|
||||
revision={push.revision}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</Container>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPushes;
|
|
@ -1,66 +1,41 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Badge, Navbar, Nav } from 'reactstrap';
|
||||
import { Navbar, Nav, NavItem } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import LogoMenu from '../shared/LogoMenu';
|
||||
import { getJobsUrl } from '../helpers/url';
|
||||
import Login from '../shared/auth/Login';
|
||||
import HelpMenu from '../shared/HelpMenu';
|
||||
|
||||
import { resultColorMap } from './helpers';
|
||||
|
||||
export default class Navigation extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
user,
|
||||
setUser,
|
||||
result,
|
||||
notify,
|
||||
repo,
|
||||
revision,
|
||||
children,
|
||||
} = this.props;
|
||||
const overallResult = result ? resultColorMap[result] : 'none';
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Navbar dark color="dark" sticky="top" className="flex-column">
|
||||
<Nav className="w-100 justify-content-between">
|
||||
<LogoMenu menuText="Push Health" />
|
||||
<h4>
|
||||
<Badge color={overallResult}>
|
||||
<a
|
||||
href={getJobsUrl({ repo, revision })}
|
||||
className="text-white"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span title="repository">{repo}</span> -
|
||||
<span title="revision" className="ml-1">
|
||||
{revision}
|
||||
</span>
|
||||
</a>
|
||||
</Badge>
|
||||
</h4>
|
||||
<Login user={user} setUser={setUser} notify={notify} />
|
||||
</Nav>
|
||||
{children}
|
||||
</Navbar>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
const Navigation = ({ user, setUser, notify }) => (
|
||||
<Navbar expand sticky="top" className="top-navbar">
|
||||
<LogoMenu menuText="Push Health" />
|
||||
<Nav className="navbar navbar-inverse">
|
||||
<NavItem>
|
||||
<Link to="/push-health" className="menu-items nav-link btn-view-nav">
|
||||
My Pushes
|
||||
</Link>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
{' '}
|
||||
<Link
|
||||
to="/push-health/usage"
|
||||
className="menu-items nav-link btn-view-nav"
|
||||
>
|
||||
Usage
|
||||
</Link>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
<Navbar className="ml-auto">
|
||||
<HelpMenu />
|
||||
<Login user={user} setUser={setUser} notify={notify} />
|
||||
</Navbar>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
Navigation.propTypes = {
|
||||
user: PropTypes.shape({}).isRequired,
|
||||
setUser: PropTypes.func.isRequired,
|
||||
repo: PropTypes.string.isRequired,
|
||||
revision: PropTypes.string.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
result: PropTypes.string,
|
||||
children: PropTypes.element,
|
||||
};
|
||||
|
||||
Navigation.defaultProps = {
|
||||
result: '',
|
||||
children: null,
|
||||
};
|
||||
export default Navigation;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { resultColorMap, getIcon } from './helpers';
|
||||
|
||||
const StatusButton = ({ title, result, count, repo, revision }) => {
|
||||
let resultText = 'Passed';
|
||||
|
||||
if (result === 'fail') {
|
||||
resultText = `Failures (${count})`;
|
||||
} else if (result === 'unknown') {
|
||||
resultText = 'In progress';
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Link
|
||||
className="status-link link-darker-secondary pb-1"
|
||||
to={`/push-health/push?repo=${repo}&revision=${revision}&tab=${title.toLowerCase()}`}
|
||||
>
|
||||
{title} <FontAwesomeIcon icon={faChevronRight} />
|
||||
</Link>
|
||||
<br />
|
||||
<div className="pt-2">
|
||||
<FontAwesomeIcon
|
||||
icon={getIcon(result)}
|
||||
className={`mr-2 text-${resultColorMap[result]}`}
|
||||
/>
|
||||
<span className={`text-${resultColorMap[result]}`}>{resultText}</span>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
StatusButton.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
result: PropTypes.string.isRequired,
|
||||
count: PropTypes.number,
|
||||
revision: PropTypes.string.isRequired,
|
||||
repo: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
StatusButton.defaultProps = {
|
||||
count: 0,
|
||||
};
|
||||
|
||||
export default StatusButton;
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Alert, Table, Jumbotron, Badge } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { getData } from '../helpers/http';
|
||||
import { getProjectUrl } from '../helpers/location';
|
||||
|
@ -66,12 +67,12 @@ class Usage extends Component {
|
|||
return (
|
||||
<tr key={revision} data-testid={`facet-${revision}`}>
|
||||
<td data-testid="facet-link">
|
||||
<a
|
||||
href={`/push-health?repo=try&revision=${revision}`}
|
||||
<Link
|
||||
to={`./push?repo=try&revision=${revision}`}
|
||||
title="See Push Health"
|
||||
>
|
||||
{revision}
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
<td>{author}</td>
|
||||
<td>{toShortDateStr(pushTimestamp)}</td>
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import {
|
||||
faClock,
|
||||
faExclamationTriangle,
|
||||
faCheck,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export const resultColorMap = {
|
||||
pass: 'success',
|
||||
fail: 'danger',
|
||||
indeterminate: 'darker-secondary',
|
||||
indeterminate: 'secondary',
|
||||
done: 'darker-info',
|
||||
'in progress': 'darker-secondary',
|
||||
'in progress': 'secondary',
|
||||
none: 'darker-info',
|
||||
unknown: 'secondary',
|
||||
};
|
||||
|
||||
export const taskResultColorMap = {
|
||||
|
@ -31,3 +38,13 @@ export const filterTests = (tests, searchStr, showParentMatches) => {
|
|||
export const filterJobs = (jobs, showParentMatches) => {
|
||||
return jobs.filter((job) => job.failedInParent === showParentMatches);
|
||||
};
|
||||
|
||||
export const getIcon = (result) => {
|
||||
switch (result) {
|
||||
case 'pass':
|
||||
return faCheck;
|
||||
case 'fail':
|
||||
return faExclamationTriangle;
|
||||
}
|
||||
return faClock;
|
||||
};
|
||||
|
|
|
@ -3,18 +3,17 @@ body {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
.absolute-progress {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 35%;
|
||||
top: 60px;
|
||||
left: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.relative {
|
||||
.progress-relative {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
max-height: 200px;
|
||||
min-width: 200px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
|
@ -29,6 +28,10 @@ input[type='checkbox'] {
|
|||
font-size: 24px;
|
||||
}
|
||||
|
||||
.menu-items {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pointable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -46,6 +49,9 @@ input[type='checkbox'] {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-link {
|
||||
font-size: 17.5px;
|
||||
}
|
||||
/* Details Tab Panel */
|
||||
|
||||
.tab-content {
|
||||
|
@ -58,6 +64,9 @@ input[type='checkbox'] {
|
|||
color: white !important;
|
||||
}
|
||||
|
||||
.commit-header {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
/*
|
||||
Need this instead of the Bootstrap h-100 class because the `!important` in that class
|
||||
throws off the layout when tabs are switched.
|
||||
|
|
|
@ -12,7 +12,7 @@ const choices = [
|
|||
{ url: '/jobs', text: 'Treeherder' },
|
||||
{ url: '/perfherder', text: 'Perfherder' },
|
||||
{ url: '/intermittent-failures', text: 'Intermittent Failures View' },
|
||||
{ url: '/push-health', text: 'Push Health Usage' },
|
||||
{ url: '/push-health', text: 'Push Health' },
|
||||
];
|
||||
|
||||
export default class LogoMenu extends React.PureComponent {
|
||||
|
@ -23,7 +23,7 @@ export default class LogoMenu extends React.PureComponent {
|
|||
return (
|
||||
<UncontrolledDropdown>
|
||||
<DropdownToggle
|
||||
className="btn-view-nav"
|
||||
className="btn-view-nav menu-items"
|
||||
id="th-logo"
|
||||
caret
|
||||
title="Treeherder services"
|
||||
|
|
|
@ -47,9 +47,9 @@ class PushHealthStatus extends Component {
|
|||
revision,
|
||||
);
|
||||
|
||||
if (!failureStatus) {
|
||||
statusCallback(data);
|
||||
this.setState({ ...data });
|
||||
if (!failureStatus && data.length) {
|
||||
statusCallback(data[0]);
|
||||
this.setState({ ...data[0] });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ export class Revision extends React.PureComponent {
|
|||
<a
|
||||
title={`Open revision ${commitRevision} on ${repo.url}`}
|
||||
href={repo.getRevisionHref(commitRevision)}
|
||||
className={`commit-sha ${commitShaClass}`}
|
||||
className={commitShaClass}
|
||||
>
|
||||
{commitRevision.substring(0, 12)}
|
||||
</a>
|
||||
|
@ -98,7 +98,7 @@ export class Revision extends React.PureComponent {
|
|||
<AuthorInitials title={`${name}: ${email}`} author={name} />
|
||||
<span
|
||||
data-testid={comment}
|
||||
className={`ml-2 revision-comment overflow-hidden text-nowrap ${commentColor} ${commentFont}`}
|
||||
className={`ml-2 revision-comment overflow-hidden text-truncate ${commentColor} ${commentFont}`}
|
||||
id={`revision${revision}`}
|
||||
>
|
||||
<em>
|
||||
|
@ -143,6 +143,6 @@ Revision.propTypes = {
|
|||
};
|
||||
|
||||
Revision.defaultProps = {
|
||||
commitShaClass: '',
|
||||
commitShaClass: 'commit-sha',
|
||||
commentFont: '',
|
||||
};
|
||||
|
|
|
@ -7,12 +7,13 @@ import { getPercentComplete } from '../helpers/display';
|
|||
const StatusProgress = (props) => {
|
||||
const {
|
||||
counts: { success, testfailed, busted, running, pending },
|
||||
customStyle,
|
||||
} = props;
|
||||
const failed = testfailed || 0 + busted || 0;
|
||||
const percentComplete = props.counts ? getPercentComplete(props.counts) : 0;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className={customStyle}>
|
||||
<VictoryPie
|
||||
data={[
|
||||
{ x: 'success', y: success },
|
||||
|
@ -25,14 +26,13 @@ const StatusProgress = (props) => {
|
|||
<VictoryTooltip pointerLength={0} flyoutComponent={<div />} />
|
||||
}
|
||||
labelRadius={({ innerRadius }) => innerRadius}
|
||||
height={200}
|
||||
width={200}
|
||||
padding={{ top: 15, bottom: 15 }}
|
||||
height={250}
|
||||
width={250}
|
||||
innerRadius={70}
|
||||
radius={85}
|
||||
/>
|
||||
<div className="absolute">
|
||||
<div style={{ fontSize: '30px' }}>{percentComplete}%</div>
|
||||
<div className="absolute-progress">
|
||||
<div className="metric-name">{percentComplete}%</div>
|
||||
<div>Complete</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -41,6 +41,11 @@ const StatusProgress = (props) => {
|
|||
|
||||
StatusProgress.propTypes = {
|
||||
counts: PropTypes.objectOf(PropTypes.number).isRequired,
|
||||
customStyle: PropTypes.string,
|
||||
};
|
||||
|
||||
StatusProgress.defaultProps = {
|
||||
customStyle: '',
|
||||
};
|
||||
|
||||
export default StatusProgress;
|
||||
|
|
Загрузка…
Ссылка в новой задаче