diff --git a/Panel.qml b/Panel.qml index a9ab025..7306b77 100644 --- a/Panel.qml +++ b/Panel.qml @@ -379,7 +379,9 @@ Panel { PanelSectionHeader { width: parent.width - text: "OWNED REPOSITORIES " + root.displayedRepositories.length + "/" + github.repositories.length + // Driven by the fetched scope, not the setting, so it cannot claim + // to list organization repositories before a refresh brings them in. + text: (github.fetchedRepositoryScope === "owned" ? "OWNED REPOSITORIES " : "REPOSITORIES ") + root.displayedRepositories.length + "/" + github.repositories.length foreground: root.foreground fontFamily: root.fontFamily } @@ -450,7 +452,7 @@ Panel { Text { visible: root.displayedRepositories.length === 0 width: parent.width - text: github.repositories.length === 0 ? "No owned repositories loaded." : "No repositories match these filters." + text: github.repositories.length === 0 ? "No repositories loaded." : "No repositories match these filters." color: root.dim font.family: root.fontFamily font.pixelSize: Style.font.body diff --git a/README.md b/README.md index 3acadd1..0123fdb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The dashboard is ordered by urgency so the most actionable work appears first: - **Assigned issues** — keep track of open issues assigned to you - **Active GitHub Actions** — monitor queued, pending, requested, waiting, and running workflows - **Recent workflow failures** — jump directly to failed, timed-out, or action-required runs -- **Owned repositories** — browse every repository you own with open issue, open PR, star, and active workflow counts +- **Repositories** — browse the repositories you own, and optionally those you reach through an organization, with open issue, open PR, star, and active workflow counts Repository search, metric filters, and sorting make even large GitHub accounts manageable. Filter to repositories with issues, PRs, stars, or active Actions, then sort by the metric that matters. @@ -118,7 +118,7 @@ The notifications footer also carries **Mark all read**. The first click capture ## Repository dashboard -Every owned repository includes: +Every listed repository includes: - Open issue count - Open pull request count @@ -130,20 +130,40 @@ Use the filter chips to show all repositories or only repositories with a non-ze ## Settings -Configure the widget through Omarchy's bar widget settings: +Configure the widget through Omarchy's bar widget settings. Existing installations retain the narrower repository scope and bounded Actions scan: -- Refresh interval -- Include archived repositories -- Include forks -- Include review requests and issues from archived repositories -- Include review requests on drafts -- Maximum displayed repositories -- Actions scan mode: off, recent repositories, or all repositories -- Number of recent repositories to scan -- Actions request concurrency -- Failed Actions time window and maximum result count +| Setting | Default | +| --- | --- | +| Refresh interval | 900 seconds (15 minutes) | +| Include archived repositories | Off | +| Include forks | Off | +| Repository scope | **Owned** | +| Include review requests and issues from archived repositories | Off | +| Include review requests on drafts | Off | +| Maximum displayed repositories | 25 | +| Actions scan | **Recent repositories** | +| Recent repository scan limit | 15 | +| Actions request concurrency | 6 | +| Failed Actions window | 7 days | +| Maximum failed Actions | 20 | + +**Repository scope** controls both the repository dashboard and the candidate repositories for Actions scanning. **Owned and organizations** is opt-in. With the default **Recent repositories** scan, Actions requests remain capped to the 15 most recently updated repositories in that wider scope. + +**All repositories** is also opt-in and starts six paginated Actions request streams per repository on every refresh. Combining it with **Owned and organizations** can consume substantial GitHub API capacity in large organizations. Use **Recent repositories** or **Off** for a bounded scan, and increase the refresh interval when broader monitoring is required. + +Set these options from the command line after installing the plugin: + +```bash +omarchy bar set robzolkos.github repositoryScope "Owned and organizations" +omarchy bar set robzolkos.github actionScanBehavior "Recent repositories" +``` + +Restore the narrowest behavior with: -The defaults deliberately balance freshness and GitHub API usage. Accounts that need exhaustive workflow monitoring can select **All repositories**. +```bash +omarchy bar set robzolkos.github repositoryScope "Owned" +omarchy bar set robzolkos.github actionScanBehavior "Off" +``` Review requests and assigned issues from archived repositories are hidden by default because archived repositories are read-only. Review requests on draft pull requests are also hidden by default, while teams that use drafts for early feedback can include them. Each behavior has its own setting. @@ -170,7 +190,7 @@ The shell watches local plugin files, making QML iteration fast. `Service.qml` schedules an executable helper, `omarchy-github-fetch`, which calls GitHub exclusively through `gh api` and processes responses with `jq`. -- GraphQL retrieves every owned repository and exact aggregate counts. +- GraphQL retrieves every repository in the configured scope and exact aggregate counts. - REST retrieves notifications and workflow runs. - GitHub issue search retrieves review requests and assigned issues. - GraphQL search retrieves your authored pull requests together with the head commit's `statusCheckRollup`, so check state costs no extra request. diff --git a/Service.qml b/Service.qml index 0f46ec3..35d945d 100644 --- a/Service.qml +++ b/Service.qml @@ -13,6 +13,7 @@ Item { property string state: "loading" property string message: "Loading GitHub…" property string login: "" + property string fetchedRepositoryScope: "owned" property string fetchedAt: "" property var notifications: [] property int notificationsRevision: 0 @@ -83,12 +84,19 @@ Item { return text === "true" || text === "yes" || text === "on" || text === "1"; } + // Matched against the known options rather than by substring, so an option + // added later falls back to the narrower scope instead of silently widening + // it. `fetchedRepositoryScope` reports what the last payload contained. + function repositoryMode() { + return String(setting("repositoryScope", "Owned")).toLowerCase() === "owned and organizations" ? "organizations" : "owned"; + } + function actionMode() { var value = String(setting("actionScanBehavior", "Recent repositories")).toLowerCase(); if (value === "off") return "off"; - if (value.indexOf("all") === 0) + if (value === "all repositories") return "all"; return "recent"; @@ -99,7 +107,7 @@ Item { } function command() { - return [helperPath(), "--include-archived", boolSetting("includeArchived", false) ? "true" : "false", "--include-forks", boolSetting("includeForks", false) ? "true" : "false", "--include-archived-reviews", boolSetting("includeArchivedReviewRequests", false) ? "true" : "false", "--include-draft-reviews", boolSetting("includeDraftReviewRequests", false) ? "true" : "false", "--action-scan", actionMode(), "--action-repo-limit", String(intSetting("actionScanRepoLimit", 15, 5, 200)), "--concurrency", String(intSetting("actionScanConcurrency", 6, 1, 12)), "--failed-days", String(intSetting("failedActionDays", 7, 1, 30)), "--failed-limit", String(intSetting("failedActionLimit", 20, 1, 100))]; + return [helperPath(), "--include-archived", boolSetting("includeArchived", false) ? "true" : "false", "--include-forks", boolSetting("includeForks", false) ? "true" : "false", "--repository-scope", repositoryMode(), "--include-archived-reviews", boolSetting("includeArchivedReviewRequests", false) ? "true" : "false", "--include-draft-reviews", boolSetting("includeDraftReviewRequests", false) ? "true" : "false", "--action-scan", actionMode(), "--action-repo-limit", String(intSetting("actionScanRepoLimit", 15, 5, 200)), "--concurrency", String(intSetting("actionScanConcurrency", 6, 1, 12)), "--failed-days", String(intSetting("failedActionDays", 7, 1, 30)), "--failed-limit", String(intSetting("failedActionLimit", 20, 1, 100))]; } function refresh() { @@ -121,6 +129,7 @@ Item { state = String(data.state || "error"); message = String(data.message || ""); login = String(data.login || ""); + fetchedRepositoryScope = String(data.repositoryScope || "owned"); fetchedAt = String(data.fetchedAt || ""); notifications = Array.isArray(data.notifications) ? data.notifications : []; notificationsRevision++; diff --git a/manifest.json b/manifest.json index b3b23ed..d5a45ef 100644 --- a/manifest.json +++ b/manifest.json @@ -5,7 +5,7 @@ "version": "0.2.2", "author": "Rob Zolkos", "license": "MIT", - "description": "A keyboard-friendly GitHub inbox for notifications, reviews, your own pull requests, assigned issues, Actions, and owned repositories.", + "description": "A keyboard-friendly GitHub inbox for notifications, reviews, your own pull requests, assigned issues, Actions, and repositories.", "kinds": ["bar-widget"], "activation": "on-demand", "entryPoints": { @@ -22,6 +22,7 @@ "refreshIntervalSec": 900, "includeArchived": false, "includeForks": false, + "repositoryScope": "Owned", "includeArchivedReviewRequests": false, "includeDraftReviewRequests": false, "maxDisplayedRepos": 25, @@ -35,10 +36,11 @@ { "key": "refreshIntervalSec", "type": "integer", "label": "Refresh interval (seconds)", "min": 60, "max": 3600, "step": 60, "defaultValue": 900 }, { "key": "includeArchived", "type": "boolean", "label": "Include archived repositories", "defaultValue": false }, { "key": "includeForks", "type": "boolean", "label": "Include forked repositories", "defaultValue": false }, + { "key": "repositoryScope", "type": "enum", "label": "Repository scope", "options": ["Owned", "Owned and organizations"], "defaultValue": "Owned", "description": "List only repositories you own, or include organization repositories. This scope also supplies candidates for Actions scanning." }, { "key": "includeArchivedReviewRequests", "type": "boolean", "label": "Include review requests and issues from archived repositories", "defaultValue": false, "description": "An archived repository is read-only, so the request cannot be withdrawn and the work cannot be done." }, { "key": "includeDraftReviewRequests", "type": "boolean", "label": "Include review requests on drafts", "defaultValue": false, "description": "Draft pull requests are not ready for review, but some teams do ask for early feedback on them." }, { "key": "maxDisplayedRepos", "type": "integer", "label": "Maximum displayed repositories", "min": 10, "max": 500, "step": 5, "defaultValue": 25, "description": "Caps rendered repository rows while search and filtering continue to use the complete fetched list." }, - { "key": "actionScanBehavior", "type": "enum", "label": "Actions scan", "options": ["Off", "Recent repositories", "All repositories"], "defaultValue": "Recent repositories", "description": "Scan no repositories, the most recently updated repositories, or every owned repository for active and recent failed workflow runs." }, + { "key": "actionScanBehavior", "type": "enum", "label": "Actions scan", "options": ["Off", "Recent repositories", "All repositories"], "defaultValue": "Recent repositories", "description": "Scan no repositories, a capped set of recent repositories, or every repository in the configured scope. All repositories can use substantial API capacity with organization scope." }, { "key": "actionScanRepoLimit", "type": "integer", "label": "Recent repository scan limit", "min": 5, "max": 200, "step": 5, "defaultValue": 15 }, { "key": "actionScanConcurrency", "type": "integer", "label": "Actions scan concurrency", "min": 1, "max": 12, "step": 1, "defaultValue": 6 }, { "key": "failedActionDays", "type": "integer", "label": "Failed Actions window (days)", "min": 1, "max": 30, "step": 1, "defaultValue": 7 }, diff --git a/omarchy-github-fetch b/omarchy-github-fetch index 31f7ac9..8ee48d2 100755 --- a/omarchy-github-fetch +++ b/omarchy-github-fetch @@ -7,6 +7,7 @@ include_archived=false include_forks=false include_archived_reviews=false include_draft_reviews=false +repository_scope="owned" action_scan="recent" action_limit=15 concurrency=6 @@ -26,6 +27,9 @@ Usage: omarchy-github-fetch [options] archived repositories (default: false) --include-draft-reviews BOOL Include review requests on draft pull requests (default: false) + --repository-scope MODE owned, or organizations to also list the + repositories you reach through an organization + (default: owned) --action-scan MODE off, recent, or all (default: recent) --action-repo-limit N Repositories scanned in recent mode (default: 15) --concurrency N Concurrent Actions requests, 1-12 (default: 6) @@ -48,6 +52,7 @@ while (($#)); do --include-forks) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; include_forks=$2; shift 2 ;; --include-archived-reviews) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; include_archived_reviews=$2; shift 2 ;; --include-draft-reviews) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; include_draft_reviews=$2; shift 2 ;; + --repository-scope) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; repository_scope=${2,,}; shift 2 ;; --action-scan) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; action_scan=${2,,}; shift 2 ;; --action-repo-limit) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; action_limit=$2; shift 2 ;; --concurrency) [[ $# -ge 2 ]] || { echo "missing value for $1" >&2; exit 2; }; concurrency=$2; shift 2 ;; @@ -66,9 +71,13 @@ integer "$concurrency" && ((concurrency >= 1 && concurrency <= 12)) || { echo "i integer "$failed_days" && ((failed_days >= 1 && failed_days <= 30)) || { echo "invalid --failed-days" >&2; exit 2; } integer "$failed_limit" && ((failed_limit >= 1 && failed_limit <= 100)) || { echo "invalid --failed-limit" >&2; exit 2; } [[ $action_scan == off || $action_scan == recent || $action_scan == all ]] || { echo "invalid --action-scan" >&2; exit 2; } +[[ $repository_scope == owned || $repository_scope == organizations ]] || { echo "invalid --repository-scope" >&2; exit 2; } +# Interpolated into the GraphQL document, so it comes from the validated enum +# above. The argument itself never reaches the query. +if [[ $repository_scope == organizations ]]; then owner_affiliations="[OWNER,ORGANIZATION_MEMBER]"; else owner_affiliations="OWNER"; fi emit_state() { - jq -n --arg state "$1" --arg message "$2" '{schemaVersion:1,state:$state,message:$message,login:"",fetchedAt:(now|todateiso8601),notifications:[],reviewRequests:[],assignedIssues:[],myPullRequests:[],myPullRequestsTotal:0,actions:[],failedActions:[],repositories:[],warnings:[],rateLimit:null}' + jq -n --arg state "$1" --arg message "$2" --arg scope "$repository_scope" '{schemaVersion:1,state:$state,message:$message,login:"",repositoryScope:$scope,fetchedAt:(now|todateiso8601),notifications:[],reviewRequests:[],assignedIssues:[],myPullRequests:[],myPullRequestsTotal:0,actions:[],failedActions:[],repositories:[],warnings:[],rateLimit:null}' } command -v jq >/dev/null 2>&1 || { printf '%s\n' '{"schemaVersion":1,"state":"error","message":"jq is required.","notifications":[],"reviewRequests":[],"assignedIssues":[],"myPullRequests":[],"myPullRequestsTotal":0,"actions":[],"failedActions":[],"repositories":[],"warnings":[]}' ; exit 0; } @@ -232,7 +241,7 @@ cursor=null printf '[]\n' >"$tmp/repos.json" graphql_ok=true while :; do - if ! page=$(gh api graphql -f query='query($cursor:String) { viewer { login repositories(first:100,after:$cursor,ownerAffiliations:OWNER,orderBy:{field:UPDATED_AT,direction:DESC}) { nodes { name nameWithOwner url isArchived isFork stargazerCount updatedAt issues(states:OPEN){totalCount} pullRequests(states:OPEN){totalCount} } pageInfo { hasNextPage endCursor } } } rateLimit { remaining resetAt cost } }' -F cursor="$cursor" 2>"$tmp/graphql.err"); then api_error repositories "$tmp/graphql.err"; graphql_ok=false; break; fi + if ! page=$(gh api graphql -f query='query($cursor:String) { viewer { login repositories(first:100,after:$cursor,ownerAffiliations:'"$owner_affiliations"',orderBy:{field:UPDATED_AT,direction:DESC}) { nodes { name nameWithOwner url isArchived isFork stargazerCount updatedAt issues(states:OPEN){totalCount} pullRequests(states:OPEN){totalCount} } pageInfo { hasNextPage endCursor } } } rateLimit { remaining resetAt cost } }' -F cursor="$cursor" 2>"$tmp/graphql.err"); then api_error repositories "$tmp/graphql.err"; graphql_ok=false; break; fi printf '%s\n' "$page" >"$tmp/page.json" login=$(jq -r '.data.viewer.login // ""' "$tmp/page.json") jq '[.data.viewer.repositories.nodes[] | {name,nameWithOwner,url,archived:.isArchived,fork:.isFork,stars:.stargazerCount,issues:.issues.totalCount,prs:.pullRequests.totalCount,updatedAt,activeActions:0}]' "$tmp/page.json" >"$tmp/page.repos" @@ -280,4 +289,4 @@ repo_count=$(jq 'length' "$tmp/repos.json") if [[ $graphql_ok == false && $repo_count -eq 0 ]]; then state=error; message="GitHub repositories could not be loaded."; else state=ready; message=""; fi rate_remaining=$(jq -r '.remaining // 999999' "$tmp/rate.json") if { [[ $rate_remaining =~ ^[0-9]+$ ]] && ((rate_remaining<=0)); } || grep -qiE 'rate.?limit|secondary rate' "$warnings"; then state=rate-limited; message="GitHub API rate limit reached."; fi -jq -n --arg state "$state" --arg message "$message" --arg login "${login:-}" --slurpfile notifications "$tmp/notifications.json" --slurpfile reviews "$tmp/reviews.json" --slurpfile issues "$tmp/issues.json" --slurpfile mypulls "$tmp/mypulls.json" --slurpfile mypullstotal "$tmp/mypulls-total.json" --slurpfile actions "$tmp/actions.json" --slurpfile failed "$tmp/failed.json" --slurpfile repositories "$tmp/repos.json" --argjson warnings "$warnings_json" --slurpfile rate "$tmp/rate.json" '{schemaVersion:1,state:$state,message:$message,login:$login,fetchedAt:(now|todateiso8601),notifications:$notifications[0],reviewRequests:$reviews[0],assignedIssues:$issues[0],myPullRequests:$mypulls[0],myPullRequestsTotal:$mypullstotal[0],actions:$actions[0],failedActions:$failed[0],repositories:$repositories[0],warnings:$warnings,rateLimit:$rate[0]}' +jq -n --arg state "$state" --arg message "$message" --arg login "${login:-}" --arg scope "$repository_scope" --slurpfile notifications "$tmp/notifications.json" --slurpfile reviews "$tmp/reviews.json" --slurpfile issues "$tmp/issues.json" --slurpfile mypulls "$tmp/mypulls.json" --slurpfile mypullstotal "$tmp/mypulls-total.json" --slurpfile actions "$tmp/actions.json" --slurpfile failed "$tmp/failed.json" --slurpfile repositories "$tmp/repos.json" --argjson warnings "$warnings_json" --slurpfile rate "$tmp/rate.json" '{schemaVersion:1,state:$state,message:$message,login:$login,repositoryScope:$scope,fetchedAt:(now|todateiso8601),notifications:$notifications[0],reviewRequests:$reviews[0],assignedIssues:$issues[0],myPullRequests:$mypulls[0],myPullRequestsTotal:$mypullstotal[0],actions:$actions[0],failedActions:$failed[0],repositories:$repositories[0],warnings:$warnings,rateLimit:$rate[0]}' diff --git a/tests/helper-test.sh b/tests/helper-test.sh index 25182cb..66fe804 100755 --- a/tests/helper-test.sh +++ b/tests/helper-test.sh @@ -9,6 +9,7 @@ assert_jq() { jq -e "$1" <<<"$2" >/dev/null || fail "$3"; } bash -n "$HELPER" "$HELPER" --help >/dev/null if "$HELPER" --action-scan invalid >/dev/null 2>&1; then fail "invalid scan mode succeeded"; fi +if "$HELPER" --repository-scope invalid >/dev/null 2>&1; then fail "invalid repository scope succeeded"; fi if "$HELPER" --failed-days 0 >/dev/null 2>&1; then fail "invalid failed window succeeded"; fi if "$HELPER" --mark-notification-read nope >/dev/null 2>&1; then fail "invalid notification id succeeded"; fi if "$HELPER" --mark-all-read-before nope >/dev/null 2>&1; then fail "invalid last-read timestamp succeeded"; fi @@ -61,6 +62,12 @@ if [[ $1 == api && $2 == graphql ]]; then # being conflated with a pending run. cat <<'JSON' {"data":{"search":{"issueCount":2,"nodes":[{"number":7,"title":"Ship it","url":"https://github.com/octocat/hello/pull/7","updatedAt":"2026-01-05T00:00:00Z","isDraft":false,"repository":{"nameWithOwner":"octocat/hello"},"commits":{"nodes":[{"commit":{"statusCheckRollup":{"state":"FAILURE"}}}]}},{"number":9,"title":"No CI here","url":"https://github.com/octocat/quiet/pull/9","updatedAt":"2026-01-04T00:00:00Z","isDraft":true,"repository":{"nameWithOwner":"octocat/quiet"},"commits":{"nodes":[{"commit":{"statusCheckRollup":null}}]}}]}}} +JSON + exit 0 + fi + if [[ $* == *'ownerAffiliations:[OWNER,ORGANIZATION_MEMBER]'* ]]; then + cat <<'JSON' +{"data":{"viewer":{"login":"octocat","repositories":{"nodes":[{"name":"hello","nameWithOwner":"octocat/hello","url":"https://github.com/octocat/hello","isArchived":false,"isFork":false,"stargazerCount":42,"updatedAt":"2026-01-01T00:00:00Z","issues":{"totalCount":3},"pullRequests":{"totalCount":2}},{"name":"work","nameWithOwner":"acme/work","url":"https://github.com/acme/work","isArchived":false,"isFork":false,"stargazerCount":7,"updatedAt":"2026-01-06T00:00:00Z","issues":{"totalCount":4},"pullRequests":{"totalCount":5}},{"name":"old","nameWithOwner":"octocat/old","url":"https://github.com/octocat/old","isArchived":true,"isFork":false,"stargazerCount":1,"updatedAt":"2020-01-01T00:00:00Z","issues":{"totalCount":0},"pullRequests":{"totalCount":0}}],"pageInfo":{"hasNextPage":false,"endCursor":null}}},"rateLimit":{"remaining":4998,"resetAt":"2026-01-01T01:00:00Z","cost":2}}} JSON exit 0 fi @@ -118,6 +125,7 @@ assert_jq '.notifications|length == 2 and .[0].url == "https://github.com/octoca assert_jq '.reviewRequests|length == 1 and .[0].repository == "octocat/hello"' "$out" "review requests" assert_jq '(.assignedIssues|length == 1) and (.assignedIssues[0].url|endswith("/issues/8"))' "$out" "assigned issues" assert_jq '(.actions|length == 1) and (.failedActions|length == 1)' "$out" "active and failed actions separated" +assert_jq '.repositoryScope == "owned"' "$out" "default repository scope reported" assert_jq '.rateLimit.remaining == 4999 and (.warnings|length) == 0' "$out" "rate limit and warnings" assert_jq '(.myPullRequests|length == 2) and (.myPullRequests[0].id == "octocat/hello#7") and (.myPullRequests[0].checks == "FAILURE")' "$out" "authored pull requests with check rollup" assert_jq '(.myPullRequests[1].checks == "NONE") and (.myPullRequests[1].draft == true)' "$out" "missing rollup falls back to NONE" @@ -145,6 +153,18 @@ out_archived=$(PATH="$sandbox:$PATH" "$HELPER" --action-scan off --include-archi assert_jq '.myPullRequests|length == 2' "$out_archived" "authored pull requests survive the archived setting" grep -q 'author:@me' "$GH_TEST_LOG" || fail "authored pull request search did not run" if grep -q 'archived:false' "$GH_TEST_LOG"; then fail "archived filter applied despite --include-archived true"; fi +# Each scope run truncates the log first, so the greps below read only the run +# they belong to rather than an earlier one that used the other affiliation. +: >"$GH_TEST_LOG" +out_owned=$(PATH="$sandbox:$PATH" "$HELPER" --action-scan off) +assert_jq '.repositoryScope == "owned" and (.repositories|length) == 1 and ([.repositories[].nameWithOwner]|index("acme/work")|not)' "$out_owned" "owned scope excludes organization repositories" +grep -q 'ownerAffiliations:OWNER,' "$GH_TEST_LOG" || fail "default scope did not query owned repositories" +: >"$GH_TEST_LOG" +out_scoped=$(PATH="$sandbox:$PATH" "$HELPER" --action-scan off --repository-scope organizations) +assert_jq '.repositoryScope == "organizations" and (.repositories|length) == 2 and ([.repositories[].nameWithOwner]|index("acme/work") != null)' "$out_scoped" "organization scope includes organization repositories" +grep -q 'ownerAffiliations:\[OWNER,ORGANIZATION_MEMBER\],' "$GH_TEST_LOG" || fail "organization scope did not reach the query" +if grep -q 'ownerAffiliations:OWNER,' "$GH_TEST_LOG"; then fail "owned affiliation used despite the organization scope"; fi + : >"$GH_TEST_LOG" mark=$(PATH="$sandbox:$PATH" "$HELPER" --mark-notification-read 123) assert_jq '.state == "ready" and .notificationId == "123"' "$mark" "mark notification read" diff --git a/tests/panel-source-test.sh b/tests/panel-source-test.sh index ea4dac7..6ea765f 100755 --- a/tests/panel-source-test.sh +++ b/tests/panel-source-test.sh @@ -30,4 +30,9 @@ assert_contains $'function markSelectedRead() {\n if (github.loading || githu assert_contains $'PanelActionButton {\n visible: linkRow.showReadAction\n enabled: !github.loading && !github.marking' \ "notification row marking is enabled during refresh" +assert_contains 'github.fetchedRepositoryScope === "owned" ? "OWNED REPOSITORIES " : "REPOSITORIES "' \ + "the repository heading does not follow the fetched scope" +assert_contains '"No repositories loaded."' \ + "the repository empty state still claims a scope" + echo "panel source tests passed" diff --git a/tests/service-source-test.sh b/tests/service-source-test.sh index f111f7e..b76d14f 100755 --- a/tests/service-source-test.sh +++ b/tests/service-source-test.sh @@ -8,6 +8,15 @@ fail() { echo "FAIL: $*" >&2; exit 1; } assert_contains() { [[ $SERVICE_SOURCE == *"$1"* ]] || fail "$2" } +assert_contains 'String(setting("repositoryScope", "Owned")).toLowerCase() === "owned and organizations" ? "organizations" : "owned"' \ + "an unrecognised repository scope no longer falls back to the narrower one" +assert_contains '"--repository-scope", repositoryMode()' \ + "the repository scope setting is not passed to the helper" +assert_contains 'fetchedRepositoryScope = String(data.repositoryScope || "owned");' \ + "the panel cannot tell which scope the payload was fetched with" +assert_contains $'if (value === "all repositories")\n return "all";' \ + "the full Actions scan does not require an exact setting match" + assert_not_contains() { [[ $SERVICE_SOURCE != *"$1"* ]] || fail "$2" }