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 <cursoragent@cursor.com>
This commit is contained in:
parent
98dd33a20e
commit
225ca27c99
3 changed files with 67 additions and 4 deletions
|
|
@ -123,7 +123,7 @@ export function buildDetailLinesForRequestType(jiraTicket, requestTypeId, fieldM
|
||||||
const rawValue = getFieldValueFromTicket(jiraTicket, fieldDef);
|
const rawValue = getFieldValueFromTicket(jiraTicket, fieldDef);
|
||||||
const formatted = formatFieldValue(rawValue, fieldDef.format);
|
const formatted = formatFieldValue(rawValue, fieldDef.format);
|
||||||
if (formatted) {
|
if (formatted) {
|
||||||
lines.push({ label: fieldDef.label, value: formatted });
|
lines.push({ label: fieldDef.label, value: formatted, format: fieldDef.format });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,21 @@ function buildPlainTextHeader(jiraTicket) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLongDetailValue(line) {
|
||||||
|
if (line.format === 'textarea') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return line.value.includes('\n') || line.value.length > 120;
|
||||||
|
}
|
||||||
|
|
||||||
function buildDetailBlocks(detailLines) {
|
function buildDetailBlocks(detailLines) {
|
||||||
if (!detailLines.length) {
|
if (!detailLines.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const compactLines = detailLines.filter(line => !isLongDetailValue(line));
|
||||||
|
const longLines = detailLines.filter(line => isLongDetailValue(line));
|
||||||
|
|
||||||
const blocks = [
|
const blocks = [
|
||||||
{
|
{
|
||||||
type: 'TextBlock',
|
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({
|
blocks.push({
|
||||||
type: 'TextBlock',
|
type: 'TextBlock',
|
||||||
text: line.label,
|
text: `**${line.label}**`,
|
||||||
weight: 'Bolder',
|
weight: 'Bolder',
|
||||||
spacing: 'Medium',
|
spacing: 'Medium',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
|
|
@ -109,7 +130,7 @@ function buildDetailBlocks(detailLines) {
|
||||||
function formatDetailLinesPlain(detailLines) {
|
function formatDetailLinesPlain(detailLines) {
|
||||||
let text = '**Details:**\n';
|
let text = '**Details:**\n';
|
||||||
for (const line of detailLines) {
|
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;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
test/approvalCards.test.js
Normal file
42
test/approvalCards.test.js
Normal file
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue