Skip to content

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

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

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

Workflow file for this run

name: Manual Test (Issues → Project)
on:
issues:
types: [opened, edited]
# the job writes to issues & projects, so default perms are tightened
permissions:
contents: read
issues: write
jobs:
process-manual-test:
runs-on: ubuntu-latest
steps:
# ─────────────────────────────── 1. Checkout ──────────────────────────────
- name: Checkout
uses: actions/checkout@v3
# ───────────────────────────── 2. Parse form ──────────────────────────────
- name: Parse issue form
id: parse # ← identifier used later
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 ───────────────────────────
- name: Build field set
id: fields
uses: actions/github-script@v6
with:
github-token: ${{ secrets.PROJECTS_TOKEN }}
script: |
const fs = require('fs');
const path = require('path');
// ——— helper ———
function pick(obj, key){
const v = obj[key];
return Array.isArray(v) ? v[0] ?? '' : (v ?? '');
}
// ---------- load parsed JSON from previous step ----------
const form = JSON.parse(process.env.FORM_JSON);
const alias = pick(form,'select_a_test');
const lvVersion = pick(form,'labview_version_used');
const lvBitness = pick(form,'labview_bitness');
const osUsed = pick(form,'operating_system');
const testResult = pick(form,'test_result');
const notes = form.notes_or_screenshots_optional ?? '';
if (!alias || !lvVersion || !lvBitness || !osUsed || !testResult)
core.setFailed('Required form value missing.');
if (!['Passed','Failed','Needs Review'].includes(testResult))
core.setFailed(`Invalid Test Result: ${testResult}`);
// ---------- read markdown table inside template ----------
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))
core.setFailed('manual-test-report.yml not found in repository.');
const lines = fs.readFileSync(tplPath,'utf8')
.split('\n')
.map(l=>l.trimStart())
.filter(l=>l.startsWith('|')); // only table rows
const key = alias.replace(/_/g,' ').toLowerCase();
let numeric='', estimate='';
for (const r of lines){
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();
const m = cols[3].match(/\/([0-9]{7})\.md\)?$/);
numeric = m?.[1] ?? '';
break;
}
if (!numeric) core.setFailed(`No table row found for “${alias}”`);
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.form-json }}
# ────────── 4. Get previous test’s End Date (step id: prev) – token fixed ──
- name: Get previous test's end date
id: prev
uses: actions/github-script@v6
with:
github-token: ${{ secrets.PROJECTS_TOKEN }} # ← was missing
script: |
const {owner,repo} = context.repo;
const curIssue = +process.env.CUR_NUMBER;
const author = context.payload.issue.user.login;
const queryStr = `repo:${owner}/${repo} label:manual-test author:${author} ` +
`in:title "[Manual Test]" sort:created-desc`;
const res = await github.graphql(`
query($q:String!){
search(query:$q, type:ISSUE, first:10){
nodes{
... on Issue{
number
projectItems(first:50){
nodes{
fieldValues(first:50){
nodes{
... on ProjectV2ItemFieldDateValue{
date
field{ name }
}
}
}
}
}
}
}
}
}`, {q: queryStr});
let prevEnd='';
for (const n of res.search.nodes){
if (n.number === curIssue) continue;
for (const fv of n.projectItems.nodes.flatMap(x=>x.fieldValues.nodes)){
if (fv.field?.name === 'End Date' && fv.date){ prevEnd = fv.date; break; }
}
if (prevEnd) break;
}
core.setOutput('prev_end', prevEnd);
core.info(prevEnd ? `Previous End Date: ${prevEnd}` : 'No previous End Date');
env:
CUR_NUMBER: ${{ github.event.issue.number }}
# ─────────────────────── 5. Push data to the project ───────────────────────
- name: Update project fields
uses: actions/github-script@v6
with:
github-token: ${{ secrets.PROJECTS_TOKEN }}
script: |
const {owner,repo} = context.repo;
const issueNode = context.payload.issue.node_id;
const createdDate = context.payload.issue.created_at.substring(0,19)+'Z';
// ---------- fetch existing project items ----------
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;
// ---------- create project item when none ----------
if (items.length === 0){
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}”`);
}
// ---------- 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});
}
// ---------- iterate every project item ----------
for (const it of items){
const fv = await github.graphql(`
query($id:ID!){
node(id:$id){
... on ProjectV2Item{
fieldValues(first:50){
nodes{
__typename
... on ProjectV2ItemFieldTextValue{
text
field{ id name }
}
... on ProjectV2ItemFieldDateValue{
date
field{ id name }
}
... on ProjectV2ItemFieldSingleSelectValue{
id
name
field{ id name }
}
}
}
}
}
}`, {id: it.id});
// Build a quick lookup map: field‑name → {fieldId,type,node}
const map = {};
for (const n of fv.node.fieldValues.nodes){
if (!n.field) continue; // safeguard
map[n.field.name] = {
fieldId : n.field.id,
type : n.__typename,
node : n
};
}
// End Date  — write once
if (map['End Date'] && !map['End Date'].node.date){
await setField(it.project.id, it.id, map['End Date'].fieldId,
{date: createdDate});
}
// Start Date — refresh if previous exists
if (process.env.PREV && map['Start Date']){
await setField(it.project.id, it.id, map['Start Date'].fieldId,
{date: process.env.PREV});
}
// plain text fields
const pairs = [
['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 pairs){
if (map[fname])
await setField(it.project.id, it.id, map[fname].fieldId, {text: val});
}
// Test Result (single‑select)
if (map['Test Result'] &&
map['Test Result'].type === 'ProjectV2ItemFieldSingleSelectValue'){
await setField(it.project.id, it.id, map['Test Result'].fieldId, {
singleSelectOptionId: map['Test Result'].node.id
});
}
}
env:
PREV: ${{ steps.prev.outputs.prev_end }}
NUMID: ${{ steps.fields.outputs.numeric_test_id }}
EST: ${{ steps.fields.outputs.estimate_text }}
OS: ${{ steps.fields.outputs.os_used }}
LV: ${{ steps.fields.outputs.labview_version }}
BIT: ${{ steps.fields.outputs.labview_bitness }}
NOTES: ${{ steps.fields.outputs.notes }}