[Manual Test] {{ test_id }} by {{ user_name }} #60
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] | |
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. Check out repo so templates are available | |
# ────────────────────────────────────────────────────────────────────── | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 1 | |
# ────────────────────────────────────────────────────────────────────── | |
# 2. Parse the Issue‑Form data | |
# – we point the parser *into* the ISSUE_TEMPLATE directory | |
# ────────────────────────────────────────────────────────────────────── | |
- name: Parse issue form | |
id: parse | |
uses: issue-ops/[email protected] | |
with: | |
body: ${{ github.event.issue.body }} | |
# "manual-test-report.yml" lives inside .github/ISSUE_TEMPLATE | |
issue-form-template: manual-test-report.yml | |
# ────────────────────────────────────────────────────────────────────── | |
# 3. Build a simple field map for later steps | |
# ────────────────────────────────────────────────────────────────────── | |
- name: Build field set | |
id: build | |
shell: bash | |
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 | |
# (unchanged from the previous working file) | |
# ────────────────────────────────────────────────────────────────────── | |
- name: Update project fields | |
uses: actions/github-script@v6 | |
env: | |
ORG_LOGIN: ni | |
PROJ_NUMBER: 29 | |
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: | | |
/* ────────────────────────────────────────────────────────────── | |
GitHub‑Script: create the project item (if missing), ensure | |
every custom field exists, autogenerate select options, and | |
then write the values. Detailed logging remains enabled so | |
you can trace each API call from the Actions log. | |
─────────────────────────────────────────────────────────── */ | |
const orgLogin = process.env.ORG_LOGIN; | |
const projectNumber = parseInt(process.env.PROJ_NUMBER,10); | |
const contentId = context.payload.issue.node_id; | |
const log = (m)=>core.info(` ${m}`); | |
const warn = (m)=>core.warning(` ${m}`); | |
// 1) Locate the project | |
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.`); | |
log(`➡️ Using project “${project.title}”`); const projectId = project.id; | |
// 2) Ensure an item exists for this issue | |
async function ensureItem(){ | |
const res = await github.graphql(` | |
query($pid:ID!,$cid:ID!){ | |
node(id:$pid){ | |
... on ProjectV2{ | |
items(first:1,filterBy:{contentId:$cid}){ | |
nodes{ id } | |
} | |
} | |
} | |
}`, {pid:projectId,cid:contentId}); | |
const found = res.node.items.nodes[0]; | |
if(found){ log(`ℹ️ Found item ${found.id}`); return found.id; } | |
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 / create fields | |
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 hit = field.options.find(o=>o.name===optName); | |
if(hit) return hit.id; | |
log(`⚙️ Adding option “${optName}”`); | |
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 wanted = [ | |
{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 wanted){ | |
if(!f.v){ warn(`Skipping “${f.n}” – empty`); 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('🎉 Done'); |