From fa65f45d22516812080e3c4e6a6b360c2eb11642 Mon Sep 17 00:00:00 2001 From: Ribas160 Date: Wed, 29 Oct 2025 15:44:31 +0200 Subject: [PATCH 1/5] feat: support for new group managers and teams features --- src/teams/index.ts | 43 +++++++++++++++++++++ src/users/index.ts | 46 +++++++++++++++++++++++ tests/teams/api.test.ts | 80 ++++++++++++++++++++++++++++++++++++++++ tests/users/api.test.ts | 82 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+) diff --git a/src/teams/index.ts b/src/teams/index.ts index b8b7d2089..edce6f919 100644 --- a/src/teams/index.ts +++ b/src/teams/index.ts @@ -12,6 +12,40 @@ import { import { ProjectsGroupsModel } from '../projectsGroups'; export class Teams extends CrowdinApi { + /** + * @param groupId group identifier + * @param options request options + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Teams/operation/api.groups.teams.getMany + */ + listGroupTeams( + groupId: number, + options?: TeamsModel.ListGroupTeamsOptions, + ): Promise> { + let url = `${this.url}/groups/${groupId}/teams`; + url = this.addQueryParam(url, 'orderBy', options?.orderBy); + return this.get(url, this.defaultConfig()); + } + + /** + * @param groupId group identifier + * @param request request body + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Teams/operation/api.groups.teams.patch + */ + updateGroupTeams(groupId: number, request: PatchRequest[]): Promise> { + const url = `${this.url}/groups/${groupId}/teams`; + return this.patch(url, request, this.defaultConfig()); + } + + /** + * @param groupId group identifier + * @param teamId team identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Teams/operation/api.groups.teams.get + */ + getGroupTeam(groupId: number, teamId: number): Promise> { + const url = `${this.url}/groups/${groupId}/teams/${teamId}`; + return this.get(url, this.defaultConfig()); + } + /** * @param teamId team identifier * @param options request options @@ -177,6 +211,10 @@ export class Teams extends CrowdinApi { } export namespace TeamsModel { + export interface ListGroupTeamsOptions { + orderBy?: string; + } + export interface ProjectPermissions { id: number; roles: ProjectRole[]; @@ -240,6 +278,11 @@ export namespace TeamsModel { updatedAt: string; } + export interface TeamGroup { + id: number; + team: Team; + } + export interface AddTeamRequest { name: string; } diff --git a/src/users/index.ts b/src/users/index.ts index a8a99e2be..eb3dc9c51 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -19,6 +19,41 @@ import { TeamsModel } from '../teams'; * Use API to get the list of organization users and to check the information on a specific user. */ export class Users extends CrowdinApi { + /** + * @param groupId group identifier + * @param options optional parameters for the request + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Users/operation/api.groups.managers.getMany + */ + listGroupManagers( + groupId: number, + options?: UsersModel.ListGroupManagersOptions, + ): Promise> { + let url = `${this.url}/groups/${groupId}/managers`; + url = this.addQueryParam(url, 'teamIds', options?.teamIds?.toString()); + url = this.addQueryParam(url, 'orderBy', options?.orderBy); + return this.get(url, this.defaultConfig()); + } + + /** + * @param groupId group identifier + * @param request request body + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Users/operation/api.groups.managers.patch + */ + updateGroupManagers(groupId: number, request: PatchRequest[]): Promise> { + const url = `${this.url}/groups/${groupId}/managers`; + return this.patch(url, request, this.defaultConfig()); + } + + /** + * @param groupId group identifier + * @param userId user identifier + * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Users/operation/api.groups.managers.get + */ + getGroupManager(groupId: number, userId: number): Promise> { + const url = `${this.url}/groups/${groupId}/managers/${userId}`; + return this.get(url, this.defaultConfig()); + } + /** * @param projectId project identifier * @param options optional parameters for the request @@ -270,6 +305,11 @@ export class Users extends CrowdinApi { } export namespace UsersModel { + export interface ListGroupManagersOptions { + teamIds?: number[]; + orderBy?: string; + } + export interface ListProjectMembersOptions extends PaginationOptions { search?: string; role?: Role; @@ -324,6 +364,12 @@ export namespace UsersModel { export type OrganizationRoles = 'admin' | 'manager' | 'vendor' | 'client'; + export interface GroupManager { + id: number; + user: User; + teams: TeamsModel.Team[]; + } + export interface ProjectMember { id: number; username: string; diff --git a/tests/teams/api.test.ts b/tests/teams/api.test.ts index 8803f3a69..fe0dbd053 100644 --- a/tests/teams/api.test.ts +++ b/tests/teams/api.test.ts @@ -13,11 +13,67 @@ describe('Tasks API', () => { const userId = 4; const name = 'Test team'; const permissionId = 1213; + const groupId = 5; const limit = 25; beforeAll(() => { scope = nock(api.url) + .get(`/groups/${groupId}/teams`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: teamId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .patch( + `/groups/${groupId}/teams`, + [ + { + op: 'remove', + path: `/${teamId}`, + }, + ], + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: [ + { + data: { + id: teamId, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .get(`/groups/${groupId}/teams/${teamId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + id: teamId, + }, + }) .get(`/teams/${teamId}/projects/permissions`, undefined, { reqheaders: { Authorization: `Bearer ${api.token}`, @@ -205,6 +261,30 @@ describe('Tasks API', () => { scope.done(); }); + it('List group teams', async () => { + const teams = await api.listGroupTeams(groupId); + expect(teams.data.length).toBe(1); + expect(teams.data[0].data.id).toBe(teamId); + expect(teams.pagination.limit).toBe(limit); + }); + + it('Update group teams', async () => { + const teams = await api.updateGroupTeams(groupId, [ + { + op: 'remove', + path: `/${teamId}`, + }, + ]); + expect(teams.data.length).toBe(1); + expect(teams.data[0].data.id).toBe(teamId); + expect(teams.pagination.limit).toBe(limit); + }); + + it('Get group team', async () => { + const team = await api.getGroupTeam(groupId, teamId); + expect(team.data.id).toBe(teamId); + }); + it('List Team Projects Permissions', async () => { const permissions = await api.listTeamProjectPermissions(teamId); expect(permissions.data.length).toBe(1); diff --git a/tests/users/api.test.ts b/tests/users/api.test.ts index 9665c72ee..ee3bb479a 100644 --- a/tests/users/api.test.ts +++ b/tests/users/api.test.ts @@ -14,11 +14,69 @@ describe('Users API', () => { const email = 'test@test.com'; const permissionId = 123; const contributionId = 456; + const groupId = 5; + const userId = 12; const limit = 25; beforeAll(() => { scope = nock(api.url) + .get(`/groups/${groupId}/managers`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: [ + { + data: { + id: id, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .patch( + `/groups/${groupId}/managers`, + [ + { + op: 'remove', + path: `/${userId}`, + value: {}, + }, + ], + { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }, + ) + .reply(200, { + data: [ + { + data: { + id: id, + }, + }, + ], + pagination: { + offset: 0, + limit: limit, + }, + }) + .get(`/groups/${groupId}/managers/${userId}`, undefined, { + reqheaders: { + Authorization: `Bearer ${api.token}`, + }, + }) + .reply(200, { + data: { + id: id, + }, + }) .get(`/projects/${projectId}/members`, undefined, { reqheaders: { Authorization: `Bearer ${api.token}`, @@ -258,6 +316,30 @@ describe('Users API', () => { scope.done(); }); + it('List group managers', async () => { + const managers = await api.listGroupManagers(groupId); + expect(managers.data.length).toBe(1); + expect(managers.data[0].data.id).toBe(id); + expect(managers.pagination.limit).toBe(limit); + }); + + it('Update group managers', async () => { + const managers = await api.updateGroupManagers(groupId, [ + { + op: 'remove', + path: `/${userId}`, + value: {}, + }, + ]); + expect(managers.data.length).toBe(1); + expect(managers.data[0].data.id).toBe(id); + }); + + it('Get group manager', async () => { + const manager = await api.getGroupManager(groupId, userId); + expect(manager.data.id).toBe(id); + }); + it('List Project Members', async () => { const members = await api.listProjectMembers(projectId); expect(members.data.length).toBe(1); From 78f537bc3aeee59b37ba38fdc65492c16c2e08db Mon Sep 17 00:00:00 2001 From: Mikhail Romanov <42250412+Ribas160@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:58:08 +0200 Subject: [PATCH 2/5] Extend ListGroupManagersOptions with PaginationOptions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/users/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/users/index.ts b/src/users/index.ts index eb3dc9c51..fbbb6a774 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -305,7 +305,7 @@ export class Users extends CrowdinApi { } export namespace UsersModel { - export interface ListGroupManagersOptions { + export interface ListGroupManagersOptions extends PaginationOptions { teamIds?: number[]; orderBy?: string; } From 9845926f4f5b5b3e10bdd06696e6199433a4f397 Mon Sep 17 00:00:00 2001 From: Mikhail Romanov <42250412+Ribas160@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:59:32 +0200 Subject: [PATCH 3/5] fix: listGroupManagers should return list Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/users/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/users/index.ts b/src/users/index.ts index fbbb6a774..fcbb522a4 100644 --- a/src/users/index.ts +++ b/src/users/index.ts @@ -31,7 +31,7 @@ export class Users extends CrowdinApi { let url = `${this.url}/groups/${groupId}/managers`; url = this.addQueryParam(url, 'teamIds', options?.teamIds?.toString()); url = this.addQueryParam(url, 'orderBy', options?.orderBy); - return this.get(url, this.defaultConfig()); + return this.getList(url, options?.limit, options?.offset); } /** From f3506a0df1a68e8fb7696056044ef5a632040f75 Mon Sep 17 00:00:00 2001 From: Mikhail Romanov <42250412+Ribas160@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:00:11 +0200 Subject: [PATCH 4/5] fix: extend ListGroupTeamsOptions with PaginationOptions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/teams/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/teams/index.ts b/src/teams/index.ts index edce6f919..528a547fc 100644 --- a/src/teams/index.ts +++ b/src/teams/index.ts @@ -211,7 +211,7 @@ export class Teams extends CrowdinApi { } export namespace TeamsModel { - export interface ListGroupTeamsOptions { + export interface ListGroupTeamsOptions extends PaginationOptions { orderBy?: string; } From 52bc230bf0bde57550377ee5f8c5e1babe0c81f4 Mon Sep 17 00:00:00 2001 From: Mikhail Romanov <42250412+Ribas160@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:00:38 +0200 Subject: [PATCH 5/5] fix: listGroupTeams should return a list Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/teams/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/teams/index.ts b/src/teams/index.ts index 528a547fc..baf79cd85 100644 --- a/src/teams/index.ts +++ b/src/teams/index.ts @@ -23,7 +23,7 @@ export class Teams extends CrowdinApi { ): Promise> { let url = `${this.url}/groups/${groupId}/teams`; url = this.addQueryParam(url, 'orderBy', options?.orderBy); - return this.get(url, this.defaultConfig()); + return this.getList(url, options?.limit, options?.offset); } /**