From 7b52b158323c3caacc73be75868821638f4290c4 Mon Sep 17 00:00:00 2001 From: Guru Date: Wed, 5 Aug 2026 03:20:09 +0100 Subject: [PATCH 1/5] ci: add under-review proposals to tracking board (#17) --- .../workflows/add-under-review-to-board.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/add-under-review-to-board.yml diff --git a/.github/workflows/add-under-review-to-board.yml b/.github/workflows/add-under-review-to-board.yml new file mode 100644 index 00000000..0c5314a1 --- /dev/null +++ b/.github/workflows/add-under-review-to-board.yml @@ -0,0 +1,67 @@ +name: Add under-review proposals to board + +# When an issue in this repo is labeled `under-review`, add it to the +# "RFP & LPrize Tracking" org project (#17) and set its Status to +# "Proposal review". The default GITHUB_TOKEN cannot write to org-level +# Projects v2, so this uses a PAT stored as the PROJECTS_PAT secret +# (classic PAT with `repo` + `project`, or a fine-grained PAT with +# Organization projects: Read & write and Issues: Read). + +on: + issues: + types: [labeled] + +permissions: + contents: read + +jobs: + add-to-proposal-review: + if: github.event.label.name == 'under-review' + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.PROJECTS_PAT }} + script: | + const ORG = 'logos-co'; + const PROJECT_NUMBER = 17; + const TARGET_STATUS = 'Proposal review'; + const issueNodeId = context.payload.issue.node_id; + + // 1. Resolve project id, Status field id, and the target option id + // (looked up by name so it survives option-id regeneration). + const meta = await github.graphql(` + query($org:String!, $num:Int!) { + organization(login:$org){ + projectV2(number:$num){ + id + field(name:"Status"){ + ... on ProjectV2SingleSelectField { id options { id name } } + } + } + } + }`, { org: ORG, num: PROJECT_NUMBER }); + + const project = meta.organization.projectV2; + const statusField = project.field; + const option = statusField.options.find(o => o.name === TARGET_STATUS); + if (!option) { core.setFailed(`No Status option named "${TARGET_STATUS}"`); return; } + + // 2. Add the issue to the project (idempotent: returns the existing + // item if it is already on the board, without duplicating). + const added = await github.graphql(` + mutation($project:ID!, $content:ID!) { + addProjectV2ItemById(input:{projectId:$project, contentId:$content}){ item { id } } + }`, { project: project.id, content: issueNodeId }); + const itemId = added.addProjectV2ItemById.item.id; + + // 3. Set Status = Proposal review. + await github.graphql(` + mutation($project:ID!, $item:ID!, $field:ID!, $opt:String!) { + updateProjectV2ItemFieldValue(input:{ + projectId:$project, itemId:$item, fieldId:$field, + value:{ singleSelectOptionId:$opt } + }){ projectV2Item { id } } + }`, { project: project.id, item: itemId, field: statusField.id, opt: option.id }); + + core.info(`Issue #${context.payload.issue.number} → "${TARGET_STATUS}".`); From 7add5162383e781ee8ec4ff0c201fa7454a487e2 Mon Sep 17 00:00:00 2001 From: Guru Date: Wed, 5 Aug 2026 03:36:23 +0100 Subject: [PATCH 2/5] ci: target 'Priority review' column (renamed from Proposal review) --- .github/workflows/add-under-review-to-board.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/add-under-review-to-board.yml b/.github/workflows/add-under-review-to-board.yml index 0c5314a1..a81ca74e 100644 --- a/.github/workflows/add-under-review-to-board.yml +++ b/.github/workflows/add-under-review-to-board.yml @@ -2,7 +2,7 @@ name: Add under-review proposals to board # When an issue in this repo is labeled `under-review`, add it to the # "RFP & LPrize Tracking" org project (#17) and set its Status to -# "Proposal review". The default GITHUB_TOKEN cannot write to org-level +# "Priority review". The default GITHUB_TOKEN cannot write to org-level # Projects v2, so this uses a PAT stored as the PROJECTS_PAT secret # (classic PAT with `repo` + `project`, or a fine-grained PAT with # Organization projects: Read & write and Issues: Read). @@ -25,7 +25,7 @@ jobs: script: | const ORG = 'logos-co'; const PROJECT_NUMBER = 17; - const TARGET_STATUS = 'Proposal review'; + const TARGET_STATUS = 'Priority review'; const issueNodeId = context.payload.issue.node_id; // 1. Resolve project id, Status field id, and the target option id From f1db7312900fdf27f64d13982bb38071c57a4466 Mon Sep 17 00:00:00 2001 From: Guru Date: Wed, 5 Aug 2026 03:48:34 +0100 Subject: [PATCH 3/5] ci: also sync 'proposal' label -> Received Proposals (no override of existing columns) --- .../workflows/add-under-review-to-board.yml | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/.github/workflows/add-under-review-to-board.yml b/.github/workflows/add-under-review-to-board.yml index a81ca74e..23fbe592 100644 --- a/.github/workflows/add-under-review-to-board.yml +++ b/.github/workflows/add-under-review-to-board.yml @@ -1,11 +1,15 @@ -name: Add under-review proposals to board +name: Sync RFP proposals to tracking board -# When an issue in this repo is labeled `under-review`, add it to the -# "RFP & LPrize Tracking" org project (#17) and set its Status to -# "Priority review". The default GITHUB_TOKEN cannot write to org-level -# Projects v2, so this uses a PAT stored as the PROJECTS_PAT secret -# (classic PAT with `repo` + `project`, or a fine-grained PAT with -# Organization projects: Read & write and Issues: Read). +# Keeps the "RFP & LPrize Tracking" org project (#17) in sync with proposal +# issues in this repo: +# * label `proposal` -> add to board as "Received Proposals" +# (ONLY if not already on the board in another +# column — never overrides an existing status) +# * label `under-review` -> move to "Priority review" (always) +# +# The default GITHUB_TOKEN cannot write to org-level Projects v2, so this uses +# a PAT stored as the PROJECTS_PAT secret (classic PAT with `repo` + `project`, +# or a fine-grained PAT with Organization projects: Read & write + Issues: Read). on: issues: @@ -15,8 +19,8 @@ permissions: contents: read jobs: - add-to-proposal-review: - if: github.event.label.name == 'under-review' + sync: + if: github.event.label.name == 'proposal' || github.event.label.name == 'under-review' runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 @@ -25,11 +29,15 @@ jobs: script: | const ORG = 'logos-co'; const PROJECT_NUMBER = 17; - const TARGET_STATUS = 'Priority review'; + const label = context.payload.label.name; const issueNodeId = context.payload.issue.node_id; + const issueNumber = context.payload.issue.number; - // 1. Resolve project id, Status field id, and the target option id - // (looked up by name so it survives option-id regeneration). + // Target column per label. + const TARGET = label === 'under-review' ? 'Priority review' : 'Received Proposals'; + + // 1. Resolve project id, Status field id, and option ids (by name, + // so this survives Projects option-id regeneration). const meta = await github.graphql(` query($org:String!, $num:Int!) { organization(login:$org){ @@ -44,18 +52,46 @@ jobs: const project = meta.organization.projectV2; const statusField = project.field; - const option = statusField.options.find(o => o.name === TARGET_STATUS); - if (!option) { core.setFailed(`No Status option named "${TARGET_STATUS}"`); return; } + const option = statusField.options.find(o => o.name === TARGET); + if (!option) { core.setFailed(`No Status option named "${TARGET}"`); return; } + + // 2. Is this issue already on the board, and does it have a status? + const existing = await github.graphql(` + query($id:ID!) { + node(id:$id){ + ... on Issue { + projectItems(first:20){ + nodes{ + id + project{ id } + fieldValueByName(name:"Status"){ + ... on ProjectV2ItemFieldSingleSelectValue { name } + } + } + } + } + } + }`, { id: issueNodeId }); + + const boardItem = (existing.node.projectItems.nodes || []) + .find(n => n.project && n.project.id === project.id); + const currentStatus = boardItem && boardItem.fieldValueByName + ? boardItem.fieldValueByName.name : null; + + // For `proposal`: never override an item that already sits in a + // column. Only place brand-new / status-less items. + if (label === 'proposal' && currentStatus) { + core.info(`#${issueNumber} already on board in "${currentStatus}" — leaving as is.`); + return; + } - // 2. Add the issue to the project (idempotent: returns the existing - // item if it is already on the board, without duplicating). + // 3. Add to the project (idempotent) and set the target status. const added = await github.graphql(` mutation($project:ID!, $content:ID!) { addProjectV2ItemById(input:{projectId:$project, contentId:$content}){ item { id } } }`, { project: project.id, content: issueNodeId }); const itemId = added.addProjectV2ItemById.item.id; - // 3. Set Status = Proposal review. await github.graphql(` mutation($project:ID!, $item:ID!, $field:ID!, $opt:String!) { updateProjectV2ItemFieldValue(input:{ @@ -64,4 +100,4 @@ jobs: }){ projectV2Item { id } } }`, { project: project.id, item: itemId, field: statusField.id, opt: option.id }); - core.info(`Issue #${context.payload.issue.number} → "${TARGET_STATUS}".`); + core.info(`#${issueNumber} → "${TARGET}".`); From 03afe9ef5e9ac61aceb51ca2bd5a419e57fd6161 Mon Sep 17 00:00:00 2001 From: Guru Date: Wed, 5 Aug 2026 03:51:06 +0100 Subject: [PATCH 4/5] ci: Received Proposals excludes issues that already have under-review --- .../workflows/add-under-review-to-board.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/add-under-review-to-board.yml b/.github/workflows/add-under-review-to-board.yml index 23fbe592..49667355 100644 --- a/.github/workflows/add-under-review-to-board.yml +++ b/.github/workflows/add-under-review-to-board.yml @@ -2,10 +2,12 @@ name: Sync RFP proposals to tracking board # Keeps the "RFP & LPrize Tracking" org project (#17) in sync with proposal # issues in this repo: -# * label `proposal` -> add to board as "Received Proposals" -# (ONLY if not already on the board in another -# column — never overrides an existing status) -# * label `under-review` -> move to "Priority review" (always) +# * label `proposal` -> add to board as "Received Proposals", but ONLY +# if the issue does NOT already carry `under-review` +# and is not already on the board in another column +# (never overrides an existing status). +# * label `under-review` -> move to "Priority review" (always). +# Net effect: "Received Proposals" = has `proposal`, not yet `under-review`. # # The default GITHUB_TOKEN cannot write to org-level Projects v2, so this uses # a PAT stored as the PROJECTS_PAT secret (classic PAT with `repo` + `project`, @@ -32,6 +34,8 @@ jobs: const label = context.payload.label.name; const issueNodeId = context.payload.issue.node_id; const issueNumber = context.payload.issue.number; + const issueLabels = (context.payload.issue.labels || []) + .map(l => (typeof l === 'string' ? l : l.name)); // Target column per label. const TARGET = label === 'under-review' ? 'Priority review' : 'Received Proposals'; @@ -78,6 +82,13 @@ jobs: const currentStatus = boardItem && boardItem.fieldValueByName ? boardItem.fieldValueByName.name : null; + // For `proposal`: it belongs in "Received Proposals" only while it + // has NOT yet been moved to review. If it already carries + // `under-review`, that path owns it — do nothing here. + if (label === 'proposal' && issueLabels.includes('under-review')) { + core.info(`#${issueNumber} already has under-review — not a Received Proposal.`); + return; + } // For `proposal`: never override an item that already sits in a // column. Only place brand-new / status-less items. if (label === 'proposal' && currentStatus) { From 8445722b66677a9147798ae72279bc18ca3f51c5 Mon Sep 17 00:00:00 2001 From: Guru Date: Wed, 5 Aug 2026 04:06:07 +0100 Subject: [PATCH 5/5] ci: auto-route 'rejected' label -> Rejected proposals --- .github/workflows/add-under-review-to-board.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/add-under-review-to-board.yml b/.github/workflows/add-under-review-to-board.yml index 49667355..bc760cb5 100644 --- a/.github/workflows/add-under-review-to-board.yml +++ b/.github/workflows/add-under-review-to-board.yml @@ -7,6 +7,7 @@ name: Sync RFP proposals to tracking board # and is not already on the board in another column # (never overrides an existing status). # * label `under-review` -> move to "Priority review" (always). +# * label `rejected` -> move to "Rejected proposals" (always; terminal). # Net effect: "Received Proposals" = has `proposal`, not yet `under-review`. # # The default GITHUB_TOKEN cannot write to org-level Projects v2, so this uses @@ -22,7 +23,7 @@ permissions: jobs: sync: - if: github.event.label.name == 'proposal' || github.event.label.name == 'under-review' + if: github.event.label.name == 'proposal' || github.event.label.name == 'under-review' || github.event.label.name == 'rejected' runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 @@ -38,7 +39,10 @@ jobs: .map(l => (typeof l === 'string' ? l : l.name)); // Target column per label. - const TARGET = label === 'under-review' ? 'Priority review' : 'Received Proposals'; + const TARGET = + label === 'under-review' ? 'Priority review' + : label === 'rejected' ? 'Rejected proposals' + : 'Received Proposals'; // 1. Resolve project id, Status field id, and option ids (by name, // so this survives Projects option-id regeneration).