diff --git a/__tests__/services/zendesk-api-service.spec.ts b/__tests__/services/zendesk-api-service.spec.ts index 96b5abe..cca8f0a 100644 --- a/__tests__/services/zendesk-api-service.spec.ts +++ b/__tests__/services/zendesk-api-service.spec.ts @@ -309,6 +309,128 @@ describe("ZendeskService", () => { ).rejects.toThrow(RangeError); expect(requestMock).toHaveBeenCalledTimes(0); }); + + describe("getTags", () => { + it("should call the API and return the tags", async () => { + const tags = [{ name: "tag1" }]; + requestMock.mockResolvedValueOnce({ tags }); + + const result = await service.getTags(); + + expect(requestMock).toHaveBeenCalledWith(`/api/v2/tags`); + expect(result).toEqual(tags); + }); + + it("should continue calling the API until next_page disappears", async () => { + const tags = [{ name: "tag1" }]; + requestMock + .mockResolvedValueOnce({ tags, next_page: "next_page" }) + .mockResolvedValueOnce({ tags: [] }); + + const result = await service.getTags(); + + expect(requestMock).toHaveBeenCalledTimes(2); + expect(requestMock).toHaveBeenNthCalledWith(1, `/api/v2/tags`); + expect(requestMock).toHaveBeenNthCalledWith(2, "next_page"); + expect(result).toEqual(tags); + }); + + it("should only call the API one time with fetchAllTags set to false", async () => { + const tags = [{ name: "tag1" }]; + requestMock.mockResolvedValueOnce({ tags, next_page: "next_page" }); + + const result = await service.getTags(false); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(requestMock).toHaveBeenCalledWith(`/api/v2/tags`); + expect(result).toEqual(tags); + }); + }); + + describe("getGroups", () => { + it("should call the API and return the groups", async () => { + const groups = [{ name: "group1" }]; + requestMock.mockResolvedValueOnce({ groups }); + + const result = await service.getGroups(); + + expect(requestMock).toHaveBeenCalledWith(`/api/v2/groups`); + expect(result).toEqual(groups); + }); + + it("should continue calling the API until next_page disappears", async () => { + const groups = [{ name: "group1" }]; + requestMock + .mockResolvedValueOnce({ groups, next_page: "next_page" }) + .mockResolvedValueOnce({ groups: [] }); + + const result = await service.getGroups(); + + expect(requestMock).toHaveBeenCalledTimes(2); + expect(requestMock).toHaveBeenNthCalledWith(1, `/api/v2/groups`); + expect(requestMock).toHaveBeenNthCalledWith(2, "next_page"); + expect(result).toEqual(groups); + }); + + it("should only call the API one time with fetchAllGroups set to false", async () => { + const groups = [{ name: "group1" }]; + requestMock.mockResolvedValueOnce({ groups, next_page: "next_page" }); + + const result = await service.getGroups(false); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(requestMock).toHaveBeenCalledWith(`/api/v2/groups`); + expect(result).toEqual(groups); + }); + }); + + describe("getOrganizations", () => { + it("should call the API and return the organizations", async () => { + const organizations = [{ name: "organization1" }]; + requestMock.mockResolvedValueOnce({ organizations }); + + const result = await service.getOrganizations(); + + expect(requestMock).toHaveBeenCalledWith(`/api/v2/organizations`); + expect(result).toEqual(organizations); + }); + + it("should continue calling the API until next_page disappears", async () => { + const organizations = [{ name: "organization1" }]; + requestMock + .mockResolvedValueOnce({ organizations, next_page: "next_page" }) + .mockResolvedValueOnce({ organizations: [] }); + + const result = await service.getOrganizations(); + + expect(requestMock).toHaveBeenCalledTimes(2); + expect(requestMock).toHaveBeenNthCalledWith(1, `/api/v2/organizations`); + expect(requestMock).toHaveBeenNthCalledWith(2, "next_page"); + expect(result).toEqual(organizations); + }); + + it("should only call the API one time with fetchAllOrganizations set to false", async () => { + const organizations = [{ name: "organization1" }]; + requestMock.mockResolvedValueOnce({ organizations, next_page: "next_page" }); + + const result = await service.getOrganizations(false); + + expect(requestMock).toHaveBeenCalledTimes(1); + expect(requestMock).toHaveBeenCalledWith(`/api/v2/organizations`); + expect(result).toEqual(organizations); + }); + }); + describe("getLocales", () => { + it("should fetch and return locales", async () => { + const locales = [{ locale: "en-US" }]; + requestMock.mockResolvedValueOnce({ locales }); + + const result = await service.getLocales(); + + expect(requestMock).toHaveBeenCalledWith(`/api/v2/locales`); + expect(result).toEqual(locales); + }); + }); }); }); }); diff --git a/package.json b/package.json index 6a349f2..20d6cba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zendesk/zaf-toolbox", - "version": "0.2.9", + "version": "0.2.10", "description": "A toolbox for ZAF application built with 🩷 by Zendesk Labs", "main": "lib/src/index.js", "types": "lib/src/index.d.ts", diff --git a/src/models/index.ts b/src/models/index.ts index 0a4d9bb..9c33efc 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -8,3 +8,4 @@ export * from "@models/requester"; export * from "@models/whats-app-template"; export * from "@models/zendesk-user"; export * from "@models/custom-objects"; +export * from "@models/zendesk-api"; diff --git a/src/models/zendesk-api.ts b/src/models/zendesk-api.ts new file mode 100644 index 0000000..7121852 --- /dev/null +++ b/src/models/zendesk-api.ts @@ -0,0 +1,59 @@ +export interface IZendeskResponse { + count: number; + next_page: string | null; + previous_page: string | null; +} + +export interface IZendeskTag { + count: number; + name: string; +} + +export interface IZendeskGroup { + id: number; + name: string; + created_at: string; + updated_at: string; + is_public: boolean; +} + +export interface IZendeskOrganizations { + id: number; + name: string; + created_at: string; + updated_at: string; + domain_names: string[]; + details: string; + notes: string; + group_id: number | null; + shared_tickets: boolean; + shared_comments: boolean; + tags: string[]; + external_id: string | null; + url: string; +} + +export interface IZendeskLocale { + id: number; + name: string; + locale: string; + created_at: string; + updated_at: string; + url: string; +} + +export interface ITagsResults extends IZendeskResponse { + tags: IZendeskTag[]; +} + +export interface IGroupsResults extends IZendeskResponse { + groups: IZendeskGroup[]; +} + +export interface IOrganizationsResults extends IZendeskResponse { + organizations: IZendeskOrganizations[]; +} + +export interface ILocalesResults { + locales: IZendeskLocale[]; +} diff --git a/src/models/zendesk-user.ts b/src/models/zendesk-user.ts index 4bd0125..2fe18df 100644 --- a/src/models/zendesk-user.ts +++ b/src/models/zendesk-user.ts @@ -1,3 +1,5 @@ +import { IZendeskResponse } from "./zendesk-api"; + export interface IZendeskUser { id: number; url: string; @@ -62,12 +64,6 @@ export interface IKeyTitleUserField { title: string; } -interface IZendeskResponse { - count: number; - next_page: string | null; - previous_page: string | null; -} - export interface ISearchUserResults extends IZendeskResponse { users: IZendeskUser[]; } diff --git a/src/services/zendesk-api-service.ts b/src/services/zendesk-api-service.ts index ce9ec66..40a6012 100644 --- a/src/services/zendesk-api-service.ts +++ b/src/services/zendesk-api-service.ts @@ -10,7 +10,15 @@ import { IUserFieldsResults, IZendeskUserField, IZendeskUserFieldValue, - HttpMethod + HttpMethod, + ITagsResults, + IGroupsResults, + IOrganizationsResults, + ILocalesResults, + IZendeskTag, + IZendeskLocale, + IZendeskGroup, + IZendeskOrganizations } from "@models/index"; import { convertContentMessageToHtml } from "@utils/convert-content-message-to-html"; import { getFromClient } from "@utils/get-from-client"; @@ -24,6 +32,32 @@ export const UPDATE_USER_FIELD_MAX_USERS = 90; export class ZendeskApiService { public constructor(public client: Client) {} + /** + * Generic method to fetch all paginated results from a given endpoint. + * + * @param url The initial API endpoint URL. + * @param fetchAll Whether to fetch all pages or just the first. + * @param extractArrayFn Function to extract the array of items from the response. + * @returns A promise resolving to a flattened array of all items. + */ + private async fetchAllPaginatedResults( + url: string, + fetchAll: boolean, + extractArrayFn: (response: TResponse) => TItem[] + ): Promise { + const results: TResponse[] = [await this.client.request(url)]; + + if (fetchAll) { + while (true) { + const nextPage = (results[results.length - 1] as TResponse & { next_page?: string }).next_page; + if (!nextPage) break; + results.push(await this.client.request(nextPage)); + } + } + + return results.flatMap(extractArrayFn); + } + /** * Retrieve the requirement id from the requirement file. The identifier is only the name of the requirement. * @@ -95,52 +129,24 @@ export class ZendeskApiService { */ public async searchUsers( query: string, - fetchAllPages = true + fetchAllUsers = true ): Promise[]> { - const results = [ - await this.client.request>(`/api/v2/users/search?query=${encodeURI(query)}`) - ]; - - if (fetchAllPages) { - while (true) { - const nextPage = results[results.length - 1].next_page; - - if (!nextPage) { - break; - } - - results.push(await this.client.request>(nextPage)); - } - } - - return results - .flat() - .map(({ users }) => users) - .flat(); + return this.fetchAllPaginatedResults, IZendeskUser>( + `/api/v2/users/search?query=${encodeURI(query)}`, + fetchAllUsers, + (response) => response.users + ); } /** * Fetch all user fields */ public async getUserFields(fetchAllFields = true): Promise { - const results = [await this.client.request(`/api/v2/user_fields`)]; - - if (fetchAllFields) { - while (true) { - const nextPage = results[results.length - 1].next_page; - - if (!nextPage) { - break; - } - - results.push(await this.client.request(nextPage)); - } - } - - return results - .flat() - .map(({ user_fields }) => user_fields) - .flat(); + return this.fetchAllPaginatedResults( + `/api/v2/user_fields`, + fetchAllFields, + (response) => response.user_fields + ); } /** @@ -209,4 +215,42 @@ export class ZendeskApiService { } }); } + /** + * Fetch all user instance tags + */ + public async getTags(fetchAllTags = true): Promise { + return this.fetchAllPaginatedResults( + `/api/v2/tags`, + fetchAllTags, + (response) => response.tags + ); + } + /** + * Fetch all user instance groups + */ + public async getGroups(fetchAllGroups = true): Promise { + return this.fetchAllPaginatedResults( + `/api/v2/groups`, + fetchAllGroups, + (response) => response.groups + ); + } + /** + * Fetch all user instance organizations + */ + public async getOrganizations(fetchAllOrganizations = true): Promise { + return this.fetchAllPaginatedResults( + `/api/v2/organizations`, + fetchAllOrganizations, + (response) => response.organizations + ); + } + /** + * Fetch all user instance locales + */ + public async getLocales(): Promise { + const results = await this.client.request(`/api/v2/locales`); + + return results.locales; + } }