[Manual Test] {{ test_id }} by {{ user_name }} #51
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: Manual Test Processor | |
on: | |
issues: | |
types: [opened, edited] # run on new or modified issues | |
workflow_dispatch: | |
inputs: | |
number: | |
description: 'Issue number (optional override)' | |
required: false | |
type: number | |
jobs: | |
process-manual-test: | |
runs-on: ubuntu-latest | |
steps: | |
# ------------------------------------------------ | |
# 1. Check out the repository | |
# ------------------------------------------------ | |
- name: Checkout | |
uses: actions/checkout@v3 | |
# ------------------------------------------------ | |
# 2. Parse the issue body into JSON | |
# ------------------------------------------------ | |
- name: Parse manual‑test issue | |
id: parse | |
uses: issue-ops/[email protected] | |
with: | |
body: ${{ github.event.issue.body }} | |
issue-form-template: manual-test-report.yml | |
workspace: ${{ github.workspace }} | |
# ------------------------------------------------ | |
# 3. Build field set – read the markdown table and extract ID & estimate | |
# ------------------------------------------------ | |
- name: Build field set | |
id: build | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.PROJECTS_PAT }} | |
script: | | |
const fs = require('fs'); | |
const path = require('path'); | |
const form = JSON.parse(process.env.FORM_JSON); | |
const alias = (form.select_a_test ?? [])[0] ?? ''; | |
const lvVersion = (form.labview_version_used ?? [])[0] ?? ''; | |
const lvBitness = (form.labview_bitness ?? [])[0] ?? ''; | |
const osUsed = (form.operating_system ?? [])[0] ?? ''; | |
const testResult = (form.test_result ?? [])[0] ?? ''; | |
const notes = form.notes_or_screenshots_optional ?? ''; | |
function fail(msg){ core.setFailed(msg); return; } | |
// quick validation ───────────────────────── | |
if (!alias) fail('Missing Select‑a‑Test value'); | |
if (!lvVersion) fail('Missing LabVIEW Version'); | |
if (!lvBitness) fail('Missing LabVIEW Bitness'); | |
if (!osUsed) fail('Missing Operating System'); | |
if (!testResult) fail('Missing Test Result'); | |
const valid = ['Passed','Failed','Needs Review']; | |
if (!valid.includes(testResult)) | |
fail(`Invalid Test Result: ${testResult}`); | |
// read the table inside the template file ── | |
const root = process.env.GITHUB_WORKSPACE; | |
let tplPath = path.join(root, '.github', 'ISSUE_TEMPLATE', 'manual-test-report.yml'); | |
if (!fs.existsSync(tplPath)) | |
tplPath = path.join(root, 'manual-test-report.yml'); | |
if (!fs.existsSync(tplPath)) | |
fail('Cannot locate manual-test-report.yml template file.'); | |
const templateText = fs.readFileSync(tplPath, 'utf8'); | |
const rows = templateText.split('\n') | |
.map(l => l.trimStart()) | |
.filter(l => l.startsWith('|')); | |
if (rows.length === 0) | |
fail('No markdown table found inside the issue template.'); | |
// find alias row ──────────────────────────── | |
const key = alias.replace(/_/g,' ').toLowerCase(); | |
let numeric='', estimate=''; | |
for (const r of rows){ | |
const cols = r.split('|').map(c => c.trim()); | |
if (cols.length < 4) continue; | |
if (cols[1].toLowerCase() !== key) continue; | |
estimate = cols[2].replace(/\*/g,'').trim(); | |
if (!estimate) fail(`No Est. Time for “${cols[1]}”`); | |
const m = cols[3].match(/\/([0-9]{7})\.md\)?$/); | |
if (!m) fail('Link column does not contain 7‑digit .md filename'); | |
numeric = m[1]; | |
break; | |
} | |
if (!numeric) fail(`No matching row for alias ${alias}`); | |
core.notice(`Matched "${alias}" → TestID ${numeric}, Estimate ${estimate}`); | |
// outputs ─────────────────────────────────── | |
core.setOutput('numeric_test_id', numeric); | |
core.setOutput('estimate_text', estimate); | |
core.setOutput('labview_version', lvVersion); | |
core.setOutput('labview_bitness', lvBitness); | |
core.setOutput('os_used', osUsed); | |
core.setOutput('test_result', testResult); | |
core.setOutput('notes', notes); | |
env: | |
FORM_JSON: ${{ steps.parse.outputs.json }} | |
# ------------------------------------------------ | |
# 4. Update the repository project fields | |
# ------------------------------------------------ | |
- name: Update project fields | |
uses: actions/github-script@v6 | |
with: | |
github-token: ${{ secrets.PROJECTS_PAT }} | |
script: | | |
const {owner,repo} = context.repo; | |
const issueNode = context.payload.issue.node_id; | |
core.startGroup('🔍 Locating project item(s)'); | |
// fetch project items linked to this issue | |
const q = await github.graphql(` | |
query($id:ID!){ | |
node(id:$id){ | |
... on Issue{ | |
projectItems(first:50){ nodes{ id project{id title} } } | |
} | |
} | |
}`, {id: issueNode}); | |
let items = q.node.projectItems.nodes; | |
core.info(`Found ${items.length} existing project item(s).`); | |
core.endGroup(); | |
// create a new item if none exist | |
if (items.length === 0){ | |
core.startGroup('➕ Creating project item'); | |
const pl = await github.graphql(` | |
query($o:String!,$r:String!){ | |
repository(owner:$o,name:$r){ | |
projectsV2(first:1){nodes{id title}} | |
} | |
}`, {o: owner, r: repo}); | |
const proj = pl.repository.projectsV2.nodes[0]; | |
if (!proj) core.setFailed('No repository project found.'); | |
const add = await github.graphql(` | |
mutation($p:ID!,$c:ID!){ | |
addProjectV2ItemById(input:{projectId:$p,contentId:$c}){item{id}} | |
}`, {p: proj.id, c: issueNode}); | |
items = [{id: add.addProjectV2ItemById.item.id, project: proj}]; | |
core.notice(`Created project item in “${proj.title}”`); | |
core.endGroup(); | |
} | |
// helper to update one field | |
async function setField(pid, iid, fid, value){ | |
await github.graphql(` | |
mutation($p:ID!,$i:ID!,$f:ID!,$v:ProjectV2FieldValue!){ | |
updateProjectV2ItemFieldValue(input:{ | |
projectId:$p,itemId:$i,fieldId:$f,value:$v | |
}){ projectV2Item{id} } | |
}`, {p: pid, i: iid, f: fid, v: value}); | |
} | |
core.startGroup('✏️ Updating fields'); | |
for (const it of items){ | |
core.notice(`Processing item ${it.id} in project “${it.project.title}”`); | |
const fv = await github.graphql(` | |
query($id:ID!){ | |
node(id:$id){ | |
... on ProjectV2Item{ | |
fieldValues(first:50){ | |
nodes{ | |
__typename | |
... on ProjectV2ItemFieldTextValue{ | |
text | |
field{ ... on ProjectV2FieldCommon{ id name } } | |
} | |
... on ProjectV2ItemFieldSingleSelectValue{ | |
id | |
name | |
field{ ... on ProjectV2FieldCommon{ id name } } | |
} | |
} | |
} | |
} | |
} | |
}`, {id: it.id}); | |
const map = {}; | |
for (const n of fv.node.fieldValues.nodes){ | |
if (!n.field || !n.field.name) continue; // guard against undefined | |
map[n.field.name] = { | |
fieldId : n.field.id, | |
type : n.__typename, | |
node : n | |
}; | |
} | |
core.info(`Fields present: ${Object.keys(map).join(', ')}`); | |
// plain‑text fields | |
const textPairs = [ | |
['TestID', process.env.NUMID], | |
['Estimate', process.env.EST], | |
['Operating System', process.env.OS], | |
['LabVIEW Version', process.env.LV], | |
['LabVIEW Bitness', process.env.BIT], | |
['Notes', process.env.NOTES] | |
]; | |
for (const [fname, val] of textPairs){ | |
if (map[fname]){ | |
core.info(`→ ${fname}: "${val}"`); | |
await setField(it.project.id, it.id, map[fname].fieldId, {text: val}); | |
} else { | |
core.warning(`Field “${fname}” not found in project – skipped.`); | |
} | |
} | |
// Test Result (single‑select) | |
if (map['Test Result'] && map['Test Result'].type === 'ProjectV2ItemFieldSingleSelectValue'){ | |
core.info(`→ Test Result: selecting option id ${map['Test Result'].node.id}`); | |
await setField(it.project.id, it.id, map['Test Result'].fieldId, { | |
singleSelectOptionId: map['Test Result'].node.id | |
}); | |
} else { | |
core.warning('Field “Test Result” not found or not single‑select – skipped.'); | |
} | |
} | |
core.endGroup(); | |
env: | |
NUMID: ${{ steps.build.outputs.numeric_test_id }} | |
EST: ${{ steps.build.outputs.estimate_text }} | |
OS: ${{ steps.build.outputs.os_used }} | |
LV: ${{ steps.build.outputs.labview_version }} | |
BIT: ${{ steps.build.outputs.labview_bitness }} | |
NOTES: ${{ steps.build.outputs.notes }} |