Skip to content

Commit

Permalink
Merge branch 'ISSUE_5163' of https://github.com/3drepo/3drepo.io into…
Browse files Browse the repository at this point in the history
… ISSUE_5173
  • Loading branch information
Amantini1997 committed Sep 25, 2024
2 parents 57b85eb + 90ffe3d commit d92b22d
Show file tree
Hide file tree
Showing 19 changed files with 533 additions and 196 deletions.
4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"dependencies": {
"@azure/msal-node": "1.18.4",
"@elastic/elasticsearch": "7.17.0",
"@elastic/elasticsearch": "7.17.14",
"amqplib": "0.8.0",
"apidoc": "0.29.0",
"app-config": "1.0.1",
Expand All @@ -46,7 +46,7 @@
"dayjs": "1.11.11",
"device": "0.3.12",
"ejs": "3.1.10",
"elastic-apm-node": "3.41.1",
"elastic-apm-node": "4.7.3",
"express": "4.19.2",
"express-body-parser-error-handler": "1.0.7",
"express-session": "1.18.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ const generateSchema = (newEntry, modelType, teamspace, project, modelId) => {
const name = modelNameType(teamspace, project, modelId);
const number = modelNumberType(teamspace, project, modelId);

const drawingCalibration = Yup.object().shape({
verticalRange: Yup.array().of(Yup.number()).length(2).required()
.test('valid-verticalRange', 'The second number of the range must be larger than the first', (value) => value && value[0] <= value[1]),
units: types.strings.unit.required(),
});

const commonProps = {
name: newEntry ? name.required() : name,
desc: types.strings.shortDescription,
Expand All @@ -96,7 +102,10 @@ const generateSchema = (newEntry, modelType, teamspace, project, modelId) => {
const schema = {
...commonProps,
...(modelType === modelTypes.DRAWING
? { number: newEntry ? number.required() : number }
? {
number: newEntry ? number.required() : number,
calibration: newEntry ? drawingCalibration.required() : drawingCalibration,
}
: {
unit: newEntry ? types.strings.unit.required() : types.strings.unit,
code: types.strings.code,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@
*/

const { addCalibration, getCalibration, getCalibrationForMultipleRevisions } = require('../../../../../models/calibrations');
const { getDrawingById, updateModelSettings } = require('../../../../../models/modelSettings');
const { getRevisionByIdOrTag, getRevisions, getRevisionsByQuery } = require('../../../../../models/revisions');
const { UUIDToString } = require('../../../../../utils/helper/uuids');
const { calibrationStatuses } = require('../../../../../models/calibrations.constants');
const { convertArrayUnits } = require('../../../../../utils/helper/units');
const { events } = require('../../../../../services/eventsManager/eventsManager.constants');
const { modelTypes } = require('../../../../../models/modelSettings.constants');
const { publish } = require('../../../../../services/eventsManager/eventsManager');
const { templates } = require('../../../../../utils/responseCodes');

const Calibrations = { };
const Calibrations = {};

const getRevIdToCalibMap = (revCalibrations) => {
const revIdToCalib = {};
Expand All @@ -40,16 +42,23 @@ Calibrations.getCalibration = async (teamspace, project, drawing, revision) => {
const projection = {
_id: 0,
horizontal: 1,
verticalRange: 1,
units: 1,
createdAt: 1,
createdBy: 1,
units: 1,
};

const latestCalibration = await getCalibration(teamspace, project, drawing, revision, projection);
const [latestCalibration, { calibration: drawingData }] = await Promise.all([
getCalibration(teamspace, project, drawing, revision, projection),
getDrawingById(teamspace, drawing, { _id: 0, calibration: 1 }),
]);

if (latestCalibration) {
return { calibration: latestCalibration, status: calibrationStatuses.CALIBRATED };
return {
calibration: {
...latestCalibration,
verticalRange: convertArrayUnits(drawingData.verticalRange, drawingData.units, latestCalibration.units),
},
status: calibrationStatuses.CALIBRATED };
}

const { timestamp } = await getRevisionByIdOrTag(teamspace, drawing,
Expand All @@ -67,7 +76,14 @@ Calibrations.getCalibration = async (teamspace, project, drawing, revision) => {
for (let i = 0; i < previousRevisions.length; ++i) {
const data = revIdToCalib[UUIDToString(previousRevisions[i]._id)];
if (data) {
return { calibration: data.latestCalibration, status: calibrationStatuses.UNCONFIRMED };
return {
calibration: {
...data.latestCalibration,
verticalRange: convertArrayUnits(drawingData.verticalRange, drawingData.units,
data.latestCalibration.units),
},
status: calibrationStatuses.UNCONFIRMED,
};
}
}
}
Expand Down Expand Up @@ -112,14 +128,21 @@ Calibrations.getCalibrationStatusForAllRevs = async (teamspace, project, drawing
Calibrations.addCalibration = async (teamspace, project, drawing, revision, createdBy, calibration) => {
const existingCalibration = await getCalibration(teamspace, project, drawing, revision, { _id: 1 });

await addCalibration(teamspace, project, drawing, revision, createdBy, calibration);
const { verticalRange, units, ...calibrationData } = calibration;

await Promise.all([
addCalibration(teamspace, project, drawing, revision, createdBy, { ...calibrationData, units }),
updateModelSettings(teamspace, project, drawing, { calibration: { verticalRange, units } }),
]);

if (!existingCalibration) {
publish(events.REVISION_UPDATED, { teamspace,
publish(events.REVISION_UPDATED, {
teamspace,
project,
model: drawing,
modelType: modelTypes.DRAWING,
data: { _id: revision, calibration: calibrationStatuses.CALIBRATED } });
data: { _id: revision, calibration: calibrationStatuses.CALIBRATED },
});
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,6 @@ Drawings.deleteFavourites = async (username, teamspace, project, favouritesToRem
};

Drawings.getSettings = (teamspace, drawing) => getDrawingById(teamspace,
drawing, { name: 1, number: 1, type: 1, desc: 1 });
drawing, { name: 1, number: 1, type: 1, desc: 1, calibration: 1 });

module.exports = Drawings;
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ const establishRoutes = (modelType) => {
* number: SC1-SFT-V1-01-M3-ST-30_10_30-0001
* type: Structural
* desc: The Drawing of the Lego House
* calibration: { verticalRange: [0,10], units: m }
*/
router.get('/:model', hasReadAccessToModel[modelType], getModelSettings(modelType), formatModelSettings);

Expand Down
41 changes: 41 additions & 0 deletions backend/src/v5/utils/helper/units.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (C) 2024 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

const { isNumber } = require('./typeCheck');

const UnitsHelper = {};

const UNITS_CONVERSION_FACTORS_TO_METRES = {
m: 1,
dm: 10,
cm: 100,
mm: 1000,
ft: 3.28084,
};

UnitsHelper.convertArrayUnits = (array, fromUnit, toUnit) => {
const fromFactor = UNITS_CONVERSION_FACTORS_TO_METRES[fromUnit];
const toFactor = UNITS_CONVERSION_FACTORS_TO_METRES[toUnit];

if (!array.every(isNumber) || !fromFactor || !toFactor) {
return null;
}

return array.map((n) => (n / fromFactor) * toFactor);
};

module.exports = UnitsHelper;
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ const testAddModel = () => {
name,
number: modelType === modelTypes.DRAWING ? ServiceHelper.generateRandomString() : undefined,
unit: modelType === modelTypes.DRAWING ? undefined : 'mm',
calibration: modelType === modelTypes.DRAWING ? { verticalRange: [0, 5], units: 'mm' } : undefined,
type: modelType === modelTypes.FEDERATION ? undefined : ServiceHelper.generateRandomString(),
});

Expand Down Expand Up @@ -924,6 +925,7 @@ const testGetSettings = () => {
type: settings.properties.type,
...(modelType === modelTypes.DRAWING ? {
number: settings.properties.number,
calibration: settings.properties.calibration,
} : {
code: settings.properties.properties.code,
unit: settings.properties.properties.unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const { times } = require('lodash');

const { modelTypes } = require(`${src}/models/modelSettings.constants`);
const { templates } = require(`${src}/utils/responseCodes`);
const { convertArrayUnits } = require(`${src}/utils/helper/units`);

let server;
let agent;
Expand Down Expand Up @@ -124,7 +125,7 @@ const testGetCalibration = () => {
key: users.tsAdmin.apiKey,
ts: teamspace,
projectId: project.id,
drawingId: models.drawWithRevisions._id,
drawing: models.drawWithRevisions,
revisionId: revisions.rev2._id,
};

Expand All @@ -138,27 +139,30 @@ const testGetCalibration = () => {
['the user is not a member of the teamspace', { ...params, key: users.nobody.apiKey }, false, templates.teamspaceNotFound],
['the user does not have access to the drawing', { ...params, key: users.noProjectAccess.apiKey }, false, templates.notAuthorized],
['the project does not exist', { ...params, projectId: ServiceHelper.generateRandomString() }, false, templates.projectNotFound],
['the drawing does not exist', { ...params, drawingId: ServiceHelper.generateRandomString() }, false, templates.drawingNotFound],
['the drawing does not exist', { ...params, drawing: ServiceHelper.generateRandomString() }, false, templates.drawingNotFound],
['the revision does not exist', { ...params, revisionId: ServiceHelper.generateRandomString() }, false, templates.revisionNotFound],
['the model is of wrong type', { ...params, drawingId: models.container._id }, false, templates.drawingNotFound],
['the drawing has no revisions', { ...params, drawingId: models.drawWithNoRevisions._id }, false, templates.revisionNotFound],
['the model is of wrong type', { ...params, drawing: models.container }, false, templates.drawingNotFound],
['the drawing has no revisions', { ...params, drawing: models.drawWithNoRevisions }, false, templates.revisionNotFound],
['the drawing has revisions but revisions have no calibrations', { ...params, revisionId: revisions.rev1._id }, false, templates.calibrationNotFound],
['the drawing has revisions and revision has calibrations', params, true],
['the drawing has revisions and previous revision has calibrations', { ...params, revisionId: revisions.rev3._id }, true],
])('Get Calibration', (desc, parameters, success, error) => {
test(`should ${success ? 'succeed' : `fail with ${error.code}`} if ${desc}`, async () => {
const expectedStatus = success ? templates.ok.status : error.status;
const route = ({ ts, projectId, drawingId, revisionId, key }) => `/v5/teamspaces/${ts}/projects/${projectId}/drawings/${drawingId}/revisions/${revisionId}/calibrations?key=${key}`;
const route = ({ ts, projectId, drawing, revisionId, key }) => `/v5/teamspaces/${ts}/projects/${projectId}/drawings/${drawing._id}/revisions/${revisionId}/calibrations?key=${key}`;

const res = await agent.get(route(parameters)).expect(expectedStatus);

if (success) {
const latestCalibration = calibrations.reduce(
(max, cal) => (max.createdAt > cal.createdAt ? max : cal));
const { horizontal, verticalRange, units } = latestCalibration;
expect(res.body).toEqual({ horizontal,
verticalRange,
units,

const { verticalRange, units: drawingUnits } = parameters.drawing.properties.calibration;

expect(res.body).toEqual({
verticalRange: convertArrayUnits(verticalRange, drawingUnits, latestCalibration.units),
units: latestCalibration.units,
horizontal: latestCalibration.horizontal,
createdAt: res.body.createdAt,
createdBy: res.body.createdBy });
} else {
Expand Down Expand Up @@ -188,8 +192,8 @@ const testAddCalibration = () => {
model: times(2, () => times(3, () => ServiceHelper.generateRandomNumber())),
drawing: times(2, () => times(2, () => ServiceHelper.generateRandomNumber())),
},
verticalRange: [0, 10],
units: 'm',
verticalRange: [ServiceHelper.generateRandomNumber(0, 10), ServiceHelper.generateRandomNumber(11, 20)],
units: 'mm',
};

beforeAll(async () => {
Expand Down Expand Up @@ -217,24 +221,31 @@ const testAddCalibration = () => {
test(`should ${success ? 'succeed' : `fail with ${error.code}`} if ${desc}`, async () => {
const expectedStatus = success ? templates.ok.status : error.status;
const route = ({ ts, projectId, drawingId, revisionId, usePrevious, key }) => `/v5/teamspaces/${ts}/projects/${projectId}/drawings/${drawingId}/revisions/${revisionId}/calibrations?key=${key}&usePrevious=${usePrevious}`;
const drawingRoute = ({ ts, projectId, drawingId, key }) => `/v5/teamspaces/${ts}/projects/${projectId}/drawings/${drawingId}?key=${key}`;

let lastRevBeforePost;
let lastCalBeforePost;
if (success && parameters.usePrevious) {
const { body } = await agent.get(route(parameters));
lastRevBeforePost = body;
lastCalBeforePost = body;
}

const res = await agent.post(route(parameters)).send(payload).expect(expectedStatus);

if (success) {
const { body: newlyCreatedRev } = await agent.get(route(parameters));
const { body: newlyCreatedCal } = await agent.get(route(parameters));
const { body: updatedDrawing } = await agent.get(drawingRoute(parameters));

const calibrationData = parameters.usePrevious ? lastRevBeforePost : payload;
const calibrationData = parameters.usePrevious ? lastCalBeforePost : payload;

expect(newlyCreatedRev).toEqual({
expect(newlyCreatedCal).toEqual({
...calibrationData,
createdAt: newlyCreatedRev.createdAt,
createdBy: newlyCreatedRev.createdBy,
createdAt: newlyCreatedCal.createdAt,
createdBy: newlyCreatedCal.createdBy,
});

expect(updatedDrawing.calibration).toEqual({
verticalRange: calibrationData.verticalRange,
units: calibrationData.units,
});
} else {
expect(res.body.code).toEqual(error.code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,13 @@ const modelAddedTest = () => {
setTimeout(reject, 1000);
});

const payload = { name: ServiceHelper.generateRandomString(),
const payload = {
name: ServiceHelper.generateRandomString(),
type: generateRandomString(),
number: generateRandomString() };
number: generateRandomString(),
calibration: { verticalRange: [0, 10], units: 'm' },
};

const res = await agent.post(`/v5/teamspaces/${teamspace}/projects/${project.id}/drawings?key=${user.apiKey}`)
.send(payload)
.expect(templates.ok.status);
Expand Down
16 changes: 14 additions & 2 deletions backend/tests/v5/e2e/services/chat/modelEvents/revisions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,18 @@ const revisionUpdateTest = () => {
setTimeout(reject, 1000);
});

const modelUpdateSocketPromise = new Promise((resolve, reject) => {
socket.on(EVENTS.DRAWING_SETTINGS_UPDATE, resolve);
setTimeout(reject, 1000);
});

const calibration = {
horizontal: {
model: [[0, 0, 0], [1, 1, 1]],
drawing: [[0, 0], [1, 1]],
},
verticalRange: [0, 10],
units: 'm',
verticalRange: [ServiceHelper.generateRandomNumber(0, 5), ServiceHelper.generateRandomNumber(6, 10)],
units: 'mm',
};

await agent.post(`/v5/teamspaces/${teamspace}/projects/${project.id}/drawings/${drawing._id}/revisions/${drawingRevision._id}/calibrations?key=${user.apiKey}`)
Expand All @@ -147,6 +152,13 @@ const revisionUpdateTest = () => {
},
});

await expect(modelUpdateSocketPromise).resolves.toEqual({
...data,
data: {
calibration: { verticalRange: calibration.verticalRange, units: calibration.units },
},
});

// calibrating an already calibrated drawing should not trigger the event anymore
const socketPromise2 = new Promise((resolve, reject) => {
socket.on(EVENTS.DRAWING_REVISION_UPDATE, resolve);
Expand Down
8 changes: 5 additions & 3 deletions backend/tests/v5/helper/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,14 @@ db.createRevision = async (teamspace, project, model, revision, modelType) => {
};

db.createCalibration = async (teamspace, project, drawing, revision, calibration) => {
const formattedCalibration = {
const formattedCalibration = deleteIfUndefined({
...calibration,
_id: stringToUUID(calibration._id),
project: stringToUUID(project),
drawing,
rev_id: stringToUUID(revision),
};
verticalRange: undefined,
});

await DbHandler.insertOne(teamspace, 'drawings.calibrations', formattedCalibration);
};
Expand Down Expand Up @@ -494,7 +495,7 @@ ServiceHelper.generateCalibration = () => ({
drawing: times(2, () => times(2, () => ServiceHelper.generateRandomNumber())),
},
verticalRange: [0, 10],
units: 'm',
units: 'mm',
createdAt: ServiceHelper.generateRandomDate(),
createdBy: ServiceHelper.generateRandomString(),
});
Expand All @@ -504,6 +505,7 @@ ServiceHelper.generateRandomModelProperties = (modelType = modelTypes.CONTAINER)
...(modelType === modelTypes.DRAWING ? {
number: ServiceHelper.generateRandomString(),
type: ServiceHelper.generateRandomString(),
calibration: { verticalRange: [ServiceHelper.generateRandomNumber(0, 10), ServiceHelper.generateRandomNumber(11, 20)], units: 'm' },
modelType,
} : {
properties: {
Expand Down
Loading

0 comments on commit d92b22d

Please sign in to comment.