Implemented the feature to identify and update estimates based on the ACR value, consumption start date, and ramp-up
This commit is contained in:
166
dist/firefox/background.js
vendored
166
dist/firefox/background.js
vendored
@@ -175,6 +175,8 @@ function createMessageTypes() {
|
||||
requestInstallBase: "opportunitiesExtension.requestInstallBase",
|
||||
requestCustomerServiceRequest: "opportunitiesExtension.requestCustomerServiceRequest",
|
||||
requestWlActions: "opportunitiesExtension.requestWlActions",
|
||||
requestWorkloads: "opportunitiesExtension.requestWorkloads",
|
||||
requestWorkloadForecastUpdate: "opportunitiesExtension.requestWorkloadForecastUpdate",
|
||||
requestActionTeamMembers: "opportunitiesExtension.requestActionTeamMembers",
|
||||
requestActionFindLookup: "opportunitiesExtension.requestActionFindLookup",
|
||||
requestMgmtAction: "opportunitiesExtension.requestMgmtAction",
|
||||
@@ -923,6 +925,140 @@ function createSpaWorkloadActions({ fetchJson }) {
|
||||
return { requestWlActions };
|
||||
}
|
||||
|
||||
// Source: src/services/spa/workloads.js
|
||||
function createSpaWorkloads({ fetchJson, getCachedWorkloads, normalizeCacheMaxAge, invalidateWorkloadsCache }) {
|
||||
async function requestWorkloadForecastUpdate(message) {
|
||||
const debugRequests = [];
|
||||
try {
|
||||
const id = String(message.workloadId ?? "");
|
||||
const token = message.authorization?.value;
|
||||
if (!/^\d+$/.test(id)) throw new Error("A numeric workload ID is required.");
|
||||
if (typeof token !== "string" || !token.trim()) throw new Error("WWB-TS uiaasHeader.value is unavailable.");
|
||||
if (!Array.isArray(message.payload) || !message.payload.length) throw new Error("Monthly estimates are required.");
|
||||
const periods = new Set();
|
||||
const payload = message.payload.map(({ year, month, amount, updatedBy }) => {
|
||||
if (!Number.isInteger(year) || year < 1000 || year > 9999 || !Number.isInteger(month) || month < 1 || month > 12
|
||||
|| typeof amount !== "number" || !Number.isFinite(amount) || amount < 0
|
||||
|| typeof updatedBy !== "string" || !updatedBy.trim()) throw new Error("Invalid monthly estimate.");
|
||||
const period = `${year}-${month}`;
|
||||
if (periods.has(period)) throw new Error("Duplicate forecast month.");
|
||||
periods.add(period);
|
||||
return { year, month, amount, updatedBy: updatedBy.trim() };
|
||||
});
|
||||
const result = await fetchJson(`https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/workloads/${id}/forecast`, {
|
||||
method: "PATCH", credentials: "include",
|
||||
headers: { Accept: "application/json", "Content-Type": "application/json", "wwb-provider-authorization": token.trim() },
|
||||
body: JSON.stringify(payload)
|
||||
}, "requestWorkloadForecastUpdate", debugRequests, { allowEmpty: true });
|
||||
await invalidateWorkloadsCache().catch(() => {});
|
||||
return { ok: true, result, debugRequests };
|
||||
} catch (error) {
|
||||
error.debugRequests = debugRequests;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function requestWorkloads(message) {
|
||||
const customerId = String(message.customerId ?? "").trim();
|
||||
const token = message.authorization?.value;
|
||||
if (!/^\d+$/.test(customerId)) throw new Error("A numeric CustomerAccount.PartyId is required.");
|
||||
if (typeof token !== "string" || !token.trim()) throw new Error("WWB-TS uiaasHeader.value is unavailable.");
|
||||
const result = await getCachedWorkloads(customerId, token.trim(), normalizeCacheMaxAge(message.cacheMaxAgeMs), message.principal);
|
||||
return { ok: true, customerId, ...result };
|
||||
}
|
||||
|
||||
async function fetchAllWorkloads(customerId, token) {
|
||||
const debugRequests = [];
|
||||
const workloads = new Map();
|
||||
let offset = 0;
|
||||
let limit = 500;
|
||||
try {
|
||||
for (let page = 0; page < 1000; page += 1) {
|
||||
const url = new URL(`https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/customers/${customerId}/workloads`);
|
||||
for (const [name, value] of Object.entries({ workloadStatus: "Pipeline,Implementation,Live", opportunityForecastType: "All",
|
||||
includeForEstimates: "ALL", financialYear: "ROLLING_4_QTRS", offset, limit })) url.searchParams.set(name, String(value));
|
||||
const result = await fetchJson(url.toString(), {
|
||||
method: "GET", credentials: "include",
|
||||
headers: { Accept: "application/json", "wwb-provider-authorization": token }
|
||||
}, "requestWorkloads", debugRequests);
|
||||
if (!result || !Array.isArray(result.workloads) || typeof result.hasNext !== "boolean"
|
||||
|| result.workloads.some(item => !item || !/^\d+$/.test(String(item.id ?? "")))) {
|
||||
throw new Error("requestWorkloads returned an invalid collection.");
|
||||
}
|
||||
const countBefore = workloads.size;
|
||||
for (const item of result.workloads) workloads.set(String(item.id), item);
|
||||
if (!result.hasNext) return { workloads: [...workloads.values()], debugRequests };
|
||||
const responseOffset = Number(result.offset ?? offset);
|
||||
const responseLimit = Number(result.limit ?? limit);
|
||||
if (workloads.size === countBefore || !Number.isSafeInteger(responseOffset) || responseOffset !== offset
|
||||
|| !Number.isSafeInteger(responseLimit) || responseLimit <= 0 || !Number.isSafeInteger(responseOffset + responseLimit)) {
|
||||
throw new Error("requestWorkloads pagination did not advance.");
|
||||
}
|
||||
offset = responseOffset + responseLimit;
|
||||
limit = responseLimit;
|
||||
}
|
||||
throw new Error("requestWorkloads exceeded the pagination limit.");
|
||||
} catch (error) {
|
||||
error.debugRequests = debugRequests;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return { requestWorkloads, fetchAllWorkloads, requestWorkloadForecastUpdate };
|
||||
}
|
||||
|
||||
// Source: src/features/products/workloads-cache.js
|
||||
function createWorkloadsCache({ fetchAllWorkloads, storageLocalGet, storageLocalSet }) {
|
||||
const KEY = "opportunitiesExtension.workloadsCache.v1";
|
||||
let cachePromise = null;
|
||||
let writePromise = Promise.resolve();
|
||||
let generation = 0;
|
||||
const inFlight = new Map();
|
||||
|
||||
function read() {
|
||||
if (!cachePromise) cachePromise = storageLocalGet(KEY)
|
||||
.then(result => result?.[KEY] && typeof result[KEY] === "object" && !Array.isArray(result[KEY]) ? result[KEY] : {})
|
||||
.catch(() => ({}));
|
||||
return cachePromise;
|
||||
}
|
||||
function write(cache) {
|
||||
writePromise = writePromise.catch(() => {}).then(() => storageLocalSet({ [KEY]: cache }));
|
||||
return writePromise;
|
||||
}
|
||||
async function invalidate() {
|
||||
generation += 1;
|
||||
inFlight.clear();
|
||||
cachePromise = Promise.resolve({});
|
||||
await write({});
|
||||
}
|
||||
async function get(customerId, token, maxAgeMs, principal) {
|
||||
const scope = typeof principal === "string" ? principal.trim().toLowerCase() : "";
|
||||
// isMyWorkload is user-specific. Never persist a result without a known principal.
|
||||
if (!scope) return { ...await fetchAllWorkloads(customerId, token), fromCache: false, cachedAt: Date.now() };
|
||||
const key = JSON.stringify([scope, customerId]);
|
||||
const currentGeneration = generation;
|
||||
const cache = await read();
|
||||
if (generation !== currentGeneration) return get(customerId, token, maxAgeMs, principal);
|
||||
const entry = cache[key];
|
||||
const age = Date.now() - Number(entry?.fetchedAt);
|
||||
if (Array.isArray(entry?.items) && age >= 0 && age < maxAgeMs) {
|
||||
return { workloads: entry.items, cachedAt: Number(entry.fetchedAt), fromCache: true, debugRequests: [] };
|
||||
}
|
||||
if (!inFlight.has(key)) {
|
||||
const flight = fetchAllWorkloads(customerId, token).then(async result => {
|
||||
const cachedAt = Date.now();
|
||||
if (generation === currentGeneration) {
|
||||
cache[key] = { fetchedAt: cachedAt, items: result.workloads };
|
||||
await write(cache).catch(() => {});
|
||||
}
|
||||
return { ...result, cachedAt, fromCache: false };
|
||||
}).finally(() => { if (inFlight.get(key) === flight) inFlight.delete(key); });
|
||||
inFlight.set(key, flight);
|
||||
}
|
||||
return inFlight.get(key);
|
||||
}
|
||||
return { get, invalidate };
|
||||
}
|
||||
|
||||
// Source: src/services/spa/action-options.js
|
||||
function createSpaActionOptions({ fetchJson }) {
|
||||
const baseUrl = "https://spa.oracle.com/oalcrm/web/api/provider-proxy/wwb-provider/service/workbench-proxy/wwb/mgmt/workloads";
|
||||
@@ -2520,6 +2656,9 @@ function registerBackgroundMessages({
|
||||
requestInstallBase,
|
||||
requestCustomerServiceRequest,
|
||||
requestWlActions,
|
||||
requestWorkloads,
|
||||
requestWorkloadForecastUpdate,
|
||||
invalidateWorkloadsCache,
|
||||
requestActionTeamMembers,
|
||||
requestActionFindLookup,
|
||||
requestMgmtAction,
|
||||
@@ -2597,7 +2736,7 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
}
|
||||
|
||||
if (message.type === messageTypes.invalidateProductsCache) {
|
||||
invalidateProductsCache().then(() => invalidateResourceUser()).then(() => sendResponse({ ok: true })).catch(error => sendResponse({ ok: false, error: error.message || "Unable to clear products cache." }));
|
||||
Promise.all([invalidateProductsCache(), invalidateWorkloadsCache()]).then(() => invalidateResourceUser()).then(() => sendResponse({ ok: true })).catch(error => sendResponse({ ok: false, error: error.message || "Unable to clear products cache." }));
|
||||
return true;
|
||||
}
|
||||
if (message.type === messageTypes.getXsrfToken) {
|
||||
@@ -2686,6 +2825,15 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ([messageTypes.requestWorkloads, messageTypes.requestWorkloadForecastUpdate].includes(message.type)) {
|
||||
const request = message.type === messageTypes.requestWorkloads ? requestWorkloads : requestWorkloadForecastUpdate;
|
||||
request(message).then(sendResponse).catch(error => sendResponse({
|
||||
ok: false, error: error.message || "Unable to load workloads.", httpStatus: error.httpStatus,
|
||||
debugRequests: Array.isArray(error.debugRequests) ? error.debugRequests : []
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === messageTypes.requestWlActions) {
|
||||
requestWlActions(message).then(sendResponse).catch(error => sendResponse({
|
||||
ok: false, error: error.message || "Unable to load workload actions.",
|
||||
@@ -2876,6 +3024,17 @@ function createBackgroundApplication(overrides = {}) {
|
||||
const requestAccountPlans = (...args) => spaAccounts.requestAccountPlans(...args);
|
||||
const requestInstallBase = (...args) => spaInstallBase.requestInstallBase(...args);
|
||||
const spaWorkloadActions = createSpaWorkloadActions({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
|
||||
const workloadsCache = createWorkloadsCache({
|
||||
fetchAllWorkloads: (...args) => spaWorkloads.fetchAllWorkloads(...args),
|
||||
storageLocalGet: (...args) => platformBackgroundApi.storageLocalGet(...args),
|
||||
storageLocalSet: (...args) => platformBackgroundApi.storageLocalSet(...args)
|
||||
});
|
||||
const spaWorkloads = createSpaWorkloads({
|
||||
invalidateWorkloadsCache: () => workloadsCache.invalidate(),
|
||||
fetchJson: (...args) => httpBackgroundJson.fetchJson(...args),
|
||||
getCachedWorkloads: (...args) => workloadsCache.get(...args),
|
||||
normalizeCacheMaxAge: value => productsCache.normalizeCacheMaxAge(value)
|
||||
});
|
||||
const requestWlActions = (...args) => spaWorkloadActions.requestWlActions(...args);
|
||||
const spaActionOptions = createSpaActionOptions({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
|
||||
const requestActionTeamMembers = (...args) => spaActionOptions.requestActionTeamMembers(...args);
|
||||
@@ -2907,6 +3066,9 @@ function createBackgroundApplication(overrides = {}) {
|
||||
function start() {
|
||||
srRequestHeaders.start();
|
||||
startBackgroundApplication({
|
||||
requestWorkloads: message => spaWorkloads.requestWorkloads(message),
|
||||
requestWorkloadForecastUpdate: message => spaWorkloads.requestWorkloadForecastUpdate(message),
|
||||
invalidateWorkloadsCache: () => workloadsCache.invalidate(),
|
||||
invalidateResourceUser: () => fusionSrCreate.invalidateResourceUser(),
|
||||
requestWeekDetails: message => timeEntries.requestWeekDetails(message),
|
||||
requestTimeEntries: message => timeEntries.requestTimeEntries(message),
|
||||
@@ -2934,6 +3096,8 @@ function createBackgroundApplication(overrides = {}) {
|
||||
httpBackgroundJson,
|
||||
productsBackgroundJobs,
|
||||
productsCache,
|
||||
workloadsCache,
|
||||
spaWorkloads,
|
||||
fusionProducts,
|
||||
authXsrfRecovery,
|
||||
authXsrfCookies,
|
||||
|
||||
558
dist/firefox/content.js
vendored
558
dist/firefox/content.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user