Skip to content

[Manual Test] {{ test_id }} by {{ user_name }} #14

[Manual Test] {{ test_id }} by {{ user_name }}

[Manual Test] {{ test_id }} by {{ user_name }} #14

Workflow file for this run

name: Process Manual Test Issues
on:
issues:
types: [opened, edited, labeled] # we still ignore β€œunlabeled”
concurrency:
group: manual-test-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
process-manual-test:
runs-on: ubuntu-latest
steps:
# ──────────────────────────────────────────────────────────────
# 1Β Save the entire event payload (so every script has it)
# ──────────────────────────────────────────────────────────────
- name: Capture GitHub event payload
run: |
echo "GITHUB_EVENT_PAYLOAD=$(echo '${{ toJson(github.event) }}' | base64 -w0)" >> "$GITHUB_ENV"
# ──────────────────────────────────────────────────────────────
# 2Β Quick gate – title must start β€œ\[ManualΒ Test]” & have label
# ──────────────────────────────────────────────────────────────
- name: Preliminary label/title check
id: label_and_title
run: |
evt=$(echo "$GITHUB_EVENT_PAYLOAD" | base64 -d)
labels=$(echo "$evt" | jq -r '.issue.labels[].name')
title=$( echo "$evt" | 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: Stop early when skipping
if: steps.label_and_title.outputs.skip == 'true'
run: echo "⏭️ Not a Manual‑Test issue – workflow finished." && exit 0
# ──────────────────────────────────────────────────────────────
# 3Β Parse the issue body and export fields
# ──────────────────────────────────────────────────────────────
- name: Parse form fields
id: parse_body
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const {core} = global;
const payload = JSON.parse(Buffer.from(process.env.GITHUB_EVENT_PAYLOAD,'base64'));
const body = (payload.issue.body || '').trim();
// helper – ignore blank lines
const getField = label => {
const lines = body.split('\n');
for (let i = 0; i < lines.length; i++) {
if (!lines[i].includes(label)) continue;
for (let j = i + 1; j < lines.length; j++) {
const t = lines[j].trim();
if (t) return t;
}
return null;
}
return null;
};
// required fields
const map = {
test_id: 'πŸ§ͺ Select a Test',
test_result: 'βœ… Test Result',
os_used: 'πŸ–₯️ Operating System',
labview_version: '🧰 LabVIEW Version Used',
labview_bitness: 'πŸ’» LabVIEW Bitness'
};
const out = {};
for (const [k,lbl] of Object.entries(map)) {
const v = getField(lbl);
if (!v) { core.setFailed(`Missing '${lbl}'`); return; }
out[k] = v;
}
// validate test result
if (!['Passed','Failed','Needs Review'].includes(out.test_result)) {
core.setFailed(`Invalid TestΒ Result '${out.test_result}'`); return;
}
// optional notes
out.notes = getField('πŸ“ Notes or Screenshots (optional)') || '';
// surface everything as outputs
for (const [k,v] of Object.entries(out)) core.setOutput(k,v);
# ──────────────────────────────────────────────────────────────
# 4Β Extract numeric part of TestID (used later)
# ──────────────────────────────────────────────────────────────
- name: Extract numeric TestID
id: extract_id
run: |
raw="${{ steps.parse_body.outputs.test_id }}"
num=$(echo "$raw" | grep -oE '[0-9]+' || true)
if [ -z "$num" ]; then
echo "❌ Cannot derive numeric TestID from '$raw'" >&2; exit 1; fi
echo "numeric_test_id=$num" >> "$GITHUB_OUTPUT"
# ──────────────────────────────────────────────────────────────
# 5aΒ Locate previous Manual‑Test issue by the same author βœ” FIXED
# ──────────────────────────────────────────────────────────────
- name: Find previous [Manual Test] issue
id: find_previous
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const {github,context,core} = global;
const currentNum = context.issue.number; // βœ… fixed
const authorLogin = context.payload.issue.user.login; // βœ… fixed
const {owner,repo} = context.repo;
const q = `repo:${owner}/${repo} is:issue label:manual-test `
+ `author:${authorLogin} in:title "[Manual Test]"`;
const {data:{items}} = await github.rest.search.issuesAndPullRequests({
q, sort:'created', order:'desc', per_page:15 });
const prev = items.find(i => i.number !== currentNum);
if (!prev) {
core.info('No previous Manual‑Test issue for author.');
core.setOutput('prev_num',''); return;
}
core.info(`Previous issue is #${prev.number}`);
core.setOutput('prev_num', String(prev.number));
# ──────────────────────────────────────────────────────────────
# 5bΒ Read β€œEndΒ Date” from that previous issue (if any)
# ──────────────────────────────────────────────────────────────
- name: Read End Date from previous issue
id: read_prev_end
if: steps.find_previous.outputs.prev_num != ''
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const {github,core} = global;
const prevNum = parseInt(core.getInput('prev_num'),10);
const {owner,repo} = github.context.repo;
const {data:prevIssue} = await github.rest.issues.get({
owner, repo, issue_number: prevNum
});
if (!prevIssue.node_id) { core.setOutput('prev_end',''); return; }
// walk project items β†’ field β€œEndΒ Date”
const itemsQ = `
query($id:ID!){
node(id:$id){ ... on Issue {
projectItems(first:50){ nodes{ id } } } } }`;
const {node} = await github.graphql(itemsQ,{id:prevIssue.node_id});
let found='';
for (const it of node.projectItems.nodes){
const fvQ=`
query($id:ID!){
node(id:$id){ ... on ProjectV2Item {
fieldValues(first:50){
nodes{
__typename
... on ProjectV2ItemFieldDateValue{
field{ ... on ProjectV2FieldCommon{ name id } }
date
}
}
}
}}
}`;
const {node:item} = await github.graphql(fvQ,{id:it.id});
for (const f of item.fieldValues.nodes){
if (f.__typename==='ProjectV2ItemFieldDateValue' && f.field.name==='End Date' && f.date){
found=f.date; break; }
}
if (found) break;
}
core.setOutput('prev_end',found);
# ──────────────────────────────────────────────────────────────
# 5cΒ Update all project‑fields on the *current* issue
# ──────────────────────────────────────────────────────────────
- name: Update fields on linked project item(s)
env:
NUMERIC_ID : ${{ steps.extract_id.outputs.numeric_test_id }}
PREV_END : ${{ steps.read_prev_end.outputs.prev_end }}
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 }}
ESTIMATE_NUM : "" # placeholder – calculated elsewhere
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const {github,core,context} = global;
const issueNodeId = context.payload.issue.node_id;
if (!issueNodeId){ core.notice('No node_id – cannot touch project'); return; }
// pull all project‑item IDs for this issue
const itemsQ = `
query($id:ID!){
node(id:$id){ ... on Issue {
projectItems(first:50){ nodes{ id project{ id title } } } } }
}`;
const {node} = await github.graphql(itemsQ,{id:issueNodeId});
const items = node.projectItems.nodes;
if (!items.length){ core.notice('Issue not in any project'); return; }
// helper to read field values for an item
const readFields = async 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 ProjectV2ItemFieldSingleSelectValue{
field{... on ProjectV2FieldCommon{ id name }} name }
... on ProjectV2ItemFieldDateValue{
field{... on ProjectV2FieldCommon{ id name }} date }
... on ProjectV2ItemFieldNumberValue{
field{... on ProjectV2FieldCommon{ id name }} number }
}
}
}}
}`;
const {node} = await github.graphql(q,{id:itemId});
const m={};
for (const v of node.fieldValues.nodes){
const n=v.field?.name; if(!n)continue;
let cur = v.text ?? v.name ?? v.date ?? v.number ?? '';
m[n]={id:v.field.id, type:v.__typename, current:cur};
}
return m;
};
// helper to write value
const write = async (proj,item,field,val) => {
await github.graphql(`
mutation($proj:ID!,$item:ID!,$field:ID!,$val:ProjectV2FieldValue!){
updateProjectV2ItemFieldValue(input:{
projectId:$proj,itemId:$item,fieldId:$field,value:$val}){
projectV2Item{id}
}}
`,{proj,item,field,val});
};
// data from env
const data = {
'TestID' : {type:'text', value:process.env.NUMERIC_ID},
'LabVIEW Version' : {type:'text', value:process.env.LV_VERSION},
'LabVIEW Bitness' : {type:'text', value:process.env.LV_BITNESS},
'Operating System' : {type:'text', value:process.env.OS_USED},
'Notes' : {type:'text', value:process.env.NOTES},
'Estimate' : {type:'number', value:Number(process.env.ESTIMATE_NUM||0)},
'Test Result' : {type:'single', value:process.env.TEST_RESULT},
'End Date' : {type:'date', value:context.payload.issue.created_at},
'Start Date' : {type:'date', value:process.env.PREV_END}
};
for (const it of items){
const fields = await readFields(it.id);
for (const [fname,def] of Object.entries(data)){
if (!fields[fname]) continue; // field not in project
const cur = fields[fname].current || '';
if (fname==='End Date' && cur) continue; // don't overwrite
if (fname==='Start Date' && cur) continue; // don't overwrite
if (fname==='Start Date' && !def.value) continue; // nothing to set
const valObj = (
def.type==='text' ? {text:def.value} :
def.type==='number' ? {number:def.value} :
def.type==='date' ? {date:def.value} :
/*single*/ {singleSelectValue:def.value}
);
await write(it.project.id, it.id, fields[fname].id, valObj);
}
core.info(`βœ“ Updated item in project β€œ${it.project.title}”`);
}
- name: Done
run: echo "Workflow completed βœ”"