[Manual Test] {{ test_id }} by {{ user_name }} #57
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 Report | |
on: | |
issues: | |
types: [opened, edited] # run on creation and later edits | |
permissions: | |
contents: read | |
issues: write | |
pull-requests: write | |
jobs: | |
process-manual-test: | |
if: contains(github.event.issue.title, '[Manual Test]') | |
runs-on: ubuntu-latest | |
steps: | |
# ------------------------------------------------------------------- | |
# 1) Get the repo so the issue‑form template is available | |
# ------------------------------------------------------------------- | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 1 | |
# ------------------------------------------------------------------- | |
# 2) Parse the form data out of the issue body | |
# ------------------------------------------------------------------- | |
- name: Parse issue form | |
id: parse | |
uses: issue-ops/[email protected] | |
with: | |
body: ${{ github.event.issue.body }} | |
issue-form-template: .github/ISSUE_TEMPLATE/manual-test-report.yml | |
workspace: ${{ github.workspace }} | |
# ------------------------------------------------------------------- | |
# 3) Collect everything we want to push into the project | |
# ------------------------------------------------------------------- | |
- name: Build field set | |
id: build | |
run: | | |
{ | |
echo "test_id=${{ steps.parse.outputs.numeric_test_id }}" | |
echo "estimate=${{ steps.parse.outputs.estimate_text }}" | |
echo "lv_version=${{ steps.parse.outputs.labview_version }}" | |
echo "bitness=${{ steps.parse.outputs.labview_bitness }}" | |
echo "os_used=${{ steps.parse.outputs.os_used }}" | |
echo "test_result=${{ steps.parse.outputs.test_result }}" | |
echo "notes=${{ steps.parse.outputs.notes }}" | |
} >> "$GITHUB_OUTPUT" | |
# ------------------------------------------------------------------- | |
# 4) Update / create fields in organisation project 29 | |
# ------------------------------------------------------------------- | |
- name: Update project fields | |
uses: actions/github-script@v6 | |
env: | |
ORG_LOGIN: ni # organisation login | |
PROJ_NUMBER: 29 # project number from the URL | |
TEST_ID: ${{ steps.build.outputs.test_id }} | |
ESTIMATE: ${{ steps.build.outputs.estimate }} | |
LV_VERSION: ${{ steps.build.outputs.lv_version }} | |
BITNESS: ${{ steps.build.outputs.bitness }} | |
OS_USED: ${{ steps.build.outputs.os_used }} | |
TEST_RESULT: ${{ steps.build.outputs.test_result }} | |
NOTES: ${{ steps.build.outputs.notes }} | |
with: | |
github-token: ${{ secrets.PROJECTS_PAT }} | |
script: | | |
const orgLogin = process.env.ORG_LOGIN; | |
const projectNumber = parseInt(process.env.PROJ_NUMBER,10); | |
const contentId = context.payload.issue.node_id; | |
//---------------------------------------------------------------- | |
// Helpers | |
//---------------------------------------------------------------- | |
const log = (m)=>core.info(` ${m}`); | |
const warn = (m)=>core.warning(` ${m}`); | |
//---------------------------------------------------------------- | |
// 1) Resolve organisation‑project node‑ID | |
//---------------------------------------------------------------- | |
log(`🔍 Looking up Project #${projectNumber} in org “${orgLogin}”`); | |
const pj = await github.graphql(` | |
query($org:String!,$num:Int!){ | |
organization(login:$org){ | |
projectV2(number:$num){ id title } | |
} | |
}`, {org:orgLogin, num:projectNumber}); | |
const project = pj.organization.projectV2; | |
if(!project) core.setFailed(`Project #${projectNumber} not found.`); | |
const projectId = project.id; | |
log(`➡️ Using project “${project.title}” (ID: ${projectId})`); | |
//---------------------------------------------------------------- | |
// 2) Ensure an item exists for this issue | |
//---------------------------------------------------------------- | |
async function ensureItem(){ | |
const initial = await github.graphql(` | |
query($pid:ID!){ | |
node(id:$pid){ | |
... on ProjectV2{ | |
items(first:100){ | |
nodes{ id content{...on Issue{id}} } | |
pageInfo{hasNextPage endCursor} | |
} | |
} | |
} | |
}`, {pid:projectId}); | |
let node = initial.node; | |
let itemId = node.items.nodes.find(n => n.content?.id===contentId)?.id; | |
let cursor = node.items.pageInfo.endCursor; | |
let more = node.items.pageInfo.hasNextPage; | |
while(!itemId && more){ | |
const nxt = await github.graphql(` | |
query($pid:ID!,$after:String){ | |
node(id:$pid){ | |
... on ProjectV2{ | |
items(first:100,after:$after){ | |
nodes{ id content{...on Issue{id}} } | |
pageInfo{hasNextPage endCursor} | |
} | |
} | |
} | |
}`, {pid:projectId, after:cursor}); | |
node = nxt.node; | |
itemId = node.items.nodes.find(n => n.content?.id===contentId)?.id; | |
cursor = node.items.pageInfo.endCursor; | |
more = node.items.pageInfo.hasNextPage; | |
} | |
if(itemId){ | |
log(`ℹ️ Found existing project item ${itemId}`); | |
return itemId; | |
} | |
log('➕ Adding issue to project…'); | |
const add = await github.graphql(` | |
mutation($pid:ID!, $cid:ID!){ | |
addProjectV2ItemById(input:{projectId:$pid, contentId:$cid}){ | |
item{id} | |
} | |
}`, {pid:projectId, cid:contentId}); | |
return add.addProjectV2ItemById.item.id; | |
} | |
//---------------------------------------------------------------- | |
// 3) Fetch fields, create any missing, ensure options | |
//---------------------------------------------------------------- | |
async function fetchFields(){ | |
const res = await github.graphql(` | |
query($pid:ID!){ | |
node(id:$pid){ | |
... on ProjectV2{ | |
fields(first:100){ | |
nodes{ | |
__typename | |
... on ProjectV2FieldCommon { id name dataType } | |
... on ProjectV2SingleSelectField { | |
id name dataType | |
options { id name } | |
} | |
} | |
} | |
} | |
} | |
}`, {pid:projectId}); | |
return res.node.fields.nodes; | |
} | |
async function createField(name,type){ | |
log(`⚙️ Creating field “${name}”`); | |
const res = await github.graphql(` | |
mutation($pid:ID!,$name:String!,$type:ProjectV2CustomFieldDataType!){ | |
createProjectV2Field(input:{ | |
projectId:$pid, name:$name, dataType:$type, | |
${type==='SINGLE_SELECT' | |
? 'singleSelectOptions:[{name:"Passed"},{name:"Failed"},{name:"Needs Review"}]' | |
: ''} | |
}){ | |
field{ | |
__typename | |
... on ProjectV2FieldCommon { id name dataType } | |
... on ProjectV2SingleSelectField { | |
id name dataType | |
options { id name } | |
} | |
} | |
} | |
}`, {pid:projectId, name, type}); | |
return res.createProjectV2Field.field; | |
} | |
async function ensureOption(field,optName){ | |
if(field.dataType!=='SINGLE_SELECT') return null; | |
const found = field.options.find(o=>o.name===optName); | |
if(found) return found.id; | |
log(`⚙️ Adding option “${optName}” to “${field.name}”`); | |
const res = await github.graphql(` | |
mutation($pid:ID!,$fid:ID!,$name:String!){ | |
createProjectV2SingleSelectFieldOption(input:{ | |
projectId:$pid, fieldId:$fid, name:$name | |
}){ | |
option{id} | |
} | |
}`, {pid:projectId,fid:field.id,name:optName}); | |
return res.createProjectV2SingleSelectFieldOption.option.id; | |
} | |
async function setField(itemId,field,val){ | |
let payload; | |
if(field.dataType==='SINGLE_SELECT'){ | |
const optId = await ensureOption(field,val); | |
payload = { singleSelectOptionId: optId }; | |
}else{ | |
payload = { text: val }; | |
} | |
await github.graphql(` | |
mutation($pid:ID!,$iid:ID!,$fid:ID!,$val:ProjectV2FieldValue!){ | |
updateProjectV2ItemFieldValue(input:{ | |
projectId:$pid, itemId:$iid, fieldId:$fid, value:$val | |
}){ | |
projectV2Item{id} | |
} | |
}`, {pid:projectId,iid:itemId,fid:field.id,val:payload}); | |
log(`✅ “${field.name}” → “${val}”`); | |
} | |
//---------------------------------------------------------------- | |
// 4) Execute | |
//---------------------------------------------------------------- | |
const itemId = await ensureItem(); | |
let fields = await fetchFields(); | |
const toSet = [ | |
{n:'TestID', v:process.env.TEST_ID, t:'TEXT'}, | |
{n:'Estimate', v:process.env.ESTIMATE, t:'TEXT'}, | |
{n:'Operating System', v:process.env.OS_USED, t:'TEXT'}, | |
{n:'LabVIEW Version', v:process.env.LV_VERSION, t:'TEXT'}, | |
{n:'LabVIEW Bitness', v:process.env.BITNESS, t:'TEXT'}, | |
{n:'Notes', v:process.env.NOTES, t:'TEXT'}, | |
{n:'Test Result', v:process.env.TEST_RESULT, t:'SINGLE_SELECT'} | |
]; | |
log('✏️ Updating project fields'); | |
for(const f of toSet){ | |
if(!f.v){ warn(`Skipping “${f.n}” – empty value`); continue; } | |
let fld = fields.find(x=>x.name===f.n); | |
if(!fld){ | |
fld = await createField(f.n,f.t); | |
fields.push(fld); | |
} | |
await setField(itemId,fld,f.v); | |
} | |
log('🎉 Project update finished'); |