PageSourceSearch

https://ukairquality.net/Scripts/reports/api/ReportApiManager.js?v=4.272.8.29%20(23.58)

js ukairquality.net collected 2026-10-08 15:56:49 UTC 44,780 bytes, 1,072 lines download raw bytes

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.