Revision 0535d452dd426ba62d6d312766906ddd1230c7eb authored by Andrey Zhavoronkov on 02 November 2023, 15:59:39 UTC, committed by GitHub on 02 November 2023, 15:59:39 UTC
This PR speeds up the preparation of chunks by: 1. loading images once instead of twice in each writer, 2. as well as by allowing simultaneous preparation of more than 1 chunk using multithreading. This allows to reduce the time for preparation of chunks for 4895 images from 0:04:36 to 0:01:20 in case of preparation of 3 chunks in parallel and 0:02:46 in case of 1 chunk in my environment. Co-authored-by: Maria Khrustaleva <maya17grd@gmail.com>
1 parent 1f8d5d3
organization.ts
// Copyright (C) 2021-2022 Intel Corporation
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import { SerializedOrganization, SerializedOrganizationContact, SerializedUser } from './server-response-types';
import { checkObjectType, isEnum } from './common';
import config from './config';
import { MembershipRole } from './enums';
import { ArgumentError, DataError } from './exceptions';
import PluginRegistry from './plugins';
import serverProxy from './server-proxy';
import User from './user';
interface SerializedInvitationData {
created_date: string;
key: string;
owner: SerializedUser;
}
interface SerializedMembershipData {
id: number;
user: SerializedUser;
is_active: boolean;
joined_date: string;
role: MembershipRole;
invitation: SerializedInvitationData | null;
}
export class Invitation {
#createdDate: string;
#owner: User | null;
#key: string;
constructor(initialData: SerializedInvitationData) {
this.#createdDate = initialData.created_date;
this.#owner = initialData.owner ? new User(initialData.owner) : null;
this.#key = initialData.key;
}
get owner(): User | null {
return this.#owner;
}
get createdDate(): string {
return this.#createdDate;
}
get key(): string {
return this.#key;
}
}
export class Membership {
#id: number;
#user: User;
#isActive: boolean;
#joinedDate: string;
#role: MembershipRole;
#invitation: Invitation | null;
constructor(initialData: SerializedMembershipData) {
this.#id = initialData.id;
this.#user = new User(initialData.user);
this.#isActive = initialData.is_active;
this.#joinedDate = initialData.joined_date;
this.#role = initialData.role;
this.#invitation = initialData.invitation ? new Invitation(initialData.invitation) : null;
}
get id(): number {
return this.#id;
}
get user(): User {
return this.#user;
}
get isActive(): boolean {
return this.#isActive;
}
get joinedDate(): string {
return this.#joinedDate;
}
get role(): MembershipRole {
return this.#role;
}
get invitation(): Invitation {
return this.#invitation;
}
}
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: SerializedOrganizationContact;
public name: string;
public description: string;
constructor(initialData: SerializedOrganization) {
const data: SerializedOrganization = {
id: undefined,
slug: undefined,
name: undefined,
description: undefined,
created_date: undefined,
updated_date: undefined,
owner: undefined,
contact: undefined,
};
for (const prop of Object.keys(data)) {
if (prop in initialData) {
data[prop] = initialData[prop];
}
}
if (data.owner) data.owner = new User(data.owner);
checkObjectType('slug', data.slug, 'string');
if (typeof data.name !== 'undefined') {
checkObjectType('name', data.name, 'string');
}
if (typeof data.description !== 'undefined') {
checkObjectType('description', data.description, 'string');
}
if (typeof data.id !== 'undefined') {
checkObjectType('id', data.id, 'number');
}
if (typeof data.contact !== 'undefined') {
checkObjectType('contact', data.contact, 'object');
for (const prop in data.contact) {
if (typeof data.contact[prop] !== 'string') {
throw new ArgumentError(
`Contact fields must be strings,tried to set ${typeof data.contact[prop]}`,
);
}
}
}
if (typeof data.owner !== 'undefined' && data.owner !== null) {
checkObjectType('owner', data.owner, null, User);
}
Object.defineProperties(this, {
id: {
get: () => data.id,
},
slug: {
get: () => data.slug,
},
name: {
get: () => data.name,
set: (name) => {
if (typeof name !== 'string') {
throw new ArgumentError(`Name property must be a string, tried to set ${typeof name}`);
}
data.name = name;
},
},
description: {
get: () => data.description,
set: (description) => {
if (typeof description !== 'string') {
throw new ArgumentError(
`Description property must be a string, tried to set ${typeof description}`,
);
}
data.description = description;
},
},
contact: {
get: () => ({ ...data.contact }),
set: (contact) => {
if (typeof contact !== 'object') {
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 new ArgumentError(
`Contact fields must be strings, tried to set ${typeof contact[prop]}`,
);
}
}
data.contact = { ...contact };
},
},
owner: {
get: () => data.owner,
},
createdDate: {
get: () => data.created_date,
},
updatedDate: {
get: () => data.updated_date,
},
});
}
// 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
public async members(page = 1, page_size = 10): Promise<Membership[]> {
const result = await PluginRegistry.apiWrapper.call(
this,
Organization.prototype.members,
this.slug,
page,
page_size,
);
return result;
}
// 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
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
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
public async updateMembership(membershipId: number, role: MembershipRole): Promise<void> {
const result = await PluginRegistry.apiWrapper.call(
this,
Organization.prototype.updateMembership,
membershipId,
role,
);
return result;
}
// 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,
membershipId,
);
return result;
}
public async resendInvitation(key: string): Promise<void> {
const result = await PluginRegistry.apiWrapper.call(
this,
Organization.prototype.resendInvitation,
key,
);
return result;
}
}
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);
}
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);
const memberships = await Promise.all(result.results.map(async (rawMembership) => {
const { invitation } = rawMembership;
let rawInvitation = null;
if (invitation) {
try {
rawInvitation = await serverProxy.organizations.invitation(invitation);
// eslint-disable-next-line no-empty
} catch (e) {}
}
return new Membership({
...rawMembership,
invitation: rawInvitation,
});
}));
memberships.count = result.count;
return memberships;
},
},
});
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.organization = {
organizationID: null,
organizationSlug: 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()}`);
}
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()}`);
}
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.username],
}],
}),
});
const [membership] = result.results;
if (!membership) {
throw new DataError(
`Could not find membership for user ${user.username} in organization ${this.slug}`,
);
}
await serverProxy.organizations.deleteMembership(membership.id);
}
},
},
});
Object.defineProperties(Organization.prototype.resendInvitation, {
implementation: {
writable: false,
enumerable: false,
value: async function implementation(key: string) {
checkObjectType('key', key, 'string');
if (typeof this.id === 'number') {
await serverProxy.organizations.resendInvitation(key);
}
},
},
});
Computing file changes ...