未验证 提交 f719f58d 编写于 作者: K Kirill Lakhov 提交者: GitHub

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: NBoris Sekachev <boris.sekachev@yandex.ru>
上级 e7c16064
......@@ -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');
......
此差异已折叠。
......@@ -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);
}
},
},
});
// 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;
// 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', () => {
......
// 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 }));
......
// 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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册