UNPKG

spitfirepm

Version:

Client API Tools for Spitfire Project Management

1,013 lines (1,012 loc) 326 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sfRestClient = exports.InvokeOptions = exports.WCCData = exports.NVPair = exports.SFFileUploadContext = exports.ValidationMode = exports.LoggingLevels = void 0; const SwaggerClients_1 = require("./SwaggerClients"); const _SwaggerClientExports = require("./SwaggerClients"); const BrowserExtensionChecker_1 = require("./BrowserExtensionChecker"); const RESTClientBase = require("./APIClientBase"); // avoid conflict with same in SwaggerClient when loaded by classic UI const string_extensions_1 = require("./string.extensions"); //import {dialog} from "jquery-ui"; const ClientPackageVersion = "23.9330.5"; // originally modified for typescript and linter requirements by Uladzislau Kumakou of XB Software var LoggingLevels; (function (LoggingLevels) { LoggingLevels[LoggingLevels["None"] = 0] = "None"; LoggingLevels[LoggingLevels["Normal"] = 1] = "Normal"; LoggingLevels[LoggingLevels["Verbose"] = 2] = "Verbose"; LoggingLevels[LoggingLevels["Debug"] = 9] = "Debug"; LoggingLevels[LoggingLevels["VerboseDebug"] = 92] = "VerboseDebug"; })(LoggingLevels = exports.LoggingLevels || (exports.LoggingLevels = {})); var ValidationMode; (function (ValidationMode) { ValidationMode["none"] = "0"; /** 1,T*,Y* are true, empty, 0,F*,N* are false */ ValidationMode["boolean"] = "1"; ValidationMode["numeric"] = "2"; ValidationMode["positive"] = "5"; ValidationMode["integer"] = "6"; ValidationMode["positiveinteger"] = "7"; ValidationMode["date"] = "3"; ValidationMode["futuredate"] = "4"; /** sort of like past date */ ValidationMode["untildate"] = "8"; /** non empty, can be combined with other validations */ ValidationMode["required"] = "16"; ValidationMode["filename"] = "32"; ValidationMode["url"] = "64"; })(ValidationMode = exports.ValidationMode || (exports.ValidationMode = {})); class _SessionClientGetWCCShare { constructor(apiPromise, forPageHash) { this.APIResult = null; this.AsOf = Date.now(); this.IsResolved = false; this.APIResult = apiPromise; this.ForNavHash = forPageHash; apiPromise.finally(() => { this.IsResolved = true; this.AsOf = Date.now(); }); } AppliesFor(testHash) { return !this.Expired() && (this.ForNavHash === testHash); } Expired() { return (this.IsResolved && (Date.now() - this.AsOf) > 3210); } } class SFFileUploadContext { constructor(mode) { this.mode = mode; } } exports.SFFileUploadContext = SFFileUploadContext; class PartStorageData { CFGLoader() { if (!this._InitializationResultPromise) throw new Error("This part was never initialized: " + this._ReferenceKey); return this._InitializationResultPromise; } static ClearCache() { this._LoadedParts = new Map(); } /** * * @param client sfRestClient * @param partName something like ActionItems or ProjectCA * @param forDocType GUID * @param forProject project ID; if empty client.GetPageProjectKey() is used * @param context * @param usingCfg if supplied, stored, otherwise resolved * @returns * @see RegisterRestoredCFG */ static PartStorageDataFactory(client, partName, forDocType, forProject, context, usingCfg) { if (!forProject) forProject = client.GetPageProjectKey(); const ReferenceKey = PartStorageData.GetPartContextKey(partName, forDocType, forProject, context); let thisPart; if (PartStorageData._LoadedParts.has(ReferenceKey)) thisPart = PartStorageData._LoadedParts.get(ReferenceKey); else { thisPart = new PartStorageData(client, partName, forDocType, forProject, context); if (usingCfg && usingCfg.PartName === partName) { thisPart._InitializationResultPromise = new Promise((preloaded) => { thisPart.CFG = usingCfg; preloaded(usingCfg); }); return thisPart; } const api = new SwaggerClients_1.UICFGClient(PartStorageData._SiteURL); try { thisPart._InitializationResultPromise = api.getLiveDisplay(partName, forDocType, forProject, context); if (thisPart._InitializationResultPromise) { thisPart._InitializationResultPromise.then((r) => { thisPart.CFG = r; }); } } catch (error) { console.warn(error); thisPart.CFG = new SwaggerClients_1.UIDisplayPart(); throw error; } } return thisPart; } /** * * @param client * @param lookupName * @param usingCfg * @returns * @see RegisterRestoredLookupCFG */ static PartStorageDataLookupFactory(client, lookupName, usingCfg) { var ReferenceKey = PartStorageData.GetPartContextKey(lookupName, "lookup", "lookup", "lookup"); var thisPart; if (PartStorageData._LoadedParts.has(ReferenceKey)) thisPart = PartStorageData._LoadedParts.get(ReferenceKey); else { thisPart = new PartStorageData(client, lookupName, "lookup", "lookup", "lookup"); if (usingCfg && usingCfg.PartName === lookupName) { thisPart._InitializationResultPromise = new Promise((preloaded) => { thisPart.CFG = usingCfg; preloaded(usingCfg); }); return thisPart; } var api = new SwaggerClients_1.UICFGClient(PartStorageData._SiteURL); thisPart._InitializationResultPromise = api.getLookupDisplay(lookupName); if (thisPart._InitializationResultPromise) { thisPart._InitializationResultPromise.then((r) => { thisPart.CFG = r; }); } } return thisPart; } static GetPartContextKey(partName, forDocType, forProject, context) { return `${partName}[${context}]::${forDocType}#${forProject}`; // "{0}[{2}]::{1}".sfFormat(partName, forDocType, context); } GetDataModelBuildContextKey() { PartStorageData._DMCount++; return `DVM-${this._ForPartName}#${PartStorageData._DMCount}`; } constructor(client, partName, forDocType, forProject, context) { this.CFG = null; this.DataModels = new Map(); this.RestClient = client; this._PromiseList = null; this._ReferenceKey = PartStorageData.GetPartContextKey(partName, forDocType, forProject, context); this._ForPartName = partName; PartStorageData._LoadedParts.set(this._ReferenceKey, this); this._InitializationResultPromise = null; if (!PartStorageData._SiteURL) { var ApplicationPath = window.__HTTPApplicationName(); if (ApplicationPath.toLocaleLowerCase() === "powerux") ApplicationPath = 'sfPMS'; PartStorageData._SiteURL = `${window.location.origin}/${ApplicationPath || 'sfPMS'}`; } } } PartStorageData._DMCount = 0; PartStorageData._LoadedParts = new Map(); /** * Options for Query Alias Popup ( QAPopInfo ) */ class QAInfoOptions { // URLCallback: () => string //dvFor:string // "*"; /** * Finds closest element with .sfUIPopQA and its matching .uiQA-xxx and creates options class from CSS -- attributes * * * .sfQA-RoleRouteInfo { * * --dialog-title:"Routes that Reference Role" ; * --dialog-class:sfUIShowInfo; * --dialog-value-class: false * --dialog-empty-text:"No route references were found." ; * --dialogQueryL:"util/jstNodes.ashx/qa/RoleMemberInfo/$K"; * } */ static QAInfoOptionsFromCSSFactory(forElement) { var thisPart; var title = "", cssClass = "sfUIShowInfo", emptyText = "Nothing to see here!", queryURL = "", ValueCSS = ""; if (!forElement.hasClass("sfUIPopQA")) forElement = forElement.closest(".sfUIPopQA"); if (forElement.length === 0) { console.log("QAInfoOptionsFromCSSFactory() could not find .sfUIPopQA "); return null; } title = QAInfoOptions.CSSPropertyValueOrEmpty(forElement, "--dialog-title"); cssClass = QAInfoOptions.CSSPropertyValueOrEmpty(forElement, "--dialog-class", cssClass); ValueCSS = QAInfoOptions.CSSPropertyValueOrEmpty(forElement, "--dialog-value-class"); emptyText = QAInfoOptions.CSSPropertyValueOrEmpty(forElement, "--dialog-empty-text", emptyText); queryURL = QAInfoOptions.CSSPropertyValueOrEmpty(forElement, "--dialog-query"); thisPart = new QAInfoOptions(title, cssClass, emptyText, queryURL); if (ValueCSS) thisPart.ValueCSS = ValueCSS.trim(); thisPart.LoadFromDataAttributes(forElement); return thisPart; } /** * * @param fromElement * @param cssName * @param defaultValue Specify if default is not empty string * @returns */ static CSSPropertyValueOrEmpty(fromElement, cssName, defaultValue) { let CSSValue = fromElement.css(cssName); if (CSSValue) { CSSValue = CSSValue.trim(); if (CSSValue.startsWith("'") || CSSValue.startsWith("\"")) { try { // CSSValue = eval(CSSValue); // Instead of eval, use string replace CSSValue = CSSValue.replace(/['"]/g, '').trim(); CSSValue = CSSValue.trim(); } catch (ex) { console.warn("CSSPropertyValueOrEmpty could not EVAL {0}".sfFormat(CSSValue)); } } } return CSSValue?.trim() ?? defaultValue ?? ""; } LoadFromDataAttributes(fromElement) { var elementData = fromElement.data(); if ("dialogtitle" in elementData) this.DialogTitle = elementData.dialogtitle; if ("dialogcss" in elementData) this.DialogCSS = elementData.dialogcss.trim(); if ("valuecss" in elementData) this.ValueCSS = elementData.valuecss.trim(); if ("emptydialogtext" in elementData) this.EmptyDialogText = elementData.emptydialogtext.trim(); if ("dialogtitle" in elementData) { this.DialogTitle = elementData.dialogtitle; } } constructor(title, cssClass, emptyText, queryURL) { //, urlCallback: () => string ) { this.DialogTitle = title; // "Routes that Reference Role" ; this.DialogCSS = "sfUIShowInfo"; if (cssClass) this.DialogCSS = cssClass.trim(); // "sfUIShowInfo"; this.EmptyDialogText = emptyText; // "No route references were found." ; //this.dvFor = "*"; //this.URLCallback = urlCallback; this.QueryURL = queryURL; if (!this.DialogCSS) this.DialogCSS = "sfUIShowInfo"; if (!this.EmptyDialogText) this.EmptyDialogText = "Nothing to see here..."; } } class NVPair { } exports.NVPair = NVPair; class WCCData { } exports.WCCData = WCCData; class InvokeOptions { } exports.InvokeOptions = InvokeOptions; ; ; /** Spitfire PM Client * @example top.sfClient.GetDV(...) * @example Access static methods or shared properties from console: top.staticBase._IconMap */ class sfRestClient { ServerVersion() { var result = ""; if (sfRestClient._WCC.Version) result = sfRestClient._WCC.Version; return result; } /** * Applies removals, then changes, then additions * @param rawData array of Data Model or View Model * @param keyName name of key field in this data model * @param changes (Add,Change,Remote) * @returns merged rawData * @see BuildDataSummary */ ApplyDataChanges(rawData, keyName, changes) { var RESTClient = this; var StartAtTicks = Date.now(); var RemoveCount = 0, ChangeCount = 0, AddCount = 0; if (!rawData) rawData = []; if (changes.Remove) { changes.Remove.forEach(element => { var foundRow = RESTClient.FindRowIndexByKey(rawData, keyName, element); if (typeof foundRow === "number" && foundRow >= 0) { rawData.splice(foundRow, 1); RemoveCount++; } else if (sfRestClient._Options.LogLevel >= LoggingLevels.Verbose) console.log("ApplyDataChanges(REMOVE) did not find a row with key {0}".sfFormat(element)); }); } if (changes.Change) { changes.Change.forEach((element) => { var thisKey = element[keyName]; var foundRow = RESTClient.FindRowIndexByKey(rawData, keyName, thisKey); if (typeof foundRow === "number" && foundRow >= 0) { rawData[foundRow] = element; ChangeCount++; } else { if (sfRestClient._Options.LogLevel >= LoggingLevels.Verbose) console.log("ApplyDataChanges(CHANGE) did not find a row with key {0} (changed to add)".sfFormat(element)); changes.Add?.push(element); // !!! does this work? } }); } if (changes.Add) { changes.Add.forEach((element) => { var thisKey = element[keyName]; var foundRow = RESTClient.FindRowIndexByKey(rawData, keyName, thisKey); if (typeof foundRow !== "number" || foundRow < 0) { rawData.push(element); AddCount++; } else { rawData[foundRow] = element; ChangeCount++; } }); } if (sfRestClient._Options.LogLevel >= LoggingLevels.Verbose) console.log("ApplyDataChanges({0}) removed {1}, changed {2}, added {3} in {4}t ".sfFormat(keyName, RemoveCount, ChangeCount, AddCount, Date.now() - StartAtTicks)); return rawData; } /** * Extracts key, etag pairs from a set of data * @param rawData array of Data Models or View Models, must have etag * @param keyName * @returns array of key, etag pairs, suitable for passing to getChange* API endpoints * @see ApplyDataChanges */ BuildDataSummary(rawData, keyName) { var result = []; if (!rawData) return result; if (!Array.isArray(rawData)) { var SingleElementArrayOfRawData = []; SingleElementArrayOfRawData.push(rawData); rawData = SingleElementArrayOfRawData; } else { if (rawData.length === 0) return result; } if (!(keyName in rawData[0])) { console.warn("BuildDataSummary() - rawData does not include keyName {0} ".sfFormat(keyName)); return result; } if (!("ETag" in rawData[0])) { console.warn("BuildDataSummary() - rawData does not include ETag".sfFormat(keyName)); return result; } var RESTClient = this; rawData.forEach(function (row) { if (row[keyName] && !result.find(el => el.RowKey === row[keyName])) { result.push(new _SwaggerClientExports.CurrentDataSummary({ "RowKey": row[keyName], "ETag": row["ETag"] })); } }); return result; } /** * Async builds a View Model for the rawData, given part context. - use .then() */ BuildViewModelForContext(partName, context, forDocType, rawData) { if (!sfRestClient._z.WCCLoaded) this.LoadUserSessionInfo(); var RESTClient = this; var thisPart = PartStorageData.PartStorageDataFactory(this, partName, forDocType, "", context); if (!thisPart) { console.warn("Count not resolve part {0}".sfFormat(PartStorageData.GetPartContextKey(partName, forDocType, "", context))); var EmptyPromise = new Promise((resolve) => resolve(rawData)); return EmptyPromise; } var FinalViewModelPromise = new Promise((finalResolve) => { thisPart.CFGLoader().then(() => { var ViewModelPromise = this._ConstructViewModel(thisPart, rawData); ViewModelPromise.then((r) => { finalResolve(r); const RawResultIsArray = (r && Array.isArray(r)); let rowCount = 1; if (!RawResultIsArray) { if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`BuildViewModelForContext GA ${partName} ${typeof r} isArray ${Array.isArray(r)} - single row`, r); } else rowCount = r.length; RESTClient.GAViewModelEvent(partName, rowCount); }); }); }); return FinalViewModelPromise; } // /** // * Legacy version of BuildViewModelForContext - use .done() // * @deprecated use BuildViewModelForContext() // */ // BuildViewModel(partName: string, context: string, rawData: any, unusedCfgData: undefined, forDocType: GUID | undefined): JQueryPromise<any> { // if (!sfRestClient._z.WCCLoaded) this.LoadUserSessionInfo(); // var thisPart: PartStorageData | undefined = PartStorageData.PartStorageDataFactory(this, partName, forDocType, "",context); // var darnSoon = $.Deferred(); // var ResultReady = darnSoon.promise(); // // what purpose would this serve?? if (cfg) thisPart.CFGLoader = cfg; // thisPart.CFGLoader().then(() => { // this._ConstructViewModel(thisPart!, rawData) // .then((r) => darnSoon.resolve(r)) // } // ); // return ResultReady; // } /** * Updates row visibility on the server and updates the in-memory flags for display of this row * @param partName ProjTeam or ProjectPublicInfo * @param rawData ViewModel containing raw row data */ ToggleRowVisibility(partName, rawData) { var DataField = "TeamList"; var api; const FlagVisibleFieldName = "_DefaultRowVisible"; var ServerUpdatePromise; var RowKey = ""; var NewValue = false; var APIContext = ""; if (partName === "ProjTeam") { DataField = "TeamList"; api = new SwaggerClients_1.ProjectTeamClient(this._SiteURL); NewValue = !(rawData[FlagVisibleFieldName]); RowKey = rawData["UserProjectKey"]; APIContext = this.GetPageProjectKey(); ServerUpdatePromise = api.patchProjectTeam(APIContext, RowKey, DataField, NewValue.toString()); } else if (partName === "ProjectPublicInfo") { DataField = "UserList"; api = new SwaggerClients_1.ProjectsClient(this._SiteURL); NewValue = !(rawData[FlagVisibleFieldName]); RowKey = rawData["UserProjectKey"]; APIContext = this.EmptyKey; // future! get current user key ServerUpdatePromise = api.patchUserProjectList(APIContext, RowKey, DataField, NewValue.toString()); } else { console.warn("ToggleRowVisibility - Unsupported: ", partName); ServerUpdatePromise = undefined; } var FinalPromise = new Promise((finalResolve) => { if (!ServerUpdatePromise) { finalResolve(false); return; } ServerUpdatePromise.then(() => { rawData[FlagVisibleFieldName] = NewValue; rawData[DataField] = NewValue; finalResolve(true); }).catch((reason) => { console.warn("ToggleRowVisibility({0},{1}) Failed persist {2} to {3}, {4}".sfFormat(partName, RowKey, DataField, NewValue, reason)); finalResolve(false); }); }); return FinalPromise; } /** Writes to console log * @argument msg text to write * @argument level msg is suppressed if logging level is less than specified, default is verbose */ Log(msg, level = LoggingLevels.Verbose) { if (sfRestClient._Options.LogLevel >= level) console.log(msg); } /** * Applies CFG data to raw Data Model, returns promise that resolves when View Model is ready */ _ConstructViewModel(thisPart, rawData) { if (!thisPart || !thisPart.CFG || !thisPart.CFG.UIItems) { console.error('_ConstructViewModel requires CFG and UI', thisPart); throw `Cannot construct requested ViewModel`; } var StartAtTicks = Date.now(); var DataModelBuildKey = thisPart.GetDataModelBuildContextKey(); var FailCount = 0; var SingleInstanceMode = false; if (!Array.isArray(rawData)) { SingleInstanceMode = true; var SingleElementArrayOfRawData = []; SingleElementArrayOfRawData.push(rawData); rawData = SingleElementArrayOfRawData; } thisPart.DataModels.set(DataModelBuildKey, rawData); thisPart._PromiseList = []; // this loop builds PromiseList thisPart.CFG.UIItems.forEach(element => thisPart.RestClient._ApplyUICFGtoRawData(element, thisPart, DataModelBuildKey)); var ViewModelPromise = new Promise((resolve) => { $.when.apply($, thisPart._PromiseList) .done(function () { var FinalData = thisPart.DataModels.get(DataModelBuildKey); if (SingleInstanceMode) FinalData = FinalData[0]; resolve(FinalData); thisPart.DataModels.delete(DataModelBuildKey); if (sfRestClient._Options.LogLevel >= LoggingLevels.Verbose) console.log("ViewModel {0} complete in {1}t".sfFormat(DataModelBuildKey, Date.now() - StartAtTicks)); }).fail(function (jqXHR, textStatus, errorThrown) { if (FailCount === 0) { var FinalData = thisPart.DataModels.get(DataModelBuildKey); if (SingleInstanceMode) FinalData = FinalData[0]; resolve(FinalData); thisPart.DataModels.delete(DataModelBuildKey); FailCount++; } console.warn("ViewModel {0} failed to complete cleanly; {2} fails; {1}t".sfFormat(DataModelBuildKey, Date.now() - StartAtTicks, FailCount)); }); }); return ViewModelPromise; } async WaitForSessionLoaded() { if (!sfRestClient._z.WCCLoaded) try { await this.LoadUserSessionInfo(); } catch (ex) { console.warn('IsSessionLoaded()', ex); } } /** * async: returns an numeric bit-flag indicating the user's permission level (R=1,I=2,U=4,D=8,S=16) * When the user has admin permissions, these are blended (unless ucModule = WORK) * @param ucModule WORK,SYS, PAGE, PART,LIST * @param ucFunction see xsfUCFunction * @param optionalDTK guid or undefined * @param optionalProject */ CheckPermit(ucModule, ucFunction, optionalDTK, optionalProject, optionalReference) { var RESTClient = this; //was $.Deferred(); var DeferredPermitResult = new Promise(async (ResolveThisPermit, rejectThisPermit) => { if (!sfRestClient._z.WCCLoaded) { let retryCount = 0; while (!sfRestClient._z.WCCLoaded && retryCount < 9) try { const usePageName = ((retryCount++ < 3) && (sfRestClient.ResolvedPageInfo.LastResolvedPageTypeName & RESTClient.PageTypeNames.Unauthenticated) === RESTClient.PageTypeNames.Unauthenticated) ? `${string_extensions_1.sfApplicationRootPath}/wx/#!/main/home` : undefined; await RESTClient.LoadUserSessionInfo(false, usePageName); } catch (ex) { rejectThisPermit(ex.message); return; } } if (typeof optionalDTK !== "string") optionalDTK = ""; if (typeof optionalReference !== "string") optionalReference = ""; if (typeof optionalProject !== "string" || !optionalProject) optionalProject = "0"; var PermitCacheID = ucModule + "_" + ucFunction + "_T" + optionalDTK.replaceAll("-", "") + "_R" + optionalReference + "_P" + optionalProject; var ValueFromCache = sfRestClient._UserPermitResultCache.get(PermitCacheID); if (typeof ValueFromCache === "number") { if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}(${ucModule}:${ucFunction},${optionalProject}) = ${ValueFromCache}; from static cache `); ResolveThisPermit(ValueFromCache); return; } var UCFK = ""; var ThisProjectPermitSet; var UCFKDeferredResult = $.Deferred(); var UCFKPromise = UCFKDeferredResult.promise(); var PPSDeferredResult = $.Deferred(); var PPSPromise = PPSDeferredResult.promise(); if (!sfRestClient.PermitMapLoaded()) { // new approach, lets wait for a map await RESTClient.LoadUCFunctionMap(); if (!sfRestClient.PermitMapLoaded()) { // still no map!? console.warn("CheckPermits() could not load Permit Map!!"); } } if (ucModule.length === 36) { UCFK = ucModule; UCFKDeferredResult.resolve(UCFK); } else if (sfRestClient._UCPermitMap && ucModule in sfRestClient._UCPermitMap && ucFunction in sfRestClient._UCPermitMap[ucModule]) { UCFK = sfRestClient._UCPermitMap[ucModule][ucFunction]; UCFKDeferredResult.resolve(UCFK); } else UCFKDeferredResult.resolve(RESTClient.EmptyKey); if (!UCFK) { if (sfRestClient._WCC.UserKey === RESTClient.EmptyKey) console.warn(`CheckPermit(): >>>> No user/session!! <<<< Therefore no permission for ${ucModule}|${ucFunction}! LOGIN AGAIN!`); else console.warn(`CheckPermit could not find ${ucModule}|${ucFunction} - verify proper case/trim!`); ResolveThisPermit(0); return; } if (!sfRestClient.GlobalPermitAPIPromise) { if (!(sfRestClient._LoadedPermits.has("0"))) { // global permissions var api = new SwaggerClients_1.SessionClient(this._SiteURL); sfRestClient.GlobalPermitAPIPromise = api.getProjectPermits("0"); sfRestClient._LoadedPermits.set("0", new _SwaggerClientExports.UCPermitSet()); // this prevents repeat requests if (sfRestClient.GlobalPermitAPIPromise) { sfRestClient.GlobalPermitAPIPromise.then((r) => { if (r) { //if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}() Received Global Permits from server...`); sfRestClient._LoadedPermits.set("0", r); if (sfRestClient._Options.LogLevel >= LoggingLevels.Verbose) console.log(`CheckPermit#${RESTClient.ThisInstanceID}() Loaded Global Permits from server...`); } }); } } } if (sfRestClient.GlobalPermitAPIPromise) { //if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}() Waiting for Global Permits from server...`); await sfRestClient.GlobalPermitAPIPromise; if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}() Global Permits ready() `, sfRestClient._LoadedPermits.get(optionalProject)); } if (!(sfRestClient._LoadedPermits.has(optionalProject))) { var apiResult; var MyAPIRequest = false; if (!sfRestClient._LoadingPermitRequests.has(optionalProject)) { var api = new SwaggerClients_1.SessionClient(this._SiteURL); apiResult = api.getProjectPermits(optionalProject); sfRestClient._LoadingPermitRequests.set(optionalProject, apiResult); MyAPIRequest = true; } else apiResult = sfRestClient._LoadingPermitRequests.get(optionalProject); if (apiResult) { apiResult.then((r) => { if (r) { if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}() Loaded Project ${optionalProject} Permits from server...`); sfRestClient._LoadedPermits.set(optionalProject, r); ThisProjectPermitSet = r; PPSDeferredResult.resolve(r); if (MyAPIRequest && optionalProject) sfRestClient._LoadingPermitRequests.delete(optionalProject); } }); } } else { ThisProjectPermitSet = sfRestClient._LoadedPermits.get(optionalProject); PPSDeferredResult.resolve(ThisProjectPermitSet); } var finalCheck = [PPSPromise, UCFKPromise]; $.when.apply($, finalCheck).done(function () { var finalPermit = 0; var GlobalPermits = sfRestClient._LoadedPermits.get("0")?.Permits; $.each([ThisProjectPermitSet?.Permits, GlobalPermits], function CheckOneSource(sourceIdx, thisSource) { if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}(${ucModule}:${ucFunction},${optionalProject}) checking ${thisSource === ThisProjectPermitSet?.Permits ? "project" : "global"}`); $.each(thisSource, function OneCapabilityCheck(ThisUCFK, capabilitySet) { if (ThisUCFK === UCFK) { if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}(${ucModule}:${ucFunction},${optionalProject}) UCFK ${UCFK}, cl:`, capabilitySet); $.each(capabilitySet, function OnePermitCheck(_n, p) { if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}(${ucModule}:${ucFunction},${optionalProject}) UCFK ${UCFK}, p:`, p); var thisPermitValue = 0; if (p.IsGlobal || RESTClient._PermitMatches(p, optionalDTK, optionalReference)) { if (p.ReadOK) thisPermitValue += RESTClient.PermissionFlags.Read; if (p.InsOK) thisPermitValue += RESTClient.PermissionFlags.Insert; if (p.UpdOK) thisPermitValue += RESTClient.PermissionFlags.Update; if (p.DelOK) thisPermitValue += RESTClient.PermissionFlags.Delete; if (p.BlanketOK) thisPermitValue += RESTClient.PermissionFlags.Special; } finalPermit |= thisPermitValue; return (finalPermit !== 31); }); } return (finalPermit !== 31); }); }); if (ucModule !== "WORK") finalPermit = finalPermit | sfRestClient._WCC.AdminLevel; sfRestClient._UserPermitResultCache.set(PermitCacheID, finalPermit); if (sfRestClient._Options.LogLevel >= LoggingLevels.Debug) console.log(`CheckPermit#${RESTClient.ThisInstanceID}(${ucModule}:${ucFunction},${optionalProject}) = Usr:${finalPermit},Adm:${sfRestClient._WCC.AdminLevel}; static cache set `); ResolveThisPermit(finalPermit); // we do have global permits above: what use case was this for??? // if (finalPermit === 31) { // RESTClient._UserPermitResultCache.set(PermitCacheID, finalPermit); // ResolveThisPermit(finalPermit); // } // else { // // not ideal: future we need a way to hold global permits here too // var api : SessionClient = RESTClient.NewAPIClient("SessionClient"); // api.getCapabilityPermits(ucModule,ucFunction.replaceAll(".","!").replaceAll("/","@"),optionalProject,optionalDTK).then((r)=>{ // RESTClient._UserPermitResultCache.set(PermitCacheID, r); // ResolveThisPermit(r); // }); // } }); }); return DeferredPermitResult; // wait for .then, use (r) } /** * Resolves list of part names with permissions * @returns object with part names and permission flags */ GetPagePartPermits(forPageName, pageKey) { if (typeof pageKey === "undefined") pageKey = "0"; if (typeof forPageName === "undefined") { forPageName = this.ResolvePageTypeName(); } var DeferredPermitResult = new Promise((ResolveList) => { var finalCheck = []; var PartNameList = []; var PageParts = {}; if (forPageName === this.PageTypeNames.HomeDashboard) { PartNameList = ["ActionItems", "ProjectList", "AlertList"]; } else if (forPageName === this.PageTypeNames.ProjectDashboard) { PartNameList = ["ProjTeam", "ProjectKPI", "ProjLinks", "ProjectCA", "ProjNote", "ProjPhoto", "ProjWeather"]; if (pageKey.length <= 1) pageKey = this.GetPageProjectKey(); // special case for project dashboards: ProjDocMenu PageParts["ProjDocMenu"] = 1; } else if (forPageName === this.PageTypeNames.ExecutiveDashboard) { PartNameList = ["ProjectRES"]; //if (pageKey!.length <= 1) pageKey = this.GetPageProjectKey(); // special case for project dashboards: ProjDocMenu } else if (forPageName === this.PageTypeNames.Catalog) { PartNameList = ["DocSearch", "CatVersions", "Folders"]; } else { console.warn("GetPagePartPermits() does not (yet) support ", forPageName); } PartNameList.forEach(element => { var OnePartPermitDeferred = $.Deferred(); var OnePartPermitDeferredPromise = OnePartPermitDeferred.promise(); finalCheck.push(OnePartPermitDeferredPromise); if (!pageKey && typeof pageKey === "string") pageKey = undefined; this.CheckPermit("PART", element, undefined, pageKey).then((r) => { PageParts[element] = r; OnePartPermitDeferred.resolve(r); }); }); $.when.apply($, finalCheck).done(function () { ResolveList(PageParts); }); }); return DeferredPermitResult; } /** * Returns set of choices for an auto-complete based on the seed value * @param lookupName * @param seedValue 0 or more characters which will limit the suggestions * @param dependsOn 0 to 4 values required by the lookup for context; @see sfRestClient.GatherDependsOnValues to process string * @param limit a reasonable number of suggestions to be returned; zero or negative replaced with option.SuggestionLimit * @returns promise of suggestions. reuses a matching promise from withing about 4 minutes * @description context is provided from GetPageContextValue("dsCacheKey") */ GetLookupSuggestions(lookupName, seedValue, dependsOn, limit = -1) { var apiResultPromise; var RESTClient = this; var api = new SwaggerClients_1.LookupClient(); var DependsOnSet = ["", "", "", "", ""]; if (limit <= 0) limit = sfRestClient._Options.SuggestionLimit; if (Array.isArray(dependsOn)) { $.each(dependsOn, function (i, v) { DependsOnSet[i] = v; }); } else if (dependsOn) { DependsOnSet[0] = dependsOn; } //DependsOnSet = [1, "a", 2.5, true, null, undefined, {key: "value"}]; DependsOnSet = DependsOnSet.map(String); // makes sure all the elements are strings const suggestionGroupKey = `${lookupName}[${seedValue.sfHashCode()}]@${DependsOnSet[0].sfHashCode()}x${DependsOnSet[1].sfHashCode()}x${DependsOnSet[2].sfHashCode()}x${DependsOnSet[3].sfHashCode()}x`; const TimeNow = Date.now(); if (sfRestClient.RecentSuggestionAsOf.has(suggestionGroupKey)) { if (sfRestClient.RecentSuggestionAsOf.get(suggestionGroupKey) < TimeNow) { // expired sfRestClient.RecentSuggestionAsOf.delete(suggestionGroupKey); } else { if (sfRestClient.RecentSuggestionResultMap.has(suggestionGroupKey)) { apiResultPromise = sfRestClient.RecentSuggestionResultMap.get(suggestionGroupKey); if (apiResultPromise) return apiResultPromise; } } } //var apiResultPromise: Promise<Suggestion[] | null> = api.getSuggestions4(lookupName, "1", DependsOnSet[0], DependsOnSet[1], DependsOnSet[2], DependsOnSet[3],seedValue,limit); var SuggestionContext = new _SwaggerClientExports.QueryFilters(); SuggestionContext.DependsOn = DependsOnSet; SuggestionContext.MatchingSeed = seedValue; SuggestionContext.ResultLimit = limit; apiResultPromise = api.getSuggestionsWithContext(lookupName, RESTClient.GetPageDataContext(), SuggestionContext); sfRestClient.RecentSuggestionAsOf.set(suggestionGroupKey, TimeNow + sfRestClient.suggestionCacheLifespan); sfRestClient.RecentSuggestionResultMap.set(suggestionGroupKey, apiResultPromise); return apiResultPromise; } /** returns GetPageContextValue("dsCacheKey") */ GetPageDataContext() { let result = this.GetPageContextValue("dsCacheKey"); return result; } ; /** Returns list of recent documents from server as updated by PopDoc() calls */ GetRecentDocuments() { return sfRestClient.RecentDocumentList; } /** * Returns a View Model constructured for the result rows matching specified lookup context and filters * @param lookupName * @param filterValues default to {} can include NVPs and zero to 4 dependsOn strings * @returns */ GetLookupResults(lookupName, /** * either a string or string array (up to 4 elements); */ filterValues) { var apiResultPromise; var RESTClient = this; var api = new SwaggerClients_1.LookupClient(); var FinalViewModelPromise = new Promise((finalResolve) => { apiResultPromise = api.getLookupResultAll(lookupName, RESTClient.GetPageDataContext(), filterValues); apiResultPromise.then((lookupResultData) => { var thisPart = PartStorageData.PartStorageDataLookupFactory(this, lookupName); thisPart.CFGLoader().then(() => { var ViewModelPromise = this._ConstructViewModel(thisPart, lookupResultData); ViewModelPromise.then((r) => finalResolve(r)); }); }); }); return FinalViewModelPromise; } /** removes an exact match from the session DV cache */ ClearDV(displayName, keyValue, /** * either a string or string array (up to 4 elements) */ dependsOn) { const requestData = this._getDVRequestString(displayName, keyValue, dependsOn); const cacheKey = `GetDV:L${requestData.length}H${requestData.sfHashCode()}`; try { var result = sessionStorage.getItem(cacheKey); if (typeof result === "string") { sessionStorage.removeItem(cacheKey); return true; } } catch (err2) { throw `ClearDV() cache error: ${err2.message}`; } return false; } /** * Get Display Value using DV-Name and key value, with 0 to 4 dependencies. * @param displayName the name of a display value rule (eg sfUser, RoleName, etc) * @param keyValue the primary or most significant key * @param dependsOn optional array of context values (multi-part key); 0 to 4 elements allowed; hint: use 'empty' for an empty value * @param autoVary force bypass of cache * @returns Promise for String */ GetDV(displayName, keyValue, /** * either a string or string array (up to 4 elements) */ dependsOn, /** * when true, a unique value is added to defeat cache */ autoVary) { var apiResultPromise; if (!keyValue || keyValue === this.EmptyKey) return new Promise((resolve) => resolve("")); if (displayName === "0") return new Promise((resolve) => resolve(keyValue)); var requestData = this._getDVRequestString(displayName, keyValue, dependsOn); if (autoVary) requestData += `?${this._getVaryByQValue()}`; const cacheKey = `GetDV:L${requestData.length}H${requestData.sfHashCode()}`; try { var result = sessionStorage.getItem(cacheKey); if (!result) { // continues below - must get value } if (typeof result === "string") { var CacheResult = JSON.parse(result); if ((Date.now() - CacheResult.w) < sfRestClient._Options.DVCacheLife) { apiResultPromise = new Promise((resolve) => resolve(CacheResult.v)); return apiResultPromise; } } // if falls through, we get a fresh value } catch (err2) { new Error("GetDV() cache error: " + err2.message); } if (this._CachedDVRequests.has(cacheKey)) { if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`GetDV(${displayName}:${keyValue}) reused pending request `); return this._CachedDVRequests.get(cacheKey); // already requested, still pending or not doesn't matter } var RESTClient = this; var api = new SwaggerClients_1.LookupClient(); var DependsOnSet = ["undefined", "undefined", "undefined", "undefined"]; if (Array.isArray(dependsOn)) { $.each(dependsOn, function (i, v) { if (v !== undefined) DependsOnSet[i] = v; }); } else if (dependsOn) DependsOnSet[0] = dependsOn; //removed all "undefined" from the end, leaving at least one for (let index = DependsOnSet.length - 1; index >= 1; index--) { if (DependsOnSet[index - 1] != "undefined") break; if (DependsOnSet[index] == "undefined") DependsOnSet.pop(); } var DVFilters = new _SwaggerClientExports.DVRequest(); DVFilters.DVName = displayName; DVFilters.MatchingValue = keyValue; DVFilters.DependsOn = DependsOnSet; DVFilters.RequestID = cacheKey; //DVFilters.DataContext = "1"; var apiResultPromise = this._ThrottleDVRequests(api, DVFilters); if (apiResultPromise) { apiResultPromise.then((dvResult) => { if (dvResult) { sessionStorage.setItem(cacheKey, JSON.stringify({ v: dvResult, w: Date.now() })); if (RESTClient._CachedDVRequests.has(cacheKey)) RESTClient._CachedDVRequests.delete(cacheKey); } }); } this._CachedDVRequests.set(cacheKey, apiResultPromise); return apiResultPromise; } /** * Throttles and batches requests for display values (DVs) to improve performance. * If there are pending DV requests, it adds the new request to a queue and sets up a timer to batch the requests. * Otherwise, it directly calls the `getDisplayableValue` method on the `LookupClient` API. * * @param api - The `LookupClient` API instance to use for making DV requests. * @param dvRequest - The DV request object containing the necessary parameters. * @returns A Promise that resolves to the requested display value, or `null` if the request fails. */ _ThrottleDVRequests(api, dvRequest) { var DVResultPromise; var RESTClient = this; if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`ThrottleDV(${dvRequest.DVName}:${dvRequest.MatchingValue}) with ${this._CachedDVRequests.size} pending `); if (this._CachedDVRequests.size > 0) { DVResultPromise = new Promise((resolvedTo) => { if (!RESTClient._DVRequestTimerHandle) { if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`ThrottleDV() created BatchDV `); RESTClient._DVRequestTimerHandle = setTimeout(() => { var thisGroup = []; Object.assign(thisGroup, RESTClient._DVRequestQueue); RESTClient._DVRequestQueue = []; RESTClient._DVRequestTimerHandle = undefined; if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`BatchDV() with ${thisGroup.length} in group `); api.getDisplayValueCollection(thisGroup) .then((dvList) => { if (sfRestClient._Options.LogLevel >= LoggingLevels.VerboseDebug) console.log(`BatchDV().THEN `, dvList); if (dvList) dvList.forEach(element => { if (element.value) { var DVDonePromise = this._DVThrottledResolvers.get(element.value); if (DVDonePromise) { DVDonePromise(element.label); this._DVThrottledResolvers.delete(element.value); } else console.warn("Batch DV could not find element", element); } else console.warn("Batch DV could not find cacheId", element); });