From dc948b6bea9f849cb0ed5c5bb3b229799e74dfb8 Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Mon, 8 Jul 2024 08:19:44 +0200 Subject: [PATCH 1/7] feat: report archives api (#405) --- src/reports/index.ts | 168 ++++++++++++++++++++++++++++++- tests/reports/api.test.ts | 201 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 1 deletion(-) diff --git a/src/reports/index.ts b/src/reports/index.ts index a89a5d8a9..aa7758e78 100644 --- a/src/reports/index.ts +++ b/src/reports/index.ts @@ -15,8 +15,152 @@ import { * You can then export reports in .xlsx or .csv file formats. * Report generation is an asynchronous operation and shall be completed with a sequence of API methods. */ -//TODO add missing endpoints (https://github.com/crowdin/crowdin-api-client-js/issues/391) export class Reports extends CrowdinApi { + /** + * @param options optional parameters for the request + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.getMany + */ + listOrganizationReportArchives( + options?: ReportsModel.ListReportArchiveParams, + ): Promise> { + let url = `${this.url}/reports/archives`; + url = this.addQueryParam(url, 'scopeId', options?.scopeId); + url = this.addQueryParam(url, 'scopeType', options?.scopeType); + return this.getList(url, options?.limit, options?.offset); + } + + /** + * @param archiveId archive identifier + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.get + */ + getOrganizationReportArchive(archiveId: number): Promise> { + const url = `${this.url}/reports/archives/${archiveId}`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param archiveId archive identifier + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.delete + */ + deleteOrganizationReportArchive(archiveId: number): Promise { + const url = `${this.url}/reports/archives/${archiveId}`; + return this.delete(url, this.defaultConfig()); + } + + /** + * @param archiveId archive identifier + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.exports.post + */ + exportOrganizationReportArchive( + archiveId: number, + request: { format?: ReportsModel.Format } = {}, + ): Promise>> { + const url = `${this.url}/reports/archives/${archiveId}/exports`; + return this.post(url, request, this.defaultConfig()); + } + + /** + * @param archiveId archive identifier + * @param exportId export identifier + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.exports.get + */ + checkOrganizationReportArchiveStatus( + archiveId: number, + exportId: string, + ): Promise>> { + const url = `${this.url}/reports/archives/${archiveId}/exports/${exportId}`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param archiveId archive identifier + * @param exportId export identifier + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.reports.archives.exports.download.get + */ + downloadOrganizationReportArchive(archiveId: number, exportId: string): Promise> { + const url = `${this.url}/reports/archives/${archiveId}/exports/${exportId}/download`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param options optional parameters for the request + * @see https://developer.crowdin.com/api/v2/#operation/api.reports.archives.getMany + */ + listUserReportArchives( + userId: number, + options?: ReportsModel.ListReportArchiveParams, + ): Promise> { + let url = `${this.url}/users/${userId}/reports/archives`; + url = this.addQueryParam(url, 'scopeId', options?.scopeId); + url = this.addQueryParam(url, 'scopeType', options?.scopeType); + return this.getList(url, options?.limit, options?.offset); + } + + /** + * @param userId user identifier + * @param archiveId archive identifier + * @see https://developer.crowdin.com/api/v2/#operation/api.users.reports.archives.get + */ + getUserReportArchive(userId: number, archiveId: number): Promise> { + const url = `${this.url}/users/${userId}/reports/archives/${archiveId}`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param archiveId archive identifier + * @see https://developer.crowdin.com/api/v2/#operation/api.users.reports.archives.delete + */ + deleteUserReportArchive(userId: number, archiveId: number): Promise { + const url = `${this.url}/users/${userId}/reports/archives/${archiveId}`; + return this.delete(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param archiveId archive identifier + * @see https://developer.crowdin.com/api/v2/#operation/api.reports.archives.exports.post + */ + exportUserReportArchive( + userId: number, + archiveId: number, + request: { format?: ReportsModel.Format } = {}, + ): Promise>> { + const url = `${this.url}/users/${userId}/reports/archives/${archiveId}/exports`; + return this.post(url, request, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param archiveId archive identifier + * @param exportId export identifier + * @see https://developer.crowdin.com/api/v2/#operation/api.users.reports.archives.exports.get + */ + checkUserReportArchiveStatus( + userId: number, + archiveId: number, + exportId: string, + ): Promise>> { + const url = `${this.url}/users/${userId}/reports/archives/${archiveId}/exports/${exportId}`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param archiveId archive identifier + * @param exportId export identifier + * @see https://developer.crowdin.com/api/v2/#operation/api.users.reports.archives.exports.download.get + */ + downloadUserReportArchive( + userId: number, + archiveId: number, + exportId: string, + ): Promise> { + const url = `${this.url}/users/${userId}/reports/archives/${archiveId}/exports/${exportId}/download`; + return this.get(url, this.defaultConfig()); + } + /** * @param groupId group identifier * @param request request body @@ -186,6 +330,28 @@ export class Reports extends CrowdinApi { } export namespace ReportsModel { + export interface ReportArchive { + id: number; + scopeType: number; + scopeId: number; + userId: number; + name: string; + webUrl: string; + scheme: any; + createdAt: string; + } + + export interface ListReportArchiveParams extends PaginationOptions { + scopeType: string; + scopeId: number; + } + + export interface ReportArchiveStatusAttribute { + format: Format; + reportName: string; + schema: any; + } + export type GroupReportSchema = GroupTranslationCostsPostEditingSchema | GroupTopMembersSchema; export type OrganizationReportSchema = GroupTranslationCostsPostEditingSchema | GroupTopMembersSchema; diff --git a/tests/reports/api.test.ts b/tests/reports/api.test.ts index d75f2ff41..023106d85 100644 --- a/tests/reports/api.test.ts +++ b/tests/reports/api.test.ts @@ -31,9 +31,148 @@ describe('Reports API', () => { netRateSchemes: [], }; const isPublic = false; + const userId = 123; + const archiveId = 456; + const exportId = 'qweds12'; beforeAll(() => { scope = nock(api.url) + .get('/reports/archives', undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: archiveId, + }, + }, + ], + pagination: { + offset: 0, + limit: 1, + }, + }) + .get(`/reports/archives/${archiveId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + id: archiveId, + }, + }) + .delete(`/reports/archives/${archiveId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200) + .post( + `/reports/archives/${archiveId}/exports`, + {}, + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + identifier: exportId, + }, + }) + .get(`/reports/archives/${archiveId}/exports/${exportId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + identifier: exportId, + }, + }) + .get(`/reports/archives/${archiveId}/exports/${exportId}/download`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: downloadLink, + }, + }) + .get(`/users/${userId}/reports/archives`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: archiveId, + }, + }, + ], + pagination: { + offset: 0, + limit: 1, + }, + }) + .get(`/users/${userId}/reports/archives/${archiveId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + id: archiveId, + }, + }) + .delete(`/users/${userId}/reports/archives/${archiveId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200) + .post( + `/users/${userId}/reports/archives/${archiveId}/exports`, + {}, + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + identifier: exportId, + }, + }) + .get(`/users/${userId}/reports/archives/${archiveId}/exports/${exportId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + identifier: exportId, + }, + }) + .get(`/users/${userId}/reports/archives/${archiveId}/exports/${exportId}/download`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + url: downloadLink, + }, + }) .post( `/groups/${groupId}/reports`, { @@ -225,6 +364,68 @@ describe('Reports API', () => { scope.done(); }); + it('List Organization Report Archives', async () => { + const archives = await api.listOrganizationReportArchives(); + expect(archives.data.length).toBe(1); + expect(archives.data[0].data.id).toBe(archiveId); + expect(archives.pagination.limit).toBe(1); + }); + + it('Get Organization Report Archive', async () => { + const archive = await api.getOrganizationReportArchive(archiveId); + expect(archive.data.id).toBe(archiveId); + }); + + it('Delete Organization Report Archive', async () => { + await api.deleteOrganizationReportArchive(archiveId); + }); + + it('Export Organization Report Archive', async () => { + const status = await api.exportOrganizationReportArchive(archiveId); + expect(status.data.identifier).toBe(exportId); + }); + + it('Check Organization Report Archive Export Status', async () => { + const status = await api.checkOrganizationReportArchiveStatus(archiveId, exportId); + expect(status.data.identifier).toBe(exportId); + }); + + it('Download Organization Report Archive', async () => { + const link = await api.downloadOrganizationReportArchive(archiveId, exportId); + expect(link.data.url).toBe(downloadLink); + }); + + it('List User Report Archives', async () => { + const archives = await api.listUserReportArchives(userId); + expect(archives.data.length).toBe(1); + expect(archives.data[0].data.id).toBe(archiveId); + expect(archives.pagination.limit).toBe(1); + }); + + it('Get User Report Archive', async () => { + const archive = await api.getUserReportArchive(userId, archiveId); + expect(archive.data.id).toBe(archiveId); + }); + + it('Delete User Report Archive', async () => { + await api.deleteUserReportArchive(userId, archiveId); + }); + + it('Export User Report Archive', async () => { + const status = await api.exportUserReportArchive(userId, archiveId); + expect(status.data.identifier).toBe(exportId); + }); + + it('Check User Report Archive Export Status', async () => { + const status = await api.checkUserReportArchiveStatus(userId, archiveId, exportId); + expect(status.data.identifier).toBe(exportId); + }); + + it('Download User Report Archive', async () => { + const link = await api.downloadUserReportArchive(userId, archiveId, exportId); + expect(link.data.url).toBe(downloadLink); + }); + it('Generate Group Report', async () => { const report = await api.generateGroupReport(groupId, { name: reportName, From 978f9d36c270a68f6ffb98d4e55a660e788fc2ac Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sat, 13 Jul 2024 11:26:44 +0200 Subject: [PATCH 2/7] feat: create task types updates (#406) --- src/tasks/index.ts | 303 ++++++++++++++++++++++++++++++++------------- 1 file changed, 219 insertions(+), 84 deletions(-) diff --git a/src/tasks/index.ts b/src/tasks/index.ts index 1ce7da8f6..b2d9dc300 100644 --- a/src/tasks/index.ts +++ b/src/tasks/index.ts @@ -59,26 +59,7 @@ export class Tasks extends CrowdinApi { * @param request request body * @see https://developer.crowdin.com/api/v2/#operation/api.projects.tasks.post */ - addTask( - projectId: number, - request: //TODO review create task types (https://github.com/crowdin/crowdin-api-client-js/issues/376) - | TasksModel.CreateTaskEnterpriseRequest - | TasksModel.CreateTaskEnterpriseVendorRequest - | TasksModel.CreateTaskEnterpriseStringsBasedRequest - | TasksModel.CreateTaskEnterpriseVendorStringsBasedRequest - | TasksModel.CreateTaskRequest - | TasksModel.CreateTaskStringsBasedRequest - | TasksModel.CreateLanguageServiceTaskRequest - | TasksModel.CreateLanguageServiceTaskStringsBasedRequest - | TasksModel.CreateTaskVendorOhtRequest - | TasksModel.CreateTaskVendorOhtStringsBasedRequest - | TasksModel.CreateTaskVendorGengoRequest - | TasksModel.CreateTaskVendorGengoStringsBasedRequest - | TasksModel.CreateTaskVendorTranslatedRequest - | TasksModel.CreateTaskVendorTranslatedStringsBasedRequest - | TasksModel.CreateTaskVendorManualRequest - | TasksModel.CreateTaskVendorManualStringsBasedRequest, - ): Promise> { + addTask(projectId: number, request: TasksModel.CreateTaskRequest): Promise> { const url = `${this.url}/projects/${projectId}/tasks`; return this.post(url, request, this.defaultConfig()); } @@ -251,20 +232,18 @@ export namespace TasksModel { projectId: number; creatorId: number; type: Type | TypeVendor; - vendor: string; status: Status; title: string; assignees: Assignee[]; assignedTeams: AssignedTeam[]; - fileIds: number[]; progress: Progress; translateProgress: Progress; sourceLanguageId: string; targetLanguageId: string; description: string; translationUrl: string; + webUrl: string; wordsCount: number; - filesCount: number; commentsCount: number; deadline: string; startedAt: string; @@ -276,11 +255,13 @@ export namespace TasksModel { updatedAt: string; sourceLanguage: LanguagesModel.Language; targetLanguages: LanguagesModel.Language[]; - branchIds: number[]; labelIds: number[]; excludeLabelIds: number[]; precedingTaskId: number; - webUrl: string; + filesCount: number; + fileIds: number[]; + branchIds: number[]; + vendor: string; } export interface ListUserTasksOptions extends PaginationOptions { @@ -293,25 +274,64 @@ export namespace TasksModel { isArchived: boolean; } - interface CreateTaskBase { + //Task create definitions + + export type CreateTaskRequest = + | CreateTaskEnterpriseByBranchIds + | CreateTaskEnterpriseByFileIds + | CreateTaskEnterpriseByStringIds + | CreateTaskEnterpriseVendorByBranchIds + | CreateTaskEnterpriseVendorByFileIds + | CreateTaskEnterpriseVendorByStringIds + | CreateTaskEnterprisePendingTask + | CreateTaskByFileIds + | CreateTaskByStringIds + | CreateTaskByBranchIds + | CreateTaskByFileIdsLanguageService + | CreateTaskByStringIdsLanguageService + | CreateTaskByBranchIdsLanguageService + | CreateTaskVendorOhtByFileIds + | CreateTaskVendorOhtByStringIds + | CreateTaskVendorOhtByBranchIds + | CreateTaskVendorGengoByFileIds + | CreateTaskVendorGengoByStringIds + | CreateTaskVendorGengoByBranchIds + | CreateTaskVendorManualByFileIds + | CreateTaskVendorManualByStringIds + | CreateTaskVendorManualByBranchIds + | CreateTaskPendingTask + | CreateTaskPendingTaskLanguageService + | CreateTaskPendingTaskVendorManual; + + export interface CreateTaskEnterpriseByBranchIds { + type: Type; + workflowStepId: number; title: string; languageId: string; - fileIds: number[]; - status?: RequestStatus; - description?: string; + branchIds: number[]; labelIds?: number[]; excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; + splitContent?: boolean; + skipAssignedStrings?: boolean; + assignees?: CreateTaskAssignee[]; + assignedTeams?: AssignedTeam[]; + includePreTranslatedStringsOnly?: boolean; + deadline?: string; + startedAt?: string; dateFrom?: string; dateTo?: string; } - export interface CreateTaskEnterpriseRequest extends CreateTaskBase { + export interface CreateTaskEnterpriseByStringIds { type: Type; workflowStepId: number; - /** - * @deprecated - */ - splitFiles?: boolean; + title: string; + languageId: string; + stringIds: number[]; + status?: RequestStatus; + description?: string; splitContent?: boolean; skipAssignedStrings?: boolean; assignees?: CreateTaskAssignee[]; @@ -319,103 +339,221 @@ export namespace TasksModel { includePreTranslatedStringsOnly?: boolean; deadline?: string; startedAt?: string; + dateFrom?: string; + dateTo?: string; } - export interface CreateTaskEnterpriseVendorRequest extends CreateTaskBase { - workflowStepId: number; - skipAssignedStrings?: boolean; - includePreTranslatedStringsOnly?: boolean; + export type CreateTaskEnterpriseVendorByStringIds = Omit< + CreateTaskEnterpriseByStringIds, + 'type' | 'status' | 'splitContent' | 'assignees' | 'assignedTeams' + >; + + export type CreateTaskEnterpriseVendorByBranchIds = Omit< + CreateTaskEnterpriseByBranchIds, + 'type' | 'status' | 'splitContent' | 'assignees' | 'assignedTeams' + >; + + export type CreateTaskEnterpriseByFileIds = Omit & { + fileIds: number[]; + }; + + export type CreateTaskEnterpriseVendorByFileIds = Omit< + CreateTaskEnterpriseByFileIds, + 'type' | 'status' | 'splitContent' | 'assignees' | 'assignedTeams' + >; + + export interface CreateTaskEnterprisePendingTask { + precedingTaskId: number; + type: Type.PROOFREAD; + title: string; + description?: string; + assignees?: CreateTaskAssignee[]; + assignedTeams?: AssignedTeam[]; deadline?: string; - startedAt?: string; } - export interface CreateTaskRequest extends CreateTaskBase { + export interface CreateTaskByFileIds { + title: string; + languageId: string; type: Type; - splitFiles?: boolean; + fileIds: number[]; + labelIds?: number[]; + excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; splitContent?: boolean; skipAssignedStrings?: boolean; - skipUntranslatedStrings?: boolean; includePreTranslatedStringsOnly?: boolean; assignees?: CreateTaskAssignee[]; deadline?: string; startedAt?: string; + dateFrom?: string; + dateTo?: string; } - export interface CreateLanguageServiceTaskRequest extends CreateTaskBase { + export type CreateTaskByStringIds = Omit & { + stringIds: number; + }; + + export type CreateTaskByBranchIds = Omit & { branchIds: number }; + + export interface CreateTaskByFileIdsLanguageService { + title: string; + languageId: string; type: TypeVendor; - vendor: string; - skipUntranslatedStrings?: boolean; + vendor: 'crowdin_language_service'; + fileIds: number[]; + labelIds?: number[]; + excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; includePreTranslatedStringsOnly?: boolean; - includeUntranslatedStringsOnly?: boolean; + assignees?: CreateTaskAssignee[]; + dateFrom?: string; + dateTo?: string; } - export interface CreateTaskVendorOhtRequest extends CreateTaskBase { + export type CreateTaskByStringIdsLanguageService = Omit< + CreateTaskByFileIdsLanguageService, + 'fileIds' | 'labelIds' | 'excludeLabelIds' + > & { + stringIds: number[]; + }; + + export type CreateTaskByBranchIdsLanguageService = Omit & { + branchIds: number[]; + }; + + export interface CreateTaskVendorOhtByFileIds { + title: string; + languageId: string; type: TypeVendor; - vendor: string; + vendor: 'oht'; + fileIds: number[]; + labelIds?: number[]; + excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; expertise?: Expertise; - skipUntranslatedStrings?: boolean; + editService?: boolean; includePreTranslatedStringsOnly?: boolean; - includeUntranslatedStringsOnly?: boolean; + dateFrom?: string; + dateTo?: string; } - export interface CreateTaskVendorGengoRequest extends CreateTaskBase { + export type CreateTaskVendorOhtByStringIds = Omit< + CreateTaskVendorOhtByFileIds, + 'fileIds' | 'labelIds' | 'excludeLabelIds' + > & { + stringIds: number[]; + }; + + export type CreateTaskVendorOhtByBranchIds = Omit & { + branchIds: number[]; + }; + + export interface CreateTaskVendorGengoByFileIds { + title: string; + languageId: string; type: TypeVendor.TRANSLATE_BY_VENDOR; vendor: 'gengo'; + fileIds: number[]; + labelIds?: number[]; + excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; expertise?: 'standard' | 'pro'; tone?: Tone; purpose?: Purpose; customerMessage?: string; usePreferred?: boolean; editService?: boolean; + dateFrom?: string; + dateTo?: string; } - export interface CreateTaskVendorTranslatedRequest extends CreateTaskBase { - type: TypeVendor.TRANSLATE_BY_VENDOR; - vendor: 'translated'; - expertise?: TranslatedExpertise; - subject?: Subject; - } + export type CreateTaskVendorGengoByStringIds = Omit< + CreateTaskVendorGengoByFileIds, + 'fileIds' | 'labelIds' | 'excludeLabelIds' + > & { + stringIds: number[]; + }; - export interface CreateTaskVendorManualRequest extends CreateTaskBase { + export type CreateTaskVendorGengoByBranchIds = Omit & { + branchIds: number[]; + }; + + export interface CreateTaskVendorManualByFileIds { + title: string; + languageId: string; type: TypeVendor; - vendor: string; + vendor: + | 'alconost' + | 'babbleon' + | 'tomedes' + | 'e2f' + | 'write_path_admin' + | 'inlingo' + | 'acclaro' + | 'translate_by_humans' + | 'lingo24' + | 'assertio_language_services' + | 'gte_localize' + | 'kettu_solutions' + | 'languageline_solutions'; + fileIds: number[]; + labelIds?: number[]; + excludeLabelIds?: number[]; + status?: RequestStatus; + description?: string; skipAssignedStrings?: boolean; - skipUntranslatedStrings?: boolean; includePreTranslatedStringsOnly?: boolean; assignees?: CreateTaskAssignee[]; deadline?: string; startedAt?: string; + dateFrom?: string; + dateTo?: string; } - export type CreateTaskEnterpriseVendorStringsBasedRequest = Omit & { - branchIds: number[]; - }; - - export type CreateTaskEnterpriseStringsBasedRequest = Omit & { - branchIds: number[]; + export type CreateTaskVendorManualByStringIds = Omit< + CreateTaskVendorManualByFileIds, + 'fileIds' | 'labelIds' | 'excludeLabelIds' + > & { + stringIds: number[]; }; - export type CreateTaskStringsBasedRequest = Omit & { branchIds: number[] }; - - export type CreateTaskVendorManualStringsBasedRequest = Omit & { + export type CreateTaskVendorManualByBranchIds = Omit & { branchIds: number[]; }; - export type CreateTaskVendorTranslatedStringsBasedRequest = Omit & { - branchIds: number[]; - }; + export interface CreateTaskPendingTask { + precedingTaskId: number; + type: Type.PROOFREAD; + title: string; + description?: string; + assignees?: CreateTaskAssignee[]; + deadline?: string; + } - export type CreateTaskVendorGengoStringsBasedRequest = Omit & { - branchIds: number[]; - }; + export interface CreateTaskPendingTaskLanguageService { + precedingTaskId: number; + type: TypeVendor.PROOFREAD_BY_VENDOR; + vendor: 'crowdin_language_service'; + title: string; + description?: string; + deadline?: string; + } - export type CreateTaskVendorOhtStringsBasedRequest = Omit & { - branchIds: number[]; - }; + export interface CreateTaskPendingTaskVendorManual { + precedingTaskId: number; + type: TypeVendor.PROOFREAD_BY_VENDOR; + vendor: CreateTaskVendorManualByFileIds['vendor']; + title: string; + description?: string; + deadline?: string; + } - export type CreateLanguageServiceTaskStringsBasedRequest = Omit & { - branchIds: number[]; - }; + //END export interface CreateTaskAssignee { id: number; @@ -465,15 +603,12 @@ export namespace TasksModel { | 'marketing-consumer-media' | 'business-finance' | 'legal-certificate' - | 'cv' | 'medical' - | 'patents' | 'ad-words-banners' | 'automotive-aerospace' | 'scientific' | 'scientific-academic' | 'tourism' - | 'certificates-translation' | 'training-employee-handbooks' | 'forex-crypto'; From 082f32506f76f5f08440b73553350313ba4289ac Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sat, 13 Jul 2024 11:47:52 +0200 Subject: [PATCH 3/7] feat: files import/export options update (#407) --- src/sourceFiles/index.ts | 125 +++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 37 deletions(-) diff --git a/src/sourceFiles/index.ts b/src/sourceFiles/index.ts index 7d57cfb07..67bf2fa71 100644 --- a/src/sourceFiles/index.ts +++ b/src/sourceFiles/index.ts @@ -566,7 +566,7 @@ export namespace SourceFilesModel { type?: FileType; parserVersion?: number; importOptions?: ImportOptions; - exportOptions?: GeneralExportOptions | PropertyExportOptions; + exportOptions?: ExportOptions; excludedTargetLanguages?: string[]; attachLabelIds?: number[]; } @@ -576,21 +576,30 @@ export namespace SourceFilesModel { name?: string; updateOption?: UpdateOption; importOptions?: ImportOptions; - exportOptions?: GeneralExportOptions | PropertyExportOptions | JavaScriptExportOptions | MdExportOptions; + exportOptions?: ExportOptions; attachLabelIds?: number[]; detachLabelIds?: number[]; replaceModifiedContext?: boolean; } - //TODO review import options types + export type ExportOptions = + | GeneralExportOptions + | PropertyExportOptions + | JavaScriptExportOptions + | MdExportOptions; + export type ImportOptions = | SpreadsheetImportOptions | XmlImportOptions - | OtherImportOptions + | WebXmlImportOptions | DocxFileImportOptions | HtmlFileImportOptions | HtmlFrontMatterFileImportOptions - | MdxV1FileImportOptions; + | MdxFileImportOptions + | MdFileImportOptions + | StringCatalogFileImportOptions + | AdocFileImportOptions + | OtherImportOptions; export interface RestoreFile { revisionId: number; @@ -640,6 +649,7 @@ export namespace SourceFilesModel { | 'nsh' | 'wxl' | 'xliff' + | 'xliff_two' | 'html' | 'haml' | 'txt' @@ -650,17 +660,34 @@ export namespace SourceFilesModel { | 'fm_md' | 'mediawiki' | 'docx' + | 'xlsx' | 'sbv' + | 'properties_play' + | 'properties_xml' + | 'maxthon' + | 'go_json' + | 'dita' + | 'mif' + | 'idml' + | 'stringsdict' + | 'plist' | 'vtt' + | 'vdf' | 'srt' - | 'arb'; + | 'stf' + | 'toml' + | 'contentful_rt' + | 'svg' + | 'js' + | 'coffee' + | 'nestjs_i18n'; export interface SpreadsheetImportOptions { - firstLineContainsHeader: boolean; - contentSegmentation: boolean; - srxStorageId: number; - importTranslations: boolean; - scheme: Scheme; + firstLineContainsHeader?: boolean; + contentSegmentation?: boolean; + srxStorageId?: number; + importTranslations?: boolean; + scheme?: Scheme; } export interface Scheme { @@ -676,39 +703,63 @@ export namespace SourceFilesModel { } export interface XmlImportOptions { - translateContent: boolean; - translateAttributes: boolean; - contentSegmentation: boolean; - translatableElements: string[]; - srxStorageId: number; + translateContent?: boolean; + translateAttributes?: boolean; + inlineTags?: string[]; + contentSegmentation?: boolean; + translatableElements?: string[]; + srxStorageId?: number; + } + + export interface WebXmlImportOptions { + inlineTags?: string[]; + contentSegmentation?: boolean; + srxStorageId?: number; } export interface DocxFileImportOptions { - cleanTagsAggressively: boolean; - translateHiddenText: boolean; - translateHyperlinkUrls: boolean; - translateHiddenRowsAndColumns: boolean; - importNotes: boolean; - importHiddenSlides: boolean; - contentSegmentation: boolean; - srxStorageId: number; + cleanTagsAggressively?: boolean; + translateHiddenText?: boolean; + translateHyperlinkUrls?: boolean; + translateHiddenRowsAndColumns?: boolean; + importNotes?: boolean; + importHiddenSlides?: boolean; + contentSegmentation?: boolean; + srxStorageId?: number; } export interface HtmlFileImportOptions { - excludedElements: string[]; - contentSegmentation: boolean; - srxStorageId: number; + excludedElements?: string[]; + inlineTags?: string[]; + contentSegmentation?: boolean; + srxStorageId?: number; } export interface HtmlFrontMatterFileImportOptions extends HtmlFileImportOptions { - excludedFrontMatterElements: string[]; + excludedFrontMatterElements?: string[]; } - export interface MdxV1FileImportOptions { - excludedFrontMatterElements: string[]; - excludeCodeBlocks: boolean; - contentSegmentation: boolean; - srxStorageId: number; + export interface MdxFileImportOptions { + excludedFrontMatterElements?: string[]; + excludeCodeBlocks?: boolean; + contentSegmentation?: boolean; + srxStorageId?: number; + } + + export interface MdFileImportOptions { + excludedFrontMatterElements?: string[]; + excludeCodeBlocks?: boolean; + inlineTags?: string[]; + contentSegmentation?: boolean; + srxStorageId?: number; + } + + export interface StringCatalogFileImportOptions { + importKeyAsSource?: boolean; + } + + export interface AdocFileImportOptions { + excludeIncludeDirectives?: boolean; } export interface OtherImportOptions { @@ -717,17 +768,17 @@ export namespace SourceFilesModel { } export interface GeneralExportOptions { - exportPattern: string; + exportPattern?: string; } export interface PropertyExportOptions { - escapeQuotes: EscapeQuotes; - exportPattern: string; + escapeQuotes?: EscapeQuotes; + exportPattern?: string; escapeSpecialCharacters?: 0 | 1; } export interface JavaScriptExportOptions { - exportPattern: string; + exportPattern?: string; exportQuotes?: ExportQuotes; } From 7313b035b610fcc86f336b54d1efa133d95a83b6 Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sat, 13 Jul 2024 12:27:47 +0200 Subject: [PATCH 4/7] feat: user api new endpoints (#408) * feat: user api new methods * fix --- src/users/index.ts | 72 +++++++++++++++++++++++- tests/users/api.test.ts | 122 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/src/users/index.ts b/src/users/index.ts index 4702cb12f..8c167f1da 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -8,6 +8,8 @@ import { ResponseList, ResponseObject, } from '../core'; +import { ProjectsGroupsModel } from '../projectsGroups'; +import { TeamsModel } from '../teams'; /** * Users API gives you the possibility to get profile information about the currently authenticated user. @@ -15,7 +17,6 @@ import { * In Crowdin Enterprise users are the members of your organization with the defined access levels. * Use API to get the list of organization users and to check the information on a specific user. */ -//TODO add missing endpoints export class Users extends CrowdinApi { /** * @param projectId project identifier @@ -209,6 +210,54 @@ export class Users extends CrowdinApi { const url = `${this.url}/user`; return this.get(url, this.defaultConfig()); } + + /** + * @param request request body + * @see https://developer.crowdin.com/api/v2/#operation/api.user.patch + */ + editAuthenticatedUser(request: PatchRequest[]): Promise> { + const url = `${this.url}/user`; + return this.patch(url, request, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param options request options + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.users.projects.permissions.getMany + */ + listUserProjectPermissions( + userId: number, + options?: PaginationOptions, + ): Promise> { + const url = `${this.url}/users/${userId}/projects/permissions`; + return this.getList(url, options?.limit, options?.offset); + } + + /** + * @param userId user identifier + * @param request request body + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.users.projects.permissions.patch + */ + editUserProjectPermissions( + userId: number, + request: PatchRequest[], + ): Promise> { + const url = `${this.url}/users/${userId}/projects/permissions`; + return this.patch(url, request, this.defaultConfig()); + } + + /** + * @param userId user identifier + * @param options request options + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.users.projects.contributions.getMany + */ + listUserProjectContributions( + userId: number, + options?: PaginationOptions, + ): Promise> { + const url = `${this.url}/users/${userId}/projects/contributions`; + return this.getList(url, options?.limit, options?.offset); + } } export namespace UsersModel { @@ -335,6 +384,27 @@ export namespace UsersModel { permissions?: Permissions; } + export interface ProjectPermissions { + id: number; + roles: ProjectRole[]; + project: ProjectsGroupsModel.Project | ProjectsGroupsModel.ProjectSettings; + teams: TeamsModel.Team[]; + } + + export interface Contributions { + id: number; + translated: Contribution; + approved: Contribution; + voted: Contribution; + commented: Contribution; + project: ProjectsGroupsModel.Project | ProjectsGroupsModel.ProjectSettings; + } + + export interface Contribution { + strings: number; + words?: number; + } + export interface Permissions { [lang: string]: string | { workflowStepIds: number[] | 'all' }; } diff --git a/tests/users/api.test.ts b/tests/users/api.test.ts index 3fb69e738..9665c72ee 100644 --- a/tests/users/api.test.ts +++ b/tests/users/api.test.ts @@ -12,6 +12,8 @@ describe('Users API', () => { const projectId = 24; const memberId = 78; const email = 'test@test.com'; + const permissionId = 123; + const contributionId = 456; const limit = 25; @@ -166,6 +168,89 @@ describe('Users API', () => { data: { id: id, }, + }) + .patch( + '/user', + [ + { + value: email, + op: 'replace', + path: '/email', + }, + ], + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + id: id, + }, + }) + .get(`/users/${id}/projects/permissions`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: permissionId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .patch( + `/users/${id}/projects/permissions`, + [ + { + op: 'remove', + path: `/${permissionId}`, + }, + ], + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: [ + { + data: { + id: permissionId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .get(`/users/${id}/projects/contributions`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: contributionId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, }); }); @@ -241,4 +326,41 @@ describe('Users API', () => { const user = await api.getAuthenticatedUser(); expect(user.data.id).toBe(id); }); + + it('Edit Authenticated User', async () => { + const user = await api.editAuthenticatedUser([ + { + op: 'replace', + path: '/email', + value: email, + }, + ]); + expect(user.data.id).toBe(id); + }); + + it('List User Projects Permissions', async () => { + const permissions = await api.listUserProjectPermissions(id); + expect(permissions.data.length).toBe(1); + expect(permissions.data[0].data.id).toBe(permissionId); + expect(permissions.pagination.limit).toBe(limit); + }); + + it('Permissions Batch Operations', async () => { + const permissions = await api.editUserProjectPermissions(id, [ + { + op: 'remove', + path: `/${permissionId}`, + }, + ]); + expect(permissions.data.length).toBe(1); + expect(permissions.data[0].data.id).toBe(permissionId); + expect(permissions.pagination.limit).toBe(limit); + }); + + it('List User Projects Contributions', async () => { + const contributions = await api.listUserProjectContributions(id); + expect(contributions.data.length).toBe(1); + expect(contributions.data[0].data.id).toBe(contributionId); + expect(contributions.pagination.limit).toBe(limit); + }); }); From 515a01d1ad3bb8b55607c66c3f9a179c709bd2f1 Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sat, 13 Jul 2024 12:45:47 +0200 Subject: [PATCH 5/7] feat: teams api new methods (#409) --- src/teams/index.ts | 40 ++++++++++++++++++++++--- tests/teams/api.test.ts | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/teams/index.ts b/src/teams/index.ts index 36d8a4961..f4d947be7 100644 --- a/src/teams/index.ts +++ b/src/teams/index.ts @@ -8,9 +8,35 @@ import { ResponseList, ResponseObject, } from '../core'; +import { ProjectsGroupsModel } from '../projectsGroups'; -//TODO add missing endpoints export class Teams extends CrowdinApi { + /** + * @param teamId team identifier + * @param options request options + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.teams.projects.permissions.getMany + */ + listTeamProjectPermissions( + teamId: number, + options?: PaginationOptions, + ): Promise> { + const url = `${this.url}/teams/${teamId}/projects/permissions`; + return this.getList(url, options?.limit, options?.offset); + } + + /** + * @param teamId team identifier + * @param request request body + * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.teams.projects.permissions.patch + */ + editTeamProjectPermissions( + teamId: number, + request: PatchRequest[], + ): Promise> { + const url = `${this.url}/teams/${teamId}/projects/permissions`; + return this.patch(url, request, this.defaultConfig()); + } + /** * @param projectId project identifier * @param request request body @@ -145,19 +171,25 @@ export class Teams extends CrowdinApi { } export namespace TeamsModel { + export interface ProjectPermissions { + id: number; + roles: ProjectRole[]; + project: ProjectsGroupsModel.Project | ProjectsGroupsModel.ProjectSettings; + } + export interface AddTeamToProjectRequest { teamId: number; + managerAccess?: boolean; + developerAccess?: boolean; + roles?: ProjectRole[]; /** * @deprecated */ accessToAllWorkflowSteps?: boolean; - managerAccess?: boolean; - developerAccess?: boolean; /** * @deprecated */ permissions?: Permissions; - roles?: ProjectRole[]; } export interface ProjectTeamResources { diff --git a/tests/teams/api.test.ts b/tests/teams/api.test.ts index f90e2acec..8803f3a69 100644 --- a/tests/teams/api.test.ts +++ b/tests/teams/api.test.ts @@ -12,11 +12,57 @@ describe('Tasks API', () => { const teamId = 3; const userId = 4; const name = 'Test team'; + const permissionId = 1213; const limit = 25; beforeAll(() => { scope = nock(api.url) + .get(`/teams/${teamId}/projects/permissions`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: permissionId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .patch( + `/teams/${teamId}/projects/permissions`, + [ + { + op: 'remove', + path: `/${permissionId}`, + }, + ], + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: [ + { + data: { + id: permissionId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) .post( `/projects/${projectId}/teams`, { @@ -159,6 +205,25 @@ describe('Tasks API', () => { scope.done(); }); + it('List Team Projects Permissions', async () => { + const permissions = await api.listTeamProjectPermissions(teamId); + expect(permissions.data.length).toBe(1); + expect(permissions.data[0].data.id).toBe(permissionId); + expect(permissions.pagination.limit).toBe(limit); + }); + + it('Permissions Batch Operations', async () => { + const permissions = await api.editTeamProjectPermissions(teamId, [ + { + op: 'remove', + path: `/${permissionId}`, + }, + ]); + expect(permissions.data.length).toBe(1); + expect(permissions.data[0].data.id).toBe(permissionId); + expect(permissions.pagination.limit).toBe(limit); + }); + it('Add team to project', async () => { const teams = await api.addTeamToProject(projectId, { teamId: teamId, From ee7efe8678bdfc31ae3052bfadf6498e593c2de4 Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sat, 13 Jul 2024 18:12:27 +0200 Subject: [PATCH 6/7] feat: branch new api methods (#410) --- src/sourceFiles/index.ts | 48 ++++++++++++++++++++++++++++- tests/sourceFiles/api.test.ts | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/sourceFiles/index.ts b/src/sourceFiles/index.ts index 67bf2fa71..23b5ebd56 100644 --- a/src/sourceFiles/index.ts +++ b/src/sourceFiles/index.ts @@ -7,6 +7,7 @@ import { PatchRequest, ResponseList, ResponseObject, + Status, } from '../core'; /** @@ -15,8 +16,48 @@ import { * Use API to keep the source files up to date, check on file revisions, and manage project branches. * Before adding source files to the project, upload each file to the Storage first. */ -//TODO add missing branch endpoints (https://github.com/crowdin/crowdin-api-client-js/issues/380) export class SourceFiles extends CrowdinApi { + /** + * @param projectId project identifier + * @param branchId branch identifier + * @param cloneId clone branch identifier + * @see https://developer.crowdin.com/api/v2/string-based/#operation/api.projects.branches.clones.branch.get + */ + getClonedBranch( + projectId: number, + branchId: number, + cloneId: string, + ): Promise> { + const url = `${this.url}/projects/${projectId}/branches/${branchId}/clones/${cloneId}/branch`; + return this.get(url, this.defaultConfig()); + } + + /** + * @param projectId project identifier + * @param branchId branch identifier + * @param request request body + * @see https://developer.crowdin.com/api/v2/string-based/#operation/api.projects.branches.clones.post + */ + clonedBranch( + projectId: number, + branchId: number, + request: SourceFilesModel.CloneBranchRequest, + ): Promise>> { + const url = `${this.url}/projects/${projectId}/branches/${branchId}/clones`; + return this.post(url, request, this.defaultConfig()); + } + + /** + * @param projectId project identifier + * @param branchId branch identifier + * @param cloneId clone branch identifier + * @see https://developer.crowdin.com/api/v2/string-based/#operation/api.projects.branches.clones.get + */ + checkBranchClonedStatus(projectId: number, branchId: number, cloneId: string): Promise>> { + const url = `${this.url}/projects/${projectId}/branches/${branchId}/clones/${cloneId}`; + return this.get(url, this.defaultConfig()); + } + /** * @param projectId project identifier * @param options optional parameters for the request @@ -494,6 +535,11 @@ export namespace SourceFilesModel { priority?: Priority; } + export interface CloneBranchRequest { + name: string; + title?: string; + } + export type Priority = 'low' | 'normal' | 'high'; export interface ListProjectDirectoriesOptions extends PaginationOptions { diff --git a/tests/sourceFiles/api.test.ts b/tests/sourceFiles/api.test.ts index afb6d3f5d..4f1571f33 100644 --- a/tests/sourceFiles/api.test.ts +++ b/tests/sourceFiles/api.test.ts @@ -27,11 +27,50 @@ describe('Source Files API', () => { const buildId = 121212; + const cloneId = 'test12312'; + const fileId = 321; const limit = 25; beforeAll(() => { scope = nock(api.url) + .get(`/projects/${projectId}/branches/${branchId}/clones/${cloneId}/branch`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + id: branchId, + name: branchName, + }, + }) + .post( + `/projects/${projectId}/branches/${branchId}/clones`, + { + name: branchName, + }, + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: { + identifier: cloneId, + }, + }) + .get(`/projects/${projectId}/branches/${branchId}/clones/${cloneId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + identifier: cloneId, + }, + }) .get(`/projects/${projectId}/branches`, undefined, { reqheaders: { Authorization: `Bearer ${api.token}`, @@ -408,6 +447,24 @@ describe('Source Files API', () => { scope.done(); }); + it('Get Cloned Branch', async () => { + const branch = await api.getClonedBranch(projectId, branchId, cloneId); + expect(branch.data.id).toBe(branchId); + expect(branch.data.name).toBe(branchName); + }); + + it('Clone branch', async () => { + const clone = await api.clonedBranch(projectId, branchId, { + name: branchName, + }); + expect(clone.data.identifier).toBe(cloneId); + }); + + it('Check Branch Clone Status', async () => { + const clone = await api.checkBranchClonedStatus(projectId, branchId, cloneId); + expect(clone.data.identifier).toBe(cloneId); + }); + it('List project branches', async () => { const branches = await api.listProjectBranches(projectId, { name: branchName }); expect(branches.data.length).toBe(1); From c58c74b6593244e3266b4822a2a476a890ab8450 Mon Sep 17 00:00:00 2001 From: yevheniyJ Date: Sat, 13 Jul 2024 16:13:26 +0000 Subject: [PATCH 7/7] chore: version 1.35.0 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4946b9b5e..0c95fbee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@crowdin/crowdin-api-client", - "version": "1.34.0", + "version": "1.35.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@crowdin/crowdin-api-client", - "version": "1.34.0", + "version": "1.35.0", "license": "MIT", "dependencies": { "axios": "^1" diff --git a/package.json b/package.json index 0c138ea8b..85e21f0ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@crowdin/crowdin-api-client", - "version": "1.34.0", + "version": "1.35.0", "description": "JavaScript library for Crowdin API", "main": "out/index.js", "types": "out/index.d.ts",