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:
Sarah Clements 2020-12-02 14:42:08 -08:00 коммит произвёл GitHub
Родитель 77870bc405
Коммит be58c0d3d7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
32 изменённых файлов: 1553 добавлений и 326 удалений

Просмотреть файл

@ -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>

222
ui/push-health/MyPushes.jsx Normal file
Просмотреть файл

@ -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;