You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
7.8 KiB
TypeScript
247 lines
7.8 KiB
TypeScript
// Copyright (C) 2020-2022 Intel Corporation
|
|
// Copyright (C) 2022 CVAT.ai Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
comments: [],
|
|
frame: undefined,
|
|
created_date: undefined,
|
|
owner: undefined,
|
|
resolved: undefined,
|
|
};
|
|
|
|
for (const property in data) {
|
|
if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
|
|
data[property] = initialData[property];
|
|
}
|
|
}
|
|
|
|
if (data.owner && !(data.owner instanceof User)) data.owner = new User(data.owner);
|
|
|
|
if (data.comments) {
|
|
data.comments = data.comments.map((comment) => new Comment(comment));
|
|
}
|
|
|
|
if (typeof data.created_date === 'undefined') {
|
|
data.created_date = new Date().toISOString();
|
|
}
|
|
|
|
Object.defineProperties(
|
|
this,
|
|
Object.freeze({
|
|
id: {
|
|
get: () => data.id,
|
|
},
|
|
position: {
|
|
get: () => data.position,
|
|
set: (value) => {
|
|
if (Array.isArray(value) || value.some((coord) => typeof coord !== 'number')) {
|
|
throw new ArgumentError(`Array of numbers is expected. Got ${value}`);
|
|
}
|
|
data.position = value;
|
|
},
|
|
},
|
|
job: {
|
|
get: () => data.job,
|
|
},
|
|
comments: {
|
|
get: () => [...data.comments],
|
|
},
|
|
frame: {
|
|
get: () => data.frame,
|
|
},
|
|
createdDate: {
|
|
get: () => data.created_date,
|
|
},
|
|
owner: {
|
|
get: () => data.owner,
|
|
},
|
|
resolved: {
|
|
get: () => data.resolved,
|
|
},
|
|
__internal: {
|
|
get: () => data,
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
|
|
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 });
|
|
return acc;
|
|
}, []);
|
|
|
|
return quickhull(points)
|
|
.map((point) => [point.x, point.y])
|
|
.flat();
|
|
}
|
|
|
|
return coordinates;
|
|
}
|
|
|
|
// 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
|
|
public async resolve(user: User): Promise<void> {
|
|
const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.resolve, user);
|
|
return result;
|
|
}
|
|
|
|
// 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
|
|
public async delete(): Promise<void> {
|
|
await PluginRegistry.apiWrapper.call(this, Issue.prototype.delete);
|
|
}
|
|
|
|
public serialize(): RawIssueData {
|
|
const { comments } = this;
|
|
const data: RawIssueData = {
|
|
position: this.position,
|
|
frame: this.frame,
|
|
comments: comments.map((comment) => comment.serialize()),
|
|
};
|
|
|
|
if (typeof this.id === 'number') {
|
|
data.id = this.id;
|
|
}
|
|
if (typeof this.job === 'number') {
|
|
data.job = this.job;
|
|
}
|
|
if (typeof this.createdDate === 'string') {
|
|
data.created_date = this.createdDate;
|
|
}
|
|
if (typeof this.resolved === 'boolean') {
|
|
data.resolved = this.resolved;
|
|
}
|
|
if (this.owner instanceof User) {
|
|
data.owner = this.owner.serialize().id;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
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}"`);
|
|
}
|
|
|
|
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}"`);
|
|
}
|
|
|
|
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);
|
|
}
|
|
},
|
|
},
|
|
});
|