-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathQcObject.service.js
More file actions
233 lines (211 loc) · 9.62 KB
/
QcObject.service.js
File metadata and controls
233 lines (211 loc) · 9.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
import { LogManager } from '@aliceo2/web-ui';
import { isObjectOfTypeChecker, parseObjects } from '../../common/library/qcObject/utils.js';
import QCObjectDto from '../dtos/QCObjectDto.js';
import QcObjectIdentificationDto from '../dtos/QcObjectIdentificationDto.js';
/**
* @typedef {import('../repositories/ChartRepository.js').ChartRepository} ChartRepository
*/
const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/obj-service`;
/**
* High-level service class for retrieving and composing object information from storage (CCDB/QCDB)
* @class
*/
export class QcObjectService {
/**
* Setup service constructor and initialize needed dependencies
* @param {CcdbService} dbService - CCDB service to retrieve raw information about the QC objects
* @param {ChartRepository} chartRepository - service to be used for retrieving configurations on saved layouts
* @param {RootService} rootService - root library to be used for interacting with ROOT Objects
*/
constructor(dbService, chartRepository, rootService) {
/**
* @type {CcdbService}
*/
this._dbService = dbService;
/**
* @type {ChartRepository}
*/
this._chartRepository = chartRepository;
/**
* @type {RootService}
*/
this._rootService = rootService;
/**
* @constant
* @type {string}
*/
this._DB_URL = `${this._dbService._protocol}://${this._dbService._hostname}:${this._dbService._port}`;
this._cache = {
objects: undefined,
lastUpdate: undefined,
};
this._logger = LogManager.getLogger(LOG_FACILITY);
}
/**
* Method to update list of objects paths currently stored in cache by a configured cache prefix
* Prefix will not be accepted as passed parameter so that the configured file one is used
* @returns {void}
*/
async refreshCache() {
try {
const objects = await this._dbService.getObjectsTreeList(this._dbService.CACHE_PREFIX);
const parsedObjects = parseObjects(objects, QCObjectDto);
this._cache = {
objects: parsedObjects,
lastUpdate: Date.now(),
};
return true;
} catch (error) {
const lastUpdateStr = this._cache.lastUpdate
? new Date(this._cache.lastUpdate).toISOString()
: 'never';
this._logger.errorMessage(
`Cache refresh failed. Last update: ${lastUpdateStr}. Error: ${error.message || error}`,
{ level: 1, facility: LOG_FACILITY },
);
return false;
}
}
/**
* Returns a list of objects (their latest version) based on a given prefix (e.g. 'qc'; default to config file
* specified prefix); Fields wished to be requested for each object can be passed through the fields parameter;
* If fields list is missing, a default list will be used: [name, created, lastModified]
* The service can return objects either:
* * from cache if it is requested by the client and the system is configured to use a cache;
* * make a new request and get data directly from data service
* * @example Equivalent of URL request: `/latest/qc/TPC/object.*`
* @param {object} options - An object that contains query parameters among other arguments
* @param {string|Regex} options.prefix - Prefix for which CCDB should search for objects.
* @param {Array<string>} options.fields - List of fields that should be requested for each object
* @param {boolean} options.useCache - if the list should be the cached version or not
* @param {Array<string>} options.filters - Filter object by which the objects from ccdb are filtered.
* @returns {Promise.<Array<QcObjectLeaf>>} - results of objects with required fields
* @rejects {Error}
*/
async retrieveLatestVersionOfObjects({ prefix = this._dbService.PREFIX, fields, useCache = true, filters }) {
const hasFilters = typeof filters === 'object' && Object.keys(filters).length;
if (!hasFilters) {
// Use /tree endpoint of DataService
if (useCache && this._cache.objects?.length) {
return this._cache.objects.filter((object) => object.name.startsWith(prefix));
}
const objects = await this._dbService.getObjectsTreeList(prefix);
return parseObjects(objects, QCObjectDto);
}
// If filters are provided, use /latest endpoint of DataService
const objects = await this._dbService.getObjectsLatestVersionList({ prefix, filters, fields });
return parseObjects(objects, QCObjectDto);
}
/**
* Using `browse` option, request a list of versions(identifications) for a specified path for an object.
* Use the first version to retrieve details about the exact object.
* From the information retrieved above, use the location attribute and pass it to JSROOT to get a JSON
* decompressed version of the ROOT object which will be plotted/drawn with JSROOT.draw on the client side.
* @param {object} options - An object that contains query parameters among other arguments
* @param {string} options.path - name(known as path) of the object to retrieve information
* @param {number|null} options.validFrom - timestamp in ms
* @param {string} options.id - id with respect to CCDB storage
* @param {string} options.filters - filter attributes for specific objects
* @returns {Promise<QcObject>} - QC objects with information CCDB and root
* @throws {Error}
*/
async retrieveQcObject({ path, validFrom = undefined, id = undefined, filters = {} }) {
/**
* @type {CcdbObjectIdentification}
*/
let identification = { path, validFrom, id, filters };
if (!validFrom) {
/*
* If no validFrom timestamp was provided, it means the exact version of the object is unknown.
* Thus, we need to query versions of the object before retrieving details of the latest version of the object.
*/
const rawIdentification = await this._dbService.getObjectIdentification({ path, filters });
Object.assign(identification, rawIdentification);
identification = QcObjectIdentificationDto.fromGetFormat(identification);
}
const objectDetails = await this._dbService.getObjectDetails(identification);
const object = QCObjectDto.toStandardObject(objectDetails);
const rootObj = await this._getJsRootFormat(this._DB_URL + object.location);
/**
* @type {CcdbObjectIdentification}
*/
const partialIdentification = {
path,
filters,
};
// Partial identification to retrieve various versions of an object with specified name and metadata attributes
let versions = await this._dbService.getObjectVersions(partialIdentification, 1000);
versions = versions.map((version) => QcObjectIdentificationDto.fromGetFormat(version));
return {
...object,
root: rootObj,
versions,
};
}
/**
* Retrieve an object by its id (stored in the customized data service) with its information
* @param {object} options - An object that contains query parameters among other arguments
* @param {string} options.qcObjectId - id of the object configuration stored in QCG database (different than CCDB)
* @param {string} options.id - id of the object to be retrieved as per CCDB etag
* @param {number|null} options.validFrom - timestamp in ms
* @param {object} options.filters - filter as string to be sent to CCDB
* @returns {Promise<QcObject>} - QC objects with information CCDB and root
* @throws {Error} - if object with specified id is not found
*/
async retrieveQcObjectByQcgId({ qcObjectId, id, validFrom = undefined, filters = {} }) {
const result = this._chartRepository.getObjectById(qcObjectId);
if (!result) {
throw new Error(`Object with id ${qcObjectId} not found`);
}
const { object, layoutName, tabName } = result;
const { name, options = {}, ignoreDefaults = false } = object;
const qcObject = await this.retrieveQcObject({ path: name, validFrom, id, filters });
return {
...qcObject,
layoutDisplayOptions: options,
layoutName,
tabName,
ignoreDefaults,
};
}
/**
* Retrieves a root object from url-location provided and parses it depending on its type:
* * if it is a checker, uses default JSON utility to parse it and replace 'bigint' with string
* * if of ROOT type, uses jsroot.toJSON
* @param {string} url - location of Root file to be retrieved
* @returns {Promise<JSON.Error>} - JSON version of the object
*/
async _getJsRootFormat(url) {
const file = await this._rootService.openFile(`${url}+`);
const root = await file.readObject('ccdb_object');
root['_typename'] = root['mTreatMeAs'] || root['_typename'];
if (isObjectOfTypeChecker(root)) {
/**
* Due to QC Checker big ints, JSON utility has to be overridden to parse 'bigint' types and replace with string
*/
return JSON.parse(JSON.stringify(root, (_, value) => typeof value === 'bigint' ? value.toString() : value));
}
const rootJson = await this._rootService.toJSON(root);
return rootJson;
}
/**
* Check the database service and return the interval specified for refreshing the cache
* @returns {number} - ms for interval to refresh cache
*/
getCacheRefreshRate() {
return this._dbService.CACHE_REFRESH_RATE;
}
}