From 225ca27c9975b9cc497335e3e8a2d834313a8051 Mon Sep 17 00:00:00 2001 From: jmcqueen Date: Tue, 28 Jul 2026 12:45:14 -0400 Subject: [PATCH] Render compact approval details in Adaptive Card FactSets. Use a two-column FactSet for short fields and keep multi-line textarea values in separate blocks. Co-authored-by: Cursor --- lib/requestTypeFields.js | 2 +- services/approvalCards.js | 27 +++++++++++++++++++++--- test/approvalCards.test.js | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 test/approvalCards.test.js diff --git a/lib/requestTypeFields.js b/lib/requestTypeFields.js index 025582c..4eef13d 100644 --- a/lib/requestTypeFields.js +++ b/lib/requestTypeFields.js @@ -123,7 +123,7 @@ export function buildDetailLinesForRequestType(jiraTicket, requestTypeId, fieldM const rawValue = getFieldValueFromTicket(jiraTicket, fieldDef); const formatted = formatFieldValue(rawValue, fieldDef.format); if (formatted) { - lines.push({ label: fieldDef.label, value: formatted }); + lines.push({ label: fieldDef.label, value: formatted, format: fieldDef.format }); } } diff --git a/services/approvalCards.js b/services/approvalCards.js index e3b4505..2f805b6 100644 --- a/services/approvalCards.js +++ b/services/approvalCards.js @@ -71,11 +71,21 @@ function buildPlainTextHeader(jiraTicket) { return text; } +function isLongDetailValue(line) { + if (line.format === 'textarea') { + return true; + } + return line.value.includes('\n') || line.value.length > 120; +} + function buildDetailBlocks(detailLines) { if (!detailLines.length) { return []; } + const compactLines = detailLines.filter(line => !isLongDetailValue(line)); + const longLines = detailLines.filter(line => isLongDetailValue(line)); + const blocks = [ { type: 'TextBlock', @@ -87,10 +97,21 @@ function buildDetailBlocks(detailLines) { }, ]; - for (const line of detailLines) { + if (compactLines.length) { + blocks.push({ + type: 'FactSet', + spacing: 'Small', + facts: compactLines.map(line => ({ + title: line.label, + value: line.value, + })), + }); + } + + for (const line of longLines) { blocks.push({ type: 'TextBlock', - text: line.label, + text: `**${line.label}**`, weight: 'Bolder', spacing: 'Medium', wrap: true, @@ -109,7 +130,7 @@ function buildDetailBlocks(detailLines) { function formatDetailLinesPlain(detailLines) { let text = '**Details:**\n'; for (const line of detailLines) { - text += `\n**${line.label}:**\n${line.value}\n`; + text += `- **${line.label}:** ${line.value.replace(/\n/g, '\n ')}\n`; } return text; } diff --git a/test/approvalCards.test.js b/test/approvalCards.test.js new file mode 100644 index 0000000..0b896c7 --- /dev/null +++ b/test/approvalCards.test.js @@ -0,0 +1,42 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { buildRequestApproval } from '../services/approvalCards.js'; + +const sampleTicket = { + issue: { + key: 'REQUEST-152413', + fields: { + status: { name: 'Pending Approval' }, + summary: 'Requesting full Jira access for Melissa Carr', + reporter: { displayName: 'Cj Nolan' }, + customfield_10010: { + requestType: { id: '209', name: 'Application Access' }, + }, + priority: { value: '4 - Medium' }, + created: '2026-07-28T20:33:16.000Z', + updated: '2026-07-28T20:33:32.000Z', + customfield_10346: 'Carr', + customfield_10314: 'Melissa', + customfield_10318: 'Store Ops-Technology', + customfield_10310: { value: 'Employee' }, + customfield_10419: 'CarrM@ae.com', + customfield_10365: { displayName: 'Cj Nolan' }, + }, + }, + transition: { transitionName: 'Manager Approval' }, + user: { displayName: 'Automation for Jira' }, +}; + +describe('buildRequestApproval', () => { + it('renders compact details in a FactSet', async () => { + const { card } = await buildRequestApproval(sampleTicket); + const factSet = card.body.find(block => block.type === 'FactSet'); + + assert.ok(factSet, 'expected a FactSet block for compact details'); + assert.ok(factSet.facts.some(fact => fact.title === 'First name' && fact.value === 'Melissa')); + assert.ok(factSet.facts.some(fact => fact.title === 'Network user name' && fact.value === 'CarrM@ae.com')); + + const labelBlocks = card.body.filter(block => block.type === 'TextBlock' && block.text === 'First name'); + assert.equal(labelBlocks.length, 0, 'compact fields should not use per-field label blocks'); + }); +});