1 2/*************ReportApiManager****************** 3 4 * Manage the report api calls 5 * Ensure 1 api call per configurations (for excel,graph, summary and table) 6 * Uses ApiDataManager and report to manage and handle the data 7 * Divided to regions: 8 * 1. constructor region: construct data and config 9 * 2. api call region: make the api call for report 10 * 3. data manager region: makes the data processing for api call 11 * 4. Utils,Validators,Filters,Converters region: validates and filter the data from report 12 * 13 * The route from client to api and back: 14 * table: from multisataionTable.cshtml -> reportApiManager -> report -> api 15 * api -> report -> baseMultiStatain -> view 16 17 *ðð½ðð½ðð½ðð½ðð½ðð½ Important ðð½ðð½ðð½ðð½ðð½ðð½ðð½ 18 * ReportApiManager is deleted with each report exit for data correctness 19 * keep it that way!!! 20 *********************************************/ 21function ReportApiManager() { 22 let manager; 23 const this_ = this; 24 25 this.apiDataManager; 26 this.contructor = (reqData, callBackConfig = null) => { 27 this.req_data = configValidator(reqData) 28 if (typeof this.report == 'undefined') { 29 this.report = new Report(); 30 } 31 if (typeof this.apiDataManager === 'undefined' || this.apiDataManager == null) { 32 this.apiDataManager = new ApiDataManager(); 33 this.dataBooting(this.req_data.fromTb) 34 } 35 if (null != callBackConfig) { 36 this.callback_config = callBackConfig 37 this.callback_config["apiDataManager"] = this.apiDataManager 38 } 39 } 40 41 /** 42 * Builds and starts one station's table request using the active table presentation. 43 * The returned promise and the optional callbacks settle with the same formatted 44 * station payload so both legacy callback callers and promise-based transitions work. 45 * @param {Function|null} resolve Optional legacy success callback. 46 * @param {string|number} stationId Station identifier used to build the request. 47 * @param {Function|null} reject Optional legacy failure callback. 48 * @returns {Promise<Array<Object>>} The formatted response and validation data. 49 */ 50 this.sendTableAPIRequest = (resolve, stationId, reject = null) => { 51 let data_object = { 52 report_config: "", 53 params_for_url: "", 54 url: "", 55 includeSummary: false, 56 onlySummary: false 57 }; 58 let config = Object.assign({}, this.callback_config); 59 this.setDataForApiCall(data_object, stationId, config.m_ReportConfig.actualReportType, config.kendoElementType); 60 config["stationId"] = stationId; 61 62 const requestType = JsUtils.isNotEmpty(config.m_ReportConfig.apiData) ? TYPE.POST : TYPE.GET; 63 64 return this.report.getRawDataFromApi(data_object.url, requestType, config, stationId, resolve, reject); 65 66 } 67 /** 68 * Prepares and sends a graph, Excel, or summary API request. 69 * @param {Function} resolve Resolves the caller's per-station promise. 70 * @param {Object} config Active request callback configuration. 71 * @param {Object} data_object Mutable request parameters and route holder. 72 * @param {Function|null} reject Optional rejection callback. 73 * @returns {Promise<Array<Object>>} The formatted response and validation data. 74 */ 75 this.sendAPIRequest = (resolve, config, data_object, reject = null) => { 76 this.setDataForApiCall(data_object, config.stationId, config.m_ReportConfig.actualReportType, config.kendoElementType); 77 return this.report.getStationApiData(data_object.url, config, resolve, reject) 78 } 79 80 /** 81 * Creates request-local route/configuration state so concurrent station requests 82 * cannot overwrite each other's URL or summary flags. 83 * @param {boolean} includeSummary Whether the API should include summary data. 84 * @param {boolean} onlySummary Whether the API should return only summary data. 85 * @returns {Object} A new mutable request descriptor. 86 */ 87 function createDataRequestObject(includeSummary = false, onlySummary = false) { 88 return { 89 report_config: "", 90 params_for_url: "", 91 url: "", 92 includeSummary, 93 onlySummary 94 }; 95 } 96 97 /** 98 * Supports both the legacy callback-only Report loader and the promise-returning 99 * loader. This prevents independently cached scripts from producing undefined 100 * station results during a rolling client update. 101 * @param {Function} invokeRequest Starts a request with resolve and reject callbacks. 102 * @returns {Promise<*>} A promise settled by either the callbacks or returned promise. 103 */ 104 function createSettlingRequest(invokeRequest) { 105 return new Promise((resolve, reject) => { 106 let requestPromise; 107 108 try {
109 requestPromise = invokeRequest(resolve, reject); 110 } 111 catch (error) { 112 reject(error); 113 return; 114 } 115 116 if (requestPromise != null && JsUtils.isFunction(requestPromise.then)) { 117 requestPromise.then(resolve, reject); 118 } 119 }); 120 } 121 122 function setExceedanceDataForApiCall(object, station, reportType) { 123 object.report_config = this_.getNewReportData(object.includeSummary, object.onlySummary, reportType, station); 124 //override the report config to only retrieve the required channel. 125 object.report_config.filterChannels = this_.req_data.monitorChannelsByStationId[station][0]; 126 127 let params = { 128 parameters: encodeURIComponent(JSON.stringify(object.report_config)), 129 reportType: this_.req_data.reportType, 130 stationId: station, 131 showExceedancesOnly: this_.req_data.showExceedancesOnly, 132 includeMaxExceedanceValue: this_.req_data.includeMaxExceedanceValue, 133 }; 134 object.params_for_url = this_.createParamsForUrl(params); 135 136 //add the exceedances to the parameters as a list so it can be parsed by the api. 137 object.params_for_url += (() => { 138 let param = ''; 139 this_.req_data.exceedances.forEach(exceedance => { param += `&exceedances=${exceedance}` }); 140 return param; 141 })(); 142 object.url = `exceedances/counter${object.params_for_url}`; 143 return object; 144 } 145 146 /** 147 * Builds a Matrix request and selects the endpoint from the requested output shape. 148 * Graph rendering uses matrix/graph; horizontal tables, summaries, and Excel use 149 * the canonical matrix/table contract. 150 * @param {Object} object Mutable request parameters and route holder. 151 * @param {number|string} station Station identifier. 152 * @param {string} reportType Active report type. 153 * @param {number|string|null} requestedElementType Request-specific presentation. 154 * @returns {Object} The prepared Matrix request. 155 */ 156 function setMatrixDataForApiCall(object, station, reportType, requestedElementType = null) { 157 object.report_config = this_.getNewReportData(object.includeSummary, object.onlySummary, reportType, station); 158 object.report_config.groupBy = REPORT_GROUP_BY.TimePeriod; 159 //override the report config to only retrieve the required channel. 160 object.report_config.filterChannels = this_.req_data.monitorChannelsByStationId[station][0]; 161 //showMonitorStatusinReports is required to calculate the matrix data 162 object.report_config.showMonitorStatusinReports = this_.req_data.showMonitorStatusinReports; 163 object.report_config.exceedances = this_.req_data.exceedances 164 object.report_config.matrixStructure = this_.req_data.matrixStructure || MATRIX_STRUCTURE.DateXHour; 165 166 let params = { 167 parameters: encodeURIComponent(JSON.stringify(object.report_config)), 168 reportType: this_.req_data.reportType, 169 stationId: station, 170 }; 171 object.params_for_url = this_.createParamsForUrl(params); 172 173 // Presentation changes can leave callback_config describing the previous popup. 174 // Route from this request, falling back only to the normalized request data. 175 let effectiveElementType = requestedElementType ?? this_.req_data.viewType; 176 let reportViewParam = this_.getMatrixReportViewParam(effectiveElementType); 177 object.url = `matrix/${reportViewParam}${object.params_for_url}`; 178 return object; 179 } 180 181 /** 182 * Maps a Matrix presentation to the API response contract required by its renderer. 183 * @param {number|string} requestedElementType Requested Kendo/report presentation. 184 * @returns {string} Matrix API route suffix. 185 */ 186 this.getMatrixReportViewParam = (requestedElementType) => { 187 switch (requestedElementType) { 188 case REPORT_VIEW.HORIZONTAL_TABLE: 189 case REPORT_VIEW.HORIZONTAL_TABLE_SUMMARY: 190 case REPORT_VIEW.EXCEL: 191 return "table"; 192 case REPORT_VIEW.GRAPH: 193 return "graph"; 194 default: 195 return this_.req_data.viewType === REPORT_VIEW.GRAPH ? "graph" : "table"; 196 } 197 } 198 199 /** 200 * Populates a request descriptor with the report configuration and API route. 201 * Matrix routing uses the request-specific element type so retained popup callback 202 * state cannot send a graph request to the table endpoint, or the reverse. 203 * @param {Object} object Mutable request descriptor. 204 * @param {string|number} station Station identifier. 205 * @param {string} reportType Effective report type. 206 * @param {number|string|null} requestedElementType Active Kendo presentation type. 207 */ 208 this.setDataForApiCall = (object, station, reportType, requestedElementType = null) => { 209 switch (this.req_data.reportName) { 210 case REPORTS.index: 211 object.report_config = this.getNewIndexReportData(); 212 this.req_data.reportType = 'index' 213 break; 214 case REPORTS.editBlock: 215 // First pass calls preview; approved pass calls the real update endpoint. 216 const editBlockRequestMode = this.callback_config && this.callback_config.m_ReportManager && JsUtils.isFunction(this.callback_config.m_ReportManager.getEditBlockRequestMode) 217 ? this.callback_config.m_ReportManager.getEditBlockRequestMode() 218 : this.req_data.editBlockRequestMode; 219 object.url = editBlockRequestMode == EDIT_BLOCK_REQUEST_MODE.PREVIEW 220 ? 'editData/block/preview' 221 : 'editData/block'; 222 return; 223 case REPORTS.exceedance: { 224 object = setExceedanceDataForApiCall(object, station, reportType); 225 return; 226 } 227 case REPORTS.matrix: {
228 object = setMatrixDataForApiCall(object, station, reportType, requestedElementType); 229 return; 230 } 231 default: 232 object.report_config = this.getNewReportData(object.includeSummary, object.onlySummary, reportType, station); 233 break; 234 } 235 object.params_for_url = this.createParamsForUrl(object.report_config) 236 237 object.url = `stations/${station}/${this.req_data.reportType}${object.params_for_url}`; 238 } 239 /** 240 * Requests detail data for every selected station and invokes the workbook callback. 241 * Matrix requests are marked as Excel/table-shaped in the cache so a later export can 242 * reuse them without confusing them with graph-shaped data. 243 * @param {Function} callBack Formats the response and generates the workbook. 244 * @param {Array<Object>|null} summary Optional formatted summary rows. 245 * @returns {Promise<*>} Resolves after the workbook callback completes. 246 */ 247 this.sendExcelApiCall = (callBack, summary = null) => { 248 let promise_list = []; 249 //create async data for Poromis.all 250 let asyncDataPromise = (stationId) => { 251 let excelConfig = Object.assign({}, this.callback_config, { 252 stationId, 253 kendoElementType: REPORT_VIEW.EXCEL 254 }); 255 let dataObject = createDataRequestObject(); 256 return createSettlingRequest((resolve, reject) => 257 this.sendAPIRequest(resolve, excelConfig, dataObject, reject)); 258 } 259 //here we need to create a chunks 260 this.createListOfDataChunks(promise_list); 261 return Promise.all(promise_list.map((a) => { 262 return asyncDataPromise(a.station) 263 })).then((complete_response) => { 264 complete_response = filterNulls(complete_response) 265 if (this.checkIfNoDataStatus(complete_response, this.callback_config)) { 266 return 267 } 268 269 let excelDataConfig = Object.assign({}, this.req_data, { 270 viewType: REPORT_VIEW.EXCEL 271 }); 272 this.apiDataManager.setConfig(excelDataConfig) 273 return callBack(complete_response, saveCompleteData, summary); 274 }).finally(finalizeReportApiOperation); 275 } 276 277 /** 278 * Loads all table station chunks, formats and caches them, then updates the Grid. 279 * The operation resolves only after Kendo raises dataBound, allowing presentation 280 * state to commit after rendering rather than after the raw request alone. 281 * @param {kendo.ui.Grid} grid Target Grid instance. 282 * @returns {Promise<*>} The formatted table result after Grid rendering completes. 283 */ 284 this.setTableDSWithData = (grid) => { 285 let callback = this.report.checkDataAndNotifyIfEmpty 286 let promise_list = []; 287 288 let asyncDataPromise = (stationId) => createSettlingRequest((resolve, reject) => 289 this.sendTableAPIRequest(resolve, stationId, reject)); 290 this.createListOfDataChunks(promise_list); 291 return Promise.all(promise_list.map((a) => { 292 return asyncDataPromise(a.station) 293 })).then((completeData) => { 294 completeData = filterNulls(completeData) 295 if (this.checkIfNoDataStatus(completeData, this.callback_config)) { 296 throw createNoReportDataError(); 297 } 298 let res = ReportUtils.formatedDataMerge(completeData, "datetime") 299 res = formatHorizontalTableDates(res); 300 callback(res) 301 //for grid page calculator 302 this.apiDataManager.setConfig(getEffectiveCacheConfig(this.callback_config?.kendoElementType))// save configs 303 this.apiDataManager.setCompleteData(res);//save grid data 304 305 let dataSource = JsUtils.isArray(res) 306 ? res 307 : JsUtils.isArray(res.data) 308 ? res.data 309 : []; 310 311 if (this.callback_config.m_ReportConfig.reportName == REPORTS.exceedance && 312 this.callback_config.m_ReportConfig.showExceedancesOnly) { 313 reportApiManager.apiDataManager.setNumberOfRecords(dataSource); 314 } 315 return waitForGridRender(grid, () => grid.dataSource.data(dataSource), res); 316 }).catch(error => { 317 if (!error?.isNoReportData) { 318 console.error("Report table request failed.", error); 319 } 320 throw error; 321 }).finally(finalizeReportApiOperation) 322 323 } 324 325 /** 326 * Loads graph data for every station, validates its shape, caches it, and renders it. 327 * Each station receives independent request/configuration objects, and failures are 328 * rethrown so the owning presentation transition can roll back and release its guard. 329 * @param {Object} req_config_data Active graph callback and rendering configuration. 330 * @returns {Promise<Array<Object>>} Raw graph data after chart creation completes. 331 */ 332 this.setChartWithData = (req_config_data) => { 333 //create a promise list 334 let promise_list = []; 335 336 //create async data for Poromis.all 337 let asyncDataPromise = (stationId) => { 338 let stationConfig = Object.assign({}, req_config_data, { 339 stationId, 340 kendoElementType: REPORT_VIEW.GRAPH 341 }); 342 let dataObject = createDataRequestObject(); 343 return createSettlingRequest((resolve, reject) => 344 this.sendAPIRequest(resolve, stationConfig, dataObject, reject)); 345 } 346 this.createListOfDataChunks(promise_list); 347 return Promise.all(promise_list.map((a) => { 348 return asyncDataPromise(a.station) 349 })).then((combine_raw_data) => { 350 combine_raw_data = filterNulls(combine_raw_data)
351 if (this.checkIfNoDataStatus(combine_raw_data, req_config_data)) { 352 throw createNoReportDataError(); 353 } 354 if (this.req_data.reportName == "index report") { 355 this.req_data.reportType = REPORT_TYPE.Average 356 combine_raw_data = this.formatIndexRawData(combine_raw_data, req_config_data.m_ReportManager) 357 } 358 this.apiDataManager.setConfig(getEffectiveCacheConfig(req_config_data.kendoElementType)) 359 for (let station_data of combine_raw_data) { 360 this.apiDataManager.setRawData(station_data, station_data.stationId) 361 } 362 return req_config_data.m_ReportManager.createGraph(req_config_data.m_GraphContainer, 363 this.req_data, combine_raw_data, req_config_data.m_SeriesColors) 364 }).catch(error => { 365 if (!error?.isNoReportData) { 366 console.error("Report graph request failed.", error); 367 } 368 throw error; 369 }).finally(finalizeReportApiOperation); 370 } 371 372 /** 373 * Loads, formats, and caches summary data for all selected stations. 374 * @param {Function|null} callback Handles the formatted summary response. 375 * @param {boolean} propagateError Rejects for callers that own the complete 376 * request lifecycle, such as Excel export; legacy callers receive an empty result. 377 * @returns {Promise<Array<Object>>} Formatted summary rows. 378 */ 379 this.initOnlySummary = (callback = null, propagateError = false) => { 380 if (callback == null) { 381 callback = this.callback_config.callback 382 } 383 let promise_list = []; 384 385 let asyncDataPromise = (station) => { 386 let config = Object.assign({}, this.callback_config, { 387 stationId: station, 388 kendoElementType: JsUtils.isNotEmpty(this.callback_config.kendoElementType) 389 ? this.callback_config.kendoElementType 390 : REPORT_VIEW.SUMMARY 391 }); 392 let dataObject = createDataRequestObject(true, true); 393 return createSettlingRequest((resolve, reject) => 394 this.sendAPIRequest(resolve, config, dataObject, reject)); 395 } 396 this.createListOfDataChunks(promise_list); 397 return Promise.all(promise_list.map((a) => { 398 return asyncDataPromise(a.station) 399 })).then((complete_response) => { 400 complete_response = filterNulls(complete_response); 401 let res = ReportUtils.formatedDataMerge(complete_response, "Type"); 402 res = formatHorizontalSummaryDates(res); 403 this.apiDataManager.setSummary(res); 404 callback(res); 405 return res; 406 }).catch(error => { 407 console.error("Report summary request failed.", error); 408 if (propagateError) { 409 throw error; 410 } 411 return []; 412 }).finally(finalizeReportApiOperation); 413 } 414 /** 415 * addSummaryToResponse 416 * @param {any} complete_response 417 * @param {any} summary 418 * if needed merge the response and the summary(like in excel report) 419 */ 420 this.addSummaryToResponse = (complete_response, summary) => { 421 let index = 0 422 if (Array.isArray(complete_response)) { 423 for (let item of complete_response) { 424 if (JsUtils.isNotEmpty(item)) { 425 item.summary = summary[index] 426 index++; 427 } 428 else { 429 let obj = { data: [], summary: summary[index] }; 430 complete_response[index] = obj 431 index++ 432 } 433 } 434 } 435 else { 436 //check the case data comes after table/chart open and for only 1 channel and 1 station 437 complete_response.summary = summary[index] 438 } 439 } 440 /** 441 * Renders compatible cached graph data or starts a fresh graph request. 442 * Matrix cache reuse additionally requires the canonical graph response shape. 443 * @param {Object} req_config_data Active graph callback and rendering configuration. 444 * @returns {Promise<Array<Object>>} Graph data after chart creation completes. 445 */ 446 this.initChartData = (req_config_data) => { 447 //if config are the same 448 let data_for_graph = this.apiDataManager.getRawData(); 449 let requestedViewType = req_config_data.kendoElementType ?? req_c
449onfig_data.m_ReportConfig.viewType; 450 let cachedViewType = this.apiDataManager.getConfig()?.viewType; 451 452 if (this.apiDataManager.compareConfig(req_config_data.m_ReportConfig, data_for_graph) 453 && this_.isCacheViewCompatible(req_config_data.m_ReportConfig.reportName, 454 requestedViewType, cachedViewType, data_for_graph)) { 455 //if this is index report - reformat it for display 456 return Promise.resolve(req_config_data.m_ReportManager.createGraph(req_config_data.m_GraphContainer, 457 this.req_data, data_for_graph, req_config_data.m_SeriesColors)) 458 .then(() => data_for_graph) 459 .finally(finalizeReportApiOperation); 460 } 461 else { 462 //store the configs 463 this.apiDataManager.clearData(); 464 this.apiDataManager.setConfig(getEffectiveCacheConfig(requestedViewType)); 465 return this.setChartWithData(req_config_data); 466 } 467 } 468 /** 469 * Renders compatible cached table data or starts a fresh table request. 470 * Matrix cache reuse additionally requires table-shaped data; Edit Block and 471 * Running Average retain their existing forced-refresh behavior. 472 * @param {kendo.ui.Grid} grid Target Grid instance. 473 * @param {string} reportType Effective report type used by cache exclusions. 474 * @returns {Promise<*>} Table data after Grid rendering completes. 475 */ 476 this.initTableData = (grid, reportType) => { 477 if (this.req_data.reportName == REPORTS.editBlock) { 478 // Edit Block results depend on the edit body and request mode, so never reuse table cache here. 479 this.apiDataManager.clearData(); 480 return this.setTableDSWithData(grid); 481 } 482 483 let res = this.apiDataManager.getCompleteData() 484 let requestedViewType = this.callback_config?.kendoElementType ?? this.req_data.viewType; 485 let cachedViewType = this.apiDataManager.getConfig()?.viewType; 486 487 if ((this.apiDataManager.compareConfig(this.req_data, res)) 488 && this_.isCacheViewCompatible(this.req_data.reportName, requestedViewType, cachedViewType, res) 489 && !(reportType || "").includes("RunningAverage")) { 490 let options = this.callback_config.options; 491 492 manager = new kendoElementDataBuilder(res, this.callback_config); 493 manager.checkDataAndNotifyIfEmpty(res); 494 options.dataSource = res; 495 return waitForGridRender(grid, () => grid.setOptions(options), res) 496 .finally(finalizeReportApiOperation); 497 } 498 else { 499 this.apiDataManager.clearData(); 500 501 //create api call and store the configs 502 return this.setTableDSWithData(grid); 503 } 504 } 505 506 /** 507 * Verifies that cached Matrix data has the response contract required by the 508 * requested presentation. Other reports retain their existing cache behavior. 509 * @param {string} reportName 510 * @param {number|string} requestedViewType 511 * @param {number|string} cachedViewType 512 * @param {Array<Object>|Object} cachedData Cached raw or formatted response. 513 * @returns {boolean} 514 */ 515 this.isCacheViewCompatible = (reportName, requestedViewType, cachedViewType, cachedData) => { 516 if (reportName !== REPORTS.matrix) { 517 return true; 518 } 519 520 if (requestedViewType === REPORT_VIEW.GRAPH) { 521 return cachedViewType === REPORT_VIEW.GRAPH && hasMatrixMetricRows(cachedData); 522 } 523 524 const tableViewTypes = [ 525 REPORT_VIEW.HORIZONTAL_TABLE, 526 REPORT_VIEW.HORIZONTAL_TABLE_SUMMARY, 527 REPORT_VIEW.EXCEL 528 ]; 529 530 return tableViewTypes.includes(requestedViewType) 531 && tableViewTypes.includes(cachedViewType) 532 && hasMatrixTableRows(cachedData); 533 } 534 535 /** 536 * Checks for the complete canonical Matrix graph metric contract. 537 * @param {Array<Object>|Object} cachedData Cached Matrix response. 538 * @returns {boolean} True only when Min, Max, Avg, and STD typed rows are present. 539 */ 540 function hasMatrixMetricRows(cachedData) { 541 let rows = getMatrixCacheRows(cachedData); 542 543 return rows.length > 0 544 && rows.every(row => MATRIX_GRAPH_METRIC_TYPES.includes(row?.Type)) 545 && MATRIX_GRAPH_METRIC_TYPES.every(type => rows.some(row => row.Type === type)); 546 } 547 548 /** 549 * Checks for Matrix table rows, which must not carry graph metric Type values. 550 * @param {Array<Object>|Object}
550 cachedData Cached Matrix response. 551 * @returns {boolean} True for non-empty table-shaped data. 552 */ 553 function hasMatrixTableRows(cachedData) { 554 let rows = getMatrixCacheRows(cachedData); 555 556 return rows.length > 0 && rows.every(row => JsUtils.isEmpty(row?.Type)); 557 } 558 559 /** Flattens raw and formatted Matrix cache containers into response rows. */ 560 function getMatrixCacheRows(cachedData) { 561 let cacheEntries = Array.isArray(cachedData) ? cachedData : [cachedData]; 562 563 return cacheEntries.flatMap(entry => Array.isArray(entry?.data) ? entry.data : (entry ? [entry] : [])); 564 } 565 566 /** Returns a cache configuration tagged with the response shape just requested. */ 567 function getEffectiveCacheConfig(requestedViewType) { 568 return Object.assign({}, this_.req_data, { 569 viewType: requestedViewType ?? this_.req_data.viewType 570 }); 571 } 572 573 /** 574 * Resolves from Kendo's dataBound event after a Grid data change. Lightweight 575 * test/non-Kendo adapters without event support settle after their update call. 576 * @param {kendo.ui.Grid} grid Target Grid or compatible test adapter. 577 * @param {Function} updateGrid Applies the data or options update. 578 * @param {*} result Value returned when rendering completes. 579 * @returns {Promise<*>} A promise settled after dataBound or the fallback microtask. 580 */ 581 function waitForGridRender(grid, updateGrid, result) { 582 return new Promise((resolve, reject) => { 583 let settled = false; 584 const complete = () => { 585 if (settled) { 586 return; 587 } 588 settled = true; 589 if (JsUtils.isFunction(grid.unbind)) { 590 grid.unbind("dataBound", complete); 591 } 592 resolve(result); 593 }; 594 595 try { 596 let supportsDataBound = JsUtils.isFunction(grid.one); 597 if (supportsDataBound) { 598 grid.one("dataBound", complete); 599 } 600 updateGrid(); 601 if (!supportsDataBound) { 602 Promise.resolve().then(complete); 603 } 604 } 605 catch (error) { 606 if (JsUtils.isFunction(grid.unbind)) { 607 grid.unbind("dataBound", complete); 608 } 609 reject(error); 610 } 611 }); 612 } 613 614 /** 615 * Creates the typed no-data rejection consumed by the presentation lifecycle. 616 * @returns {Error} An error marked with isNoReportData. 617 */ 618 function createNoReportDataError() { 619 let error = new Error("No report data."); 620 error.isNoReportData = true; 621 return error; 622 } 623 624 /** Clears report scratch state and the loader for every terminal API/render path. */ 625 function finalizeReportApiOperation() { 626 ReportUtils.clearConfigData(); 627 AjaxLoader.hide(); 628 } 629 /** 630 * createListOfData 631 * @param {any} promise 632 * for big size of data 633 */ 634 this.createListOfData = (promise_list) => { 635 for (let [station, channels] of Object.entries(this.req_data.monitorChannelsByStationId)) { 636 promise_list.push({ station, channels }) 637 } 638 } 639 /** 640 * createListOfDataChunks 641 * @param {any} promise_list 642 * TODO- in the future create chunks for handling big data 643 */ 644 this.createListOfDataChunks = (promise_list) => { 645 for (let [station, channels] of Object.entries(this.req_data.monitorChannelsByStationId)) { 646 promise_list.push({ station, channels }) 647 } 648 } 649 /** 650 * getNewReportData 651 * prepare data for api send: 652 * create list of station 653 * for each station id we will send list of channels 654 * ðð½ 655 * 1. includeSummary , onlySummary I want always get them 656 * 2. Keep the default values of: use_back_wards and minMax 657 * 3. useBackWard is added in filterMinMaxAndTypeByType if necessary 658 * 4. this function is for all kind of report except Index Report! 659 */ 660 this.getNewReportData = (includeSummary, onlySummary, reportType, stationId) => { 661 let data 662 let obj = { 663 maxMin: "", 664 backWards: true, 665 } 666 //if this is event form- add to object more params 667 this.getEventData(obj, this.req_data) 668 try { 669 this.filterMinMaxAndTypeByType(obj, reportType) 670 data = { 671 filterChannels: getListOfChannels(this.req_data, stationId), 672 from: convertToDateTime(this.req_data.startDateAbsolute), 673 to: convertToDateTime(this.req_data.endDateAbsolute), 674 maxMin: obj.maxMin, 675 fromTimebase: this.req_data.fromTb, 676 toTimebase: this.req_data.toTb, 677 precentValid: validatePrecentValid(configManager.read(webConfigs.DataPrecent)), 678 timeBeginning: validateToTimeBegining(configManager.read(webConfigs.TimeEnding)), 679 useBackWard: obj.backWards, 680 roundType: parseInt(configManager.read(webConfigs.roun
680dType)), 681 //unitId: getUnitId(), //not in use cause we take unitid dynamically by ppbToMicrogram config 682 unitConversion: mDataConverter.isConvert(), //not in use cause we take unitid dynamically 683 includeSummary: includeSummary, 684 onlySummary: onlySummary, 685 filterByChannelId: obj.filterByChannelId, 686 isRawData: this.req_data.rawData, 687 filterOperator: obj.filterOperator, 688 filterValue: obj.filterValue, 689 groupBy: this.req_data.groupBy, 690 } 691 } 692 catch (err) { 693 console.log(err) 694 } 695 finally { 696 return data 697 } 698 } 699 /** 700 * getEventData 701 * @param {any} obj 702 * gets events extra config 703 */ 704 this.getEventData = (obj, configs) => { 705 if (configs.reportName != REPORTS.event) { 706 return 707 } 708 else { 709 obj["filterByChannelId"] = configs.filterByChannelId; 710 obj["filterOperator"] = configs.filterOperator; 711 obj["filterValue"] = configs.filterValue; 712 } 713 } 714 /** 715 * 716 * getListOfChannels 717 * @param {any} configs 718 * check if there is filterByChannelId 719 * if so - add it to channels 720 * NOTE- I assume that configs has field named monitorChannelsByStationId 721 */ 722 function getListOfChannels(configs, stationId) { 723 let channels = configs.monitorChannelsByStationId[stationId] 724 if (JsUtils.isNotEmpty(configs.filterByChannelId)) { 725 if (!channels.includes(configs.filterByChannelId)) { 726 channels.push(configs.filterByChannelId) 727 } 728 } 729 return channels.toString() 730 } 731 /** 732 * getNewIndexReportData 733 * Prepare data for api send 734 * Optional data: station id, from, to, indexType,is time beginning 735 * 736 */ 737 this.getNewIndexReportData = () => { 738 let data; 739 let to; 740 if (configManager.isTimeEnding()) { 741 to = convertToDateTime(this.req_data.endDateAbsolute); 742 } 743 else { 744 to = new DateTime(this.req_data.endDateAbsolute).apiFormat() 745 } 746 747 try { 748 data = { 749 from: convertToDateTime(this.req_data.startDateAbsolute), 750 to, 751 fromTimebase: this.req_data.fromTb, 752 toTimebase: this.req_data.toTb, 753 timeBeginning: validateToTimeBegining(configManager.read(webConfigs.TimeEnding)), 754 } 755 } 756 catch (err) { 757 console.log(err) 758 } 759 finally { 760 return data 761 } 762 763 764 } 765 766 // #endregion 767 768 //#region Utils,Validators,Filters,Converters 769 770 /** 771 * saveCompleteData 772 * @param {any} completeData 773 * for complete data save- prevent multiple api call on the same data 774 */ 775 function saveCompleteData(completeData) { 776 reportApiManager.apiDataManager.setCompleteData(completeData) 777 } 778 779 /** 780 * Delegates horizontal table date formatting to the active report manager. 781 * Non-horizontal-table responses are returned unchanged. 782 * @param {Array<Object>|Object} response Table response or merged table rows. 783 * @returns {Array<Object>|Object} Formatted response when supported by the report manager. 784 */ 785 function formatHorizontalTableDates(response) { 786 if (this_.callback_config?.kendoElementType === REPORT_VIEW.HORIZONTAL_TABLE && 787 typeof this_.callback_config?.m_ReportManager?.formatHorizontalTableDates === "function") { 788 return this_.callback_config.m_ReportManager.formatHorizontalTableDates(response); 789 } 790 791 return response; 792 } 793 794 /** 795 * Delegates horizontal summary date formatting to the active report manager. 796 * Non-horizontal-summary responses are returned unchanged. 797 * @param {Array<Object>} response Summary rows. 798 * @returns {Array<Object>} Formatted summary rows when supported by the report manager.
799 */ 800 function formatHorizontalSummaryDates(response) { 801 if (this_.callback_config?.kendoElementType === REPORT_VIEW.HORIZONTAL_TABLE_SUMMARY && 802 typeof this_.callback_config?.m_ReportManager?.formatHorizontalSummaryDates === "function") { 803 return this_.callback_config.m_ReportManager.formatHorizontalSummaryDates(response); 804 } 805 806 return response; 807 } 808 809 /** 810 * configValidator 811 * @param {any} config 812 * will validate if config in null or undefined- check if necessary 813 */ 814 function configValidator(config) { 815 return JsUtils.isNotEmpty(config) ? config : {} 816 } 817 /** 818 * convertToDateTime 819 * @param {any} dateTime 820 * Converts to date-time api format 821 */ 822 function convertToDateTime(dateTime) { 823 try { 824 return new DateTime(dateTime).apiFormat(); 825 } 826 catch (err) { 827 throw " reportApiManager : Date Time Conversion error " 828 } 829 } 830 831 this.convertToDateTime = (dateTime) => { 832 return convertToDateTime(dateTime); 833 } 834 /** 835 * validateToTimeBegining 836 * @param {any} timeEnding 837 * Validates and filter Time Ending 838 */ 839 function validateToTimeBegining(timeEnding) { 840 return !timeEnding 841 } 842 /** 843 * validatePrecentValid 844 * @param {any} precentValid 845 * Validates precentValid 846 */ 847 function validatePrecentValid(precentValid) { 848 try { 849 if (precentValid != null || precentValid != "") { 850 return precentValid 851 } 852 else { 853 throw "Please check precentValid: 'precentValid' is null or undefined " 854 } 855 } 856 catch (err) { 857 console.log(err); 858 } 859 } 860 /** 861 * getUnitId 862 * gets it from configManager 863 */ 864 function getUnitId() { 865 const unitId = configManager.read("ppbToMicrogram") 866 if (unitId) { 867 return 2 868 } 869 else { 870 return null; 871 } 872 } 873 /** 874 * reportType 875 * @param {any} reportType 876 * If report type contain min/max/forward- erase it 877 * If type is forward init useBackWard to false 878 */ 879 this.filterMinMaxAndTypeByType = (obj, reportType) => { 880 reportType = JsUtils.isNotEmpty(reportType) ? reportType : this.req_data.reportType; 881 let reportName = this.req_data.reportName; 882 883 if (reportName == REPORTS.matrix && reportType === REPORT_TYPE.Average) { 884 this.req_data.reportType = "Average"; 885 } 886 else if (reportName == REPORTS.matrix && reportType === REPORT_TYPE.MaxAverage) { 887 this.req_data.reportType = "Average"; 888 obj.maxMin = true; 889 } 890 else if (reportName == REPORTS.matrix && reportType === REPORT_TYPE.MinAverage) { 891 this.req_data.reportType = "Average"; 892 obj.maxMin = false; 893 } 894 else if (reportType === REPORT_TYPE.RunningAverage) { 895 this.req_data.reportType = "RunningAverage"; 896 } 897 else if (reportType.indexOf("Max") > -1) { 898 this.req_data.reportType = "RunningAverage"; 899 obj.maxMin = true; 900 } 901 else if (reportType.indexOf("Min") > -1) { 902 this.req_data.reportType = "RunningAverage"; 903 obj.maxMin = false;//has to be false! see api 904 } 905 if (reportType.toLowerCase().indexOf("forword") > -1) { 906 this.req_data.reportType = "RunningAverage"; 907 obj.backWards = false; 908 } 909 } 910 /** 911 * createParamsForUrl 912 * @param {any} formattedData 913 */ 914 this.createParamsForUrl = (formattedData) => { 915 try { 916 let parms = "?"; 917 for (let [key, value] of Object.entries(formattedData)) { 918 if (JsUtils.isNotEmpty(value)) { 919 parms += `${key}=${value}&` 920 } 921 } 922 const p_length = parms.length 923 return parms.substring(0, p_length - 1); 924 925 } catch (err) { 926 console.log(err) 927 } 928 } 929 /** 930 * filterConfig 931 * Prepare config to be compared 932 */ 933 this.filterConfig = () => { 934 let config = { 935 endDate: this.req_data.endDate, 936 endDateAbsolute: this.req_data.endDateAbsolute, 937 fromTb: this.req_data.fromTb, 938 monitorChannelsByStationId: this.req_data.monitorChannelsByStationId, 939 reportName: this.req_data.reportName, 940 reportType: this.req_data.reportType, 941 startDate: this.req_data.startDate, 942 startDateAbsolute: this.req_data.startDateAbsolute, 943 toTb: this.req_data.toTb, 944 } 945 return config 946 } 947 /** 948 * formatIndexRawData 949 * @param {Object} data 950 * @param {callback} manager 951 * format index raw data for chart format data(added channels field to raw data ) 952 */ 953 this.formatIndexRawData = (data, manager) => { 954 return manager.toMultiStationRawData(data) 955 } 956 /** 957 * DeleteData 958 * Very important function 959 * delete sheared data between reports movement 960 */ 961 this.DeleteData = () => { 962 try { 963 this.apiDataManager = null; 964 } 965 catch (err) { 966 throw err 967 } 968 } 969 /**
970 * checkIfNoData 971 * @param {Object} response 972 * checks if status is 204 for response- change name 973 */ 974 this.checkIfNoData = (res) => { 975 976 if (JsUtils.isEmpty(res)) { 977 return true 978 } 979 return false 980 } 981 /** 982 * Detects an empty response and delegates feedback to the active request manager. 983 * Using request-local state avoids depending on a retained callback from a previous 984 * popup presentation. 985 * @param {*} response Response collection to inspect. 986 * @param {Object|null} requestConfig Active request configuration. 987 * @returns {boolean} True when the response contains no report data. 988 */ 989 this.checkIfNoDataStatus = (response, requestConfig = null) => { 990 991 if (checkIfResponseEmpty(response)) { 992 let pageContainer = "detailedReportGrid" 993 AjaxLoader.hide() 994 let activeConfig = requestConfig || this_.callback_config || {}; 995 let manager = activeConfig.reportManager || activeConfig.m_ReportManager; 996 if (JsUtils.isNotEmpty(manager) && JsUtils.isFunction(manager.notifyPresentationNoData)) { 997 manager.notifyPresentationNoData(pageContainer) 998 } 999 else if (JsUtils.isNotEmpty(manager) && JsUtils.isFunction(manager.notifyNoReportData)) { 1000 manager.notifyNoReportData(pageContainer) 1001 } 1002 return true 1003 } 1004 return false 1005 } 1006 /** 1007 * Extracts the first non-null report payload from each station result. 1008 * Non-array entries are ignored because they cannot represent the combined 1009 * validation/report tuple returned by the station loaders. 1010 * @param {Array<*>} data Per-station request results. 1011 * @returns {Array<Object>} Non-null formatted station payloads. 1012 */ 1013 function filterNulls(data) { 1014 let result = []; 1015 for (let item of data) { 1016 if (!Array.isArray(item)) { 1017 continue; 1018 } 1019 let stationData = item.filter(function (el) { 1020 return el != null; 1021 }); 1022 if (JsUtils.isNotEmpty(stationData)) { 1023 result.push(stationData[0]) 1024 } 1025 1026 } 1027 return result 1028 1029 } 1030 /** 1031 * checkIfResponseEmpty 1032 * @param {any} response 1033 * checks if there is empty response
1034 * empty response is : null / undefined / object contains only nulls 1035 */ 1036 function checkIfResponseEmpty(response) { 1037 if (JsUtils.isEmpty(response)) { 1038 return true 1039 } 1040 else if (response.length >= 1) { 1041 let counter = 0; 1042 for (let item of response) { 1043 if (JsUtils.isEmpty(item) || (Array.isArray(item?.data) && JsUtils.isEmpty(item.data))) { 1044 counter += 1; 1045 } 1046 } 1047 if (counter == response.length) { 1048 return true; 1049 } 1050 else { 1051 return false 1052 } 1053 } 1054 } 1055 1056 /** 1057 * dataBooting 1058 * @param {any} time 1059 * for clearing the data and refreshing the data(in case of long connection) 1060 */ 1061 this.dataBooting = (time) => { 1062 let timeOut = time * 1000; 1063 setTimeout(() => { 1064 this.apiDataManager = null; 1065 this.apiDataManager = new ApiDataManager(); 1066 }, timeOut); 1067 } 1068 1069 //#endregion 1070 1071} 1072var reportApiManager = new ReportApiManager()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.