Fix ESlint problems / typescript for cvat-core (#5027)

* typescripted some files

* typescripted cloud storage

* reverted api-implementation, fixed minor errors

* updated changelog

* fixed jest test

* updated file: to link:

Co-authored-by: Boris Sekachev <boris.sekachev@yandex.ru>
main
Kirill Lakhov 3 years ago committed by GitHub
parent e7c16064c4
commit f719f58df4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -33,6 +33,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428)
- IOG and f-BRS serverless function (<https://github.com/opencv/cvat/pulls>)
- Invisible label item in label constructor when label color background is white,
or close to it (<https://github.com/opencv/cvat/pull/5041>)
- Fixed cvat-core ESlint problems (<https://github.com/opencv/cvat/pull/5027>)
### Security
- TDB

@ -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",

@ -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) {

@ -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');

@ -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<CloudStorage> {
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<void> {
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<any> {
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<string | ArrayBuffer> {
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<CloudStorageStatus> {
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<CloudStorage> {
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<void> {
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<any> {
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<string | ArrayBuffer> {
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<CloudStorageStatus> {
const result = await serverProxy.cloudStorages.getStatus(this.id);
return result;
},
},
});

@ -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;

@ -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

@ -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}

@ -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<void> {
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<void> {
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<void> {
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<void> {
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);
}
},
},
});

@ -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 <br>
* Note then you can call close() multiple times <br>
* Log duration will be computed based on the latest call <br>
* 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<void> {
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;

@ -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<void> {
});
}
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<Log>;
public ignoreRules: Record<LogType.zoomImage | LogType.changeAttribute, IgnoreRule>;
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<void> {
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<Log> {
const result = await PluginRegistry.apiWrapper.call(this, LoggerStorage.prototype.log, logType, payload, wait);
return result;
}
async save() {
public async save(): Promise<void> {
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<any>) {
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<IgnoreRule>(this.ignoreRules)) {
rule.lastLog = null;
}
this.collection = [];
this.workingTime = 0;
this.lastLogTime = Date.now();
} finally {
this.saving = false;
}
},
},
});
export default new LoggerStorage();

@ -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
* <br> It must contains keys:
* <br> <li style="margin-left: 10px;"> slug
* <br> It can contains keys:
* <br> <li style="margin-left: 10px;"> name
* <br> <li style="margin-left: 10px;"> description
* <br> <li style="margin-left: 10px;"> owner
* <br> <li style="margin-left: 10px;"> created_date
* <br> <li style="margin-left: 10px;"> updated_date
* <br> <li style="margin-left: 10px;"> 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<Organization> {
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<Membership[]> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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);
}
},
},
});

@ -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) {

@ -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', () => {

@ -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';

@ -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",

@ -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 }));

@ -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 };

@ -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';

@ -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';

@ -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"

Loading…
Cancel
Save