Skip to content

Commit 0d84ba7

Browse files
authored
Merge branch 'dev' into feature/CNF/OGUI-1831/introduce-react-hook-form
2 parents 6bd056f + 9d0f713 commit 0d84ba7

File tree

3 files changed

+105
-19
lines changed

3 files changed

+105
-19
lines changed

QualityControl/lib/services/ccdb/CcdbService.js

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* or submit itself to any jurisdiction.
1313
*/
1414

15-
import { FailedDependencyError, LogManager } from '@aliceo2/web-ui';
15+
import { FailedDependencyError, LogManager, NotFoundError } from '@aliceo2/web-ui';
1616
import { httpHeadJson, httpGetJson } from '../../utils/httpRequests.js';
1717
import {
1818
CCDB_MONITOR, CCDB_VERSION_KEY, CCDB_RESPONSE_BODY_KEYS, CCDB_FILTER_FIELDS, CCDB_RESPONSE_HEADER_KEYS,
@@ -176,7 +176,8 @@ export class CcdbService {
176176
* ```
177177
* @param {CcdbObjectIdentification} partialIdentification - fields such as path, validFrom, etc.
178178
* @returns {Promise.<CcdbObjectIdentification>} - returns object full identification
179-
* @throws {Error}
179+
* @throws {Error} throws if the object cannot be fetched
180+
* @throws {NotFoundError} throws if the object cannot be found
180181
*/
181182
async getObjectIdentification(partialIdentification) {
182183
const headers = {
@@ -185,18 +186,28 @@ export class CcdbService {
185186
};
186187
const url = `/latest${this._buildCcdbUrlPath(partialIdentification)}`;
187188

188-
const result = await httpGetJson(this._hostname, this._port, url, { headers });
189-
if (result?.objects?.length > 0) {
190-
const [qcObject] = result.objects;
191-
return {
192-
[CCDB_RESPONSE_BODY_KEYS.PATH]: qcObject[CCDB_RESPONSE_BODY_KEYS.PATH],
193-
[CCDB_RESPONSE_BODY_KEYS.VALID_FROM]: qcObject[CCDB_RESPONSE_BODY_KEYS.VALID_FROM],
194-
[CCDB_RESPONSE_BODY_KEYS.VALID_UNTIL]: qcObject[CCDB_RESPONSE_BODY_KEYS.VALID_UNTIL],
195-
[CCDB_RESPONSE_BODY_KEYS.ID]: qcObject[CCDB_RESPONSE_BODY_KEYS.ID],
196-
};
197-
} else {
198-
throw new Error(`Object: ${url} could not be found`);
189+
let result = null;
190+
try {
191+
result = await httpGetJson(this._hostname, this._port, url, { headers });
192+
} catch {
193+
throw new Error(`Failed to fetch object at url '${url}' and path '${partialIdentification.path}'.`);
199194
}
195+
196+
if (!result?.objects?.length) {
197+
const errorMessage = this._buildFilterErrorMessage(
198+
`Object at url '${url}' and path '${partialIdentification.path}' could not be found.`,
199+
partialIdentification.filters,
200+
);
201+
throw new NotFoundError(errorMessage);
202+
}
203+
204+
const [qcObject] = result.objects;
205+
return {
206+
[CCDB_RESPONSE_BODY_KEYS.PATH]: qcObject[CCDB_RESPONSE_BODY_KEYS.PATH],
207+
[CCDB_RESPONSE_BODY_KEYS.VALID_FROM]: qcObject[CCDB_RESPONSE_BODY_KEYS.VALID_FROM],
208+
[CCDB_RESPONSE_BODY_KEYS.VALID_UNTIL]: qcObject[CCDB_RESPONSE_BODY_KEYS.VALID_UNTIL],
209+
[CCDB_RESPONSE_BODY_KEYS.ID]: qcObject[CCDB_RESPONSE_BODY_KEYS.ID],
210+
};
200211
}
201212

202213
/**
@@ -215,7 +226,7 @@ export class CcdbService {
215226
* @throws {Error}
216227
*/
217228
async getObjectDetails(identification) {
218-
const { path = '', validFrom = undefined } = identification ?? {};
229+
const { path = '', filters, validFrom = undefined } = identification ?? {};
219230
if (!path || !validFrom) {
220231
throw new Error('Missing mandatory parameters: path & validFrom');
221232
}
@@ -226,15 +237,23 @@ export class CcdbService {
226237
.split(', ')
227238
.filter((location) => !location.startsWith('alien') && !location.startsWith('file'));
228239
if (!location) {
229-
throw new Error(`No location provided by CCDB for object with path: ${path}`);
240+
const errorMessage = this._buildFilterErrorMessage(
241+
`No location provided by CCDB for object with path: ${path}`,
242+
filters,
243+
);
244+
throw new Error(errorMessage);
230245
}
231246
return {
232247
...headers,
233248
location,
234249
path,
235250
};
236251
} else {
237-
throw new Error(`Unable to retrieve object: ${path} due to status: ${status}`);
252+
const errorMessage = this._buildFilterErrorMessage(
253+
`Unable to retrieve object: ${path} due to status: ${status}`,
254+
filters,
255+
);
256+
throw new Error(errorMessage);
238257
}
239258
}
240259

@@ -243,7 +262,8 @@ export class CcdbService {
243262
* The minimum required parameter to provide is the `path`
244263
* @param {CcdbObjectIdentification} identification - attributes by which the object should be queried
245264
* @returns {Promise.<JSON>} - object details for a given timestamp
246-
* @throws {Error}
265+
* @throws {Error} Thrown when an error occurs whilst fetching the object
266+
* @throws {NotFoundError} Thrown when the object cannot be found
247267
*/
248268
async getObjectLatestVersionInfo(identification) {
249269
const { path } = identification ?? {};
@@ -257,7 +277,7 @@ export class CcdbService {
257277
const url = `/latest${this._buildCcdbUrlPath(identification)}`;
258278
const { objects } = await httpGetJson(this._hostname, this._port, url, { headers: timestampHeaders });
259279
if (objects?.length <= 0) {
260-
throw new Error(`No object found for: ${path}`);
280+
throw new NotFoundError(`No object found for: ${path}`);
261281
}
262282
return objects[0];
263283
} catch {
@@ -342,4 +362,32 @@ export class CcdbService {
342362
}
343363
return url;
344364
}
365+
366+
/**
367+
* Builds a detailed error message for CCDB objects that may have been filtered out.
368+
* This method appends a filter-specific hint to a base error message when
369+
* the `filters` object contains one or more keys. It ensures proper punctuation
370+
* and provides a clear explanation for why the object might not have been found.
371+
* @param {string} baseMessage - The initial error message describing the failure.
372+
* @param {object} [filters] - Optional object representing filters applied when searching for the object.
373+
* @returns {string} - The final, human-readable error message including filter hints if applicable.
374+
*/
375+
_buildFilterErrorMessage(baseMessage, filters) {
376+
// Only append filter-specific hint if filters object exists and has keys
377+
if (filters && Object.keys(filters).length > 0) {
378+
// Ensure the base message ends with proper punctuation.
379+
// If it does NOT end with any Unicode punctuation, append a period.
380+
if (!/\p{P}$/u.test(baseMessage)) {
381+
baseMessage += '.';
382+
}
383+
if (!baseMessage.endsWith(' ')) {
384+
baseMessage += ' ';
385+
}
386+
387+
// Append a clear, descriptive filter hint.
388+
baseMessage += 'It was likely excluded by the applied filters.';
389+
}
390+
391+
return baseMessage;
392+
}
345393
}

QualityControl/test/api/objects/api-get-object.test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ export const apiGetObjectsTests = () => {
4848

4949
test('should return 500 if service fails to retrieve object', async () => {
5050
const url = `${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}&path=invalid/path`;
51-
await testResult(url, 500, { message: 'Non-2xx status code: 501', status: 500, title: 'Unknown Error' });
51+
await testResult(url, 500, {
52+
message: 'Failed to fetch object at url \'/latest/invalid/path\' and path \'invalid/path\'.',
53+
status: 500,
54+
title: 'Unknown Error',
55+
});
5256
});
5357
});
5458

QualityControl/test/lib/services/CcdbService.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,5 +471,39 @@ export const ccdbServiceTestSuite = async () => {
471471
strictEqual(ccdb._buildCcdbUrlPath(identification), '/qc/TPC/object/12322222/123332323/123-ffg/RunNumber=123456/PartName=Pass');
472472
});
473473
});
474+
475+
suite('CcdbService getObjectIdentification() Error Messages', () => {
476+
let ccdb = null;
477+
478+
before(() => {
479+
ccdb = new CcdbService(ccdbConfig);
480+
});
481+
482+
test('should throw error if object not found with filters applied', async () => {
483+
const filters = { RunNumber: 123456 };
484+
const filterString = Object.entries(filters).map(([key, value]) => `${key}=${value}`).join('/');
485+
486+
nock('http://ccdb-local:8083')
487+
.get(`/latest/path/${ID}/${filterString}`)
488+
.reply(200, { objects: [] });
489+
490+
await rejects(
491+
async () => ccdb.getObjectIdentification({ path: PATH, id: ID, filters }),
492+
// eslint-disable-next-line @stylistic/js/max-len
493+
new Error(`Object at url '/latest/path/${ID}/${filterString}' and path '${PATH}' could not be found. It was likely excluded by the applied filters.`),
494+
);
495+
});
496+
497+
test('should throw error if object not found without filters', async () => {
498+
nock('http://ccdb-local:8083')
499+
.get(`/latest/path/${ID}`)
500+
.reply(200, { objects: [] });
501+
502+
await rejects(
503+
async () => ccdb.getObjectIdentification({ path: PATH, id: ID }),
504+
new Error(`Object at url '/latest/path/${ID}' and path '${PATH}' could not be found.`),
505+
);
506+
});
507+
});
474508
});
475509
};

0 commit comments

Comments
 (0)