queue: add a build status column
This commit is contained in:
@@ -39,6 +39,11 @@ CYGWIN_VERSION_CONFIG = [
|
||||
"", "")
|
||||
]
|
||||
|
||||
BUILD_STATUS_CONFIG = [
|
||||
("https://github.com/msys2/msys2-autobuild/releases/download/status/status.json",
|
||||
"", "")
|
||||
]
|
||||
|
||||
UPDATE_INTERVAL = 60 * 5
|
||||
REQUEST_TIMEOUT = 60
|
||||
CACHE_LOCAL = False
|
||||
|
||||
@@ -170,6 +170,9 @@ class ArchMapping:
|
||||
self.skipped = set(json_object.get("skipped", []))
|
||||
|
||||
|
||||
BuildStatus = Dict[str, Dict[str, Dict[str, str]]]
|
||||
|
||||
|
||||
class AppState:
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -183,6 +186,7 @@ class AppState:
|
||||
self._arch_versions: Dict[str, Tuple[str, str, int]] = {}
|
||||
self._arch_mapping: ArchMapping = ArchMapping()
|
||||
self._cygwin_versions: CygwinVersions = {}
|
||||
self._build_status: BuildStatus = {}
|
||||
self._update_etag()
|
||||
|
||||
def _update_etag(self) -> None:
|
||||
@@ -233,6 +237,15 @@ class AppState:
|
||||
self._arch_mapping = arch_mapping
|
||||
self._update_etag()
|
||||
|
||||
@property
|
||||
def build_status(self) -> BuildStatus:
|
||||
return self._build_status
|
||||
|
||||
@build_status.setter
|
||||
def build_status(self, build_status: BuildStatus) -> None:
|
||||
self._build_status = build_status
|
||||
self._update_etag()
|
||||
|
||||
@property
|
||||
def cygwin_versions(self) -> CygwinVersions:
|
||||
return self._cygwin_versions
|
||||
|
||||
15
app/fetch.py
15
app/fetch.py
@@ -12,14 +12,14 @@ from typing import Any, Dict, Tuple, List, Set
|
||||
import httpx
|
||||
|
||||
from .appstate import state, Source, CygwinVersions, ArchMapping, get_repositories, get_arch_names, SrcInfoPackage, Package, DepType
|
||||
from .appconfig import CYGWIN_VERSION_CONFIG, REQUEST_TIMEOUT, VERSION_CONFIG, ARCH_MAPPING_CONFIG, SRCINFO_CONFIG, UPDATE_INTERVAL
|
||||
from .appconfig import CYGWIN_VERSION_CONFIG, REQUEST_TIMEOUT, VERSION_CONFIG, ARCH_MAPPING_CONFIG, SRCINFO_CONFIG, UPDATE_INTERVAL, BUILD_STATUS_CONFIG
|
||||
from .utils import version_is_newer_than, arch_version_to_msys
|
||||
from . import appconfig
|
||||
|
||||
|
||||
def get_update_urls() -> List[str]:
|
||||
urls = []
|
||||
for config in VERSION_CONFIG + SRCINFO_CONFIG + ARCH_MAPPING_CONFIG + CYGWIN_VERSION_CONFIG:
|
||||
for config in VERSION_CONFIG + SRCINFO_CONFIG + ARCH_MAPPING_CONFIG + CYGWIN_VERSION_CONFIG + BUILD_STATUS_CONFIG:
|
||||
urls.append(config[0])
|
||||
for repo in get_repositories():
|
||||
urls.append(repo.files_url)
|
||||
@@ -77,6 +77,14 @@ async def update_cygwin_versions() -> None:
|
||||
state.cygwin_versions = cygwin_versions
|
||||
|
||||
|
||||
async def update_build_status() -> None:
|
||||
print("update build status")
|
||||
url = BUILD_STATUS_CONFIG[0][0]
|
||||
print("Loading %r" % url)
|
||||
data = await get_content_cached(url, timeout=REQUEST_TIMEOUT)
|
||||
state.build_status = json.loads(data)
|
||||
|
||||
|
||||
def parse_desc(t: str) -> Dict[str, List[str]]:
|
||||
d: Dict[str, List[str]] = {}
|
||||
cat = None
|
||||
@@ -327,7 +335,8 @@ async def update_loop() -> None:
|
||||
update_arch_mapping(),
|
||||
update_cygwin_versions(),
|
||||
update_source(),
|
||||
update_sourceinfos()
|
||||
update_sourceinfos(),
|
||||
update_build_status(),
|
||||
])
|
||||
# update_arch_versions() depends on update_source()
|
||||
rounds.append([
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,6 +2,8 @@
|
||||
{% block title %}Incoming Package Updates{% endblock %}
|
||||
{% block inner_content %}
|
||||
|
||||
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">Incoming Package Updates</h4>
|
||||
@@ -11,7 +13,13 @@
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body overflow-auto">
|
||||
<table class="table table-hover table-sm">
|
||||
|
||||
<p class="text-muted">
|
||||
Packages get automatically built via <a
|
||||
href="https://github.com/msys2/msys2-autobuild#automated-build-process">msys2-autobuild</a>.
|
||||
</p>
|
||||
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Git Change Date</th>
|
||||
@@ -19,10 +27,11 @@
|
||||
<th>Repo Version</th>
|
||||
<th></th>
|
||||
<th>Git Version</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for (srcpkg, s, p) in updates %}
|
||||
{% for (srcpkg, s, p, status) in updates %}
|
||||
<tr>
|
||||
<td><a href="{{ srcpkg.history_url }}">{{ srcpkg.date }}</a></td>
|
||||
{% if s %}
|
||||
@@ -34,6 +43,24 @@
|
||||
{% endif %}
|
||||
<td>→</td>
|
||||
<td>{{ srcpkg.build_version }}</td>
|
||||
<td class="mytooltip">
|
||||
{% if status[0].url %}
|
||||
<a href="{{ status[0].url }}">{{ status[0].status }}</a>
|
||||
{% else %}
|
||||
{{ status[0].status }}
|
||||
{% endif %}
|
||||
<template class="mytooltip-content">
|
||||
<dl>
|
||||
{% for s in status %}
|
||||
<dt>{{ s.type }}</dt>
|
||||
<dd>{{ s.status }}
|
||||
{% if s.details %}
|
||||
<br>{{ s.details }}
|
||||
{% endif %}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
89
app/web.py
89
app/web.py
@@ -6,7 +6,8 @@ from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import datetime
|
||||
from typing import Callable, Any, List, Union, Dict, Optional, Tuple, Set
|
||||
from enum import Enum
|
||||
from typing import Callable, Any, List, Union, Dict, Optional, Tuple, Set, NamedTuple
|
||||
|
||||
import jinja2
|
||||
|
||||
@@ -26,6 +27,25 @@ templates = Jinja2Templates(directory=os.path.join(DIR, "templates"))
|
||||
templates.env.undefined = jinja2.StrictUndefined
|
||||
|
||||
|
||||
class PackageStatus(Enum):
|
||||
FINISHED = 'finished'
|
||||
FINISHED_BUT_BLOCKED = 'finished-but-blocked'
|
||||
FINISHED_BUT_INCOMPLETE = 'finished-but-incomplete'
|
||||
FAILED_TO_BUILD = 'failed-to-build'
|
||||
WAITING_FOR_BUILD = 'waiting-for-build'
|
||||
WAITING_FOR_DEPENDENCIES = 'waiting-for-dependencies'
|
||||
MANUAL_BUILD_REQUIRED = 'manual-build-required'
|
||||
UNKNOWN = 'unknown'
|
||||
|
||||
|
||||
PackageBuildStatus = NamedTuple('PackageBuildStatus', [
|
||||
('type', str),
|
||||
('status', str),
|
||||
('details', str),
|
||||
('url', str),
|
||||
])
|
||||
|
||||
|
||||
async def get_etag(request: Request) -> str:
|
||||
return state.etag
|
||||
|
||||
@@ -262,16 +282,77 @@ async def outofdate(request: Request, response: Response) -> Response:
|
||||
}, headers=dict(response.headers))
|
||||
|
||||
|
||||
def get_status_text(key: str) -> str:
|
||||
try:
|
||||
status = PackageStatus(key)
|
||||
except ValueError:
|
||||
return key
|
||||
if status == PackageStatus.UNKNOWN:
|
||||
return "Unknown"
|
||||
elif status == PackageStatus.FAILED_TO_BUILD:
|
||||
return "Failed to build"
|
||||
elif status == PackageStatus.FINISHED:
|
||||
return "Ready for upload"
|
||||
elif status == PackageStatus.FINISHED_BUT_BLOCKED:
|
||||
return "Ready for upload but waiting for dependencies"
|
||||
elif status == PackageStatus.FINISHED_BUT_INCOMPLETE:
|
||||
return "Ready for upload but related builds are missing"
|
||||
elif status == PackageStatus.MANUAL_BUILD_REQUIRED:
|
||||
return "Manual build required"
|
||||
elif status == PackageStatus.WAITING_FOR_BUILD:
|
||||
return "Waiting to be built"
|
||||
elif status == PackageStatus.WAITING_FOR_DEPENDENCIES:
|
||||
return "Waiting for dependencies"
|
||||
else:
|
||||
return key
|
||||
|
||||
|
||||
def get_status_priority(key: str) -> Tuple[int, str]:
|
||||
"""We want to show the most important status as the primary one"""
|
||||
|
||||
try:
|
||||
status = PackageStatus(key)
|
||||
except ValueError:
|
||||
return (-1, key)
|
||||
|
||||
order = [
|
||||
PackageStatus.UNKNOWN, PackageStatus.FINISHED, PackageStatus.FINISHED_BUT_BLOCKED,
|
||||
PackageStatus.FINISHED_BUT_INCOMPLETE, PackageStatus.WAITING_FOR_BUILD,
|
||||
PackageStatus.WAITING_FOR_DEPENDENCIES, PackageStatus.MANUAL_BUILD_REQUIRED,
|
||||
PackageStatus.FAILED_TO_BUILD]
|
||||
|
||||
try:
|
||||
return (order.index(status), key)
|
||||
except ValueError:
|
||||
return (-1, key)
|
||||
|
||||
|
||||
def get_build_status(srcinfo: SrcInfoPackage) -> List[PackageBuildStatus]:
|
||||
build_status = state.build_status
|
||||
if srcinfo.pkgbase not in build_status:
|
||||
return [PackageBuildStatus("unknown", PackageStatus.UNKNOWN.value, "", "")]
|
||||
|
||||
all_status = build_status[srcinfo.pkgbase]
|
||||
results = []
|
||||
for build_type, status in sorted(all_status.items(), key=lambda i: get_status_priority(i[1]["status"]), reverse=True):
|
||||
results.append(
|
||||
PackageBuildStatus(
|
||||
build_type, get_status_text(status.get("status", "")),
|
||||
status.get("desc", ""), status.get("url", ""))
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@router.get('/queue', dependencies=[Depends(Etag(get_etag))])
|
||||
async def queue(request: Request, response: Response) -> Response:
|
||||
# Create entries for all packages where the version doesn't match
|
||||
updates: List[Tuple[SrcInfoPackage, Optional[Source], Optional[Package]]] = []
|
||||
updates: List[Tuple[SrcInfoPackage, Optional[Source], Optional[Package], List[PackageBuildStatus]]] = []
|
||||
for s in state.sources.values():
|
||||
for k, p in sorted(s.packages.items()):
|
||||
if p.name in state.sourceinfos:
|
||||
srcinfo = state.sourceinfos[p.name]
|
||||
if version_is_newer_than(srcinfo.build_version, p.version):
|
||||
updates.append((srcinfo, s, p))
|
||||
updates.append((srcinfo, s, p, get_build_status(srcinfo)))
|
||||
break
|
||||
|
||||
# new packages
|
||||
@@ -287,7 +368,7 @@ async def queue(request: Request, response: Response) -> Response:
|
||||
for srcinfo in available.values():
|
||||
grouped[srcinfo.pkgbase] = srcinfo
|
||||
for srcinfo in grouped.values():
|
||||
updates.append((srcinfo, None, None))
|
||||
updates.append((srcinfo, None, None, get_build_status(srcinfo)))
|
||||
|
||||
updates.sort(
|
||||
key=lambda i: (i[0].date, i[0].pkgbase, i[0].pkgname),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import './style.scss';
|
||||
import 'bootstrap';
|
||||
import tippy from 'tippy.js';
|
||||
|
||||
class App {
|
||||
|
||||
@@ -15,4 +16,12 @@ class App {
|
||||
|
||||
};
|
||||
|
||||
tippy('.mytooltip', {
|
||||
allowHTML: true,
|
||||
theme: 'light-border',
|
||||
content(reference) {
|
||||
return reference.querySelector(".mytooltip-content").innerHTML;
|
||||
},
|
||||
});
|
||||
|
||||
window.App = App;
|
||||
|
||||
23
frontend/package-lock.json
generated
23
frontend/package-lock.json
generated
@@ -28,6 +28,11 @@
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.6.0.tgz",
|
||||
"integrity": "sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw=="
|
||||
},
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz",
|
||||
@@ -57,6 +62,16 @@
|
||||
"resolve": "^1.17.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-replace": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz",
|
||||
"integrity": "sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"magic-string": "^0.25.7"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
@@ -3538,6 +3553,14 @@
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
|
||||
"dev": true
|
||||
},
|
||||
"tippy.js": {
|
||||
"version": "6.2.7",
|
||||
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.2.7.tgz",
|
||||
"integrity": "sha512-k+kWF9AJz5xLQHBi3K/XlmJiyu+p9gsCyc5qZhxxGaJWIW8SMjw1R+C7saUnP33IM8gUhDA2xX//ejRSwqR0tA==",
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.4.4"
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"popper.js": "^1.16.1",
|
||||
"@rollup/plugin-commonjs": "^16.0.0",
|
||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||
"@rollup/plugin-replace": "^2.3.4",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"bootstrap": "5.0.0-alpha3",
|
||||
"node-sass": "^5.0.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"rollup": "^2.28.2",
|
||||
"rollup-plugin-postcss": "^3.1.8",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"tippy.js": "^6.2.7"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup --config",
|
||||
"dev": "rollup --config --watch"
|
||||
|
||||
@@ -3,6 +3,7 @@ import commonjs from '@rollup/plugin-commonjs';
|
||||
import {terser} from "rollup-plugin-terser";
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
const dev = process.env.ROLLUP_WATCH === 'true';
|
||||
|
||||
@@ -15,6 +16,9 @@ export default {
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs(),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
postcss({
|
||||
extract: 'index.css',
|
||||
minimize: true,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
$enable-responsive-font-sizes: true;
|
||||
@import './node_modules/bootstrap/scss/bootstrap';
|
||||
@import './node_modules/tippy.js/dist/tippy';
|
||||
@import './node_modules/tippy.js/themes/light-border';
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
Reference in New Issue
Block a user