Optimized requests to retrieve SRs and Actions
This commit is contained in:
21
README.md
21
README.md
@@ -27,7 +27,7 @@ If identity or roles cannot be validated, only these two features stay disabled;
|
||||
other opportunity features continue. Reload the page to refresh identity/roles.
|
||||
The unused identity token is discarded and redacted from debug responses.
|
||||
|
||||
Once per opening of the opportunities modal, `resquestCrmServiceRequest` fetches all pages of
|
||||
After a completed product-loading cycle with fresh API results, `resquestCrmServiceRequest` fetches all pages of
|
||||
`CRMServiceRequest` records with `SVC_PENDING_DELIVERY` and `ORA_SVC_MY_SR_TEAM_SRS`,
|
||||
using the existing general tokenrelay Bearer. It uses the Fusion `searchResources/latest`
|
||||
query endpoint, independently of the opportunity query version. Calls appear in
|
||||
@@ -37,22 +37,25 @@ Records are linked by `OpportunityName_Id_c.OptyNumber`. A green SR badge indica
|
||||
one or more matches; its tooltip lists each SR number, title, reported hours, owner,
|
||||
email and contact flags. A red badge indicates no match within that query's scope.
|
||||
A failed lookup shows `SR ?`, not a false no-match result, and opportunities still load.
|
||||
Badges follow Ramp Months and do not wait for product downloads. SR results remain
|
||||
in memory only. Reference changes share the in-flight lookup and reuse its result
|
||||
(including failures) without another request. Closing resets this state; reopening
|
||||
starts a fresh lookup, and responses from previous openings are ignored.
|
||||
Badges follow Ramp Months. SR results remain in memory only. CRM SR and Actions
|
||||
refresh only after actual successful product API requests, not cache reads.
|
||||
Cache-only, empty and entirely failed cycles neither query nor clear activities.
|
||||
In mixed cycles, Actions refreshes only workloads from fresh products and CRM SR
|
||||
refreshes once. Closing and reopening the modal preserves results and does not
|
||||
reload products or trigger these lookups. Already-running product requests may
|
||||
finish in the background and update their related activities.
|
||||
|
||||
## Workload Actions
|
||||
|
||||
Workload Actions are queried in the background after product results arrive,
|
||||
including cached products. Each distinct `RevnId` uses `requestWlActions` with
|
||||
Workload Actions are queried in the background after the product cycle completes,
|
||||
using only fresh API results. Each distinct `RevnId` uses `requestWlActions` with
|
||||
the `WWB-TS` credential in `Wwb-Provider-Authorization`. The Act badge beside SR
|
||||
aggregates all products: green means an action's `owner` matches the general
|
||||
tokenrelay response's `principal`; red means no matching owner was found. `Act ?`
|
||||
means ownership could not be confirmed (including a missing principal). Tooltips
|
||||
retain all returned actions, regardless of owner. Principal stays in memory only.
|
||||
Queries are shared per modal opening (up to four concurrently), reused
|
||||
across Reference changes, and discarded on close. Reopening queries again.
|
||||
Queries are shared per product cycle (up to four concurrently). Reference changes
|
||||
that load products from cache do not refresh activities.
|
||||
Only the first page is requested for now; `hasNext` is indicated in the tooltip.
|
||||
The debug panel groups these requests under Actions, with credentials masked.
|
||||
|
||||
|
||||
76
dist/chromium/content.js
vendored
76
dist/chromium/content.js
vendored
@@ -466,10 +466,7 @@ function createContentModal({
|
||||
renderStageFilterButtons,
|
||||
renderStatusFilterButtons,
|
||||
requestOpportunitiesForPeriod,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
reopenWorkloadActions,
|
||||
closeCrmSrPopover,
|
||||
requestTokenRelayOnce,
|
||||
ensureCurrentUser,
|
||||
resetCustomerAndOwnerFiltersForPeriod,
|
||||
@@ -503,10 +500,7 @@ function createContentModal({
|
||||
ensureEscapeKeyHandler();
|
||||
ensureCurrentUser().then(() => {
|
||||
if (existingModal.hidden) return;
|
||||
reopenWorkloadActions();
|
||||
return requestTokenRelayOnce().then(token => {
|
||||
if (!existingModal.hidden) return loadCrmServiceRequests(token, opportunitiesQueryState.periodRequestVersion);
|
||||
});
|
||||
return requestTokenRelayOnce();
|
||||
})
|
||||
.then(() => renderOpportunitiesTable()).catch(() => {});
|
||||
return;
|
||||
@@ -884,8 +878,7 @@ function createContentModal({
|
||||
|
||||
function closeOpportunitiesModal() {
|
||||
resetAuthCompletion();
|
||||
resetWorkloadActions(false);
|
||||
resetCrmServiceRequests();
|
||||
closeCrmSrPopover();
|
||||
const modal = document.getElementById(MODAL_ID);
|
||||
|
||||
if (modal) {
|
||||
@@ -1589,7 +1582,6 @@ function createOpportunitiesController({
|
||||
productsContentState,
|
||||
renderProductsProgress,
|
||||
requestOpportunitiesPage,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
requestOpptyProductsInBackground,
|
||||
@@ -1660,7 +1652,6 @@ function createOpportunitiesController({
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCrmServiceRequests(token, requestVersion);
|
||||
if (!isCurrentPeriodRequest(requestVersion)) return;
|
||||
const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1);
|
||||
|
||||
@@ -2027,6 +2018,38 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
|
||||
return { reset, load, createBadge };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/product-activities.js
|
||||
function createProductActivities({ productsContentState, opportunitiesQueryState, resetCrm,
|
||||
loadCrm, loadActions, render }) {
|
||||
let cycle = null;
|
||||
function begin(requestId, token) {
|
||||
cycle = { requestId, token, version: opportunitiesQueryState.periodRequestVersion, completed: false };
|
||||
}
|
||||
function complete(requestId) {
|
||||
const current = cycle;
|
||||
if (!current || current.completed || current.requestId !== requestId
|
||||
|| productsContentState.currentProductsRequestId !== requestId) return;
|
||||
current.completed = true;
|
||||
const products = [...productsContentState.opptyProductsByNumber.values()].filter(result => !result.error && !result.fromCache);
|
||||
if (!products.length) return;
|
||||
resetCrm();
|
||||
const seen = new Set();
|
||||
for (const result of products) {
|
||||
const items = result.items.filter(item => {
|
||||
const id = String(item?.RevnId ?? "");
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
loadActions({ ...result, items }, true);
|
||||
}
|
||||
Promise.resolve(loadCrm(current.token, current.version)).finally(() => {
|
||||
if (cycle === current && productsContentState.currentProductsRequestId === requestId) render();
|
||||
}).catch(() => {});
|
||||
}
|
||||
return { begin, complete };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/crm-sr-popover.js
|
||||
function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, environment }) {
|
||||
const { document, window, setTimeout, clearTimeout } = environment;
|
||||
@@ -2171,10 +2194,10 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
function idsFor(products) {
|
||||
return [...new Set((products?.items || []).map(item => String(item?.RevnId ?? "").trim()).filter(Boolean))];
|
||||
}
|
||||
function loadProducts(products) {
|
||||
function loadProducts(products, refresh = false) {
|
||||
if (!session.enabled || !allowsCrmActivities()) return;
|
||||
for (const revnId of idsFor(products)) {
|
||||
if (session.records.has(revnId)) continue;
|
||||
if (!refresh && session.records.has(revnId)) continue;
|
||||
const record = { status: "loading", actions: [], hasNext: false };
|
||||
session.records.set(revnId, record);
|
||||
session.queue.push({ revnId, record });
|
||||
@@ -2196,7 +2219,7 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
async function run(current, { revnId, record }) {
|
||||
try {
|
||||
const authorizations = await requestConsumerTokenServiceOnce();
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return;
|
||||
const result = await sendRuntimeMessage({ type: messageTypes.requestWlActions, revnId, authorization: authorizations?.workloadActions });
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
appendBackgroundRequestLog(result?.debugRequests);
|
||||
@@ -3626,7 +3649,8 @@ function createProductsContentJobs({
|
||||
preferencesState,
|
||||
productsContentState,
|
||||
renderOpportunitiesTable,
|
||||
loadWorkloadActions,
|
||||
beginProductActivities,
|
||||
completeProductActivities,
|
||||
sendRuntimeMessage,
|
||||
environment
|
||||
}) {
|
||||
@@ -3671,6 +3695,7 @@ function createProductsContentJobs({
|
||||
|
||||
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
productsContentState.currentProductsRequestId = requestId;
|
||||
beginProductActivities(requestId, token);
|
||||
productsContentState.opptyProductsByNumber = new Map();
|
||||
productsContentState.expandedProductOptyNumbers = new Set();
|
||||
productsContentState.productsProgress = {
|
||||
@@ -3738,7 +3763,6 @@ function createProductsContentJobs({
|
||||
cachedAt: Number(message.cachedAt) || 0,
|
||||
error: message.error || ""
|
||||
});
|
||||
loadWorkloadActions(productsContentState.opptyProductsByNumber.get(String(message.optyNumber)));
|
||||
}
|
||||
|
||||
if (Number(message.cachedAt) > productsContentState.productsLastCachedAt) {
|
||||
@@ -3753,6 +3777,7 @@ function createProductsContentJobs({
|
||||
cacheHits: Number(message.cacheHits) || 0
|
||||
};
|
||||
renderProductsProgress();
|
||||
if (message.state === "complete" || message.state === "complete-with-errors") completeProductActivities(message.requestId);
|
||||
queueProductsTableRender();
|
||||
return false;
|
||||
});
|
||||
@@ -6897,6 +6922,7 @@ function createContentApplication(overrides = {}) {
|
||||
environment
|
||||
});
|
||||
const contentModal = createContentModal({
|
||||
closeCrmSrPopover: () => crmSrPopover.close(),
|
||||
ensureCurrentUser: () => currentUser.ensure(),
|
||||
ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID,
|
||||
AUTH_STATUS: contracts.AUTH_STATUS,
|
||||
@@ -6953,10 +6979,6 @@ function createContentApplication(overrides = {}) {
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
resetCustomerAndOwnerFiltersForPeriod: (...args) => opportunitiesFilters.resetCustomerAndOwnerFiltersForPeriod(...args),
|
||||
resetOpportunitiesTable: (...args) => opportunitiesController.resetOpportunitiesTable(...args),
|
||||
resetWorkloadActions: (...args) => opportunityWorkloadActions.reset(...args),
|
||||
reopenWorkloadActions: () => opportunityWorkloadActions.reopen(),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
restoreFilterPreferences: (...args) => preferencesStorage.restoreFilterPreferences(...args),
|
||||
saveFilterPreferences: (...args) => preferencesStorage.saveFilterPreferences(...args),
|
||||
toggleCustomerFilterPanel: (...args) => opportunitiesFilters.toggleCustomerFilterPanel(...args),
|
||||
@@ -7032,7 +7054,6 @@ function createContentApplication(overrides = {}) {
|
||||
productsContentState,
|
||||
renderProductsProgress: (...args) => productsContentJobs.renderProductsProgress(...args),
|
||||
requestOpportunitiesPage: (...args) => fusionOpportunities.requestOpportunitiesPage(...args),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
requestOpptyProductsInBackground: (...args) => productsContentJobs.requestOpptyProductsInBackground(...args),
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
@@ -7172,8 +7193,16 @@ function createContentApplication(overrides = {}) {
|
||||
requestOpportunitiesForPeriod: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args),
|
||||
environment
|
||||
});
|
||||
const productActivities = createProductActivities({
|
||||
productsContentState, opportunitiesQueryState,
|
||||
resetCrm: () => opportunityCrmServiceRequests.reset(),
|
||||
loadCrm: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
loadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
render: () => productsContentJobs.queueProductsTableRender()
|
||||
});
|
||||
const productsContentJobs = createProductsContentJobs({
|
||||
loadWorkloadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
beginProductActivities: (...args) => productActivities.begin(...args),
|
||||
completeProductActivities: (...args) => productActivities.complete(...args),
|
||||
appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args),
|
||||
messageTypes,
|
||||
PRODUCTS_CACHE_TIMESTAMP_ID: contracts.PRODUCTS_CACHE_TIMESTAMP_ID,
|
||||
@@ -7395,6 +7424,7 @@ function createContentApplication(overrides = {}) {
|
||||
preferencesStorage,
|
||||
preferencesView,
|
||||
productsContentJobs,
|
||||
productActivities,
|
||||
productsDetails,
|
||||
aiSummaryContent,
|
||||
consumptionPlansAction,
|
||||
|
||||
76
dist/firefox/content.js
vendored
76
dist/firefox/content.js
vendored
@@ -466,10 +466,7 @@ function createContentModal({
|
||||
renderStageFilterButtons,
|
||||
renderStatusFilterButtons,
|
||||
requestOpportunitiesForPeriod,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
reopenWorkloadActions,
|
||||
closeCrmSrPopover,
|
||||
requestTokenRelayOnce,
|
||||
ensureCurrentUser,
|
||||
resetCustomerAndOwnerFiltersForPeriod,
|
||||
@@ -503,10 +500,7 @@ function createContentModal({
|
||||
ensureEscapeKeyHandler();
|
||||
ensureCurrentUser().then(() => {
|
||||
if (existingModal.hidden) return;
|
||||
reopenWorkloadActions();
|
||||
return requestTokenRelayOnce().then(token => {
|
||||
if (!existingModal.hidden) return loadCrmServiceRequests(token, opportunitiesQueryState.periodRequestVersion);
|
||||
});
|
||||
return requestTokenRelayOnce();
|
||||
})
|
||||
.then(() => renderOpportunitiesTable()).catch(() => {});
|
||||
return;
|
||||
@@ -884,8 +878,7 @@ function createContentModal({
|
||||
|
||||
function closeOpportunitiesModal() {
|
||||
resetAuthCompletion();
|
||||
resetWorkloadActions(false);
|
||||
resetCrmServiceRequests();
|
||||
closeCrmSrPopover();
|
||||
const modal = document.getElementById(MODAL_ID);
|
||||
|
||||
if (modal) {
|
||||
@@ -1589,7 +1582,6 @@ function createOpportunitiesController({
|
||||
productsContentState,
|
||||
renderProductsProgress,
|
||||
requestOpportunitiesPage,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
requestOpptyProductsInBackground,
|
||||
@@ -1660,7 +1652,6 @@ function createOpportunitiesController({
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCrmServiceRequests(token, requestVersion);
|
||||
if (!isCurrentPeriodRequest(requestVersion)) return;
|
||||
const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1);
|
||||
|
||||
@@ -2027,6 +2018,38 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
|
||||
return { reset, load, createBadge };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/product-activities.js
|
||||
function createProductActivities({ productsContentState, opportunitiesQueryState, resetCrm,
|
||||
loadCrm, loadActions, render }) {
|
||||
let cycle = null;
|
||||
function begin(requestId, token) {
|
||||
cycle = { requestId, token, version: opportunitiesQueryState.periodRequestVersion, completed: false };
|
||||
}
|
||||
function complete(requestId) {
|
||||
const current = cycle;
|
||||
if (!current || current.completed || current.requestId !== requestId
|
||||
|| productsContentState.currentProductsRequestId !== requestId) return;
|
||||
current.completed = true;
|
||||
const products = [...productsContentState.opptyProductsByNumber.values()].filter(result => !result.error && !result.fromCache);
|
||||
if (!products.length) return;
|
||||
resetCrm();
|
||||
const seen = new Set();
|
||||
for (const result of products) {
|
||||
const items = result.items.filter(item => {
|
||||
const id = String(item?.RevnId ?? "");
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
loadActions({ ...result, items }, true);
|
||||
}
|
||||
Promise.resolve(loadCrm(current.token, current.version)).finally(() => {
|
||||
if (cycle === current && productsContentState.currentProductsRequestId === requestId) render();
|
||||
}).catch(() => {});
|
||||
}
|
||||
return { begin, complete };
|
||||
}
|
||||
|
||||
// Source: src/features/opportunities/crm-sr-popover.js
|
||||
function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, environment }) {
|
||||
const { document, window, setTimeout, clearTimeout } = environment;
|
||||
@@ -2171,10 +2194,10 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
function idsFor(products) {
|
||||
return [...new Set((products?.items || []).map(item => String(item?.RevnId ?? "").trim()).filter(Boolean))];
|
||||
}
|
||||
function loadProducts(products) {
|
||||
function loadProducts(products, refresh = false) {
|
||||
if (!session.enabled || !allowsCrmActivities()) return;
|
||||
for (const revnId of idsFor(products)) {
|
||||
if (session.records.has(revnId)) continue;
|
||||
if (!refresh && session.records.has(revnId)) continue;
|
||||
const record = { status: "loading", actions: [], hasNext: false };
|
||||
session.records.set(revnId, record);
|
||||
session.queue.push({ revnId, record });
|
||||
@@ -2196,7 +2219,7 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
async function run(current, { revnId, record }) {
|
||||
try {
|
||||
const authorizations = await requestConsumerTokenServiceOnce();
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return;
|
||||
const result = await sendRuntimeMessage({ type: messageTypes.requestWlActions, revnId, authorization: authorizations?.workloadActions });
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
appendBackgroundRequestLog(result?.debugRequests);
|
||||
@@ -3626,7 +3649,8 @@ function createProductsContentJobs({
|
||||
preferencesState,
|
||||
productsContentState,
|
||||
renderOpportunitiesTable,
|
||||
loadWorkloadActions,
|
||||
beginProductActivities,
|
||||
completeProductActivities,
|
||||
sendRuntimeMessage,
|
||||
environment
|
||||
}) {
|
||||
@@ -3671,6 +3695,7 @@ function createProductsContentJobs({
|
||||
|
||||
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
productsContentState.currentProductsRequestId = requestId;
|
||||
beginProductActivities(requestId, token);
|
||||
productsContentState.opptyProductsByNumber = new Map();
|
||||
productsContentState.expandedProductOptyNumbers = new Set();
|
||||
productsContentState.productsProgress = {
|
||||
@@ -3738,7 +3763,6 @@ function createProductsContentJobs({
|
||||
cachedAt: Number(message.cachedAt) || 0,
|
||||
error: message.error || ""
|
||||
});
|
||||
loadWorkloadActions(productsContentState.opptyProductsByNumber.get(String(message.optyNumber)));
|
||||
}
|
||||
|
||||
if (Number(message.cachedAt) > productsContentState.productsLastCachedAt) {
|
||||
@@ -3753,6 +3777,7 @@ function createProductsContentJobs({
|
||||
cacheHits: Number(message.cacheHits) || 0
|
||||
};
|
||||
renderProductsProgress();
|
||||
if (message.state === "complete" || message.state === "complete-with-errors") completeProductActivities(message.requestId);
|
||||
queueProductsTableRender();
|
||||
return false;
|
||||
});
|
||||
@@ -6897,6 +6922,7 @@ function createContentApplication(overrides = {}) {
|
||||
environment
|
||||
});
|
||||
const contentModal = createContentModal({
|
||||
closeCrmSrPopover: () => crmSrPopover.close(),
|
||||
ensureCurrentUser: () => currentUser.ensure(),
|
||||
ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID,
|
||||
AUTH_STATUS: contracts.AUTH_STATUS,
|
||||
@@ -6953,10 +6979,6 @@ function createContentApplication(overrides = {}) {
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
resetCustomerAndOwnerFiltersForPeriod: (...args) => opportunitiesFilters.resetCustomerAndOwnerFiltersForPeriod(...args),
|
||||
resetOpportunitiesTable: (...args) => opportunitiesController.resetOpportunitiesTable(...args),
|
||||
resetWorkloadActions: (...args) => opportunityWorkloadActions.reset(...args),
|
||||
reopenWorkloadActions: () => opportunityWorkloadActions.reopen(),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
restoreFilterPreferences: (...args) => preferencesStorage.restoreFilterPreferences(...args),
|
||||
saveFilterPreferences: (...args) => preferencesStorage.saveFilterPreferences(...args),
|
||||
toggleCustomerFilterPanel: (...args) => opportunitiesFilters.toggleCustomerFilterPanel(...args),
|
||||
@@ -7032,7 +7054,6 @@ function createContentApplication(overrides = {}) {
|
||||
productsContentState,
|
||||
renderProductsProgress: (...args) => productsContentJobs.renderProductsProgress(...args),
|
||||
requestOpportunitiesPage: (...args) => fusionOpportunities.requestOpportunitiesPage(...args),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
requestOpptyProductsInBackground: (...args) => productsContentJobs.requestOpptyProductsInBackground(...args),
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
@@ -7172,8 +7193,16 @@ function createContentApplication(overrides = {}) {
|
||||
requestOpportunitiesForPeriod: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args),
|
||||
environment
|
||||
});
|
||||
const productActivities = createProductActivities({
|
||||
productsContentState, opportunitiesQueryState,
|
||||
resetCrm: () => opportunityCrmServiceRequests.reset(),
|
||||
loadCrm: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
loadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
render: () => productsContentJobs.queueProductsTableRender()
|
||||
});
|
||||
const productsContentJobs = createProductsContentJobs({
|
||||
loadWorkloadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
beginProductActivities: (...args) => productActivities.begin(...args),
|
||||
completeProductActivities: (...args) => productActivities.complete(...args),
|
||||
appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args),
|
||||
messageTypes,
|
||||
PRODUCTS_CACHE_TIMESTAMP_ID: contracts.PRODUCTS_CACHE_TIMESTAMP_ID,
|
||||
@@ -7395,6 +7424,7 @@ function createContentApplication(overrides = {}) {
|
||||
preferencesStorage,
|
||||
preferencesView,
|
||||
productsContentJobs,
|
||||
productActivities,
|
||||
productsDetails,
|
||||
aiSummaryContent,
|
||||
consumptionPlansAction,
|
||||
|
||||
@@ -32,6 +32,7 @@ module.exports = {
|
||||
"src/services/fusion/opportunities.js",
|
||||
"src/services/fusion/crm-service-requests.js",
|
||||
"src/features/opportunities/crm-service-requests.js",
|
||||
"src/features/opportunities/product-activities.js",
|
||||
"src/features/opportunities/crm-sr-popover.js",
|
||||
"src/features/opportunities/workload-actions.js",
|
||||
"src/features/opportunities/fiscal-periods.js",
|
||||
|
||||
@@ -54,6 +54,7 @@ function createContentApplication(overrides = {}) {
|
||||
environment
|
||||
});
|
||||
const contentModal = createContentModal({
|
||||
closeCrmSrPopover: () => crmSrPopover.close(),
|
||||
ensureCurrentUser: () => currentUser.ensure(),
|
||||
ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID,
|
||||
AUTH_STATUS: contracts.AUTH_STATUS,
|
||||
@@ -110,10 +111,6 @@ function createContentApplication(overrides = {}) {
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
resetCustomerAndOwnerFiltersForPeriod: (...args) => opportunitiesFilters.resetCustomerAndOwnerFiltersForPeriod(...args),
|
||||
resetOpportunitiesTable: (...args) => opportunitiesController.resetOpportunitiesTable(...args),
|
||||
resetWorkloadActions: (...args) => opportunityWorkloadActions.reset(...args),
|
||||
reopenWorkloadActions: () => opportunityWorkloadActions.reopen(),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
restoreFilterPreferences: (...args) => preferencesStorage.restoreFilterPreferences(...args),
|
||||
saveFilterPreferences: (...args) => preferencesStorage.saveFilterPreferences(...args),
|
||||
toggleCustomerFilterPanel: (...args) => opportunitiesFilters.toggleCustomerFilterPanel(...args),
|
||||
@@ -189,7 +186,6 @@ function createContentApplication(overrides = {}) {
|
||||
productsContentState,
|
||||
renderProductsProgress: (...args) => productsContentJobs.renderProductsProgress(...args),
|
||||
requestOpportunitiesPage: (...args) => fusionOpportunities.requestOpportunitiesPage(...args),
|
||||
loadCrmServiceRequests: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(),
|
||||
requestOpptyProductsInBackground: (...args) => productsContentJobs.requestOpptyProductsInBackground(...args),
|
||||
requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args),
|
||||
@@ -329,8 +325,16 @@ function createContentApplication(overrides = {}) {
|
||||
requestOpportunitiesForPeriod: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args),
|
||||
environment
|
||||
});
|
||||
const productActivities = createProductActivities({
|
||||
productsContentState, opportunitiesQueryState,
|
||||
resetCrm: () => opportunityCrmServiceRequests.reset(),
|
||||
loadCrm: (...args) => opportunityCrmServiceRequests.load(...args),
|
||||
loadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
render: () => productsContentJobs.queueProductsTableRender()
|
||||
});
|
||||
const productsContentJobs = createProductsContentJobs({
|
||||
loadWorkloadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
|
||||
beginProductActivities: (...args) => productActivities.begin(...args),
|
||||
completeProductActivities: (...args) => productActivities.complete(...args),
|
||||
appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args),
|
||||
messageTypes,
|
||||
PRODUCTS_CACHE_TIMESTAMP_ID: contracts.PRODUCTS_CACHE_TIMESTAMP_ID,
|
||||
@@ -552,6 +556,7 @@ function createContentApplication(overrides = {}) {
|
||||
preferencesStorage,
|
||||
preferencesView,
|
||||
productsContentJobs,
|
||||
productActivities,
|
||||
productsDetails,
|
||||
aiSummaryContent,
|
||||
consumptionPlansAction,
|
||||
|
||||
@@ -51,10 +51,7 @@ function createContentModal({
|
||||
renderStageFilterButtons,
|
||||
renderStatusFilterButtons,
|
||||
requestOpportunitiesForPeriod,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
reopenWorkloadActions,
|
||||
closeCrmSrPopover,
|
||||
requestTokenRelayOnce,
|
||||
ensureCurrentUser,
|
||||
resetCustomerAndOwnerFiltersForPeriod,
|
||||
@@ -88,10 +85,7 @@ function createContentModal({
|
||||
ensureEscapeKeyHandler();
|
||||
ensureCurrentUser().then(() => {
|
||||
if (existingModal.hidden) return;
|
||||
reopenWorkloadActions();
|
||||
return requestTokenRelayOnce().then(token => {
|
||||
if (!existingModal.hidden) return loadCrmServiceRequests(token, opportunitiesQueryState.periodRequestVersion);
|
||||
});
|
||||
return requestTokenRelayOnce();
|
||||
})
|
||||
.then(() => renderOpportunitiesTable()).catch(() => {});
|
||||
return;
|
||||
@@ -469,8 +463,7 @@ function createContentModal({
|
||||
|
||||
function closeOpportunitiesModal() {
|
||||
resetAuthCompletion();
|
||||
resetWorkloadActions(false);
|
||||
resetCrmServiceRequests();
|
||||
closeCrmSrPopover();
|
||||
const modal = document.getElementById(MODAL_ID);
|
||||
|
||||
if (modal) {
|
||||
|
||||
@@ -13,7 +13,6 @@ function createOpportunitiesController({
|
||||
productsContentState,
|
||||
renderProductsProgress,
|
||||
requestOpportunitiesPage,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
resetWorkloadActions,
|
||||
requestOpptyProductsInBackground,
|
||||
@@ -84,7 +83,6 @@ function createOpportunitiesController({
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCrmServiceRequests(token, requestVersion);
|
||||
if (!isCurrentPeriodRequest(requestVersion)) return;
|
||||
const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1);
|
||||
|
||||
|
||||
30
src/features/opportunities/product-activities.js
Normal file
30
src/features/opportunities/product-activities.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function createProductActivities({ productsContentState, opportunitiesQueryState, resetCrm,
|
||||
loadCrm, loadActions, render }) {
|
||||
let cycle = null;
|
||||
function begin(requestId, token) {
|
||||
cycle = { requestId, token, version: opportunitiesQueryState.periodRequestVersion, completed: false };
|
||||
}
|
||||
function complete(requestId) {
|
||||
const current = cycle;
|
||||
if (!current || current.completed || current.requestId !== requestId
|
||||
|| productsContentState.currentProductsRequestId !== requestId) return;
|
||||
current.completed = true;
|
||||
const products = [...productsContentState.opptyProductsByNumber.values()].filter(result => !result.error && !result.fromCache);
|
||||
if (!products.length) return;
|
||||
resetCrm();
|
||||
const seen = new Set();
|
||||
for (const result of products) {
|
||||
const items = result.items.filter(item => {
|
||||
const id = String(item?.RevnId ?? "");
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
loadActions({ ...result, items }, true);
|
||||
}
|
||||
Promise.resolve(loadCrm(current.token, current.version)).finally(() => {
|
||||
if (cycle === current && productsContentState.currentProductsRequestId === requestId) render();
|
||||
}).catch(() => {});
|
||||
}
|
||||
return { begin, complete };
|
||||
}
|
||||
@@ -11,10 +11,10 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
function idsFor(products) {
|
||||
return [...new Set((products?.items || []).map(item => String(item?.RevnId ?? "").trim()).filter(Boolean))];
|
||||
}
|
||||
function loadProducts(products) {
|
||||
function loadProducts(products, refresh = false) {
|
||||
if (!session.enabled || !allowsCrmActivities()) return;
|
||||
for (const revnId of idsFor(products)) {
|
||||
if (session.records.has(revnId)) continue;
|
||||
if (!refresh && session.records.has(revnId)) continue;
|
||||
const record = { status: "loading", actions: [], hasNext: false };
|
||||
session.records.set(revnId, record);
|
||||
session.queue.push({ revnId, record });
|
||||
@@ -36,7 +36,7 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
async function run(current, { revnId, record }) {
|
||||
try {
|
||||
const authorizations = await requestConsumerTokenServiceOnce();
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return;
|
||||
const result = await sendRuntimeMessage({ type: messageTypes.requestWlActions, revnId, authorization: authorizations?.workloadActions });
|
||||
if (!current.enabled || current !== session || !allowsCrmActivities()) return;
|
||||
appendBackgroundRequestLog(result?.debugRequests);
|
||||
|
||||
@@ -7,7 +7,8 @@ function createProductsContentJobs({
|
||||
preferencesState,
|
||||
productsContentState,
|
||||
renderOpportunitiesTable,
|
||||
loadWorkloadActions,
|
||||
beginProductActivities,
|
||||
completeProductActivities,
|
||||
sendRuntimeMessage,
|
||||
environment
|
||||
}) {
|
||||
@@ -52,6 +53,7 @@ function createProductsContentJobs({
|
||||
|
||||
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
productsContentState.currentProductsRequestId = requestId;
|
||||
beginProductActivities(requestId, token);
|
||||
productsContentState.opptyProductsByNumber = new Map();
|
||||
productsContentState.expandedProductOptyNumbers = new Set();
|
||||
productsContentState.productsProgress = {
|
||||
@@ -119,7 +121,6 @@ function createProductsContentJobs({
|
||||
cachedAt: Number(message.cachedAt) || 0,
|
||||
error: message.error || ""
|
||||
});
|
||||
loadWorkloadActions(productsContentState.opptyProductsByNumber.get(String(message.optyNumber)));
|
||||
}
|
||||
|
||||
if (Number(message.cachedAt) > productsContentState.productsLastCachedAt) {
|
||||
@@ -134,6 +135,7 @@ function createProductsContentJobs({
|
||||
cacheHits: Number(message.cacheHits) || 0
|
||||
};
|
||||
renderProductsProgress();
|
||||
if (message.state === "complete" || message.state === "complete-with-errors") completeProductActivities(message.requestId);
|
||||
queueProductsTableRender();
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ test("CRM fetch is called without the environment object as its receiver", async
|
||||
assert.equal(items.length, 2);
|
||||
});
|
||||
|
||||
test("CRM query preserves payload, paginates before opportunities, uses tokenrelay Bearer and logs each page", async () => {
|
||||
test("CRM query preserves payload and pagination after opportunities, uses tokenrelay Bearer and logs each page", async () => {
|
||||
const calls = [];
|
||||
const app = setup(async (url, options) => {
|
||||
const payload = JSON.parse(options.body);
|
||||
@@ -49,11 +49,12 @@ test("CRM query preserves payload, paginates before opportunities, uses tokenrel
|
||||
app.productsContentJobs.renderProductsProgress = () => {};
|
||||
app.productsContentJobs.requestOpptyProductsInBackground = () => {};
|
||||
app.fusionOpportunities.requestOpportunitiesPage = async () => {
|
||||
assert.equal(calls.length, 2, "all CRM pages must finish first");
|
||||
assert.equal(calls.length, 0, "CRM must not run before product loading");
|
||||
return [{ OptyNumber: "TEST-1" }];
|
||||
};
|
||||
await app.opportunitiesController.requestOpportunities("synthetic-token", "Current Quarter", {}, 1);
|
||||
assert.equal(states.at(-1).status, "ready");
|
||||
await app.opportunityCrmServiceRequests.load("synthetic-token", 1);
|
||||
assert.deepEqual(calls.map(call => call.payload.offset), [0, 1]);
|
||||
const { payload, options, url } = calls[0];
|
||||
assert.equal(url, "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries");
|
||||
@@ -116,6 +117,7 @@ test("CRM failures do not mislabel missing SRs or block the opportunity list", a
|
||||
app.fusionOpportunities.requestOpportunitiesPage = async () => { loaded = true; return []; };
|
||||
await app.opportunitiesController.requestOpportunities("token", "Current Quarter", {}, 1);
|
||||
assert.equal(loaded, true);
|
||||
await app.opportunityCrmServiceRequests.load("token", 1);
|
||||
assert.equal(app.opportunityCrmServiceRequests.createBadge("TEST").textContent, "SR ?");
|
||||
assert.equal(app.debugState.requestLog.at(-1).status, "failed");
|
||||
}
|
||||
|
||||
@@ -73,10 +73,12 @@ for (const [roles, allowed] of [[ ["SALES_REP", "SIMPLE_USER"], false ], [ ["SOL
|
||||
assert.equal(same, app.currentUser.ensure());
|
||||
finish({ ok: true, user: { roles } });
|
||||
await first;
|
||||
assert.deepEqual(calls, allowed ? [type, "tokenrelay", "crm", "opportunities"] : [type, "tokenrelay", "opportunities"]);
|
||||
assert.deepEqual(calls, [type, "tokenrelay", "opportunities"]);
|
||||
app.productsContentState.currentProductsRequestId = "products-cycle";
|
||||
app.productActivities.begin("products-cycle", "synthetic");
|
||||
const products = { items: [{ RevnId: "123" }] };
|
||||
app.productsContentState.opptyProductsByNumber.set("OP", products);
|
||||
app.opportunityWorkloadActions.loadProducts(products);
|
||||
app.productActivities.complete("products-cycle");
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
assert.equal(calls.includes("opportunitiesExtension.requestWlActions"), allowed);
|
||||
if (!allowed) {
|
||||
|
||||
109
tests/productActivities.test.cjs
Normal file
109
tests/productActivities.test.cjs
Normal file
@@ -0,0 +1,109 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const { loadApplication } = require("./support/application.cjs");
|
||||
const flush = () => new Promise(resolve => setImmediate(resolve));
|
||||
|
||||
function setup() {
|
||||
let listener;
|
||||
const app = loadApplication("content", {
|
||||
document: { getElementById: () => null }, window: { requestAnimationFrame() {} },
|
||||
chrome: { runtime: { onMessage: { addListener(fn) { listener = fn; } } } }
|
||||
});
|
||||
const calls = [];
|
||||
app.opportunityCrmServiceRequests.reset = () => calls.push("reset SR");
|
||||
app.opportunityWorkloadActions.reset = () => calls.push("reset Act");
|
||||
app.opportunityCrmServiceRequests.load = async token => calls.push(`SR ${token}`);
|
||||
app.opportunityWorkloadActions.loadProducts = products => calls.push(products.items[0]?.RevnId || "empty products");
|
||||
app.platformContentMessages.sendRuntimeMessage = async () => ({ ok: true });
|
||||
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
|
||||
function start(token = "token") {
|
||||
app.productsContentJobs.requestOpptyProductsInBackground([{ OptyNumber: "A" }], token);
|
||||
return app.productsContentState.currentProductsRequestId;
|
||||
}
|
||||
function progress(requestId, data) {
|
||||
listener({ type: "opportunitiesExtension.opptyProductsProgress", requestId, ...data });
|
||||
}
|
||||
return { app, calls, start, progress };
|
||||
}
|
||||
|
||||
test("follow-up calls wait for completion, deduplicate terminal messages and refresh on every new cycle", async () => {
|
||||
const h = setup();
|
||||
const first = h.start();
|
||||
h.progress(first, { state: "running", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: false });
|
||||
assert.deepEqual(h.calls, []);
|
||||
h.progress(first, { state: "complete" });
|
||||
h.progress(first, { state: "complete" });
|
||||
await flush();
|
||||
assert.deepEqual(h.calls, ["reset SR", "123", "SR token"]);
|
||||
const second = h.start("new-token");
|
||||
h.progress(first, { state: "complete", optyNumber: "B", items: [{ RevnId: "old" }] });
|
||||
h.progress(second, { state: "running", optyNumber: "A", items: [{ RevnId: "123" }] });
|
||||
assert.equal(h.calls.filter(value => value === "123").length, 1);
|
||||
h.progress(second, { state: "complete" });
|
||||
await flush();
|
||||
assert.equal(h.calls.filter(value => value === "123").length, 2);
|
||||
assert.ok(h.calls.includes("SR new-token"));
|
||||
assert.equal(h.calls.includes("old"), false);
|
||||
});
|
||||
|
||||
test("partial failures use successful products only; closed, failed and empty cycles have no follow-up", async () => {
|
||||
const h = setup();
|
||||
const first = h.start();
|
||||
h.progress(first, { optyNumber: "A", items: [{ RevnId: "123" }] });
|
||||
h.progress(first, { optyNumber: "B", items: [{ RevnId: "failed" }], error: "Synthetic failure" });
|
||||
h.progress(first, { state: "complete-with-errors" });
|
||||
await flush();
|
||||
assert.equal(h.calls.includes("failed"), false);
|
||||
assert.equal(h.calls.filter(value => value === "SR token").length, 1);
|
||||
const second = h.start();
|
||||
h.progress(second, { optyNumber: "A", error: "Synthetic failure" });
|
||||
h.progress(second, { state: "complete-with-errors" });
|
||||
const third = h.start();
|
||||
h.progress(third, { state: "error" });
|
||||
const fourth = h.start();
|
||||
h.app.productsContentState.currentProductsRequestId = "";
|
||||
h.progress(fourth, { state: "complete", optyNumber: "A", items: [{ RevnId: "closed" }] });
|
||||
h.app.productsContentJobs.requestOpptyProductsInBackground([], "empty");
|
||||
await flush();
|
||||
assert.equal(h.calls.filter(value => value.startsWith("SR ")).length, 1);
|
||||
assert.equal(h.calls.includes("closed"), false);
|
||||
});
|
||||
|
||||
test("cached products never trigger or reset activities; mixed cycles use only fresh products", async () => {
|
||||
const h = setup();
|
||||
const cached = h.start();
|
||||
h.progress(cached, { optyNumber: "A", items: [{ RevnId: "cached" }], fromCache: true });
|
||||
h.progress(cached, { state: "complete" });
|
||||
await flush();
|
||||
assert.deepEqual(h.calls, []);
|
||||
const mixed = h.start();
|
||||
h.progress(mixed, { optyNumber: "A", items: [{ RevnId: "cached" }], fromCache: true });
|
||||
h.progress(mixed, { optyNumber: "B", items: [{ RevnId: "fresh" }], fromCache: false });
|
||||
h.progress(mixed, { state: "complete" });
|
||||
await flush();
|
||||
assert.deepEqual(h.calls, ["reset SR", "fresh", "SR token"]);
|
||||
});
|
||||
|
||||
test("reopening an existing modal does not reload products or reset/query activities", async () => {
|
||||
const modal = { hidden: true, querySelector: () => ({ focus() {} }) };
|
||||
const app = loadApplication("content", {
|
||||
window: {}, document: {
|
||||
getElementById: id => id === "opportunities-extension-modal" ? modal : null,
|
||||
documentElement: { classList: { add() {} } }, addEventListener() {}
|
||||
}
|
||||
});
|
||||
app.contentStyles.ensureExtensionStyles = () => {};
|
||||
app.currentUser.ensure = async () => true;
|
||||
app.authContentSession.requestTokenRelayOnce = async () => "synthetic";
|
||||
app.opportunitiesTable.renderOpportunitiesTable = () => {};
|
||||
const calls = [];
|
||||
app.productsContentJobs.requestOpptyProductsInBackground = () => calls.push("products");
|
||||
app.opportunityCrmServiceRequests.load = () => calls.push("SR");
|
||||
app.opportunityCrmServiceRequests.reset = () => calls.push("reset SR");
|
||||
app.opportunityWorkloadActions.loadProducts = () => calls.push("Actions");
|
||||
app.opportunityWorkloadActions.reset = () => calls.push("reset Actions");
|
||||
app.contentModal.openOpportunitiesModal();
|
||||
await flush();
|
||||
assert.equal(modal.hidden, false);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
@@ -158,7 +158,7 @@ test("actions queue caps concurrency and discards results/queued jobs from close
|
||||
assert.equal(app.opportunityWorkloadActions.createBadge("A"), null);
|
||||
});
|
||||
|
||||
test("product progress triggers Actions for cached products and ignores stale product jobs", async () => {
|
||||
test("product progress triggers Actions only for fresh products and ignores stale product jobs", async () => {
|
||||
let listener;
|
||||
const calls = [];
|
||||
const app = loadApplication("content", {
|
||||
@@ -173,14 +173,19 @@ test("product progress triggers Actions for cached products and ignores stale pr
|
||||
return { ok: true, actions: [], hasNext: false };
|
||||
};
|
||||
app.productsContentState.currentProductsRequestId = "current";
|
||||
app.productActivities.begin("current", "synthetic");
|
||||
app.fusionCrmServiceRequests.requestCrmServiceRequest = async () => [];
|
||||
app.productsContentJobs.ensureProductsBackgroundMessageHandler();
|
||||
const message = { type: app.messageTypes?.opptyProductsProgress || "opportunitiesExtension.opptyProductsProgress",
|
||||
requestId: "current", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: true };
|
||||
requestId: "current", optyNumber: "A", items: [{ RevnId: "123" }], fromCache: false };
|
||||
listener({ ...message, requestId: "old" });
|
||||
await flush();
|
||||
assert.equal(calls.length, 0);
|
||||
listener(message);
|
||||
await flush();
|
||||
assert.equal(calls.length, 0);
|
||||
listener({ ...message, optyNumber: undefined, state: "complete" });
|
||||
await flush();
|
||||
assert.equal(calls.length, 1);
|
||||
assert.equal(calls[0].type, "opportunitiesExtension.requestWlActions");
|
||||
assert.equal(calls[0].revnId, "123");
|
||||
|
||||
Reference in New Issue
Block a user