[Manual Test] Create_new_instance_of_-Actor_Framework-_project #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: "Process Manual Test Issues" | |
on: | |
issues: | |
types: [opened, edited, labeled] # deliberately not listening for “unlabeled” | |
concurrency: | |
group: manual-test-${{ github.event.issue.number }} | |
cancel-in-progress: true | |
jobs: | |
process-manual-test: | |
runs-on: ubuntu-latest | |
steps: | |
################################################################### | |
# 1 Capture the full event so later steps never hit “undefined…” | |
################################################################### | |
- name: Capture event payload | |
run: | | |
echo "GITHUB_EVENT_PAYLOAD=$(echo '${{ toJson(github.event) }}' | base64 -w0)" >> "$GITHUB_ENV" | |
################################################################### | |
# 2 Early‑exit if not a genuine Manual‑Test issue | |
################################################################### | |
- name: Check label & title | |
id: gate | |
run: | | |
EVENT_JSON=$(echo "$GITHUB_EVENT_PAYLOAD" | base64 -d) | |
LABELS=$(echo "$EVENT_JSON" | jq -r '.issue.labels[].name') | |
TITLE=$(echo "$EVENT_JSON" | jq -r '.issue.title') | |
if ! echo "$LABELS" | grep -xq 'manual-test'; then | |
echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0 | |
fi | |
if [[ "$TITLE" != "[Manual Test]"* ]]; then | |
echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0 | |
fi | |
echo "skip=false" >> "$GITHUB_OUTPUT" | |
- name: Abort early | |
if: steps.gate.outputs.skip == 'true' | |
run: echo "⏭️ Not a Manual‑Test issue – skipping." && exit 0 | |
################################################################### | |
# 3 Parse the form fields in the issue body | |
################################################################### | |
- name: Parse form fields | |
id: parse_body | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const {core, context, github} = global; | |
const payload = JSON.parse(Buffer.from(process.env.GITHUB_EVENT_PAYLOAD, 'base64').toString()); | |
const body = (payload.issue.body || '').trim(); | |
// helper ────────────────────────────────────────────── | |
function getField(label) { | |
const lines = body.split('\n'); | |
for (let i = 0; i < lines.length; i++) { | |
if (lines[i].includes(label)) { | |
let j = i + 1; | |
while (j < lines.length && !lines[j].trim()) j++; // skip blanks | |
return lines[j] ? lines[j].trim() : null; | |
} | |
} | |
return null; | |
} | |
// map of our field labels | |
const map = { | |
test_id: '🧪 Select a Test', | |
labview_version: '🧰 LabVIEW Version Used', | |
labview_bitness: '💻 LabVIEW Bitness', | |
os_used: '🖥️ Operating System', | |
test_result: '✅ Test Result', | |
notes: '📝 Notes or Screenshots (optional)', | |
estimate: 'Estimate', | |
start_date: 'Start Date', | |
end_date: 'End Date' | |
}; | |
const optional = new Set(['notes','estimate','start_date','end_date']); | |
const out = {}; | |
for (const [key, label] of Object.entries(map)) { | |
const val = getField(label); | |
if (!val && !optional.has(key)) { | |
core.setFailed(`Missing required field '${label}'.`); return; | |
} | |
out[key] = val || ''; | |
} | |
// Validate ▸ Test Result | |
if (!['Passed','Failed','Needs Review'].includes(out.test_result)) { | |
core.setFailed(`Invalid Test Result '${out.test_result}'.`); return; | |
} | |
// Validate ▸ Estimate when supplied | |
if (out.estimate && !/^\d+(\.\d+)?$/.test(out.estimate)) | |
{ core.setFailed(`Estimate must be numeric (got '${out.estimate}').`); return; } | |
// expose outputs | |
for (const [k,v] of Object.entries(out)) core.setOutput(k, v); | |
################################################################### | |
# 4 Extract the numeric portion of TestID | |
################################################################### | |
- name: Extract numeric TestID | |
id: extract_test_id | |
run: | | |
raw="${{ steps.parse_body.outputs.test_id }}" | |
num=$(echo "$raw" | grep -oE '[0-9]+' || true) | |
[ -z "$num" ] && { echo "❌ No digits in TestID '$raw'"; exit 1; } | |
echo "numeric_test_id=$num" >> "$GITHUB_OUTPUT" | |
################################################################### | |
# 5a Find the author’s previous Manual‑Test issue (if any) | |
################################################################### | |
- name: Find previous Manual‑Test issue (same author) | |
id: find_previous | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
result-encoding: string | |
script: | | |
const {core, context, github} = global; | |
const {owner, repo} = context.repo; | |
const thisNumber = context.issue.number; | |
const author = context.payload.issue.user.login; | |
const q = | |
`repo:${owner}/${repo} is:issue label:manual-test author:${author} ` + | |
`in:title "[Manual Test]" sort:created-desc`; | |
const {data:{items}} = await github.rest.search.issuesAndPullRequests({q, per_page:10}); | |
const prior = items.find(i => i.number !== thisNumber); | |
core.setOutput('previous_issue_number', prior ? String(prior.number) : ''); | |
console.log(prior | |
? `Found previous issue #${prior.number}` | |
: 'No previous Manual‑Test issue for this author.'); | |
################################################################### | |
# 5b Read the “End Date” from that previous issue (if any) | |
################################################################### | |
- name: Read previous End Date | |
id: read_end_date | |
if: steps.find_previous.outputs.previous_issue_number != '' | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const {core, context, github} = global; | |
const prevNum = Number('${{ steps.find_previous.outputs.previous_issue_number }}'); | |
const {owner, repo} = context.repo; | |
const {data:prevIssue} = await github.rest.issues.get({owner, repo, issue_number: prevNum}); | |
const prevNode = prevIssue.node_id; | |
if (!prevNode) { core.setOutput('prev_end_date',''); return; } | |
// GraphQL → find End Date value, if any | |
const gql = ` | |
query($id:ID!){ | |
node(id:$id){ | |
... on Issue{ | |
projectItems(first:50){ | |
nodes{ | |
id | |
fieldValues(first:50){ | |
nodes{ | |
... on ProjectV2ItemFieldDateValue{ | |
field{ ... on ProjectV2FieldCommon{ name } } | |
date | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
}`; | |
const r = await github.graphql(gql,{id:prevNode}); | |
const items = r.node?.projectItems?.nodes || []; | |
let found = ''; | |
for(const it of items){ | |
const fv = it.fieldValues.nodes.find( | |
n => n.field?.name === 'End Date' && n.date); | |
if (fv) { found = fv.date; break; } | |
} | |
core.setOutput('prev_end_date', found); | |
console.log(`Previous End Date: ${found || 'not set'}`); | |
################################################################### | |
# 5c Update fields on *this* issue’s project item(s) | |
################################################################### | |
- name: Update Project fields | |
id: update_fields | |
uses: actions/github-script@v6 | |
env: | |
NUMERIC_ID: ${{ steps.extract_test_id.outputs.numeric_test_id }} | |
ESTIMATE_RAW: ${{ steps.parse_body.outputs.estimate }} | |
TEST_RESULT: ${{ steps.parse_body.outputs.test_result }} | |
OS_USED: ${{ steps.parse_body.outputs.os_used }} | |
LV_VERSION: ${{ steps.parse_body.outputs.labview_version }} | |
LV_BITNESS: ${{ steps.parse_body.outputs.labview_bitness }} | |
NOTES: ${{ steps.parse_body.outputs.notes }} | |
PREV_END_DATE: ${{ steps.read_end_date.outputs.prev_end_date }} | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const {core, context, github} = global; | |
const issue = context.payload.issue; | |
const nodeId = issue.node_id; | |
if (!nodeId) { core.notice('Issue has no node_id; cannot update project items'); return; } | |
// Helper → read field values for an item | |
async function fieldMap(itemId){ | |
const q = ` | |
query($id:ID!){ | |
node(id:$id){ | |
... on ProjectV2Item{ | |
fieldValues(first:50){ | |
nodes{ | |
__typename | |
... on ProjectV2ItemFieldTextValue{ | |
field{... on ProjectV2FieldCommon{ id name }} | |
text | |
} | |
... on ProjectV2ItemFieldNumberValue{ | |
field{... on ProjectV2FieldCommon{ id name }} | |
number | |
} | |
... on ProjectV2ItemFieldDateValue{ | |
field{... on ProjectV2FieldCommon{ id name }} | |
date | |
} | |
} | |
} | |
} | |
} | |
}`; | |
const r = await github.graphql(q,{id:itemId}); | |
const map={}; | |
for(const n of r.node.fieldValues.nodes){ | |
map[n.field.name]={id:n.field.id, type:n.__typename}; | |
} | |
return map; | |
} | |
// Helper → update a field | |
async function setField(projectId,itemId,fieldId,val){ | |
const m=`mutation($projectId:ID!,$itemId:ID!,$fieldId:ID!,$val:ProjectV2FieldValue!){ | |
updateProjectV2ItemFieldValue(input:{ | |
projectId:$projectId,itemId:$itemId,fieldId:$fieldId,value:$val | |
}){ clientMutationId }}`; | |
await github.graphql(m,{projectId,itemId,fieldId,val}); | |
} | |
// Gather issue’s project items | |
const qItems = ` | |
query($id:ID!){ | |
node(id:$id){ | |
... on Issue{ | |
projectItems(first:50){ | |
nodes{ id project{ id title } } | |
} | |
} | |
} | |
}`; | |
const rItems = await github.graphql(qItems,{id:nodeId}); | |
const items = rItems.node.projectItems.nodes; | |
if(!items.length){ core.notice('Issue not in any project'); return; } | |
for(const it of items){ | |
const fields = await fieldMap(it.id); | |
const pid = it.project.id; | |
// Text fields | |
if (fields['TestID']) await setField(pid,it.id,fields['TestID'].id, {text:process.env.NUMERIC_ID}); | |
if (fields['LabVIEW Version']) await setField(pid,it.id,fields['LabVIEW Version'].id, {text:process.env.LV_VERSION}); | |
if (fields['LabVIEW Bitness']) await setField(pid,it.id,fields['LabVIEW Bitness'].id, {text:process.env.LV_BITNESS}); | |
if (fields['Operating System']) await setField(pid,it.id,fields['Operating System'].id, {text:process.env.OS_USED}); | |
if (fields['Notes'] && process.env.NOTES) | |
await setField(pid,it.id,fields['Notes'].id, {text:process.env.NOTES}); | |
// Number field ▸ Estimate (optional) | |
if (fields['Estimate'] && process.env.ESTIMATE_RAW){ | |
const num = parseFloat(process.env.ESTIMATE_RAW); | |
if (!Number.isNaN(num)) | |
await setField(pid,it.id,fields['Estimate'].id,{number:num}); | |
} | |
// Date fields | |
const created = issue.created_at.substring(0,10); // YYYY‑MM‑DD | |
if (fields['End Date']) // set once | |
await setField(pid,it.id,fields['End Date'].id,{date:created}); | |
if (fields['Start Date'] && process.env.PREV_END_DATE) // chain tests | |
await setField(pid,it.id,fields['Start Date'].id,{date:process.env.PREV_END_DATE}); | |
// “Test Result” — store as text for portability | |
if (fields['Test Result']) | |
await setField(pid,it.id,fields['Test Result'].id,{text:process.env.TEST_RESULT}); | |
core.info(`✔ Updated project “${it.project.title}”`); | |
} | |
- name: ✅ Done | |
run: echo "Workflow completed successfully." |