From d0e1e20843c65862fa04f686c3d80a0eb200777b Mon Sep 17 00:00:00 2001 From: jmcqueen Date: Tue, 28 Jul 2026 11:22:51 -0400 Subject: [PATCH] Improve Webex approval card layout and remove duplicate fields. Split the card header into separate blocks for readability and keep reporter only in the footer instead of details. Co-authored-by: Cursor --- config/requestTypeFields.json | 3 - services/approvalCards.js | 149 +++++++++++++++++++++++---------- test/requestTypeFields.test.js | 5 +- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/config/requestTypeFields.json b/config/requestTypeFields.json index b8ada40..fa5b000 100644 --- a/config/requestTypeFields.json +++ b/config/requestTypeFields.json @@ -20,7 +20,6 @@ { "path": "transition.to_status", "label": "To status" }, { "path": "user.displayName", "label": "Moved by" }, { "fieldId": "priority", "label": "Priority", "format": "option" }, - { "fieldId": "reporter", "label": "Reporter", "format": "user" }, { "fieldId": "assignee", "label": "Assignee", "format": "user" }, { "fieldId": "created", "label": "Created", "format": "datetime" }, { "fieldId": "updated", "label": "Updated", "format": "datetime" } @@ -68,7 +67,6 @@ "summaryOnly": { "description": "Forms where JSM API only exposes summary — show summary + description", "displayFields": [ - { "fieldId": "summary", "label": "Summary" }, { "fieldId": "description", "label": "Description", "format": "textarea" } ] } @@ -287,7 +285,6 @@ { "fieldId": "customfield_10335", "label": "Reason for request", "format": "textarea" }, { "fieldId": "customfield_10307", "label": "Implementation date", "format": "datetime" }, { "fieldId": "customfield_10337", "label": "Reason for implementation date", "format": "textarea" }, - { "fieldId": "summary", "label": "Summary" }, { "fieldId": "description", "label": "Detailed description", "format": "textarea" } ], "notes": "Replaces retired request type 382. Transitions 81/101 inherited from 382 config — verify on first webhook." diff --git a/services/approvalCards.js b/services/approvalCards.js index 43b47b9..7436c3d 100644 --- a/services/approvalCards.js +++ b/services/approvalCards.js @@ -1,5 +1,7 @@ import { buildDetailLinesForRequestType } from '../lib/requestTypeFields.js'; +const PORTAL_BASE_URL = 'https://aeo.atlassian.net/servicedesk/customer/portal/135'; + function appendDetailLines(detailText, lines) { for (const line of lines) { detailText += ` - **${line.label}:** ${line.value}\n`; @@ -11,20 +13,104 @@ function getRequestTypeId(jiraTicket) { return jiraTicket.issue.fields?.customfield_10010?.requestType?.id; } -function buildCardPayload({ jiraTicket, cardText, detailText, assignmentText = '' }) { +function getRequestTypeLabel(jiraTicket) { + const requestTypeField = jiraTicket.issue.fields?.customfield_10010; + if (!requestTypeField) { + return null; + } + return requestTypeField.errorMessage || requestTypeField.requestType?.name || null; +} + +function buildHeaderBlocks(jiraTicket) { + const issueKey = jiraTicket.issue.key; + const blocks = [ + { + type: 'TextBlock', + text: `[${issueKey}](${PORTAL_BASE_URL}/${issueKey})`, + wrap: true, + weight: 'Bolder', + size: 'Medium', + }, + { + type: 'TextBlock', + text: jiraTicket.issue.fields.status.name, + wrap: true, + isSubtle: true, + spacing: 'None', + }, + ]; + + const requestTypeLabel = getRequestTypeLabel(jiraTicket); + if (requestTypeLabel) { + blocks.push({ + type: 'TextBlock', + text: `**Request Type:** ${requestTypeLabel}`, + wrap: true, + spacing: 'Medium', + }); + } + + if (jiraTicket.issue.fields.summary) { + blocks.push({ + type: 'TextBlock', + text: `**Summary:** ${jiraTicket.issue.fields.summary}`, + wrap: true, + spacing: 'Small', + }); + } + + return blocks; +} + +function buildPlainTextHeader(jiraTicket) { + const issueKey = jiraTicket.issue.key; + let text = `[${issueKey}](${PORTAL_BASE_URL}/${issueKey})\n`; + text += `${jiraTicket.issue.fields.status.name}\n`; + + const requestTypeLabel = getRequestTypeLabel(jiraTicket); + if (requestTypeLabel) { + text += `\n**Request Type:** ${requestTypeLabel}\n`; + } + if (jiraTicket.issue.fields.summary) { + text += `**Summary:** ${jiraTicket.issue.fields.summary}\n`; + } + + return text; +} + +function buildCardPayload({ jiraTicket, detailText, assignmentText = '' }) { + const body = [...buildHeaderBlocks(jiraTicket)]; + + if (detailText.trim()) { + body.push({ + type: 'TextBlock', + wrap: true, + text: detailText.trim(), + separator: true, + spacing: 'Medium', + }); + } + + if (assignmentText.trim()) { + body.push({ + type: 'TextBlock', + text: assignmentText.trim(), + wrap: true, + separator: true, + spacing: 'Medium', + }); + } + const requestCard = { type: 'AdaptiveCard', $schema: 'http://adaptivecards.io/schemas/adaptive-card.json', version: '1.2', - body: [ - { type: 'TextBlock', text: cardText.trim(), wrap: true }, - { type: 'TextBlock', wrap: true, text: detailText.trim(), separator: true }, - ], + body, actions: [ { type: 'Action.OpenUrl', title: 'Open Request', - url: `https://aeo.atlassian.net/servicedesk/customer/portal/135/${jiraTicket.issue.key}`, + url: `${PORTAL_BASE_URL}/${jiraTicket.issue.key}`, }, { type: 'Action.Submit', @@ -35,17 +121,10 @@ function buildCardPayload({ jiraTicket, cardText, detailText, assignmentText = ' ], }; - if (assignmentText.trim()) { - requestCard.body.push({ - type: 'TextBlock', - text: assignmentText.trim(), - wrap: true, - separator: true, - }); + let requestText = buildPlainTextHeader(jiraTicket); + if (detailText.trim()) { + requestText += `---\n${detailText}`; } - - let requestText = cardText; - requestText += `---\n${detailText}`; if (assignmentText.trim()) { requestText += `---\n${assignmentText}`; } @@ -55,18 +134,6 @@ function buildCardPayload({ jiraTicket, cardText, detailText, assignmentText = ' export function buildChangeApproval(jiraTicket, log) { return new Promise(function (resolve) { - let cardText = ''; - cardText += `[${jiraTicket.issue.key}](https://aeo.atlassian.net/servicedesk/customer/portal/135/${jiraTicket.issue.key}) - ${jiraTicket.issue.fields.status.name}\n`; - - if (jiraTicket.issue.fields.customfield_10010) { - if (jiraTicket.issue.fields.customfield_10010.errorMessage) { - cardText += `**Request Type:** ${jiraTicket.issue.fields.customfield_10010.errorMessage}\n`; - } else { - cardText += `**Request Type:** ${jiraTicket.issue.fields.customfield_10010.requestType.name}\n`; - } - } - cardText += `**Summary:** ${jiraTicket.issue.fields.summary}\n`; - let detailText = '**Details:**\n'; if (jiraTicket.issue.fields.description) { detailText += `**Description:** ${jiraTicket.issue.fields.description}\n`; @@ -87,25 +154,17 @@ export function buildChangeApproval(jiraTicket, log) { } } + const hasDetails = detailText.replace('**Details:**\n', '').trim().length > 0; log.logDebug('buildChangeApproval', `Built card for ${jiraTicket.issue.key}`); - resolve(buildCardPayload({ jiraTicket, cardText, detailText })); + resolve(buildCardPayload({ + jiraTicket, + detailText: hasDetails ? detailText : '', + })); }); } export function buildRequestApproval(jiraTicket) { return new Promise(function (resolve) { - let cardText = ''; - cardText += `[${jiraTicket.issue.key}](https://aeo.atlassian.net/servicedesk/customer/portal/135/${jiraTicket.issue.key}) - ${jiraTicket.issue.fields.status.name}\n`; - - if (jiraTicket.issue.fields.customfield_10010) { - if (jiraTicket.issue.fields.customfield_10010.errorMessage) { - cardText += `**Request Type:** ${jiraTicket.issue.fields.customfield_10010.errorMessage}\n`; - } else { - cardText += `**Request Type:** ${jiraTicket.issue.fields.customfield_10010.requestType.name}\n`; - } - } - cardText += `**Summary:** ${jiraTicket.issue.fields.summary}\n`; - let detailText = '**Details:**\n'; const requestTypeId = getRequestTypeId(jiraTicket); if (requestTypeId) { @@ -117,11 +176,17 @@ export function buildRequestApproval(jiraTicket) { if (jiraTicket.issue.fields.customfield_10318) { detailText += ` - **Position:** ${jiraTicket.issue.fields.customfield_10318}\n`; } } + const hasDetails = detailText.replace('**Details:**\n', '').trim().length > 0; + let assignmentText = ''; if (jiraTicket.issue.fields.reporter) { assignmentText += `**Reporter:** ${jiraTicket.issue.fields.reporter.displayName}\n`; } - resolve(buildCardPayload({ jiraTicket, cardText, detailText, assignmentText })); + resolve(buildCardPayload({ + jiraTicket, + detailText: hasDetails ? detailText : '', + assignmentText, + })); }); } diff --git a/test/requestTypeFields.test.js b/test/requestTypeFields.test.js index a589222..f64cfaf 100644 --- a/test/requestTypeFields.test.js +++ b/test/requestTypeFields.test.js @@ -31,7 +31,7 @@ describe('getDisplayFieldsForRequestType', () => { it('returns fields for Application Access including common and groups', () => { const fields = getDisplayFieldsForRequestType('209'); const labels = fields.map(f => f.label); - assert.ok(labels.includes('Reporter')); + assert.ok(!labels.includes('Reporter')); assert.ok(labels.includes('First name')); assert.ok(labels.includes('IT')); }); @@ -46,7 +46,8 @@ describe('getDisplayFieldsForRequestType', () => { it('uses summaryOnly for legacy single-field forms', () => { const fields = getDisplayFieldsForRequestType('211'); const labels = fields.map(f => f.label); - assert.ok(labels.includes('Summary')); + assert.ok(labels.includes('Description')); + assert.ok(!labels.includes('Summary')); assert.ok(!labels.includes('First name')); });