diff --git a/cvat/apps/engine/static/engine/js/annotationSaver.js b/cvat/apps/engine/static/engine/js/annotationSaver.js index 0f77282ad7aa..0099957c2812 100644 --- a/cvat/apps/engine/static/engine/js/annotationSaver.js +++ b/cvat/apps/engine/static/engine/js/annotationSaver.js @@ -22,7 +22,8 @@ class AnnotationSaverModel extends Listener { this._version = initialData.version; this._shapeCollection = shapeCollection; this._initialObjects = []; - this._hash = objectHash(shapeCollection.export()); + + this._hash = this._getHash(); for (const shape of initialData.shapes) { this._initialObjects[shape.id] = shape; @@ -95,8 +96,20 @@ class AnnotationSaverModel extends Listener { 'points count': totalStat.points.annotation + totalStat.points.interpolation, }); - // const annotationLogs = Logger.getLogs(); - // TODO: Save logs + const annotationLogs = Logger.getLogs(); + + try { + await $.ajax({ + url: 'api/v1/server/logs', + type: 'POST', + data: JSON.stringify(annotationLogs), + }); + } catch (errorData) { + annotationLogs.save(); + const message = `Can not send logs. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + throw Error(message); + } } _split(exported) { @@ -156,6 +169,26 @@ class AnnotationSaverModel extends Listener { return [created, updated, deleted]; } + _getHash() { + const exported = this._shapeCollection.export()[0]; + return objectHash(exported); + } + + _updateCreatedObjects(objectsToSave, savedObjects, mapping) { + const allSavedObjects = savedObjects.shapes.concat(savedObjects.tracks); + const allObjectsToSave = objectsToSave.shapes.concat(objectsToSave.tracks); + if (allSavedObjects.length !== allObjectsToSave.length) { + throw Error('Number of saved objects and objects to save is not match'); + } + + for (let idx = 0; idx < allSavedObjects.length; idx += 1) { + const objectModel = mapping.filter(el => el[0] === allObjectsToSave[idx])[0][1]; + objectModel.serverID = allSavedObjects[idx].id; + } + + this._shapeCollection.update(); + } + notify(status, message = null) { this._state.status = status; this._state.message = message; @@ -163,24 +196,25 @@ class AnnotationSaverModel extends Listener { } hasUnsavedChanges() { - return objectHash(this._shapeCollection.export()) !== this._hash; + return this._getHash() !== this._hash; } async save() { this.notify('saveStart'); try { - const exported = this._shapeCollection.export(); + const [exported, mapping] = this._shapeCollection.export(); const { flush } = this._shapeCollection; if (flush) { const data = Object.assign({}, exported, { - version: this._version += 1, + version: this._version, tags: [], }); + this._version += 1; + this.notify('saveCreated'); const savedObjects = await this._put(data); - this._shapeCollection.cleanupClientObjects(); - this._shapeCollection.import(savedObjects).update(); + this._updateCreatedObjects(exported, savedObjects, mapping); this._shapeCollection.flush = false; for (const object of savedObjects.shapes.concat(savedObjects.tracks)) { this._initialObjects[object.id] = object; @@ -189,11 +223,9 @@ class AnnotationSaverModel extends Listener { this._version = savedObjects.version; } else { const [created, updated, deleted] = this._split(exported); - this.notify('saveCreated'); const savedCreated = await this._create(created); - this._shapeCollection.cleanupClientObjects(); - this._shapeCollection.import(savedCreated).update(); + this._updateCreatedObjects(created, savedCreated, mapping); for (const object of savedCreated.shapes.concat(savedCreated.tracks)) { this._initialObjects[object.id] = object; } @@ -216,6 +248,8 @@ class AnnotationSaverModel extends Listener { this._version = savedDeleted.version; } + + await this._logs(); } catch (error) { this.notify('saveUnlocked'); this.notify('saveError', error); @@ -226,8 +260,7 @@ class AnnotationSaverModel extends Listener { throw Error(error); } - - this._hash = objectHash(this._shapeCollection.export()); + this._hash = this._getHash(); this.notify('saveDone'); setTimeout(() => { diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js index a44af062bcce..60259bfe9afd 100644 --- a/cvat/apps/engine/static/engine/js/annotationUI.js +++ b/cvat/apps/engine/static/engine/js/annotationUI.js @@ -27,7 +27,6 @@ PolyshapeEditorModel:false PolyshapeEditorView:false PolyShapeView:false - serverRequest:false ShapeBufferController:false ShapeBufferModel:false ShapeBufferView:false @@ -46,6 +45,7 @@ showMessage:false showOverlay:false buildAnnotationSaver:false + LabelsInfo:false */ @@ -75,20 +75,25 @@ function callAnnotationUI(jid) { }).fail(onError); } -function initLogger(jobID) { - if (!Logger.initializeLogger('CVAT', jobID)) - { - let message = 'Could not initialize Logger. Please immediately report the problem to support team'; +async function initLogger(jobID) { + if (!Logger.initializeLogger('CVAT', jobID)) { + const message = 'Could not initialize Logger. Please immediately report the problem to support team'; console.error(message); showMessage(message); return; } Logger.setTimeThreshold(Logger.EventType.zoomImage); + let user = null; + try { + user = await $.get('/api/v1/users/self'); + } catch (errorData) { + const message = `Could not get username. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + showMessage(message); + } - serverRequest('/api/v1/users/self', function(response) { - Logger.setUsername(response.username); - }); + Logger.setUsername(user.username); } function buildAnnotationUI(jobData, taskData, imageMetaData, annotationData, loadJobEvent) { diff --git a/cvat/apps/engine/static/engine/js/bootstrap.js b/cvat/apps/engine/static/engine/js/bootstrap.js index d38b6d642334..ad2e2a66c6af 100644 --- a/cvat/apps/engine/static/engine/js/bootstrap.js +++ b/cvat/apps/engine/static/engine/js/bootstrap.js @@ -10,27 +10,25 @@ platform:false */ -"use strict"; - String.prototype.normalize = function() { let target = this; target = target.charAt(0).toUpperCase() + target.substr(1); return target; }; -window.onload = function() { - window.onerror = function(errorMsg, url, lineNumber, colNumber, error) { +window.onload = function boot() { + window.onerror = function exception(errorMsg, url, lineNumber, colNumber, error) { Logger.sendException({ message: errorMsg, filename: url, line: lineNumber, - column: colNumber ? colNumber : '', + column: colNumber ? String(colNumber) : '', stack: error && error.stack ? error.stack : '', - browser: platform.name + ' ' + platform.version, - os: platform.os.toString(), - }).catch(() => { return; }); + client: `${platform.name} ${platform.version}`, + system: platform.os.toString(), + }).catch(() => {}); }; - let id = window.location.href.match('id=[0-9]+')[0].slice(3); + const id = window.location.href.match('id=[0-9]+')[0].slice(3); callAnnotationUI(id); }; diff --git a/cvat/apps/engine/static/engine/js/logger.js b/cvat/apps/engine/static/engine/js/logger.js index 1f86bf5d663d..a27f1ee9b66f 100644 --- a/cvat/apps/engine/static/engine/js/logger.js +++ b/cvat/apps/engine/static/engine/js/logger.js @@ -62,7 +62,7 @@ class LogCollection extends Array { var LoggerHandler = function(applicationName, jobId) { this._application = applicationName; - this._tabId = Date.now().toString().substr(-6); + this._clientID = Date.now().toString().substr(-6); this._jobId = jobId; this._username = null; this._userActivityHandler = null; @@ -83,20 +83,18 @@ var LoggerHandler = function(applicationName, jobId) return event; }; - this.sendExceptions = function(exceptions) + this.sendExceptions = function(exception) { - for (let e of exceptions) { - this._extendEvent(e); - } + this._extendEvent(exception); return new Promise( (resolve, reject) => { let xhr = new XMLHttpRequest(); - xhr.open('POST', '/save/exception/' + this._jobId); + xhr.open('POST', '/api/v1/server/exception'); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.setRequestHeader("X-CSRFToken", Cookies.get('csrftoken')); let onreject = () => { - Array.prototype.push.apply(this._logEvents, exceptions); + this._logEvents.push(exception); reject({ status: xhr.status, statusText: xhr.statusText, @@ -117,8 +115,7 @@ var LoggerHandler = function(applicationName, jobId) onreject(); }; - const data = {'exceptions': Array.from(exceptions, log => log.toString())}; - xhr.send(JSON.stringify(data)); + xhr.send(JSON.stringify(exception.toString())); }); }; @@ -140,8 +137,8 @@ var LoggerHandler = function(applicationName, jobId) application: this._application, task: this._jobId, userid: this._username, - tabid: this._tabId, - focus: document.hasFocus() + client_id: this._clientID, + focus: document.hasFocus(), }); }; @@ -258,6 +255,9 @@ var Logger = { */ LogEvent: function(type, values, closeCallback=null) { + const d = new Date(); + const time = `${d.getFullYear() + 1}-${d.getMonth() + 1}-${d.getDay()}` + + `T${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`; this._type = type; this._timestamp = Date.now(); this.onCloseCallback = closeCallback; @@ -268,7 +268,7 @@ var Logger = { { return Object.assign({ event: Logger.eventTypeToString(this._type), - timestamp: this._timestamp, + time, }, this._values); }; @@ -439,7 +439,7 @@ var Logger = { */ sendException: function(exceptionData) { - return this._logger.sendExceptions([new Logger.LogEvent(Logger.EventType.sendException, exceptionData)]); + return this._logger.sendExceptions(new Logger.LogEvent(Logger.EventType.sendException, exceptionData)); }, /** diff --git a/cvat/apps/engine/static/engine/js/server.js b/cvat/apps/engine/static/engine/js/server.js index 30e3ac151e7c..8d312abf6ace 100644 --- a/cvat/apps/engine/static/engine/js/server.js +++ b/cvat/apps/engine/static/engine/js/server.js @@ -4,30 +4,8 @@ * SPDX-License-Identifier: MIT */ -/* exported serverRequest encodeFilePathToURI */ - -/* global - showOverlay:false -*/ - -"use strict"; - -function serverRequest(url, successCallback) -{ - $.ajax({ - url: url, - dataType: "json", - success: successCallback, - error: serverError - }); -} +/* exported encodeFilePathToURI */ function encodeFilePathToURI(path) { return path.split('/').map(x => encodeURIComponent(x)).join('/'); } - -function serverError() { - let message = 'Server errors was occured. Please contact with research automation team.'; - showOverlay(message); - throw Error(message); -} diff --git a/cvat/apps/engine/static/engine/js/shapeCollection.js b/cvat/apps/engine/static/engine/js/shapeCollection.js index 803f41c6f35a..dbb8f2c94e9c 100644 --- a/cvat/apps/engine/static/engine/js/shapeCollection.js +++ b/cvat/apps/engine/static/engine/js/shapeCollection.js @@ -238,7 +238,6 @@ class ShapeCollectionModel extends Listener { // Make copy of data in order to don't affect original data data = JSON.parse(JSON.stringify(data)); - this._idx = data.shapes.concat(data.tracks).reduce((acc, el) => Math.max(acc, el.id || 0), -1); for (let imported of data.shapes.concat(data.tracks)) { // Conversion from client object format to server object format @@ -286,6 +285,8 @@ class ShapeCollectionModel extends Listener { tracks: [] }; + const mapping = []; + for (let shape of this._shapes) { if (!shape.removed) { const exported = shape.export(); @@ -308,10 +309,12 @@ class ShapeCollectionModel extends Listener { } else { data.tracks.push(exported); } + + mapping.push([exported, shape]); } } - return data; + return [data, mapping]; } find(direction) { @@ -380,7 +383,8 @@ class ShapeCollectionModel extends Listener { } add(data, type) { - const id = data.id || ++this._idx; + this._idx += 1; + const id = this._idx; const model = buildShapeModel(data, type, id, this.nextColor()); if (type.startsWith('interpolation')) { this._interpolationShapes.push(model); @@ -1253,11 +1257,15 @@ class ShapeCollectionView { case "object_url": { let active = this._controller.activeShape; if (active) { - window.cvat.search.set('frame', window.cvat.player.frames.current); - window.cvat.search.set('filter', `*[id="${active.id}"]`); - copyToClipboard(window.cvat.search.toString()); - window.cvat.search.set('frame', null); - window.cvat.search.set('filter', null); + if (typeof active.serverID !== 'undefined') { + window.cvat.search.set('frame', window.cvat.player.frames.current); + window.cvat.search.set('filter', `*[serverID="${active.serverID}"]`); + copyToClipboard(window.cvat.search.toString()); + window.cvat.search.set('frame', null); + window.cvat.search.set('filter', null); + } else { + showMessage('First save job in order to get static object URL'); + } } break; } @@ -1444,7 +1452,7 @@ class ShapeCollectionView { let newShapes = collection.currentShapes; let newModels = newShapes.map((el) => el.model); - let frameChanged = this._frameMarker != window.cvat.player.frames.current; + const frameChanged = this._frameMarker !== window.cvat.player.frames.current; if (frameChanged) { this._frameContent.node.parent = null; diff --git a/cvat/apps/engine/static/engine/js/shapeFilter.js b/cvat/apps/engine/static/engine/js/shapeFilter.js index cf843ccffce5..49cde3e277c9 100644 --- a/cvat/apps/engine/static/engine/js/shapeFilter.js +++ b/cvat/apps/engine/static/engine/js/shapeFilter.js @@ -7,25 +7,24 @@ /* exported FilterModel FilterController FilterView */ /* eslint no-unused-vars: ["error", { "caughtErrors": "none" }] */ -"use strict"; - class FilterModel { constructor(update) { - this._filter = ""; + this._filter = ''; this._update = update; this._labels = window.cvat.labelsInfo.labels(); this._attributes = window.cvat.labelsInfo.attributes(); } _convertShape(shape) { - let converted = { + const converted = { id: shape.model.id, + serverid: shape.model.serverID, label: shape.model.label, - type: shape.model.type.split("_")[1], - mode: shape.model.type.split("_")[0], - occluded: shape.interpolation.position.occluded ? true : false, + type: shape.model.type.split('_')[1], + mode: shape.model.type.split('_')[0], + occluded: Boolean(shape.interpolation.position.occluded), attr: convertAttributes(shape.interpolation.attributes), - lock: shape.model.lock + lock: shape.model.lock, }; if (shape.model.type.split('_')[1] === 'box') { diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 07e45fda6f68..b80c1114d04b 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -581,6 +581,10 @@ class ShapeModel extends Listener { return this._serverID; } + set serverID(value) { + this._serverID = value; + } + get frame() { return this._frame; }