diff --git a/CHANGELOG.md b/CHANGELOG.md index c9151e7a0792..47f84f63a93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428) - IOG and f-BRS serverless function () - Invisible label item in label constructor when label color background is white, or close to it () +- Fixed cvat-core ESlint problems () ### Security - TDB diff --git a/cvat-core/package.json b/cvat-core/package.json index fdc299d91ff8..e244b24db1bf 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -29,7 +29,7 @@ "dependencies": { "axios": "^0.27.2", "browser-or-node": "^2.0.0", - "cvat-data": "file:../cvat-data", + "cvat-data": "link:./../cvat-data", "detect-browser": "^5.2.1", "error-stack-parser": "^2.0.2", "form-data": "^4.0.0", diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts index 56500fe1e91f..22ea2ae1ff97 100644 --- a/cvat-core/src/api-implementation.ts +++ b/cvat-core/src/api-implementation.ts @@ -23,8 +23,8 @@ const config = require('./config'); const { ArgumentError } = require('./exceptions'); const { Task, Job } = require('./session'); const Project = require('./project').default; - const { CloudStorage } = require('./cloud-storage'); - const Organization = require('./organization'); + const CloudStorage = require('./cloud-storage').default; + const Organization = require('./organization').default; const Webhook = require('./webhook').default; function implementAPI(cvat) { diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index 5837cf301fdf..2225d95b9705 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -10,20 +10,20 @@ function build() { const PluginRegistry = require('./plugins').default; - const loggerStorage = require('./logger-storage'); - const Log = require('./log'); + const loggerStorage = require('./logger-storage').default; + const { Log } = require('./log'); const ObjectState = require('./object-state').default; const Statistics = require('./statistics'); - const Comment = require('./comment'); - const Issue = require('./issue'); + const Comment = require('./comment').default; + const Issue = require('./issue').default; const { Job, Task } = require('./session'); const Project = require('./project').default; const implementProject = require('./project-implementation').default; const { Attribute, Label } = require('./labels'); const MLModel = require('./ml-model'); const { FrameData } = require('./frames'); - const { CloudStorage } = require('./cloud-storage'); - const Organization = require('./organization'); + const CloudStorage = require('./cloud-storage').default; + const Organization = require('./organization').default; const Webhook = require('./webhook').default; const enums = require('./enums'); diff --git a/cvat-core/src/cloud-storage.ts b/cvat-core/src/cloud-storage.ts index 94f8c02e962e..1aab6ef40598 100644 --- a/cvat-core/src/cloud-storage.ts +++ b/cvat-core/src/cloud-storage.ts @@ -1,511 +1,393 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -(() => { - const PluginRegistry = require('./plugins').default; - const serverProxy = require('./server-proxy').default; - const { isBrowser, isNode } = require('browser-or-node'); - const { ArgumentError } = require('./exceptions'); - const { CloudStorageCredentialsType, CloudStorageProviderType } = require('./enums'); +import { isBrowser, isNode } from 'browser-or-node'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import { ArgumentError } from './exceptions'; +import { CloudStorageCredentialsType, CloudStorageProviderType, CloudStorageStatus } from './enums'; +import User from './user'; - function validateNotEmptyString(value) { - if (typeof value !== 'string') { - throw new ArgumentError(`Value must be a string. ${typeof value} was found`); - } else if (!value.trim().length) { - throw new ArgumentError('Value mustn\'t be empty string'); - } +function validateNotEmptyString(value: string): void { + if (typeof value !== 'string') { + throw new ArgumentError(`Value must be a string. ${typeof value} was found`); + } else if (!value.trim().length) { + throw new ArgumentError('Value mustn\'t be empty string'); } +} - /** - * Class representing a cloud storage - * @memberof module:API.cvat.classes - */ - class CloudStorage { - constructor(initialData) { - const data = { - id: undefined, - display_name: undefined, - description: undefined, - credentials_type: undefined, - provider_type: undefined, - resource: undefined, - account_name: undefined, - key: undefined, - secret_key: undefined, - session_token: undefined, - key_file: undefined, - specific_attributes: undefined, - owner: undefined, - created_date: undefined, - updated_date: undefined, - manifest_path: undefined, - manifests: undefined, - }; +interface RawCloudStorageData { + id?: number; + display_name?: string; + description?: string, + credentials_type?: CloudStorageCredentialsType, + provider_type?: CloudStorageProviderType, + resource?: string, + account_name?: string, + key?: string, + secret_key?: string, + session_token?: string, + key_file?: File, + specific_attributes?: string, + owner?: any, + created_date?: string, + updated_date?: string, + manifest_path?: string, + manifests?: string[], +} - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } +export default class CloudStorage { + public readonly id: number; + public displayName: string; + public description: string; + public accountName: string; + public accessKey: string; + public secretKey: string; + public token: string; + public keyFile: File; + public resource: string; + public manifestPath: string; + public provider_type: CloudStorageProviderType; + public credentials_type: CloudStorageCredentialsType; + public specificAttributes: string; + public manifests: string[]; + public readonly owner: User; + public readonly createdDate: string; + public readonly updatedDate: string; + + constructor(initialData: RawCloudStorageData) { + const data: RawCloudStorageData = { + id: undefined, + display_name: undefined, + description: undefined, + credentials_type: undefined, + provider_type: undefined, + resource: undefined, + account_name: undefined, + key: undefined, + secret_key: undefined, + session_token: undefined, + key_file: undefined, + specific_attributes: undefined, + owner: undefined, + created_date: undefined, + updated_date: undefined, + manifest_path: undefined, + manifests: undefined, + }; + + for (const property in data) { + if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { + data[property] = initialData[property]; } + } - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - id: { - get: () => data.id, + Object.defineProperties( + this, + Object.freeze({ + id: { + get: () => data.id, + }, + displayName: { + get: () => data.display_name, + set: (value) => { + validateNotEmptyString(value); + data.display_name = value; }, - /** - * Storage name - * @name displayName - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - displayName: { - get: () => data.display_name, - set: (value) => { - validateNotEmptyString(value); - data.display_name = value; - }, + }, + description: { + get: () => data.description, + set: (value) => { + if (typeof value !== 'string') { + throw new ArgumentError('Value must be string'); + } + data.description = value; }, - /** - * Storage description - * @name description - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - description: { - get: () => data.description, - set: (value) => { - if (typeof value !== 'string') { - throw new ArgumentError('Value must be string'); - } - data.description = value; - }, + }, + accountName: { + get: () => data.account_name, + set: (value) => { + validateNotEmptyString(value); + data.account_name = value; }, - /** - * Azure account name - * @name accountName - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - accountName: { - get: () => data.account_name, - set: (value) => { - validateNotEmptyString(value); - data.account_name = value; - }, + }, + accessKey: { + get: () => data.key, + set: (value) => { + validateNotEmptyString(value); + data.key = value; }, - /** - * AWS access key id - * @name accessKey - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - accessKey: { - get: () => data.key, - set: (value) => { - validateNotEmptyString(value); - data.key = value; - }, + }, + secretKey: { + get: () => data.secret_key, + set: (value) => { + validateNotEmptyString(value); + data.secret_key = value; }, - /** - * AWS secret key - * @name secretKey - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - secretKey: { - get: () => data.secret_key, - set: (value) => { - validateNotEmptyString(value); - data.secret_key = value; - }, + }, + token: { + get: () => data.session_token, + set: (value) => { + validateNotEmptyString(value); + data.session_token = value; }, - /** - * Session token - * @name token - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - token: { - get: () => data.session_token, - set: (value) => { - validateNotEmptyString(value); - data.session_token = value; - }, + }, + keyFile: { + get: () => data.key_file, + set: (file) => { + if (file instanceof File) { + data.key_file = file; + } else { + throw new ArgumentError(`Should be a file. ${typeof file} was found`); + } }, - /** - * Key file - * @name keyFile - * @type {File} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - keyFile: { - get: () => data.key_file, - set: (file) => { - if (file instanceof File) { - data.key_file = file; - } else { - throw new ArgumentError(`Should be a file. ${typeof file} was found`); - } - }, + }, + resource: { + get: () => data.resource, + set: (value) => { + validateNotEmptyString(value); + data.resource = value; }, - /** - * Unique resource name - * @name resource - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - resource: { - get: () => data.resource, - set: (value) => { - validateNotEmptyString(value); - data.resource = value; - }, + }, + manifestPath: { + get: () => data.manifest_path, + set: (value) => { + validateNotEmptyString(value); + data.manifest_path = value; }, - /** - * @name manifestPath - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - manifestPath: { - get: () => data.manifest_path, - set: (value) => { - validateNotEmptyString(value); - data.manifest_path = value; - }, + }, + providerType: { + get: () => data.provider_type, + set: (key) => { + if (key !== undefined && !!CloudStorageProviderType[key]) { + data.provider_type = CloudStorageProviderType[key]; + } else { + throw new ArgumentError('Value must be one CloudStorageProviderType keys'); + } }, - /** - * @name providerType - * @type {module:API.cvat.enums.ProviderType} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - providerType: { - get: () => data.provider_type, - set: (key) => { - if (key !== undefined && !!CloudStorageProviderType[key]) { - data.provider_type = CloudStorageProviderType[key]; - } else { - throw new ArgumentError('Value must be one CloudStorageProviderType keys'); - } - }, + }, + credentialsType: { + get: () => data.credentials_type, + set: (key) => { + if (key !== undefined && !!CloudStorageCredentialsType[key]) { + data.credentials_type = CloudStorageCredentialsType[key]; + } else { + throw new ArgumentError('Value must be one CloudStorageCredentialsType keys'); + } }, - /** - * @name credentialsType - * @type {module:API.cvat.enums.CloudStorageCredentialsType} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - credentialsType: { - get: () => data.credentials_type, - set: (key) => { - if (key !== undefined && !!CloudStorageCredentialsType[key]) { - data.credentials_type = CloudStorageCredentialsType[key]; - } else { - throw new ArgumentError('Value must be one CloudStorageCredentialsType keys'); + }, + specificAttributes: { + get: () => data.specific_attributes, + set: (attributesValue) => { + if (typeof attributesValue === 'string') { + const attrValues = new URLSearchParams( + Array.from(new URLSearchParams(attributesValue).entries()).filter( + ([key, value]) => !!key && !!value, + ), + ).toString(); + if (!attrValues) { + throw new ArgumentError('Value must match the key1=value1&key2=value2'); } - }, + data.specific_attributes = attributesValue; + } else { + throw new ArgumentError('Value must be a string'); + } }, - /** - * @name specificAttributes - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - specificAttributes: { - get: () => data.specific_attributes, - set: (attributesValue) => { - if (typeof attributesValue === 'string') { - const attrValues = new URLSearchParams( - Array.from(new URLSearchParams(attributesValue).entries()).filter( - ([key, value]) => !!key && !!value, - ), - ).toString(); - if (!attrValues) { - throw new ArgumentError('Value must match the key1=value1&key2=value2'); + }, + owner: { + get: () => data.owner, + }, + createdDate: { + get: () => data.created_date, + }, + updatedDate: { + get: () => data.updated_date, + }, + manifests: { + get: () => data.manifests, + set: (manifests) => { + if (Array.isArray(manifests)) { + for (const elem of manifests) { + if (typeof elem !== 'string') { + throw new ArgumentError('Each element of the manifests array must be a string'); } - data.specific_attributes = attributesValue; - } else { - throw new ArgumentError('Value must be a string'); } - }, + data.manifests = manifests; + } else { + throw new ArgumentError('Value must be an array'); + } }, - /** - * Instance of a user who has created the cloud storage - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - */ - updatedDate: { - get: () => data.updated_date, - }, - /** - * @name manifests - * @type {string[]} - * @memberof module:API.cvat.classes.CloudStorage - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - manifests: { - get: () => data.manifests, - set: (manifests) => { - if (Array.isArray(manifests)) { - for (const elem of manifests) { - if (typeof elem !== 'string') { - throw new ArgumentError('Each element of the manifests array must be a string'); - } - } - data.manifests = manifests; - } else { - throw new ArgumentError('Value must be an array'); - } - }, - }, - }), - ); - } + }, + }), + ); + } - /** - * Method updates data of a created cloud storage or creates new cloud storage - * @method save - * @returns {module:API.cvat.classes.CloudStorage} - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.save); - return result; - } + // Method updates data of a created cloud storage or creates new cloud storage + public async save(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.save); + return result; + } - /** - * Method deletes a cloud storage from a server - * @method delete - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.delete); - return result; - } + public async delete(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.delete); + return result; + } - /** - * Method returns cloud storage content - * @method getContent - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getContent() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getContent); - return result; - } + public async getContent(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getContent); + return result; + } - /** - * Method returns the cloud storage preview - * @method getPreview - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getPreview() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getPreview); - return result; - } + public async getPreview(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getPreview); + return result; + } - /** - * Method returns cloud storage status - * @method getStatus - * @memberof module:API.cvat.classes.CloudStorage - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async getStatus() { - const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getStatus); - return result; - } + public async getStatus(): Promise { + const result = await PluginRegistry.apiWrapper.call(this, CloudStorage.prototype.getStatus); + return result; } +} - CloudStorage.prototype.save.implementation = async function () { - function prepareOptionalFields(cloudStorageInstance) { - const data = {}; - if (cloudStorageInstance.description !== undefined) { - data.description = cloudStorageInstance.description; - } +Object.defineProperties(CloudStorage.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + function prepareOptionalFields(cloudStorageInstance: CloudStorage): RawCloudStorageData { + const data: RawCloudStorageData = {}; + if (cloudStorageInstance.description !== undefined) { + data.description = cloudStorageInstance.description; + } - if (cloudStorageInstance.accountName) { - data.account_name = cloudStorageInstance.accountName; - } + if (cloudStorageInstance.accountName) { + data.account_name = cloudStorageInstance.accountName; + } - if (cloudStorageInstance.accessKey) { - data.key = cloudStorageInstance.accessKey; - } + if (cloudStorageInstance.accessKey) { + data.key = cloudStorageInstance.accessKey; + } - if (cloudStorageInstance.secretKey) { - data.secret_key = cloudStorageInstance.secretKey; - } + if (cloudStorageInstance.secretKey) { + data.secret_key = cloudStorageInstance.secretKey; + } - if (cloudStorageInstance.token) { - data.session_token = cloudStorageInstance.token; - } + if (cloudStorageInstance.token) { + data.session_token = cloudStorageInstance.token; + } - if (cloudStorageInstance.keyFile) { - data.key_file = cloudStorageInstance.keyFile; - } + if (cloudStorageInstance.keyFile) { + data.key_file = cloudStorageInstance.keyFile; + } - if (cloudStorageInstance.specificAttributes !== undefined) { - data.specific_attributes = cloudStorageInstance.specificAttributes; - } - return data; - } - // update - if (typeof this.id !== 'undefined') { - // provider_type and recource should not change; - // send to the server only the values that have changed - const initialData = {}; - if (this.displayName) { - initialData.display_name = this.displayName; - } - if (this.credentialsType) { - initialData.credentials_type = this.credentialsType; + if (cloudStorageInstance.specificAttributes !== undefined) { + data.specific_attributes = cloudStorageInstance.specificAttributes; + } + return data; } + // update + if (typeof this.id !== 'undefined') { + // provider_type and recource should not change; + // send to the server only the values that have changed + const initialData: RawCloudStorageData = {}; + if (this.displayName) { + initialData.display_name = this.displayName; + } + if (this.credentialsType) { + initialData.credentials_type = this.credentialsType; + } - if (this.manifests) { - initialData.manifests = this.manifests; + if (this.manifests) { + initialData.manifests = this.manifests; + } + + const cloudStorageData = { + ...initialData, + ...prepareOptionalFields(this), + }; + + await serverProxy.cloudStorages.update(this.id, cloudStorageData); + return this; } + // create + const initialData: RawCloudStorageData = { + display_name: this.displayName, + credentials_type: this.credentialsType, + provider_type: this.providerType, + resource: this.resource, + manifests: this.manifests, + }; + const cloudStorageData = { ...initialData, ...prepareOptionalFields(this), }; - await serverProxy.cloudStorages.update(this.id, cloudStorageData); - return this; - } - - // create - const initialData = { - display_name: this.displayName, - credentials_type: this.credentialsType, - provider_type: this.providerType, - resource: this.resource, - manifests: this.manifests, - }; - - const cloudStorageData = { - ...initialData, - ...prepareOptionalFields(this), - }; - - const cloudStorage = await serverProxy.cloudStorages.create(cloudStorageData); - return new CloudStorage(cloudStorage); - }; + const cloudStorage = await serverProxy.cloudStorages.create(cloudStorageData); + return new CloudStorage(cloudStorage); + }, + }, +}); - CloudStorage.prototype.delete.implementation = async function () { - const result = await serverProxy.cloudStorages.delete(this.id); - return result; - }; - - CloudStorage.prototype.getContent.implementation = async function () { - const result = await serverProxy.cloudStorages.getContent(this.id, this.manifestPath); - return result; - }; +Object.defineProperties(CloudStorage.prototype.delete, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.delete(this.id); + return result; + }, + }, +}); - CloudStorage.prototype.getPreview.implementation = async function getPreview() { - return new Promise((resolve, reject) => { - serverProxy.cloudStorages - .getPreview(this.id) - .then((result) => { - if (isNode) { - resolve(global.Buffer.from(result, 'binary').toString('base64')); - } else if (isBrowser) { - const reader = new FileReader(); - reader.onload = () => { - resolve(reader.result); - }; - reader.readAsDataURL(result); - } - }) - .catch((error) => { - reject(error); - }); - }); - }; +Object.defineProperties(CloudStorage.prototype.getContent, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.getContent(this.id, this.manifestPath); + return result; + }, + }, +}); - CloudStorage.prototype.getStatus.implementation = async function () { - const result = await serverProxy.cloudStorages.getStatus(this.id); - return result; - }; +Object.defineProperties(CloudStorage.prototype.getPreview, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + return new Promise((resolve, reject) => { + serverProxy.cloudStorages + .getPreview(this.id) + .then((result) => { + if (isNode) { + resolve(global.Buffer.from(result, 'binary').toString('base64')); + } else if (isBrowser) { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(result); + } + }) + .catch((error) => { + reject(error); + }); + }); + }, + }, +}); - module.exports = { - CloudStorage, - }; -})(); +Object.defineProperties(CloudStorage.prototype.getStatus, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(): Promise { + const result = await serverProxy.cloudStorages.getStatus(this.id); + return result; + }, + }, +}); diff --git a/cvat-core/src/comment.ts b/cvat-core/src/comment.ts index e25073095954..347e23f81094 100644 --- a/cvat-core/src/comment.ts +++ b/cvat-core/src/comment.ts @@ -3,17 +3,31 @@ // // SPDX-License-Identifier: MIT -const User = require('./user').default; -const { ArgumentError } = require('./exceptions'); +import User from './user'; +import { ArgumentError } from './exceptions'; -/** - * Class representing a single comment - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Comment { - constructor(initialData) { - const data = { +export interface RawCommentData { + id?: number; + message?: string; + created_date?: string; + updated_date?: string; + owner?: any; +} + +interface SerializedCommentData extends RawCommentData{ + owner_id?: number; + issue?: number; +} + +export default class Comment { + public readonly id: number; + public readonly createdDate: string; + public readonly updatedDate: string; + public readonly owner: User; + public message: string; + + constructor(initialData: RawCommentData) { + const data: RawCommentData = { id: undefined, message: undefined, created_date: undefined, @@ -35,23 +49,9 @@ class Comment { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * @name message - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ message: { get: () => data.message, set: (value) => { @@ -61,34 +61,12 @@ class Comment { data.message = value; }, }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ createdDate: { get: () => data.created_date, }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ updatedDate: { get: () => data.updated_date, }, - /** - * Instance of a user who has created the comment - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ owner: { get: () => data.owner, }, @@ -99,8 +77,8 @@ class Comment { ); } - serialize() { - const data = { + public serialize(): SerializedCommentData { + const data: SerializedCommentData = { message: this.message, }; @@ -120,5 +98,3 @@ class Comment { return data; } } - -module.exports = Comment; diff --git a/cvat-core/src/common.ts b/cvat-core/src/common.ts index 36c7aaed09f1..41b8957fe973 100644 --- a/cvat-core/src/common.ts +++ b/cvat-core/src/common.ts @@ -60,7 +60,7 @@ export function checkExclusiveFields(obj, exclusive, ignore): void { } } -export function checkObjectType(name, value, type, instance): boolean { +export function checkObjectType(name, value, type, instance?): boolean { if (type) { if (typeof value !== type) { // specific case for integers which aren't native type in JS diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts index 2508625644ca..a2a4647100e2 100644 --- a/cvat-core/src/enums.ts +++ b/cvat-core/src/enums.ts @@ -389,6 +389,22 @@ export enum CloudStorageCredentialsType { KEY_FILE_PATH = 'KEY_FILE_PATH', } +/** + * Types of cloud storage statuses + * @enum {string} + * @name CloudStorageStatus + * @memberof module:API.cvat.enums + * @property {string} AVAILABLE 'AVAILABLE' + * @property {string} NOT_FOUND 'NOT_FOUND' + * @property {string} FORBIDDEN 'FORBIDDEN' + * @readonly + */ +export enum CloudStorageStatus { + AVAILABLE = 'AVAILABLE', + NOT_FOUND = 'NOT_FOUND', + FORBIDDEN = 'FORBIDDEN', +} + /** * Membership roles * @enum {string} diff --git a/cvat-core/src/issue.ts b/cvat-core/src/issue.ts index 27148b388343..82e6969f74da 100644 --- a/cvat-core/src/issue.ts +++ b/cvat-core/src/issue.ts @@ -3,22 +3,38 @@ // // SPDX-License-Identifier: MIT -const quickhull = require('quickhull'); - -const PluginRegistry = require('./plugins').default; -const Comment = require('./comment'); -const User = require('./user').default; -const { ArgumentError } = require('./exceptions'); -const serverProxy = require('./server-proxy').default; +import quickhull from 'quickhull'; + +import { Job } from 'session'; +import PluginRegistry from './plugins'; +import Comment, { RawCommentData } from './comment'; +import User from './user'; +import { ArgumentError } from './exceptions'; +import serverProxy from './server-proxy'; + +interface RawIssueData { + id?: number; + job?: any; + position?: number[]; + comments?: any; + frame?: number; + owner?: any; + resolved?: boolean; + created_date?: string; +} -/** - * Class representing a single issue - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Issue { - constructor(initialData) { - const data = { +export default class Issue { + public readonly id: number; + public readonly job: Job; + public readonly comments: Comment[]; + public readonly frame: number; + public readonly owner: User; + public readonly resolved: boolean; + public readonly createdDate: string; + public position: number[]; + + constructor(initialData: RawIssueData) { + const data: RawIssueData = { id: undefined, job: undefined, position: undefined, @@ -48,25 +64,9 @@ class Issue { Object.defineProperties( this, Object.freeze({ - /** - * @name id - * @type {number} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ id: { get: () => data.id, }, - /** - * Region of interests of the issue - * @name position - * @type {number[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ position: { get: () => data.position, set: (value) => { @@ -76,69 +76,21 @@ class Issue { data.position = value; }, }, - /** - * ID of a job, the issue is linked with - * @name job - * @type {number} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ job: { get: () => data.job, }, - /** - * List of comments attached to the issue - * @name comments - * @type {module:API.cvat.classes.Comment[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ comments: { get: () => [...data.comments], }, - /** - * @name frame - * @type {number} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ frame: { get: () => data.frame, }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ createdDate: { get: () => data.created_date, }, - /** - * An instance of a user who has raised the issue - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ owner: { get: () => data.owner, }, - /** - * The flag defines issue status - * @name resolved - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ resolved: { get: () => data.resolved, }, @@ -149,7 +101,7 @@ class Issue { ); } - static hull(coordinates) { + public static hull(coordinates: number[]): number[] { if (coordinates.length > 4) { const points = coordinates.reduce((acc, coord, index, arr) => { if (index % 2) acc.push({ x: arr[index - 1], y: coord }); @@ -164,82 +116,36 @@ class Issue { return coordinates; } - /** - * @typedef {Object} CommentData - * @property {string} message a comment message - * @global - */ - /** - * Method appends a comment to the issue - * For a new issue it saves comment locally, for a saved issue it saves comment on the server - * @method comment - * @memberof module:API.cvat.classes.Issue - * @param {CommentData} data - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async comment(data) { + // Method appends a comment to the issue + // For a new issue it saves comment locally, for a saved issue it saves comment on the server + public async comment(data: RawCommentData): Promise { const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.comment, data); return result; } - /** - * The method resolves the issue - * New issues are resolved locally, server-saved issues are resolved on the server - * @method resolve - * @memberof module:API.cvat.classes.Issue - * @param {module:API.cvat.classes.User} user - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async resolve(user) { + // The method resolves the issue + // New issues are resolved locally, server-saved issues are resolved on the server + public async resolve(user: User): Promise { const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.resolve, user); return result; } - /** - * The method resolves the issue - * New issues are reopened locally, server-saved issues are reopened on the server - * @method reopen - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async reopen() { + // The method reopens the issue + // New issues are reopened locally, server-saved issues are reopened on the server + public async reopen(): Promise { const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.reopen); return result; } - /** - * The method deletes the issue - * Deletes local or server-saved issues - * @method delete - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async delete() { + // The method deletes the issue + // Deletes local or server-saved issues + public async delete(): Promise { await PluginRegistry.apiWrapper.call(this, Issue.prototype.delete); } - serialize() { + public serialize(): RawIssueData { const { comments } = this; - const data = { + const data: RawIssueData = { position: this.position, frame: this.frame, comments: comments.map((comment) => comment.serialize()), @@ -265,53 +171,76 @@ class Issue { } } -Issue.prototype.comment.implementation = async function (data) { - if (typeof data !== 'object' || data === null) { - throw new ArgumentError(`The argument "data" must be an object. Got "${data}"`); - } - if (typeof data.message !== 'string' || data.message.length < 1) { - throw new ArgumentError(`Comment message must be a not empty string. Got "${data.message}"`); - } - - const comment = new Comment(data); - if (typeof this.id === 'number') { - const serialized = comment.serialize(); - serialized.issue = this.id; - const response = await serverProxy.comments.create(serialized); - const savedComment = new Comment(response); - this.__internal.comments.push(savedComment); - } else { - this.__internal.comments.push(comment); - } -}; - -Issue.prototype.resolve.implementation = async function (user) { - if (!(user instanceof User)) { - throw new ArgumentError(`The argument "user" must be an instance of a User class. Got "${typeof user}"`); - } - - if (typeof this.id === 'number') { - const response = await serverProxy.issues.update(this.id, { resolved: true }); - this.__internal.resolved = response.resolved; - } else { - this.__internal.resolved = true; - } -}; - -Issue.prototype.reopen.implementation = async function () { - if (typeof this.id === 'number') { - const response = await serverProxy.issues.update(this.id, { resolved: false }); - this.__internal.resolved = response.resolved; - } else { - this.__internal.resolved = false; - } -}; +Object.defineProperties(Issue.prototype.comment, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(data: RawCommentData) { + if (typeof data !== 'object' || data === null) { + throw new ArgumentError(`The argument "data" must be an object. Got "${data}"`); + } + if (typeof data.message !== 'string' || data.message.length < 1) { + throw new ArgumentError(`Comment message must be a not empty string. Got "${data.message}"`); + } -Issue.prototype.delete.implementation = async function () { - const { id } = this; - if (id >= 0) { - await serverProxy.issues.delete(id); - } -}; + const comment = new Comment(data); + if (typeof this.id === 'number') { + const serialized = comment.serialize(); + serialized.issue = this.id; + const response = await serverProxy.comments.create(serialized); + const savedComment = new Comment(response); + this.__internal.comments.push(savedComment); + } else { + this.__internal.comments.push(comment); + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.resolve, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(user: User) { + if (!(user instanceof User)) { + throw new ArgumentError(`The argument "user" must be an + instance of a User class. Got "${typeof user}"`); + } -module.exports = Issue; + if (typeof this.id === 'number') { + const response = await serverProxy.issues.update(this.id, { resolved: true }); + this.__internal.resolved = response.resolved; + } else { + this.__internal.resolved = true; + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.reopen, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + const response = await serverProxy.issues.update(this.id, { resolved: false }); + this.__internal.resolved = response.resolved; + } else { + this.__internal.resolved = false; + } + }, + }, +}); + +Object.defineProperties(Issue.prototype.delete, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + const { id } = this; + if (id >= 0) { + await serverProxy.issues.delete(id); + } + }, + }, +}); diff --git a/cvat-core/src/log.ts b/cvat-core/src/log.ts index ef06a3ac332f..40d7aea585fc 100644 --- a/cvat-core/src/log.ts +++ b/cvat-core/src/log.ts @@ -1,19 +1,23 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -const { detect } = require('detect-browser'); -const PluginRegistry = require('./plugins').default; -const { ArgumentError } = require('./exceptions'); -const { LogType } = require('./enums'); - -/** - * Class representing a single log - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Log { - constructor(logType, payload) { +import { detect } from 'detect-browser'; +import PluginRegistry from './plugins'; +import { LogType } from './enums'; +import { ArgumentError } from './exceptions'; + +export class Log { + public readonly id: number; + public readonly type: LogType; + public readonly time: Date; + + public payload: any; + + protected onCloseCallback: (() => void) | null; + + constructor(logType: LogType, payload: any) { this.onCloseCallback = null; this.type = logType; @@ -21,11 +25,11 @@ class Log { this.time = new Date(); } - onClose(callback) { + public onClose(callback: () => void): void { this.onCloseCallback = callback; } - validatePayload() { + public validatePayload(): void { if (typeof this.payload !== 'object') { throw new ArgumentError('Payload must be an object'); } @@ -38,7 +42,7 @@ class Log { } } - dump() { + public dump(): any { const payload = { ...this.payload }; const body = { name: this.type, @@ -58,38 +62,33 @@ class Log { }; } - /** - * Method saves a durable log in a storage
- * Note then you can call close() multiple times
- * Log duration will be computed based on the latest call
- * All payloads will be shallowly combined (all top level properties will exist) - * @method close - * @memberof module:API.cvat.classes.Log - * @param {object} [payload] part of payload can be added when close a log - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async close(payload = {}) { + // Method saves a durable log in a storage + // Note then you can call close() multiple times + // Log duration will be computed based on the latest call + // All payloads will be shallowly combined (all top level properties will exist) + public async close(payload = {}): Promise { const result = await PluginRegistry.apiWrapper.call(this, Log.prototype.close, payload); return result; } } -Log.prototype.close.implementation = function (payload) { - this.payload.duration = Date.now() - this.time.getTime(); - this.payload = { ...this.payload, ...payload }; - - if (this.onCloseCallback) { - this.onCloseCallback(); - } -}; +Object.defineProperties(Log.prototype.close, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(payload: any) { + this.payload.duration = Date.now() - this.time.getTime(); + this.payload = { ...this.payload, ...payload }; + if (this.onCloseCallback) { + this.onCloseCallback(); + } + }, + }, +}); class LogWithCount extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if (!Number.isInteger(this.payload.count) || this.payload.count < 1) { const message = `The field "count" is required for "${this.type}" log. It must be a positive integer`; throw new ArgumentError(message); @@ -98,8 +97,8 @@ class LogWithCount extends Log { } class LogWithObjectsInfo extends Log { - validatePayload() { - const generateError = (name, range) => { + public validatePayload(): void { + const generateError = (name: string, range: string): void => { const message = `The field "${name}" is required for "${this.type}" log. ${range}`; throw new ArgumentError(message); }; @@ -139,14 +138,13 @@ class LogWithObjectsInfo extends Log { } class LogWithWorkingTime extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if ( - !( - 'working_time' in this.payload) || - !typeof this.payload.working_time === 'number' || - this.payload.working_time < 0 + !('working_time' in this.payload) || + !(typeof this.payload.working_time === 'number') || + this.payload.working_time < 0 ) { const message = ` The field "working_time" is required for ${this.type} log. It must be a number not less than 0 @@ -157,8 +155,8 @@ class LogWithWorkingTime extends Log { } class LogWithExceptionInfo extends Log { - validatePayload() { - Log.prototype.validatePayload.call(this); + public validatePayload(): void { + super.validatePayload.call(this); if (typeof this.payload.message !== 'string') { const message = `The field "message" is required for ${this.type} log. It must be a string`; @@ -186,7 +184,7 @@ class LogWithExceptionInfo extends Log { } } - dump() { + public dump(): any { let body = super.dump(); const { payload } = body; const client = detect(); @@ -212,7 +210,7 @@ class LogWithExceptionInfo extends Log { } } -function logFactory(logType, payload) { +export default function logFactory(logType: LogType, payload: any): Log { const logsWithCount = [ LogType.deleteObject, LogType.mergeObjects, @@ -238,5 +236,3 @@ function logFactory(logType, payload) { return new Log(logType, payload); } - -module.exports = logFactory; diff --git a/cvat-core/src/logger-storage.ts b/cvat-core/src/logger-storage.ts index 822293723203..c9a5593a94e4 100644 --- a/cvat-core/src/logger-storage.ts +++ b/cvat-core/src/logger-storage.ts @@ -1,12 +1,13 @@ // Copyright (C) 2019-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -const PluginRegistry = require('./plugins').default; -const serverProxy = require('./server-proxy').default; -const logFactory = require('./log'); -const { ArgumentError } = require('./exceptions'); -const { LogType } = require('./enums'); +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import logFactory, { Log } from './log'; +import { LogType } from './enums'; +import { ArgumentError } from './exceptions'; const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min @@ -16,36 +17,49 @@ function sleep(ms): Promise { }); } +interface IgnoreRule { + lastLog: Log | null; + timeThreshold?: number; + ignore: (previousLog: Log, currentPayload: any) => boolean; +} + class LoggerStorage { + public clientID: string; + public lastLogTime: number; + public workingTime: number; + public collection: Array; + public ignoreRules: Record; + public isActiveChecker: (() => boolean) | null; + public saving: boolean; + constructor() { this.clientID = Date.now().toString().substr(-6); this.lastLogTime = Date.now(); this.workingTime = 0; this.collection = []; - this.ignoreRules = {}; // by event this.isActiveChecker = null; this.saving = false; - - this.ignoreRules[LogType.zoomImage] = { - lastLog: null, - timeThreshold: 1000, - ignore(previousLog) { - return Date.now() - previousLog.time < this.timeThreshold; + this.ignoreRules = { + [LogType.zoomImage]: { + lastLog: null, + timeThreshold: 1000, + ignore(previousLog: Log) { + return (Date.now() - previousLog.time.getTime()) < this.timeThreshold; + }, }, - }; - - this.ignoreRules[LogType.changeAttribute] = { - lastLog: null, - ignore(previousLog, currentPayload) { - return ( - currentPayload.object_id === previousLog.payload.object_id && - currentPayload.id === previousLog.payload.id - ); + [LogType.changeAttribute]: { + lastLog: null, + ignore(previousLog: Log, currentPayload: any) { + return ( + currentPayload.object_id === previousLog.payload.object_id && + currentPayload.id === previousLog.payload.id + ); + }, }, }; } - updateWorkingTime() { + protected updateWorkingTime(): void { if (!this.isActiveChecker || this.isActiveChecker()) { const lastLogTime = Date.now(); const diff = lastLogTime - this.lastLogTime; @@ -54,7 +68,7 @@ class LoggerStorage { } } - async configure(isActiveChecker, activityHelper) { + public async configure(isActiveChecker, activityHelper): Promise { const result = await PluginRegistry.apiWrapper.call( this, LoggerStorage.prototype.configure, @@ -64,123 +78,143 @@ class LoggerStorage { return result; } - async log(logType, payload = {}, wait = false) { + public async log(logType: LogType, payload = {}, wait = false): Promise { const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait); return result; } - async save() { + public async save(): Promise { const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.save); return result; } } -LoggerStorage.prototype.configure.implementation = function (isActiveChecker, userActivityCallback) { - if (typeof isActiveChecker !== 'function') { - throw new ArgumentError('isActiveChecker argument must be callable'); - } - - if (!Array.isArray(userActivityCallback)) { - throw new ArgumentError('userActivityCallback argument must be an array'); - } - - this.isActiveChecker = () => !!isActiveChecker(); - userActivityCallback.push(this.updateWorkingTime.bind(this)); -}; - -LoggerStorage.prototype.log.implementation = function (logType, payload, wait) { - if (typeof payload !== 'object') { - throw new ArgumentError('Payload must be an object'); - } - - if (typeof wait !== 'boolean') { - throw new ArgumentError('Payload must be an object'); - } - - if (logType in this.ignoreRules) { - const ignoreRule = this.ignoreRules[logType]; - const { lastLog } = ignoreRule; - if (lastLog && ignoreRule.ignore(lastLog, payload)) { - lastLog.payload = { - ...lastLog.payload, - ...payload, +Object.defineProperties(LoggerStorage.prototype.configure, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(isActiveChecker: () => boolean, userActivityCallback: Array) { + if (typeof isActiveChecker !== 'function') { + throw new ArgumentError('isActiveChecker argument must be callable'); + } + + if (!Array.isArray(userActivityCallback)) { + throw new ArgumentError('userActivityCallback argument must be an array'); + } + + this.isActiveChecker = () => !!isActiveChecker(); + userActivityCallback.push(this.updateWorkingTime.bind(this)); + }, + }, +}); + +Object.defineProperties(LoggerStorage.prototype.log, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(logType: LogType, payload: any, wait: boolean) { + if (typeof payload !== 'object') { + throw new ArgumentError('Payload must be an object'); + } + + if (typeof wait !== 'boolean') { + throw new ArgumentError('Wait must be boolean'); + } + + if (logType in this.ignoreRules) { + const ignoreRule = this.ignoreRules[logType]; + const { lastLog } = ignoreRule; + if (lastLog && ignoreRule.ignore(lastLog, payload)) { + lastLog.payload = { + ...lastLog.payload, + ...payload, + }; + + this.updateWorkingTime(); + return ignoreRule.lastLog; + } + } + + const logPayload = { ...payload }; + logPayload.client_id = this.clientID; + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + const log = logFactory(logType, { ...logPayload }); + if (logType in this.ignoreRules) { + this.ignoreRules[logType].lastLog = log; + } + + const pushEvent = (): void => { + this.updateWorkingTime(); + log.validatePayload(); + log.onClose(null); + this.collection.push(log); }; - this.updateWorkingTime(); - return ignoreRule.lastLog; - } - } - - const logPayload = { ...payload }; - logPayload.client_id = this.clientID; - if (this.isActiveChecker) { - logPayload.is_active = this.isActiveChecker(); - } - - const log = logFactory(logType, { ...logPayload }); - if (logType in this.ignoreRules) { - this.ignoreRules[logType].lastLog = log; - } - - const pushEvent = () => { - this.updateWorkingTime(); - log.validatePayload(); - log.onClose(null); - this.collection.push(log); - }; - - if (log.type === LogType.sendException) { - serverProxy.server.exception(log.dump()).catch(() => { - pushEvent(); - }); - - return log; - } - - if (wait) { - log.onClose(pushEvent); - } else { - pushEvent(); - } - - return log; -}; - -LoggerStorage.prototype.save.implementation = async function () { - while (this.saving) { - await sleep(100); - } - - const collectionToSend = [...this.collection]; - const lastLog = this.collection[this.collection.length - 1]; - - const logPayload = {}; - logPayload.client_id = this.clientID; - logPayload.working_time = this.workingTime; - if (this.isActiveChecker) { - logPayload.is_active = this.isActiveChecker(); - } - - if (lastLog && lastLog.type === LogType.sendTaskInfo) { - logPayload.job_id = lastLog.payload.job_id; - logPayload.task_id = lastLog.payload.task_id; - } - - const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); - collectionToSend.push(userActivityLog); - - try { - this.saving = true; - await serverProxy.logs.save(collectionToSend.map((log) => log.dump())); - for (const rule of Object.values(this.ignoreRules)) { - rule.lastLog = null; - } - this.collection = []; - this.workingTime = 0; - this.lastLogTime = Date.now(); - } finally { - this.saving = false; - } -}; + if (log.type === LogType.sendException) { + serverProxy.server.exception(log.dump()).catch(() => { + pushEvent(); + }); + + return log; + } + + if (wait) { + log.onClose(pushEvent); + } else { + pushEvent(); + } + + return log; + }, + }, +}); + +Object.defineProperties(LoggerStorage.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + while (this.saving) { + await sleep(100); + } + + const collectionToSend = [...this.collection]; + const lastLog = this.collection[this.collection.length - 1]; + + const logPayload: any = { + client_id: this.clientID, + working_time: this.workingTime, + }; -module.exports = new LoggerStorage(); + if (this.isActiveChecker) { + logPayload.is_active = this.isActiveChecker(); + } + + if (lastLog && lastLog.type === LogType.sendTaskInfo) { + logPayload.job_id = lastLog.payload.job_id; + logPayload.task_id = lastLog.payload.task_id; + } + + const userActivityLog = logFactory(LogType.sendUserActivity, logPayload); + collectionToSend.push(userActivityLog); + + try { + this.saving = true; + await serverProxy.logs.save(collectionToSend.map((log) => log.dump())); + for (const rule of Object.values(this.ignoreRules)) { + rule.lastLog = null; + } + this.collection = []; + this.workingTime = 0; + this.lastLogTime = Date.now(); + } finally { + this.saving = false; + } + }, + }, +}); + +export default new LoggerStorage(); diff --git a/cvat-core/src/organization.ts b/cvat-core/src/organization.ts index a1765862c54e..84856bf096ed 100644 --- a/cvat-core/src/organization.ts +++ b/cvat-core/src/organization.ts @@ -3,34 +3,54 @@ // // SPDX-License-Identifier: MIT -const { checkObjectType, isEnum } = require('./common'); -const config = require('./config'); -const { MembershipRole } = require('./enums'); -const { ArgumentError, ServerError } = require('./exceptions'); -const PluginRegistry = require('./plugins').default; -const serverProxy = require('./server-proxy').default; -const User = require('./user').default; - -/** - * Class representing an organization - * @memberof module:API.cvat.classes - */ -class Organization { - /** - * @param {object} initialData - Object which is used for initialization - *
It must contains keys: - *
  • slug - - *
    It can contains keys: - *
  • name - *
  • description - *
  • owner - *
  • created_date - *
  • updated_date - *
  • contact - */ - constructor(initialData) { - const data = { +import { checkObjectType, isEnum } from './common'; +import config from './config'; +import { MembershipRole } from './enums'; +import { ArgumentError, ServerError } from './exceptions'; +import PluginRegistry from './plugins'; +import serverProxy from './server-proxy'; +import User from './user'; + +interface RawOrganizationData { + id?: number, + slug?: string, + name?: string, + description?: string, + created_date?: string, + updated_date?: string, + owner?: any, + contact?: OrganizationContact, +} + +interface OrganizationContact { + email?: string; + location?: string; + phoneNumber?: string +} + +interface Membership { + user: User; + is_active: boolean; + joined_date: string; + role: MembershipRole; + invitation: { + created_date: string; + owner: User; + } | null; +} + +export default class Organization { + public readonly id: number; + public readonly slug: string; + public readonly createdDate: string; + public readonly updatedDate: string; + public readonly owner: User; + public contact: OrganizationContact; + public name: string; + public description: string; + + constructor(initialData: RawOrganizationData) { + const data: RawOrganizationData = { id: undefined, slug: undefined, name: undefined, @@ -66,7 +86,9 @@ class Organization { checkObjectType('contact', data.contact, 'object'); for (const prop in data.contact) { if (typeof data.contact[prop] !== 'string') { - throw ArgumentError(`Contact fields must be strings, tried to set ${typeof data.contact[prop]}`); + throw new ArgumentError( + `Contact fields must be strings,tried to set ${typeof data.contact[prop]}`, + ); } } } @@ -86,7 +108,7 @@ class Organization { get: () => data.name, set: (name) => { if (typeof name !== 'string') { - throw ArgumentError(`Name property must be a string, tried to set ${typeof description}`); + throw new ArgumentError(`Name property must be a string, tried to set ${typeof name}`); } data.name = name; }, @@ -95,7 +117,7 @@ class Organization { get: () => data.description, set: (description) => { if (typeof description !== 'string') { - throw ArgumentError( + throw new ArgumentError( `Description property must be a string, tried to set ${typeof description}`, ); } @@ -106,11 +128,13 @@ class Organization { get: () => ({ ...data.contact }), set: (contact) => { if (typeof contact !== 'object') { - throw ArgumentError(`Contact property must be an object, tried to set ${typeof contact}`); + throw new ArgumentError(`Contact property must be an object, tried to set ${typeof contact}`); } for (const prop in contact) { if (typeof contact[prop] !== 'string') { - throw ArgumentError(`Contact fields must be strings, tried to set ${typeof contact[prop]}`); + throw new ArgumentError( + `Contact fields must be strings, tried to set ${typeof contact[prop]}`, + ); } } data.contact = { ...contact }; @@ -128,37 +152,14 @@ class Organization { }); } - /** - * Method updates organization data if it was created before, or creates a new organization - * @method save - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async save() { + // Method updates organization data if it was created before, or creates a new organization + public async save(): Promise { const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.save); return result; } - /** - * Method returns paginatable list of organization members - * @method save - * @returns {module:API.cvat.classes.Organization} - * @param page page number - * @param page_size number of results per page - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async members(page = 1, page_size = 10) { + // Method returns paginatable list of organization members + public async members(page = 1, page_size = 10): Promise { const result = await PluginRegistry.apiWrapper.call( this, Organization.prototype.members, @@ -169,75 +170,27 @@ class Organization { return result; } - /** - * Method removes the organization - * @method remove - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async remove() { + // Method removes the organization + public async remove(): Promise { const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.remove); return result; } - /** - * Method invites new members by email - * @method invite - * @returns {module:API.cvat.classes.Organization} - * @param {string} email - * @param {string} role - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async invite(email, role) { + // Method invites new members by email + public async invite(email: string, role: MembershipRole): Promise { const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.invite, email, role); return result; } - /** - * Method allows a user to get out from an organization - * The difference between deleteMembership is that membershipId is unknown in this case - * @method leave - * @returns {module:API.cvat.classes.Organization} - * @memberof module:API.cvat.classes.Organization - * @param {module:API.cvat.classes.User} user - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async leave(user) { + // Method allows a user to get out from an organization + // The difference between deleteMembership is that membershipId is unknown in this case + public async leave(user: User): Promise { const result = await PluginRegistry.apiWrapper.call(this, Organization.prototype.leave, user); return result; } - /** - * Method allows to change a membership role - * @method updateMembership - * @returns {module:API.cvat.classes.Organization} - * @param {number} membershipId - * @param {string} role - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async updateMembership(membershipId, role) { + // Method allows to change a membership role + public async updateMembership(membershipId: number, role: MembershipRole): Promise { const result = await PluginRegistry.apiWrapper.call( this, Organization.prototype.updateMembership, @@ -247,20 +200,8 @@ class Organization { return result; } - /** - * Method allows to kick a user from an organization - * @method deleteMembership - * @returns {module:API.cvat.classes.Organization} - * @param {number} membershipId - * @memberof module:API.cvat.classes.Organization - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async deleteMembership(membershipId) { + // Method allows to kick a user from an organization + public async deleteMembership(membershipId: number): Promise { const result = await PluginRegistry.apiWrapper.call( this, Organization.prototype.deleteMembership, @@ -270,110 +211,152 @@ class Organization { } } -Organization.prototype.save.implementation = async function () { - if (typeof this.id === 'number') { - const organizationData = { - name: this.name || this.slug, - description: this.description, - contact: this.contact, - }; - - const result = await serverProxy.organizations.update(this.id, organizationData); - return new Organization(result); - } - - const organizationData = { - slug: this.slug, - name: this.name || this.slug, - description: this.description, - contact: this.contact, - }; - - const result = await serverProxy.organizations.create(organizationData); - return new Organization(result); -}; - -Organization.prototype.members.implementation = async function (orgSlug, page, pageSize) { - checkObjectType('orgSlug', orgSlug, 'string'); - checkObjectType('page', page, 'number'); - checkObjectType('pageSize', pageSize, 'number'); - - const result = await serverProxy.organizations.members(orgSlug, page, pageSize); - await Promise.all( - result.results.map((membership) => { - const { invitation } = membership; - membership.user = new User(membership.user); - if (invitation) { - return serverProxy.organizations - .invitation(invitation) - .then((invitationData) => { - membership.invitation = invitationData; - }) - .catch(() => { - membership.invitation = null; - }); +Object.defineProperties(Organization.prototype.save, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + const organizationData = { + name: this.name || this.slug, + description: this.description, + contact: this.contact, + }; + + const result = await serverProxy.organizations.update(this.id, organizationData); + return new Organization(result); } - return Promise.resolve(); - }), - ); - - result.results.count = result.count; - return result.results; -}; - -Organization.prototype.remove.implementation = async function () { - if (typeof this.id === 'number') { - await serverProxy.organizations.delete(this.id); - config.organizationID = null; - } -}; - -Organization.prototype.invite.implementation = async function (email, role) { - checkObjectType('email', email, 'string'); - if (!isEnum.bind(MembershipRole)(role)) { - throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); - } - - if (typeof this.id === 'number') { - await serverProxy.organizations.invite(this.id, { email, role }); - } -}; - -Organization.prototype.updateMembership.implementation = async function (membershipId, role) { - checkObjectType('membershipId', membershipId, 'number'); - if (!isEnum.bind(MembershipRole)(role)) { - throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); - } + const organizationData = { + slug: this.slug, + name: this.name || this.slug, + description: this.description, + contact: this.contact, + }; + + const result = await serverProxy.organizations.create(organizationData); + return new Organization(result); + }, + }, +}); + +Object.defineProperties(Organization.prototype.members, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(orgSlug: string, page: number, pageSize: number) { + checkObjectType('orgSlug', orgSlug, 'string'); + checkObjectType('page', page, 'number'); + checkObjectType('pageSize', pageSize, 'number'); + + const result = await serverProxy.organizations.members(orgSlug, page, pageSize); + await Promise.all( + result.results.map((membership) => { + const { invitation } = membership; + membership.user = new User(membership.user); + if (invitation) { + return serverProxy.organizations + .invitation(invitation) + .then((invitationData) => { + membership.invitation = invitationData; + }) + .catch(() => { + membership.invitation = null; + }); + } - if (typeof this.id === 'number') { - await serverProxy.organizations.updateMembership(membershipId, { role }); - } -}; + return Promise.resolve(); + }), + ); + + result.results.count = result.count; + return result.results; + }, + }, +}); + +Object.defineProperties(Organization.prototype.remove, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation() { + if (typeof this.id === 'number') { + await serverProxy.organizations.delete(this.id); + config.organizationID = null; + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.invite, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(email: string, role: MembershipRole) { + checkObjectType('email', email, 'string'); + if (!isEnum.bind(MembershipRole)(role)) { + throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); + } -Organization.prototype.deleteMembership.implementation = async function (membershipId) { - checkObjectType('membershipId', membershipId, 'number'); - if (typeof this.id === 'number') { - await serverProxy.organizations.deleteMembership(membershipId); - } -}; - -Organization.prototype.leave.implementation = async function (user) { - checkObjectType('user', user, null, User); - if (typeof this.id === 'number') { - const result = await serverProxy.organizations.members(this.slug, 1, 10, { - filter: JSON.stringify({ - and: [{ - '==': [{ var: 'user' }, user.id], - }], - }), - }); - const [membership] = result.results; - if (!membership) { - throw new ServerError(`Could not find membership for user ${user.username} in organization ${this.slug}`); - } - await serverProxy.organizations.deleteMembership(membership.id); - } -}; + if (typeof this.id === 'number') { + await serverProxy.organizations.invite(this.id, { email, role }); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.updateMembership, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(membershipId: number, role: MembershipRole) { + checkObjectType('membershipId', membershipId, 'number'); + if (!isEnum.bind(MembershipRole)(role)) { + throw new ArgumentError(`Role must be one of: ${Object.values(MembershipRole).toString()}`); + } -module.exports = Organization; + if (typeof this.id === 'number') { + await serverProxy.organizations.updateMembership(membershipId, { role }); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.deleteMembership, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(membershipId: number) { + checkObjectType('membershipId', membershipId, 'number'); + if (typeof this.id === 'number') { + await serverProxy.organizations.deleteMembership(membershipId); + } + }, + }, +}); + +Object.defineProperties(Organization.prototype.leave, { + implementation: { + writable: false, + enumerable: false, + value: async function implementation(user: User) { + checkObjectType('user', user, null, User); + if (typeof this.id === 'number') { + const result = await serverProxy.organizations.members(this.slug, 1, 10, { + filter: JSON.stringify({ + and: [{ + '==': [{ var: 'user' }, user.id], + }], + }), + }); + const [membership] = result.results; + if (!membership) { + throw new ServerError( + `Could not find membership for user ${user.username} in organization ${this.slug}`, + ); + } + await serverProxy.organizations.deleteMembership(membership.id); + } + }, + }, +}); diff --git a/cvat-core/src/session.ts b/cvat-core/src/session.ts index 377f9797c198..262fd8476b11 100644 --- a/cvat-core/src/session.ts +++ b/cvat-core/src/session.ts @@ -7,7 +7,7 @@ import { StorageLocation } from './enums'; import { Storage } from './storage'; const PluginRegistry = require('./plugins').default; -const loggerStorage = require('./logger-storage'); +const loggerStorage = require('./logger-storage').default; const serverProxy = require('./server-proxy').default; const { getFrame, @@ -27,7 +27,7 @@ const { } = require('./enums'); const { Label } = require('./labels'); const User = require('./user').default; -const Issue = require('./issue'); +const Issue = require('./issue').default; const { FieldUpdateTrigger, checkObjectType } = require('./common'); function buildDuplicatedAPI(prototype) { diff --git a/cvat-core/tests/api/cloud-storages.js b/cvat-core/tests/api/cloud-storages.js index a53d34d8d93b..70a7de37a1e7 100644 --- a/cvat-core/tests/api/cloud-storages.js +++ b/cvat-core/tests/api/cloud-storages.js @@ -14,7 +14,7 @@ jest.mock('../../src/server-proxy', () => { // Initialize api window.cvat = require('../../src/api'); -const { CloudStorage } = require('../../src/cloud-storage'); +const CloudStorage= require('../../src/cloud-storage').default; const { cloudStoragesDummyData } = require('../mocks/dummy-data.mock'); describe('Feature: get cloud storages', () => { diff --git a/cvat-ui/index.d.ts b/cvat-ui/index.d.ts index 93c43b1d509e..5a18ea07ae22 100644 --- a/cvat-ui/index.d.ts +++ b/cvat-ui/index.d.ts @@ -1,6 +1,8 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT +import 'redux-thunk/extend-redux'; declare module '*.svg'; declare module 'cvat-core/src/api'; diff --git a/cvat-ui/package.json b/cvat-ui/package.json index c84240b5ba4e..3182729a1686 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -35,6 +35,9 @@ "@types/resize-observer-browser": "^0.1.6", "antd": "~4.18.9", "copy-to-clipboard": "^3.3.1", + "cvat-canvas": "link:./../cvat-canvas", + "cvat-canvas3d": "link:./../cvat-canvas3d", + "cvat-core": "link:./../cvat-core", "dotenv-webpack": "^7.1.0", "error-stack-parser": "^2.0.6", "lodash": "^4.17.21", diff --git a/cvat-ui/src/components/webhooks-page/webhooks-page.tsx b/cvat-ui/src/components/webhooks-page/webhooks-page.tsx index 058ac52b6b97..98129558ef6a 100644 --- a/cvat-ui/src/components/webhooks-page/webhooks-page.tsx +++ b/cvat-ui/src/components/webhooks-page/webhooks-page.tsx @@ -64,7 +64,7 @@ function WebhooksPage(): JSX.Element | null { } useEffect(() => { - if (projectsMatch) { + if (projectsMatch && projectsMatch.params.id) { const { id } = projectsMatch.params; setOnCreateParams(`projectId=${id}`); dispatch(getWebhooksAsync({ ...updatedQuery, projectId: +id })); diff --git a/cvat-ui/src/cvat-canvas3d-wrapper.ts b/cvat-ui/src/cvat-canvas3d-wrapper.ts index 457cd7730b2b..a6d95279549a 100644 --- a/cvat-ui/src/cvat-canvas3d-wrapper.ts +++ b/cvat-ui/src/cvat-canvas3d-wrapper.ts @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -13,5 +14,7 @@ import { } from 'cvat-canvas3d/src/typescript/canvas3d'; export { - Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CameraAction, ViewsDOM, CanvasMode, + Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CameraAction, CanvasMode, }; + +export type { ViewsDOM }; diff --git a/cvat-ui/src/cvat-core-wrapper.ts b/cvat-ui/src/cvat-core-wrapper.ts index b136123ed67b..aa2120420a35 100644 --- a/cvat-ui/src/cvat-core-wrapper.ts +++ b/cvat-ui/src/cvat-core-wrapper.ts @@ -4,7 +4,7 @@ import _cvat from 'cvat-core/src/api'; import ObjectState from 'cvat-core/src/object-state'; -import Webhook from 'cvat-core/src/webhook'; +import Webhook from 'cvat-core/src/webhook'; import { Label, Attribute, RawAttribute, RawLabel, } from 'cvat-core/src/labels'; diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 29af97ae5565..acbd52454b75 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -3,7 +3,6 @@ // // SPDX-License-Identifier: MIT -// eslint-disable-next-line import/no-extraneous-dependencies import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d'; import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; import { Webhook } from 'cvat-core-wrapper'; diff --git a/yarn.lock b/yarn.lock index c26c2c313ec9..8c5932efacf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4017,7 +4017,42 @@ custom-error-instance@2.1.1: resolved "https://registry.yarnpkg.com/custom-error-instance/-/custom-error-instance-2.1.1.tgz#3cf6391487a6629a6247eb0ca0ce00081b7e361a" integrity sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg== -"cvat-data@file:cvat-data": +"cvat-canvas3d@link:./cvat-canvas3d": + version "0.0.1" + dependencies: + "@types/three" "^0.125.3" + camera-controls "^1.25.3" + three "^0.126.1" + +"cvat-canvas@link:./cvat-canvas": + version "2.15.4" + dependencies: + "@types/polylabel" "^1.0.5" + polylabel "^1.1.0" + svg.draggable.js "2.2.2" + svg.draw.js "^2.0.4" + svg.js "2.7.1" + svg.resize.js "1.4.3" + svg.select.js "3.0.1" + +"cvat-core@link:./cvat-core": + version "7.0.0" + dependencies: + axios "^0.27.2" + browser-or-node "^2.0.0" + cvat-data "link:./cvat-data" + detect-browser "^5.2.1" + error-stack-parser "^2.0.2" + form-data "^4.0.0" + jest-config "^28.1.2" + js-cookie "^3.0.1" + json-logic-js "^2.0.1" + platform "^1.3.5" + quickhull "^1.0.3" + store "^2.0.12" + tus-js-client "^2.3.0" + +"cvat-data@link:./cvat-data": version "1.0.2" dependencies: async-mutex "^0.3.2"