diff --git a/src/api.ts b/src/api.ts index 511d6e4..13035b8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -130,6 +130,8 @@ import { UserMetadata, } from "./user" import { validateOrgApiKey, validatePersonalApiKey } from "./validators" +import { AttritionReportInterval, ChampionReportInterval, ChurnReportInterval, GrowthReportInterval, OrgReport, OrgReportType, ReengagementReportInterval, ReportPagination, TopInviterReportInterval, UserReport, UserReportType } from "./reports" +import { fetchOrgReport, fetchUserReport } from "./api/reports" export function getApis(authUrl: URL, integrationApiKey: string) { function fetchTokenVerificationMetadataWrapper(): Promise { @@ -462,6 +464,62 @@ export function getApis(authUrl: URL, integrationApiKey: string) { return verifySmsChallenge(authUrl, integrationApiKey, verifySmsChallengeRequest) } + function fetchUserTopInviterReportWrapper( + reportInterval?: TopInviterReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchUserReport(authUrl, integrationApiKey, UserReportType.TOP_INVITERS, reportInterval, pagination) + } + + function fetchUserChampionReportWrapper( + reportInterval?: ChampionReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchUserReport(authUrl, integrationApiKey, UserReportType.CHAMPION, reportInterval, pagination) + } + + function fetchUserReengagementReportWrapper( + reportInterval?: ReengagementReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchUserReport(authUrl, integrationApiKey, UserReportType.REENGAGEMENT, reportInterval, pagination) + } + + function fetchUserChurnReportWrapper( + reportInterval?: ChurnReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchUserReport(authUrl, integrationApiKey, UserReportType.CHURN, reportInterval, pagination) + } + + function fetchOrgReengagementReportWrapper( + reportInterval?: ReengagementReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchOrgReport(authUrl, integrationApiKey, OrgReportType.REENGAGEMENT, reportInterval, pagination) + } + + function fetchOrgChurnReportWrapper( + reportInterval?: ChurnReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchOrgReport(authUrl, integrationApiKey, OrgReportType.CHURN, reportInterval, pagination) + } + + function fetchOrgGrowthReportWrapper( + reportInterval?: GrowthReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchOrgReport(authUrl, integrationApiKey, OrgReportType.GROWTH, reportInterval, pagination) + } + + function fetchOrgAttritionReportWrapper( + reportInterval?: AttritionReportInterval, + pagination?: ReportPagination, + ): Promise { + return fetchOrgReport(authUrl, integrationApiKey, OrgReportType.ATTRITION, reportInterval, pagination) + } + return { // fetching functions fetchTokenVerificationMetadata: fetchTokenVerificationMetadataWrapper, @@ -536,5 +594,14 @@ export function getApis(authUrl: URL, integrationApiKey: string) { verifySmsChallenge: verifySmsChallengeWrapper, // employee functions fetchEmployeeById: fetchEmployeeByIdWrapper, + // report data fetching functions + fetchUserTopInviterReport: fetchUserTopInviterReportWrapper, + fetchUserChampionReport: fetchUserChampionReportWrapper, + fetchUserReengagementReport: fetchUserReengagementReportWrapper, + fetchUserChurnReport: fetchUserChurnReportWrapper, + fetchOrgReengagementReport: fetchOrgReengagementReportWrapper, + fetchOrgChurnReport: fetchOrgChurnReportWrapper, + fetchOrgGrowthReport: fetchOrgGrowthReportWrapper, + fetchOrgAttritionReport: fetchOrgAttritionReportWrapper, } } diff --git a/src/api/reports.ts b/src/api/reports.ts new file mode 100644 index 0000000..a361505 --- /dev/null +++ b/src/api/reports.ts @@ -0,0 +1,75 @@ +import { + ApiKeyCreateException, + ApiKeyDeleteException, + ApiKeyFetchException, + ApiKeyUpdateException, + ApiKeyValidateException, + ApiKeyValidateRateLimitedException, + RateLimitedException, + ApiKeyImportException +} from "../exceptions" +import { httpRequest } from "../http" +import { OrgReport, OrgReportType, ReportPagination, UserReport, UserReportType } from "../reports" +import { ApiKeyFull, ApiKeyNew, ApiKeyResultPage, ApiKeyValidation } from "../user" +import { formatQueryParameters, isValidHex, parseSnakeCaseToCamelCase, removeBearerIfExists } from "../utils" + +const USER_REPORTS_PATH = "/api/backend/v1/user_report" +const ORG_REPORTS_PATH = "/api/backend/v1/org_report" + +// GET +export function fetchUserReport( + authUrl: URL, + integrationApiKey: string, + reportType: UserReportType, + reportInterval?: string, + pagination?: ReportPagination, +): Promise { + const request = { + report_interval: reportInterval, + page_size: pagination?.pageSize, + page_number: pagination?.pageNumber, + } + const queryString = formatQueryParameters(request) + + return httpRequest(authUrl, integrationApiKey, `${USER_REPORTS_PATH}/${reportType}?${queryString}`, "GET").then((httpResponse) => { + if (httpResponse.statusCode === 401) { + throw new Error("integrationApiKey is incorrect") + } else if (httpResponse.statusCode === 429) { + throw new RateLimitedException(httpResponse.response) + } else if (httpResponse.statusCode === 400) { + throw new ApiKeyFetchException(httpResponse.response) + } else if (httpResponse.statusCode && httpResponse.statusCode >= 400) { + throw new Error("Unknown error when creating the end user api key") + } + + return parseSnakeCaseToCamelCase(httpResponse.response) + }) +} +export function fetchOrgReport( + authUrl: URL, + integrationApiKey: string, + reportType: OrgReportType, + reportInterval?: string, + pagination?: ReportPagination, +): Promise { + const request = { + report_interval: reportInterval, + page_size: pagination?.pageSize, + page_number: pagination?.pageNumber, + } + const queryString = formatQueryParameters(request) + + return httpRequest(authUrl, integrationApiKey, `${ORG_REPORTS_PATH}/${reportType}?${queryString}`, "GET").then((httpResponse) => { + if (httpResponse.statusCode === 401) { + throw new Error("integrationApiKey is incorrect") + } else if (httpResponse.statusCode === 429) { + throw new RateLimitedException(httpResponse.response) + } else if (httpResponse.statusCode === 400) { + throw new ApiKeyFetchException(httpResponse.response) + } else if (httpResponse.statusCode && httpResponse.statusCode >= 400) { + throw new Error("Unknown error when creating the end user api key") + } + + return parseSnakeCaseToCamelCase(httpResponse.response) + }) +} diff --git a/src/index.ts b/src/index.ts index 4f43c66..3a617c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,6 +79,20 @@ export { } from "./exceptions" export type { SocialLoginProvider, SamlLoginProvider, LoginMethod } from "./loginMethod" export type { CustomRoleMappings, CustomRoleMapping } from "./customRoleMappings" +export type { + ReengagementReportInterval, + ChampionReportInterval, + ChurnReportInterval, + GrowthReportInterval, + AttritionReportInterval, + TopInviterReportInterval, + ReportPagination, + UserReport, + UserReportRecord, + OrgReport, + OrgReportRecord, + UserOrgMembershipForReport, +} from './reports' export type { UserProperties, User, diff --git a/src/reports.ts b/src/reports.ts new file mode 100644 index 0000000..228431a --- /dev/null +++ b/src/reports.ts @@ -0,0 +1,102 @@ +export type ReportPagination = { + pageSize?: number, + pageNumber?: number, +} + +// org report types + +export type OrgReportRecord = { + id: string, + reportId: string, + orgId: string, + name: string, + numUsers: number, + orgCreatedAt: number, + extraProperties: { [key: string]: any }, +} + +export type OrgReport = { + orgReports: OrgReportRecord[], + currentPage: number, + totalCount: number, + pageSize: number, + hasMoreResults: boolean, + reportTime: number, +} + +export enum OrgReportType { + ATTRITION = "attrition", + REENGAGEMENT = "reengagement", + GROWTH = "growth", + CHURN = "churn", +} + +// user report types + +export type UserOrgMembershipForReport = { + displayName: string, + orgId: string, + userRole: string, +} + +export type UserReportRecord = { + id: string, + reportId: string, + userId: string, + email: string, + userCreatedAt: number, + lastActiveAt: number, + username?: string, + firstName?: string, + lastName?: string, + orgData?: UserOrgMembershipForReport[], + extraProperties: { [key: string]: any }, +} + +export type UserReport = { + userReports: UserReportRecord[], + currentPage: number, + totalCount: number, + pageSize: number, + hasMoreResults: boolean, + reportTime: number, +} + +export enum UserReportType { + REENGAGEMENT = "reengagement", + CHURN = "churn", + TOP_INVITERS = "top_inviter", + CHAMPION = "champion", +} + +// report interval options + +export enum ReengagementReportInterval { + WEEKLY = "Weekly", + MONTHLY = "Monthly", +} +export enum ChurnReportInterval { + SEVEN_DAYS = "7", + FOURTEEN_DAYS = "14", + THIRTY_DAYS = "30", +} +export enum GrowthReportInterval { + THIRTY_DAYS = "30", + SIXTY_DAYS = "60", + NINETY_DAYS = "90", +} +export enum TopInviterReportInterval { + THIRTY_DAYS = "30", + SIXTY_DAYS = "60", + NINETY_DAYS = "90", +} +export enum ChampionReportInterval { + THIRTY_DAYS = "30", + SIXTY_DAYS = "60", + NINETY_DAYS = "90", +} +export enum AttritionReportInterval { + THIRTY_DAYS = "30", + SIXTY_DAYS = "60", + NINETY_DAYS = "90", +} \ No newline at end of file