Updated the Service Requests badge to display the hours logged on the SR

This commit is contained in:
2026-09-24 19:17:44 -03:00
parent 8371f4e89e
commit 86973f7542
5 changed files with 36 additions and 9 deletions

View File

@@ -1999,7 +1999,13 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span"); const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span");
badge.className = "opportunities-extension-crm-sr-badge"; badge.className = "opportunities-extension-crm-sr-badge";
badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing"); badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing");
badge.textContent = status === "error" ? "SR ?" : items.length ? "\u2705 SR" : "\u26d4 SR"; const hours = items.map(item => item.ReportedHours_c).filter(value =>
(typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value))
).map(Number);
const reportedHours = hours.length === items.length
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(hours.reduce((total, value) => total + value, 0))
: "N/D";
badge.textContent = status === "error" ? "SR ?" : items.length ? `\u2705 SR (${reportedHours}${reportedHours === "N/D" ? "" : "h"})` : "\u26d4 SR";
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value); const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
const descriptions = items.map(item => [ const descriptions = items.map(item => [
`Title: ${display(item.Title)}`, `Title: ${display(item.Title)}`,

View File

@@ -1999,7 +1999,13 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span"); const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span");
badge.className = "opportunities-extension-crm-sr-badge"; badge.className = "opportunities-extension-crm-sr-badge";
badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing"); badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing");
badge.textContent = status === "error" ? "SR ?" : items.length ? "\u2705 SR" : "\u26d4 SR"; const hours = items.map(item => item.ReportedHours_c).filter(value =>
(typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value))
).map(Number);
const reportedHours = hours.length === items.length
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(hours.reduce((total, value) => total + value, 0))
: "N/D";
badge.textContent = status === "error" ? "SR ?" : items.length ? `\u2705 SR (${reportedHours}${reportedHours === "N/D" ? "" : "h"})` : "\u26d4 SR";
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value); const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
const descriptions = items.map(item => [ const descriptions = items.map(item => [
`Title: ${display(item.Title)}`, `Title: ${display(item.Title)}`,

View File

@@ -35,7 +35,13 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span"); const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span");
badge.className = "opportunities-extension-crm-sr-badge"; badge.className = "opportunities-extension-crm-sr-badge";
badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing"); badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing");
badge.textContent = status === "error" ? "SR ?" : items.length ? "\u2705 SR" : "\u26d4 SR"; const hours = items.map(item => item.ReportedHours_c).filter(value =>
(typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value))
).map(Number);
const reportedHours = hours.length === items.length
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(hours.reduce((total, value) => total + value, 0))
: "N/D";
badge.textContent = status === "error" ? "SR ?" : items.length ? `\u2705 SR (${reportedHours}${reportedHours === "N/D" ? "" : "h"})` : "\u26d4 SR";
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value); const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
const descriptions = items.map(item => [ const descriptions = items.map(item => [
`Title: ${display(item.Title)}`, `Title: ${display(item.Title)}`,

View File

@@ -14,6 +14,15 @@ const sr = (number, id = "SR-1") => ({ SrNumber: id, Title: "<script>synthetic</
OpportunityName_Id_c: { OptyNumber: number }, Owner: { PersonFirstName: "Test", PersonLastName: "Owner", OpportunityName_Id_c: { OptyNumber: number }, Owner: { PersonFirstName: "Test", PersonLastName: "Owner",
PrimaryEmail: { EmailAddress: "test@example.invalid" } }, DoNotCallFlag: false }); PrimaryEmail: { EmailAddress: "test@example.invalid" } }, DoNotCallFlag: false });
test("SR badge shows reported hours, sums linked SRs and preserves missing values", async () => {
for (const [values, expected] of [[[4], "4h"], [[0], "0h"], [[2, "1.5"], "3.5h"], [[0.1, 0.2], "0.3h"], [[null], "N/D"], [[4, null], "N/D"], [[""], "N/D"], [["invalid"], "N/D"]]) {
const app = setup(async () => response({ items: values.map((value, index) => ({ ...sr("A", `SR-${index}`), ReportedHours_c: value })), hasMore: false }));
await app.opportunityCrmServiceRequests.load("synthetic", 1);
assert.equal(app.opportunityCrmServiceRequests.createBadge("A").textContent, `\u2705 SR (${expected})`);
assert.equal(app.opportunityCrmServiceRequests.createBadge("OTHER").textContent, "\u26d4 SR");
}
});
test("CRM fetch is called without the environment object as its receiver", async () => { test("CRM fetch is called without the environment object as its receiver", async () => {
let calls = 0; let calls = 0;
const app = setup(async function (url, options) { const app = setup(async function (url, options) {
@@ -79,7 +88,7 @@ test("CRM query preserves payload, paginates before opportunities, uses tokenrel
assert.equal(app.debugContentView.getRequestCategory(log), "serviceRequests"); assert.equal(app.debugContentView.getRequestCategory(log), "serviceRequests");
} }
const found = app.opportunityCrmServiceRequests.createBadge("TEST-1"); const found = app.opportunityCrmServiceRequests.createBadge("TEST-1");
assert.equal(found.textContent, "\u2705 SR"); assert.equal(found.textContent, "\u2705 SR (0h)");
assert.equal(found.attributes["data-state"], "matched"); assert.equal(found.attributes["data-state"], "matched");
for (const detail of ["SR-0", "SR-1", "Reported Hours: 0", "Test Owner", "test@example.invalid", "<script>synthetic</script>"]) for (const detail of ["SR-0", "SR-1", "Reported Hours: 0", "Test Owner", "test@example.invalid", "<script>synthetic</script>"])
assert.ok(found.attributes["aria-label"].includes(detail)); assert.ok(found.attributes["aria-label"].includes(detail));
@@ -122,7 +131,7 @@ test("stale CRM results cannot overwrite a reopened modal or continue pagination
await app.opportunityCrmServiceRequests.load("token", 2); await app.opportunityCrmServiceRequests.load("token", 2);
resolveOld(response({ items: [sr("OLD")], hasMore: true, count: 1, offset: 0 })); resolveOld(response({ items: [sr("OLD")], hasMore: true, count: 1, offset: 0 }));
await old; await old;
assert.equal(app.opportunityCrmServiceRequests.createBadge("NEW").textContent, "\u2705 SR"); assert.equal(app.opportunityCrmServiceRequests.createBadge("NEW").textContent, "\u2705 SR (0h)");
assert.equal(app.opportunityCrmServiceRequests.createBadge("OLD").textContent, "\u26d4 SR"); assert.equal(app.opportunityCrmServiceRequests.createBadge("OLD").textContent, "\u26d4 SR");
assert.equal(app.debugState.requestLog.length, 1); assert.equal(app.debugState.requestLog.length, 1);
}); });
@@ -144,8 +153,8 @@ test("Reference changes share pending pagination and reuse completed CRM results
app.opportunitiesQueryState.periodRequestVersion = 3; app.opportunitiesQueryState.periodRequestVersion = 3;
await app.opportunityCrmServiceRequests.load("token", 3); await app.opportunityCrmServiceRequests.load("token", 3);
assert.equal(calls, 2); assert.equal(calls, 2);
assert.equal(app.opportunityCrmServiceRequests.createBadge("A").textContent, "\u2705 SR"); assert.equal(app.opportunityCrmServiceRequests.createBadge("A").textContent, "\u2705 SR (0h)");
assert.equal(app.opportunityCrmServiceRequests.createBadge("B").textContent, "\u2705 SR"); assert.equal(app.opportunityCrmServiceRequests.createBadge("B").textContent, "\u2705 SR (0h)");
app.opportunityCrmServiceRequests.reset(); app.opportunityCrmServiceRequests.reset();
await app.opportunityCrmServiceRequests.load("token", 3); await app.opportunityCrmServiceRequests.load("token", 3);
assert.equal(calls, 3); assert.equal(calls, 3);

View File

@@ -124,11 +124,11 @@ test("CRM SR badge follows Ramp Months and remains visible while product data is
}]; }];
await h.app.opportunityCrmServiceRequests.load("synthetic", h.app.opportunitiesQueryState.periodRequestVersion); await h.app.opportunityCrmServiceRequests.load("synthetic", h.app.opportunitiesQueryState.periodRequestVersion);
h.app.opportunitiesTable.renderOpportunitiesTable(); h.app.opportunitiesTable.renderOpportunitiesTable();
assert.equal(findClass(h.cell("name"), "opportunities-extension-crm-sr-badge").textContent, "\u2705 SR"); assert.equal(findClass(h.cell("name"), "opportunities-extension-crm-sr-badge").textContent, "\u2705 SR (0h)");
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ RampMonths_c: 3 }] }); h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ RampMonths_c: 3 }] });
h.app.opportunitiesTable.renderOpportunitiesTable(); h.app.opportunitiesTable.renderOpportunitiesTable();
const actions = h.cell("name").children[0].children[1]; const actions = h.cell("name").children[0].children[1];
assert.deepEqual(actions.children.map(child => child.textContent), ["1", "RM 3", "\u2705 SR", "Act ?"]); assert.deepEqual(actions.children.map(child => child.textContent), ["1", "RM 3", "\u2705 SR (0h)", "Act ?"]);
findClass(h.cell("name"), "opportunities-extension-products-count").click(); findClass(h.cell("name"), "opportunities-extension-products-count").click();
assert.ok(findClass(h.rows()[1], "opportunities-extension-products-table")); assert.ok(findClass(h.rows()[1], "opportunities-extension-products-table"));
}); });