From 9762a886c813d425aec5bf3bb6ba14e1d453449d Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Mon, 30 Nov 2020 15:21:26 +0000 Subject: [PATCH 1/9] Add script to check cron job --- check-cron/README.rst | 9 ++ check-cron/report.py | 163 ++++++++++++++++++++++++++++++++++++ check-cron/requirements.txt | 2 + check-cron/results.rst | 30 +++++++ 4 files changed, 204 insertions(+) create mode 100644 check-cron/README.rst create mode 100644 check-cron/report.py create mode 100644 check-cron/requirements.txt create mode 100644 check-cron/results.rst diff --git a/check-cron/README.rst b/check-cron/README.rst new file mode 100644 index 0000000..59ebc94 --- /dev/null +++ b/check-cron/README.rst @@ -0,0 +1,9 @@ +This folder contains a script `report.py` for generating a report of +recently run CI jobs. + +To run the script, in your favorite Python environment:: + + pip install -r requirements.txt + python report.py + +This should create a new file containing the report. diff --git a/check-cron/report.py b/check-cron/report.py new file mode 100644 index 0000000..1de7fac --- /dev/null +++ b/check-cron/report.py @@ -0,0 +1,163 @@ +from collections import Counter +import datetime +from tabulate import tabulate +import os +import sys +import pickle +import urllib.parse + +import requests + + +# Repositories being monitored. +REPOS = [ + "enthought/traits", + "enthought/traitsui", + "enthought/pyface", + "enthought/enable", + "enthought/chaco", + "enthought/envisage", + "enthought/mayavi", +] + +# Only analyze runs that are not older than 6 days +NO_OLDER_THAN = datetime.timedelta(days=6) + +# Only runs triggered by this event type will be reported +EVENT = "pull_request" + + +def parse_timestamp(timestamp): + return datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ") + + +def cached(func): + """ Decorator to cache results so we are not hitting the web API all + the time when we experiment with the code. This is purely a development + tool. + """ + + def decorated(repo, event): + + pickled_file = ( + urllib.parse.quote_plus(f"{repo}_{event}") + ".pickle" + ) + if os.path.exists(pickled_file): + with open(pickled_file, "rb") as fp: + data = pickle.load(fp) + else: + data = func(repo, event) + with open(pickled_file, "wb") as fp: + pickle.dump(data, fp) + return data + + return decorated + + +# Uncomment decorator to cache responses +#@cached +def get_response(repo, event): + """ Return the JSON data for the GitHub organization/repo runs triggered + by the given event. The most recent and at most 100 runs will be returned. + """ + response = requests.get( + f"https://api.github.com/repos/{repo}/actions/runs?event={event}", + ) + if response.ok: + return response.json() + response.raise_for_status() + + +def get_latest_runs(data): + """ Given the full response, filter runs such that only the latest runs + for each workflow id are returned. + """ + id_to_latest = {} + id_to_last_run = {} + for run in data["workflow_runs"]: + workflow_id = run["workflow_id"] + created_time = parse_timestamp(run["created_at"]) + if datetime.datetime.now() - created_time > NO_OLDER_THAN: + continue + latest = id_to_latest.setdefault( + workflow_id, datetime.datetime(1, 1, 1) + ) + if created_time > latest: + id_to_latest[workflow_id] = created_time + id_to_last_run[workflow_id] = run + return list(id_to_last_run.values()) + + +def record_summary(record): + """ For a given job run record, return the formatted entries to be + reported in reStructuredText format. + """ + name = record["name"] + conclusion = record["conclusion"] + updated_at_timestamp = record["updated_at"] + marker = "✅" if conclusion == "success" else "❌" + time_delta = ( + datetime.datetime.now() - parse_timestamp(updated_at_timestamp) + ) + ndays_since = time_delta.days + time_delta.seconds / 86400.0 + return { + "Name": name, + "Conclusion": f"{marker} {conclusion}", + "Updated": updated_at_timestamp + f" ({ndays_since:.0f} days ago)", + "URL": f'[Details]({record["html_url"]})', + } + + +def to_table(repo_records): + """ For a list of job run records, collect the summary and create + a table in text format. + """ + table = [] + keys = [] + for record in repo_records: + summary = record_summary(record) + keys = list(summary.keys()) + table.append(list(summary.values())) + return tabulate(table, headers=keys, tablefmt="rst") + + +def get_repo_records(): + return { + repo: get_latest_runs(get_response(repo, EVENT)) + for repo in REPOS + } + + +def get_short_summary(repo_to_records): + # Get overall success/failure counts + conclusion_counter = Counter() + for records in repo_to_records.values(): + conclusion_counter.update(record["conclusion"] for record in records) + sadness = sum( + count for key, count in conclusion_counter.items() if key != "success" + ) + return "There is some sadness." if sadness > 0 else "Happy Day!" + + +def create_report_tables(repo_to_records, file=sys.stdout): + print("Generated on: ", datetime.date.today().isoformat(), file=file) + print("Event trigger: ", EVENT, file=file) + print(file=file) + + # Print each table + for repo, records in repo_to_records.items(): + print(f"**{repo}**", file=file) + print(to_table(records), file=file) + print(file=file) + + +def main(): + repo_to_records = get_repo_records() + + with open("results.rst", "w", encoding="utf-8") as fp: + create_report_tables(repo_to_records, file=fp) + + print(get_short_summary(repo_to_records)) + + +main() diff --git a/check-cron/requirements.txt b/check-cron/requirements.txt new file mode 100644 index 0000000..e44e33d --- /dev/null +++ b/check-cron/requirements.txt @@ -0,0 +1,2 @@ +requests +tabulate diff --git a/check-cron/results.rst b/check-cron/results.rst new file mode 100644 index 0000000..7697833 --- /dev/null +++ b/check-cron/results.rst @@ -0,0 +1,30 @@ +Generated on: 2020-11-30 +Event trigger: pull_request + +**enthought/traits** + + +**enthought/traitsui** +============== ============ ================================== ======================================================================= +Name Conclusion Updated URL +============== ============ ================================== ======================================================================= +Style check ✅ success 2020-11-30T12:11:43Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545841) +Test with PyPI ✅ success 2020-11-30T12:17:00Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545840) +Minimal setup ✅ success 2020-11-30T12:12:37Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545838) +============== ============ ================================== ======================================================================= + +**enthought/pyface** + + +**enthought/enable** + + +**enthought/chaco** + + +**enthought/envisage** + + +**enthought/mayavi** + + From 6fa4a9fb554affae2845758a5b2d1d3a892c42ad Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Mon, 30 Nov 2020 15:24:21 +0000 Subject: [PATCH 2/9] Use markdown for GitHub --- check-cron/report.py | 4 ++-- check-cron/results.md | 28 ++++++++++++++++++++++++++++ check-cron/results.rst | 30 ------------------------------ 3 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 check-cron/results.md delete mode 100644 check-cron/results.rst diff --git a/check-cron/report.py b/check-cron/report.py index 1de7fac..f62c0f4 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -118,7 +118,7 @@ def to_table(repo_records): summary = record_summary(record) keys = list(summary.keys()) table.append(list(summary.values())) - return tabulate(table, headers=keys, tablefmt="rst") + return tabulate(table, headers=keys, tablefmt="github") def get_repo_records(): @@ -154,7 +154,7 @@ def create_report_tables(repo_to_records, file=sys.stdout): def main(): repo_to_records = get_repo_records() - with open("results.rst", "w", encoding="utf-8") as fp: + with open("results.md", "w", encoding="utf-8") as fp: create_report_tables(repo_to_records, file=fp) print(get_short_summary(repo_to_records)) diff --git a/check-cron/results.md b/check-cron/results.md new file mode 100644 index 0000000..3de57dd --- /dev/null +++ b/check-cron/results.md @@ -0,0 +1,28 @@ +Generated on: 2020-11-30 +Event trigger: pull_request + +**enthought/traits** + + +**enthought/traitsui** +| Name | Conclusion | Updated | URL | +|----------------|--------------|------------------------------------|-------------------------------------------------------------------------| +| Minimal setup | ✅ success | 2020-11-30T12:12:37Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545838) | +| Style check | ✅ success | 2020-11-30T12:11:43Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545841) | +| Test with PyPI | ✅ success | 2020-11-30T12:17:00Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545840) | + +**enthought/pyface** + + +**enthought/enable** + + +**enthought/chaco** + + +**enthought/envisage** + + +**enthought/mayavi** + + diff --git a/check-cron/results.rst b/check-cron/results.rst deleted file mode 100644 index 7697833..0000000 --- a/check-cron/results.rst +++ /dev/null @@ -1,30 +0,0 @@ -Generated on: 2020-11-30 -Event trigger: pull_request - -**enthought/traits** - - -**enthought/traitsui** -============== ============ ================================== ======================================================================= -Name Conclusion Updated URL -============== ============ ================================== ======================================================================= -Style check ✅ success 2020-11-30T12:11:43Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545841) -Test with PyPI ✅ success 2020-11-30T12:17:00Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545840) -Minimal setup ✅ success 2020-11-30T12:12:37Z (0 days ago) [Details](https://github.com/enthought/traitsui/actions/runs/391545838) -============== ============ ================================== ======================================================================= - -**enthought/pyface** - - -**enthought/enable** - - -**enthought/chaco** - - -**enthought/envisage** - - -**enthought/mayavi** - - From 53476daac6ce35e4b2c93bfaabc60554c8b1ccb3 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Mon, 30 Nov 2020 15:27:06 +0000 Subject: [PATCH 3/9] Show more info --- check-cron/report.py | 7 ++++++- check-cron/results.md | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/check-cron/report.py b/check-cron/report.py index f62c0f4..9500a97 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -55,7 +55,7 @@ def decorated(repo, event): # Uncomment decorator to cache responses -#@cached +# @cached def get_response(repo, event): """ Return the JSON data for the GitHub organization/repo runs triggered by the given event. The most recent and at most 100 runs will be returned. @@ -141,9 +141,14 @@ def get_short_summary(repo_to_records): def create_report_tables(repo_to_records, file=sys.stdout): print("Generated on: ", datetime.date.today().isoformat(), file=file) + print(file=file) + print("Event trigger: ", EVENT, file=file) print(file=file) + print("Include runs no older than: ", str(NO_OLDER_THAN), file=file) + print(file=file) + # Print each table for repo, records in repo_to_records.items(): print(f"**{repo}**", file=file) diff --git a/check-cron/results.md b/check-cron/results.md index 3de57dd..747b4da 100644 --- a/check-cron/results.md +++ b/check-cron/results.md @@ -1,15 +1,18 @@ Generated on: 2020-11-30 + Event trigger: pull_request +Include runs no older than: 6 days, 0:00:00 + **enthought/traits** **enthought/traitsui** | Name | Conclusion | Updated | URL | |----------------|--------------|------------------------------------|-------------------------------------------------------------------------| -| Minimal setup | ✅ success | 2020-11-30T12:12:37Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545838) | | Style check | ✅ success | 2020-11-30T12:11:43Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545841) | | Test with PyPI | ✅ success | 2020-11-30T12:17:00Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545840) | +| Minimal setup | ✅ success | 2020-11-30T12:12:37Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545838) | **enthought/pyface** From acc6651cef0125247994fdd067467a314517942e Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Fri, 4 Dec 2020 13:40:39 +0000 Subject: [PATCH 4/9] Switch trigger to schedule and rebuild report --- check-cron/report.py | 8 ++++---- check-cron/results.md | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/check-cron/report.py b/check-cron/report.py index 9500a97..ffe915d 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -20,11 +20,11 @@ "enthought/mayavi", ] -# Only analyze runs that are not older than 6 days -NO_OLDER_THAN = datetime.timedelta(days=6) +# Only analyze runs that are not older than this long +NO_OLDER_THAN = datetime.timedelta(days=7) # Only runs triggered by this event type will be reported -EVENT = "pull_request" +EVENT = "schedule" def parse_timestamp(timestamp): @@ -143,7 +143,7 @@ def create_report_tables(repo_to_records, file=sys.stdout): print("Generated on: ", datetime.date.today().isoformat(), file=file) print(file=file) - print("Event trigger: ", EVENT, file=file) + print("Include runs triggered by: ", EVENT, file=file) print(file=file) print("Include runs no older than: ", str(NO_OLDER_THAN), file=file) diff --git a/check-cron/results.md b/check-cron/results.md index 747b4da..5f5945e 100644 --- a/check-cron/results.md +++ b/check-cron/results.md @@ -1,18 +1,18 @@ -Generated on: 2020-11-30 +Generated on: 2020-12-03 -Event trigger: pull_request +Include runs triggered by: schedule -Include runs no older than: 6 days, 0:00:00 +Include runs no older than: 7 days, 0:00:00 **enthought/traits** **enthought/traitsui** -| Name | Conclusion | Updated | URL | -|----------------|--------------|------------------------------------|-------------------------------------------------------------------------| -| Style check | ✅ success | 2020-11-30T12:11:43Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545841) | -| Test with PyPI | ✅ success | 2020-11-30T12:17:00Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545840) | -| Minimal setup | ✅ success | 2020-11-30T12:12:37Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/391545838) | +| Name | Conclusion | Updated | URL | +|--------------------|--------------|------------------------------------|-------------------------------------------------------------------------| +| ETS from source | ✅ success | 2020-12-03T00:47:30Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397303097) | +| Test with Traits 6 | ✅ success | 2020-12-03T00:43:07Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397302352) | +| Integration tests | ✅ success | 2020-12-03T00:20:46Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397266878) | **enthought/pyface** From 2ce4a916a2fe519db5719efbba81a633bd7ec6b8 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Wed, 6 Jan 2021 09:55:37 +0000 Subject: [PATCH 5/9] Rerun script manually to update results --- check-cron/results.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/check-cron/results.md b/check-cron/results.md index 5f5945e..bec0713 100644 --- a/check-cron/results.md +++ b/check-cron/results.md @@ -1,4 +1,4 @@ -Generated on: 2020-12-03 +Generated on: 2021-01-06 Include runs triggered by: schedule @@ -10,9 +10,9 @@ Include runs no older than: 7 days, 0:00:00 **enthought/traitsui** | Name | Conclusion | Updated | URL | |--------------------|--------------|------------------------------------|-------------------------------------------------------------------------| -| ETS from source | ✅ success | 2020-12-03T00:47:30Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397303097) | -| Test with Traits 6 | ✅ success | 2020-12-03T00:43:07Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397302352) | -| Integration tests | ✅ success | 2020-12-03T00:20:46Z (1 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/397266878) | +| ETS from source | ✅ success | 2020-12-31T01:05:44Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453675596) | +| Test with Traits 6 | ✅ success | 2020-12-31T01:03:58Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453674736) | +| Integration tests | ✅ success | 2020-12-31T00:35:22Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453639538) | **enthought/pyface** From 593ed3e15298dfc9d63b1e9f5e5205827816b7fc Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Wed, 6 Jan 2021 10:37:09 +0000 Subject: [PATCH 6/9] Document and small cleanups --- check-cron/report.py | 77 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/check-cron/report.py b/check-cron/report.py index ffe915d..01f233e 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -21,7 +21,8 @@ ] # Only analyze runs that are not older than this long -NO_OLDER_THAN = datetime.timedelta(days=7) +AS_RECENT_AS = datetime.timedelta(days=7) +INCLUDE_RUNS_SINCE = datetime.datetime.now() - AS_RECENT_AS # Only runs triggered by this event type will be reported EVENT = "schedule" @@ -59,6 +60,25 @@ def decorated(repo, event): def get_response(repo, event): """ Return the JSON data for the GitHub organization/repo runs triggered by the given event. The most recent and at most 100 runs will be returned. + + Parameters + ---------- + repo: str + Name for a GitHub repository, including organization + e.g. 'python/cpython' + event: str + Name of the event to monitor, e.g. 'schedule' + See GitHub Actions Web API references. + + Returns + ------- + response : dict + JSON response + + Raises + ------ + urllib2.HTMLError + If the response indicates errors. """ response = requests.get( f"https://api.github.com/repos/{repo}/actions/runs?event={event}", @@ -68,16 +88,29 @@ def get_response(repo, event): response.raise_for_status() -def get_latest_runs(data): +def get_latest_runs(data, since): """ Given the full response, filter runs such that only the latest runs for each workflow id are returned. + + Parameters + ---------- + data : dict + JSON response, e.g. output of get_response + since : datetime + Only runs created since this datetime are returned. + + Returns + ------- + last_runs : list + List of run records where each one corresponds to the latest run for + a given workflow. """ id_to_latest = {} id_to_last_run = {} for run in data["workflow_runs"]: workflow_id = run["workflow_id"] created_time = parse_timestamp(run["created_at"]) - if datetime.datetime.now() - created_time > NO_OLDER_THAN: + if created_time <= since: continue latest = id_to_latest.setdefault( workflow_id, datetime.datetime(1, 1, 1) @@ -90,7 +123,17 @@ def get_latest_runs(data): def record_summary(record): """ For a given job run record, return the formatted entries to be - reported in reStructuredText format. + reported in (rich) text format. + + Parameters + ---------- + record : dict + A single run record + + Returns + ------- + summary : dict + Filtered and formatted data. """ name = record["name"] conclusion = record["conclusion"] @@ -123,12 +166,24 @@ def to_table(repo_records): def get_repo_records(): return { - repo: get_latest_runs(get_response(repo, EVENT)) + repo: get_latest_runs(get_response(repo, EVENT), INCLUDE_RUNS_SINCE) for repo in REPOS } def get_short_summary(repo_to_records): + """ Given a mapping from repositories to its list of job run records, + return a short sentence as a summary. + + Parameters + ---------- + repo_to_records : dict(str, list(dict)) + Keys are repo names. Values are list of run job records. + + Returns + ------- + summary : str + """ # Get overall success/failure counts conclusion_counter = Counter() for records in repo_to_records.values(): @@ -140,13 +195,23 @@ def get_short_summary(repo_to_records): def create_report_tables(repo_to_records, file=sys.stdout): + """ Given a mapping from repositories to its list of job run records, + print the report table to a stream. + + Parameters + ---------- + repo_to_records : dict(str, list(dict)) + Keys are repo names. Values are list of run job records. + file : file-like + Output stream to print reports to. + """ print("Generated on: ", datetime.date.today().isoformat(), file=file) print(file=file) print("Include runs triggered by: ", EVENT, file=file) print(file=file) - print("Include runs no older than: ", str(NO_OLDER_THAN), file=file) + print("Include runs in the last: ", str(AS_RECENT_AS), file=file) print(file=file) # Print each table From 43136e37c7585e902b2a81175127ea045b4df706 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Wed, 6 Jan 2021 10:45:35 +0000 Subject: [PATCH 7/9] Change short summary to a simple stats --- check-cron/report.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/check-cron/report.py b/check-cron/report.py index 01f233e..2387224 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -184,14 +184,27 @@ def get_short_summary(repo_to_records): ------- summary : str """ - # Get overall success/failure counts - conclusion_counter = Counter() - for records in repo_to_records.values(): - conclusion_counter.update(record["conclusion"] for record in records) - sadness = sum( - count for key, count in conclusion_counter.items() if key != "success" + successes = set() + failures = set() + unknowns = set() + + for repo, records in repo_to_records.items(): + counter = Counter(record["conclusion"] for record in records) + failed_or_errorred = (len(records) - counter["success"]) > 0 + + if not records: + unknowns.add(repo) + continue + + if any(record["conclusion"] != "success" for record in records): + failures.add(repo) + continue + + successes.add(repo) + + return "OK: {} NOT OK: {} Unknown: {}".format( + len(successes), len(failures), len(unknowns) ) - return "There is some sadness." if sadness > 0 else "Happy Day!" def create_report_tables(repo_to_records, file=sys.stdout): From 68df97fae257a9d32d0db52e7ea4c60245c0ce6a Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Wed, 6 Jan 2021 10:48:43 +0000 Subject: [PATCH 8/9] Counter is not used anymore --- check-cron/report.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/check-cron/report.py b/check-cron/report.py index 2387224..21e10dd 100644 --- a/check-cron/report.py +++ b/check-cron/report.py @@ -1,4 +1,3 @@ -from collections import Counter import datetime from tabulate import tabulate import os @@ -189,8 +188,6 @@ def get_short_summary(repo_to_records): unknowns = set() for repo, records in repo_to_records.items(): - counter = Counter(record["conclusion"] for record in records) - failed_or_errorred = (len(records) - counter["success"]) > 0 if not records: unknowns.add(repo) From 11a0bc17cc16d060b214f47444d35cdd80ae45f9 Mon Sep 17 00:00:00 2001 From: Kit Yan Choi Date: Thu, 21 Jan 2021 09:16:04 +0000 Subject: [PATCH 9/9] Regenerate results by running the script --- check-cron/results.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/check-cron/results.md b/check-cron/results.md index bec0713..0407eb4 100644 --- a/check-cron/results.md +++ b/check-cron/results.md @@ -1,8 +1,8 @@ -Generated on: 2021-01-06 +Generated on: 2021-01-21 Include runs triggered by: schedule -Include runs no older than: 7 days, 0:00:00 +Include runs in the last: 7 days, 0:00:00 **enthought/traits** @@ -10,15 +10,18 @@ Include runs no older than: 7 days, 0:00:00 **enthought/traitsui** | Name | Conclusion | Updated | URL | |--------------------|--------------|------------------------------------|-------------------------------------------------------------------------| -| ETS from source | ✅ success | 2020-12-31T01:05:44Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453675596) | -| Test with Traits 6 | ✅ success | 2020-12-31T01:03:58Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453674736) | -| Integration tests | ✅ success | 2020-12-31T00:35:22Z (6 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/453639538) | +| Test with PyPI | ✅ success | 2021-01-21T03:56:35Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/500208512) | +| ETS from source | ✅ success | 2021-01-21T01:31:23Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/500004015) | +| Test with Traits 6 | ✅ success | 2021-01-21T01:28:50Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/500002737) | +| Integration tests | ✅ success | 2021-01-21T00:56:15Z (0 days ago) | [Details](https://github.com/enthought/traitsui/actions/runs/499951488) | **enthought/pyface** **enthought/enable** - +| Name | Conclusion | Updated | URL | +|-----------------|--------------|------------------------------------|-----------------------------------------------------------------------| +| ETS from source | ✅ success | 2021-01-15T01:20:51Z (6 days ago) | [Details](https://github.com/enthought/enable/actions/runs/486816393) | **enthought/chaco**