mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-12 00:45:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb31e31bec | ||
|
|
0585326fea | ||
|
|
5bca4c38f0 |
@@ -17,11 +17,11 @@ Release archives and the image checksums are signed with cosign.
|
||||
The dashboard lists only what needs attention, grouped by project. The project with the newest problem comes first:
|
||||
|
||||
- Failing workflows, including scheduled and other background ones, with a link to the run.
|
||||
- Pull requests that wait too long or fail their checks, with links and age.
|
||||
- Stale issues, as one line per project.
|
||||
- Pull requests, one line each: new ones (`pr_fresh`), ones that wait too long (`pr_waiting`) and ones that fail their checks.
|
||||
- Open issues, one line each: new ones (`issue_fresh`) and ones without recent activity (`issue_stale`).
|
||||
- Projects that failed to refresh, or loaded only in part.
|
||||
|
||||
A side panel lists every project with its latest release (or newest tag), a link to the release, and counts of workflows, open pull requests and issues. Healthy items do not appear anywhere else.
|
||||
A side panel lists every project with its latest release (or newest tag), a link to the release, and counts of workflows, open pull requests and issues. Every open non-draft pull request and every open issue raises a warning, so a project with open work is never green.
|
||||
|
||||
### Search and filters
|
||||
|
||||
@@ -48,7 +48,9 @@ A side panel lists every project with its latest release (or newest tag), a link
|
||||
| Non-draft PR open longer than `pr_warn_after` | warn |
|
||||
| PR checks failing | warn |
|
||||
| Workflow queued or running longer than `workflow_stuck_after` | warn |
|
||||
| Open issues without activity for `issue_stale_after` (one alert per repo) | warn |
|
||||
| Non-draft PR open less than `pr_warn_after` (`pr_fresh`) | warn |
|
||||
| Open issue without activity for `issue_stale_after` (`issue_stale`, one alert per issue) | warn |
|
||||
| Open issue with recent activity (`issue_fresh`, one alert per issue) | warn |
|
||||
| A section failed to load, for example a missing App permission | warn |
|
||||
| Data older than `data_stale_after` | warn |
|
||||
|
||||
@@ -121,17 +123,17 @@ actions:
|
||||
|
||||
All metrics use the `ghtracker_` prefix. Gauges are computed at scrape time.
|
||||
|
||||
| Metric | Labels |
|
||||
| -------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `repo_up`, `repo_health` (0 ok, 1 warn, 2 crit) | repo |
|
||||
| `repo_open_issues`, `repo_open_prs`, `repo_open_prs_failing_checks`, `repo_stale_issues` | repo |
|
||||
| `repo_oldest_pr_age_seconds`, `repo_last_refresh_timestamp_seconds` | repo |
|
||||
| `pr_age_seconds`, `pr_checks_failing` | repo, number |
|
||||
| `workflow_failing`, `workflow_running`, `workflow_last_run_timestamp_seconds` | repo, workflow, background |
|
||||
| `release_info`, `release_published_timestamp_seconds` | repo, version, url |
|
||||
| `alerts` | severity |
|
||||
| `refreshes_total`, `repo_refresh_errors_total`, `last_refresh_duration_seconds`, `github_rate_limit_remaining` | none |
|
||||
| `action_requests_total` (counter; result is `ok`, `rejected` or `error`, plus `auto_*` for auto actions; only with `actions`) | action, result |
|
||||
| Metric | Labels |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
||||
| `repo_up`, `repo_health` (0 ok, 1 warn, 2 crit) | repo |
|
||||
| `repo_open_issues`, `repo_open_prs`, `repo_open_prs_failing_checks`, `repo_stale_issues` | repo |
|
||||
| `repo_oldest_pr_age_seconds`, `repo_last_refresh_timestamp_seconds` | repo |
|
||||
| `pr_age_seconds`, `pr_checks_failing` | repo, number |
|
||||
| `workflow_failing`, `workflow_running`, `workflow_last_run_timestamp_seconds` | repo, workflow, background |
|
||||
| `release_info`, `release_published_timestamp_seconds` | repo, version, url |
|
||||
| `alerts` | severity |
|
||||
| `refreshes_total`, `repo_refresh_errors_total`, `last_refresh_duration_seconds`, `github_rate_limit_remaining` | none |
|
||||
| `action_requests_total` (counter; result is `ok`, `rejected` or `error`, plus `auto_*` for auto actions; only with `actions`) | action, result |
|
||||
|
||||
Example alert rules:
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestEvaluate(t *testing.T) {
|
||||
}, map[string]model.Severity{KindWorkflowStuck: model.Warn}, model.Warn},
|
||||
"pr fresh": {func(r *model.Repo) {
|
||||
r.PRs = []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-47 * time.Hour)}}
|
||||
}, map[string]model.Severity{}, model.OK},
|
||||
}, map[string]model.Severity{KindPRFresh: model.Warn}, model.Warn},
|
||||
"pr waiting warn": {func(r *model.Repo) {
|
||||
r.PRs = []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-49 * time.Hour)}}
|
||||
}, map[string]model.Severity{KindPRWaiting: model.Warn}, model.Warn},
|
||||
@@ -79,11 +79,13 @@ func TestEvaluate(t *testing.T) {
|
||||
}, map[string]model.Severity{}, model.OK},
|
||||
"pr checks failing": {func(r *model.Repo) {
|
||||
r.PRs = []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0, Checks: model.CheckSummary{State: model.ChecksFail, Failing: []string{"build"}}}}
|
||||
}, map[string]model.Severity{KindPRChecks: model.Warn}, model.Warn},
|
||||
"issues stale aggregated": {func(r *model.Repo) {
|
||||
old := t0.Add(-40 * 24 * time.Hour)
|
||||
r.Issues = []model.Issue{{Number: 1, UpdatedAt: old}, {Number: 2, UpdatedAt: old}, {Number: 3, UpdatedAt: t0}}
|
||||
}, map[string]model.Severity{KindIssuesStale: model.Warn}, model.Warn},
|
||||
}, map[string]model.Severity{KindPRFresh: model.Warn, KindPRChecks: model.Warn}, model.Warn},
|
||||
"issue stale": {func(r *model.Repo) {
|
||||
r.Issues = []model.Issue{{Number: 1, CreatedAt: t0.Add(-60 * 24 * time.Hour), UpdatedAt: t0.Add(-40 * 24 * time.Hour)}}
|
||||
}, map[string]model.Severity{KindIssueStale: model.Warn}, model.Warn},
|
||||
"issue fresh": {func(r *model.Repo) {
|
||||
r.Issues = []model.Issue{{Number: 1, CreatedAt: t0.Add(-time.Hour), UpdatedAt: t0}}
|
||||
}, map[string]model.Severity{KindIssueFresh: model.Warn}, model.Warn},
|
||||
"archived repo skips work alerts": {func(r *model.Repo) {
|
||||
r.Archived = true
|
||||
r.PRs = []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-30 * 24 * time.Hour)}}
|
||||
@@ -125,8 +127,10 @@ func TestEvaluateDisabledThresholds(t *testing.T) {
|
||||
r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-100 * 24 * time.Hour)}},
|
||||
Issues: []model.Issue{{UpdatedAt: t0.Add(-100 * 24 * time.Hour)}}}
|
||||
off := config.Thresholds{PRWarnAfter: -1, PRCritAfter: -1, IssueStaleAfter: -1, WorkflowStuck: -1, DataStaleAfter: -1}
|
||||
if a := withoutPROpen(Evaluate(&r, t0, off)); len(a) != 0 {
|
||||
t.Fatalf("disabled checks produced %+v", a)
|
||||
for _, a := range withoutPROpen(Evaluate(&r, t0, off)) {
|
||||
if a.Kind != KindPRFresh && a.Kind != KindIssueFresh {
|
||||
t.Fatalf("disabled checks produced %+v", a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +138,11 @@ func TestAgeAdvancesWithoutRefresh(t *testing.T) {
|
||||
r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-40 * time.Hour)}}}
|
||||
cfg := th()
|
||||
cfg.DataStaleAfter = -1
|
||||
if a := withoutPROpen(Evaluate(&r, t0, cfg)); len(a) != 0 {
|
||||
t.Fatalf("unexpected %+v", a)
|
||||
if a := withoutPROpen(Evaluate(&r, t0, cfg)); len(a) != 1 || a[0].Kind != KindPRFresh {
|
||||
t.Fatalf("a PR below the warn threshold must be fresh: %+v", a)
|
||||
}
|
||||
if a := withoutPROpen(Evaluate(&r, t0.Add(10*time.Hour), cfg)); len(a) != 1 || a[0].Severity != model.Warn {
|
||||
t.Fatalf("PR must cross the warn threshold as time passes: %+v", a)
|
||||
if a := withoutPROpen(Evaluate(&r, t0.Add(10*time.Hour), cfg)); len(a) != 1 || a[0].Kind != KindPRWaiting {
|
||||
t.Fatalf("PR must turn from fresh to waiting as time passes: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,9 +494,6 @@ func TestEvaluatePROpen(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := model.Repo{FullName: "o/r", Up: true, RefreshedAt: t0, Archived: tc.archived, PRs: []model.PullRequest{tc.pr}}
|
||||
alerts := Evaluate(&r, t0, th())
|
||||
if r.Health != model.OK {
|
||||
t.Fatalf("health = %v, want ok", r.Health)
|
||||
}
|
||||
if !tc.want {
|
||||
if len(alerts) != 0 {
|
||||
t.Fatalf("alerts = %+v, want none", alerts)
|
||||
@@ -501,21 +502,31 @@ func TestEvaluatePROpen(t *testing.T) {
|
||||
}
|
||||
want := model.Alert{Severity: model.OK, Repo: "o/r", Kind: KindPROpen, Subject: "#7 fix", Detail: "open for 5h",
|
||||
URL: tc.pr.URL, Ref: "abc123", Author: "alice", Since: tc.pr.CreatedAt}
|
||||
if len(alerts) != 1 || alerts[0] != want {
|
||||
if alerts = onlyPROpen(alerts); len(alerts) != 1 || alerts[0] != want {
|
||||
t.Fatalf("alerts = %+v, want [%+v]", alerts, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPROpenDoesNotChangeCounts(t *testing.T) {
|
||||
func onlyPROpen(in []model.Alert) []model.Alert {
|
||||
var out []model.Alert
|
||||
for _, a := range in {
|
||||
if a.Kind == KindPROpen {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestPROpenHasNoSeverityOfItsOwn(t *testing.T) {
|
||||
f := &fakeFetcher{fn: func(name string) (model.Repo, error) {
|
||||
return model.Repo{FullName: name, PRs: []model.PullRequest{{Number: 1, HeadSHA: "s", CreatedAt: t0}}}, nil
|
||||
}}
|
||||
c := newCollector(f, "o/a")
|
||||
c.Refresh(context.Background())
|
||||
v := c.View()
|
||||
if len(v.Alerts) != 1 || v.Alerts[0].Kind != KindPROpen || v.Crit != 0 || v.Warn != 0 || v.Repos[0].Health != model.OK {
|
||||
if len(onlyPROpen(v.Alerts)) != 1 || v.Alerts[0].Severity != model.Warn || v.Crit != 0 || v.Warn != 1 {
|
||||
t.Fatalf("view = %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ const (
|
||||
KindWorkflowStuck = "workflow_stuck"
|
||||
KindPRWaiting = "pr_waiting"
|
||||
KindPRChecks = "pr_checks_failing"
|
||||
KindIssuesStale = "issues_stale"
|
||||
KindPRFresh = "pr_fresh"
|
||||
KindIssueStale = "issue_stale"
|
||||
KindIssueFresh = "issue_fresh"
|
||||
// KindPROpen is informational plumbing for automatic actions; it has OK severity so it never moves health or counts.
|
||||
KindPROpen = "pr_open"
|
||||
)
|
||||
@@ -71,27 +73,20 @@ func Evaluate(r *model.Repo, now time.Time, th config.Thresholds) []model.Alert
|
||||
add(model.Crit, KindPRWaiting, subject, "open "+Age(age), pr.URL, pr.CreatedAt)
|
||||
case th.PRWarnAfter > 0 && age >= th.PRWarnAfter:
|
||||
add(model.Warn, KindPRWaiting, subject, "open "+Age(age), pr.URL, pr.CreatedAt)
|
||||
default:
|
||||
add(model.Warn, KindPRFresh, subject, "new, open "+Age(age), pr.URL, pr.CreatedAt)
|
||||
}
|
||||
if pr.Checks.State == model.ChecksFail {
|
||||
add(model.Warn, KindPRChecks, subject, summarizeChecks(pr.Checks), pr.URL, pr.UpdatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
if th.IssueStaleAfter > 0 {
|
||||
var stale int
|
||||
var oldest time.Time
|
||||
for _, is := range r.Issues {
|
||||
if now.Sub(is.UpdatedAt) >= th.IssueStaleAfter {
|
||||
stale++
|
||||
if oldest.IsZero() || is.UpdatedAt.Before(oldest) {
|
||||
oldest = is.UpdatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
if stale > 0 {
|
||||
add(model.Warn, KindIssuesStale, fmt.Sprintf("%d stale issues", stale),
|
||||
"no activity for "+Age(th.IssueStaleAfter)+" or more",
|
||||
r.URL+"/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc", oldest)
|
||||
for _, is := range r.Issues {
|
||||
subject := fmt.Sprintf("#%d %s", is.Number, is.Title)
|
||||
if idle := now.Sub(is.UpdatedAt); th.IssueStaleAfter > 0 && idle >= th.IssueStaleAfter {
|
||||
add(model.Warn, KindIssueStale, subject, "no activity for "+Age(idle), is.URL, is.UpdatedAt)
|
||||
} else {
|
||||
add(model.Warn, KindIssueFresh, subject, "new, open "+Age(now.Sub(is.CreatedAt)), is.URL, is.CreatedAt)
|
||||
}
|
||||
}
|
||||
return alerts
|
||||
|
||||
@@ -138,10 +138,10 @@ func TestNewAutoRunnerNilWithoutAutoActions(t *testing.T) {
|
||||
|
||||
func TestStateCarriesPROpenRef(t *testing.T) {
|
||||
st := buildState(autoView(autoPR(1, "abc", "x", nil)), true)
|
||||
if len(st.Alerts) != 1 || st.Alerts[0].Kind != collector.KindPROpen || st.Alerts[0].Ref != "abc" || st.Alerts[0].Severity != "ok" {
|
||||
if len(st.Alerts) != 2 || st.Alerts[0].Kind != collector.KindPROpen || st.Alerts[0].Ref != "abc" || st.Alerts[0].Severity != "ok" || st.Alerts[1].Kind != collector.KindPRFresh {
|
||||
t.Fatalf("alerts = %+v", st.Alerts)
|
||||
}
|
||||
if st.Counts.Crit != 0 || st.Counts.Warn != 0 || st.Repos[0].Health != "ok" {
|
||||
t.Fatalf("counts/health changed: %+v %s", st.Counts, st.Repos[0].Health)
|
||||
if st.Repos[0].Health != "warn" {
|
||||
t.Fatalf("health = %s, want warn", st.Repos[0].Health)
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+81
-100
@@ -25,7 +25,7 @@ importers:
|
||||
version: 3.3.3(@fortawesome/[email protected])([email protected]([email protected]))
|
||||
'@lucide/vue':
|
||||
specifier: ^1.49.0
|
||||
version: 1.51.0([email protected]([email protected]))
|
||||
version: 1.52.0([email protected]([email protected]))
|
||||
'@vueuse/core':
|
||||
specifier: ^15.0.0
|
||||
version: 15.0.0([email protected]([email protected]))
|
||||
@@ -40,7 +40,7 @@ importers:
|
||||
version: 4.0.3(@vue/[email protected])([email protected])([email protected]([email protected]))
|
||||
reka-ui:
|
||||
specifier: ^2.10.5
|
||||
version: 2.10.5([email protected]([email protected]))
|
||||
version: 2.11.0([email protected]([email protected]))
|
||||
tailwind-merge:
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0
|
||||
@@ -68,7 +68,7 @@ importers:
|
||||
version: 10.12.0([email protected])
|
||||
eslint-plugin-vue:
|
||||
specifier: ^10.11.1
|
||||
version: 10.11.1(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]([email protected]([email protected])))
|
||||
version: 10.11.1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]([email protected]([email protected])))
|
||||
globals:
|
||||
specifier: ^17.12.0
|
||||
version: 17.13.0
|
||||
@@ -86,7 +86,7 @@ importers:
|
||||
version: 6.0.3
|
||||
typescript-eslint:
|
||||
specifier: ^8.70.1
|
||||
version: 8.71.0([email protected]([email protected]))([email protected])
|
||||
version: 8.71.1([email protected]([email protected]))([email protected])
|
||||
vite:
|
||||
specifier: ^8.3.1
|
||||
version: 8.3.2(@types/[email protected])([email protected])
|
||||
@@ -170,8 +170,10 @@ packages:
|
||||
'@floating-ui/[email protected]':
|
||||
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
|
||||
|
||||
'@floating-ui/vue@1.1.11':
|
||||
resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==}
|
||||
'@floating-ui/vue@2.0.1':
|
||||
resolution: {integrity: sha512-043dyEKD9TDSfz080YH2vobubgg+NIo/9Fw4RAl7sEMpD8Am1DNrrSTl/GpMBjyGHoD/FbJY2QLawY+HSQsEIA==}
|
||||
peerDependencies:
|
||||
vue: '>=3.3.0'
|
||||
|
||||
'@fontsource-variable/[email protected]':
|
||||
resolution: {integrity: sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==}
|
||||
@@ -248,8 +250,8 @@ packages:
|
||||
'@keyv/[email protected]':
|
||||
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
|
||||
|
||||
'@lucide/[email protected]1.0':
|
||||
resolution: {integrity: sha512-zMkTAMP8OsPESz/GDedsZfr3VUF2IiHlj25mBZG6Pc8A8J8BdygkE8k1HhRFXEUNzytp5DqxiBQNm3RG/U2bVg==}
|
||||
'@lucide/[email protected]2.0':
|
||||
resolution: {integrity: sha512-Gwr6c14jhY6BscoFU+B9OKsr7lg8RTAKOLgW0e61kkFwkt9Q7CZmB9AyJcvhoqapEJgRg2bFoeAcO+cqpgKzEQ==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.1'
|
||||
|
||||
@@ -490,63 +492,63 @@ packages:
|
||||
'@types/[email protected]':
|
||||
resolution: {integrity: sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==}
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.71.0
|
||||
'@typescript-eslint/parser': ^8.71.1
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: '>=4.8.4 <6.1.0'
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
resolution: {integrity: sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==}
|
||||
'@typescript-eslint/[email protected].1':
|
||||
resolution: {integrity: sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@vitejs/[email protected]':
|
||||
@@ -1242,8 +1244,8 @@ packages:
|
||||
resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
[email protected]0.5:
|
||||
resolution: {integrity: sha512-/8o0y9BThE0hcHKFg+3770tWcJh3qKdOEFxLOPb9HIajXeFoH/J530cZH62jUJunbqwBgpSBL9v7nB8LJ5pDXA==}
|
||||
[email protected]1.0:
|
||||
resolution: {integrity: sha512-ilkpm4bIAol4jwzm4tpm3WVcffyGOpqksNZL1k1qO9QmIwAA+q8jroxjkUGLEn/GddfdExFfvofWfQiKZszUeQ==}
|
||||
peerDependencies:
|
||||
vue: '>= 3.4.0'
|
||||
|
||||
@@ -1307,8 +1309,8 @@ packages:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw==}
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
@@ -1418,17 +1420,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-LwcxzeliO9fkQcpJG0PoX8X5kmAhKmH9wkpDLxNabwzkQ9Zeib2YVHwFV4pcWmMLfXVfjr/dSV+DaJ3cIPgSNA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1559,14 +1550,11 @@ snapshots:
|
||||
|
||||
'@floating-ui/[email protected]': {}
|
||||
|
||||
'@floating-ui/vue@1.1.11([email protected]([email protected]))':
|
||||
'@floating-ui/vue@2.0.1([email protected]([email protected]))':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.8.0
|
||||
'@floating-ui/utils': 0.2.12
|
||||
vue-demi: 0.14.10([email protected]([email protected]))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
vue: 3.5.43([email protected])
|
||||
|
||||
'@fontsource-variable/[email protected]': {}
|
||||
|
||||
@@ -1638,7 +1626,7 @@ snapshots:
|
||||
|
||||
'@keyv/[email protected]': {}
|
||||
|
||||
'@lucide/[email protected]1.0([email protected]([email protected]))':
|
||||
'@lucide/[email protected]2.0([email protected]([email protected]))':
|
||||
dependencies:
|
||||
vue: 3.5.43([email protected])
|
||||
|
||||
@@ -1797,14 +1785,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 26.6.4
|
||||
|
||||
'@typescript-eslint/[email protected].0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])':
|
||||
'@typescript-eslint/[email protected].1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/type-utils': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/utils': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.0
|
||||
'@typescript-eslint/parser': 8.71.1([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.71.1
|
||||
'@typescript-eslint/type-utils': 8.71.1([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/utils': 8.71.1([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.1
|
||||
eslint: 10.12.0([email protected])
|
||||
ignore: 7.0.11
|
||||
natural-compare: 1.4.0
|
||||
@@ -1813,41 +1801,41 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.0
|
||||
'@typescript-eslint/scope-manager': 8.71.1
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
'@typescript-eslint/typescript-estree': 8.71.1([email protected])
|
||||
'@typescript-eslint/visitor-keys': 8.71.1
|
||||
debug: 4.4.3
|
||||
eslint: 10.12.0([email protected])
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.0([email protected])
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.1([email protected])
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
debug: 4.4.3
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
'@typescript-eslint/[email protected].1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/visitor-keys': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
'@typescript-eslint/visitor-keys': 8.71.1
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected])':
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/utils': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
'@typescript-eslint/typescript-estree': 8.71.1([email protected])
|
||||
'@typescript-eslint/utils': 8.71.1([email protected]([email protected]))([email protected])
|
||||
debug: 4.4.3
|
||||
eslint: 10.12.0([email protected])
|
||||
ts-api-utils: 2.5.0([email protected])
|
||||
@@ -1855,14 +1843,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0': {}
|
||||
'@typescript-eslint/[email protected].1': {}
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected])':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.71.0([email protected])
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.0([email protected])
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/visitor-keys': 8.71.0
|
||||
'@typescript-eslint/project-service': 8.71.1([email protected])
|
||||
'@typescript-eslint/tsconfig-utils': 8.71.1([email protected])
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
'@typescript-eslint/visitor-keys': 8.71.1
|
||||
debug: 4.4.3
|
||||
minimatch: 10.2.6
|
||||
semver: 7.8.5
|
||||
@@ -1872,20 +1860,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected])':
|
||||
'@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1([email protected]([email protected]))
|
||||
'@typescript-eslint/scope-manager': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/scope-manager': 8.71.1
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
'@typescript-eslint/typescript-estree': 8.71.1([email protected])
|
||||
eslint: 10.12.0([email protected])
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/[email protected].0':
|
||||
'@typescript-eslint/[email protected].1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.71.0
|
||||
'@typescript-eslint/types': 8.71.1
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@vitejs/[email protected]([email protected](@types/[email protected])([email protected]))([email protected]([email protected]))':
|
||||
@@ -2129,7 +2117,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]([email protected]([email protected]))):
|
||||
[email protected](@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]([email protected]([email protected]))):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.10.1([email protected]([email protected]))
|
||||
eslint: 10.12.0([email protected])
|
||||
@@ -2140,7 +2128,7 @@ snapshots:
|
||||
vue-eslint-parser: 10.4.1([email protected]([email protected]))
|
||||
xml-name-validator: 5.0.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/parser': 8.71.1([email protected]([email protected]))([email protected])
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -2529,10 +2517,9 @@ snapshots:
|
||||
dependencies:
|
||||
hookified: 2.2.0
|
||||
|
||||
[email protected]0.5([email protected]([email protected])):
|
||||
[email protected]1.0([email protected]([email protected])):
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.8.0
|
||||
'@floating-ui/vue': 1.1.11([email protected]([email protected]))
|
||||
'@floating-ui/vue': 2.0.1([email protected]([email protected]))
|
||||
'@internationalized/date': 3.12.4
|
||||
'@internationalized/number': 3.6.8
|
||||
'@tanstack/vue-virtual': 3.13.39([email protected]([email protected]))
|
||||
@@ -2542,8 +2529,6 @@ snapshots:
|
||||
defu: 6.1.7
|
||||
ohash: 2.0.12
|
||||
vue: 3.5.43([email protected])
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
@@ -2603,12 +2588,12 @@ snapshots:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
[email protected].0([email protected]([email protected]))([email protected]):
|
||||
[email protected].1([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/[email protected].0([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/parser': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
|
||||
'@typescript-eslint/utils': 8.71.0([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/[email protected].1([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/parser': 8.71.1([email protected]([email protected]))([email protected])
|
||||
'@typescript-eslint/typescript-estree': 8.71.1([email protected])
|
||||
'@typescript-eslint/utils': 8.71.1([email protected]([email protected]))([email protected])
|
||||
eslint: 10.12.0([email protected])
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
@@ -2662,10 +2647,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]([email protected]([email protected])):
|
||||
dependencies:
|
||||
vue: 3.5.43([email protected])
|
||||
|
||||
[email protected]([email protected]([email protected])):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('AttentionRow', () => {
|
||||
it('omits the age for an unknown timestamp', () => {
|
||||
const w = mount(AttentionRow, {
|
||||
props: {
|
||||
row: row({ since: '0001-01-01T00:00:00Z', kind: 'issues_stale' }),
|
||||
row: row({ since: '0001-01-01T00:00:00Z', kind: 'issue_stale' }),
|
||||
nowMs,
|
||||
critSeconds: CRIT_SECONDS,
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ const fleet = () =>
|
||||
}),
|
||||
alert({
|
||||
repo: 'b/two',
|
||||
kind: 'issues_stale',
|
||||
kind: 'issue_stale',
|
||||
severity: 'warn',
|
||||
subject: '4 stale issues',
|
||||
url: '3',
|
||||
@@ -62,7 +62,7 @@ describe('kindGroup', () => {
|
||||
['workflow_stuck', 'workflow'],
|
||||
['pr_waiting', 'pr'],
|
||||
['pr_checks_failing', 'pr'],
|
||||
['issues_stale', 'issue'],
|
||||
['issue_stale', 'issue'],
|
||||
['refresh_failed', 'data'],
|
||||
['data_stale', 'data'],
|
||||
['partial_data', 'data'],
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export const emptyFilters = (): Filters => ({
|
||||
export function kindGroup(kind: string): KindGroup {
|
||||
if (kind.startsWith('workflow_')) return 'workflow'
|
||||
if (kind.startsWith('pr_')) return 'pr'
|
||||
if (kind === 'issues_stale') return 'issue'
|
||||
if (kind.startsWith('issue_')) return 'issue'
|
||||
return 'data'
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -14,7 +14,9 @@ export const KIND_ICON: Record<string, IconDefinition> = {
|
||||
workflow_stuck: faGears,
|
||||
pr_waiting: faCodePullRequest,
|
||||
pr_checks_failing: faCodePullRequest,
|
||||
issues_stale: faCircleDot,
|
||||
pr_fresh: faCodePullRequest,
|
||||
issue_stale: faCircleDot,
|
||||
issue_fresh: faCircleDot,
|
||||
refresh_failed: faPlugCircleXmark,
|
||||
data_stale: faClockRotateLeft,
|
||||
partial_data: faTriangleExclamation,
|
||||
|
||||
Reference in New Issue
Block a user