From 590a411b3f2e45d74d8349c2f04257332f7db8c7 Mon Sep 17 00:00:00 2001 From: Matthew Mauer Date: Thu, 12 Feb 2026 14:59:13 -0500 Subject: [PATCH 1/2] fetching user insights reports --- src/api.ts | 67 +++++++++++++++++++++++++++++ src/api/reports.ts | 75 +++++++++++++++++++++++++++++++++ src/index.ts | 14 +++++++ src/reports.ts | 102 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 src/api/reports.ts create mode 100644 src/reports.ts 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..2e18e50 --- /dev/null +++ b/src/reports.ts @@ -0,0 +1,102 @@ +export type ReportPagination = { + pageSize?: number, + pageNumber?: number, +} + +// org report types + +export type OrgReportRecord = { + recordId: 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 = { + recordId: 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 From 7dc980a27f8d6e51a15475e8ca512616dfadcaa4 Mon Sep 17 00:00:00 2001 From: Matthew Mauer Date: Fri, 13 Feb 2026 12:45:14 -0500 Subject: [PATCH 2/2] fix record.id name --- src/reports.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reports.ts b/src/reports.ts index 2e18e50..228431a 100644 --- a/src/reports.ts +++ b/src/reports.ts @@ -6,7 +6,7 @@ export type ReportPagination = { // org report types export type OrgReportRecord = { - recordId: string, + id: string, reportId: string, orgId: string, name: string, @@ -40,7 +40,7 @@ export type UserOrgMembershipForReport = { } export type UserReportRecord = { - recordId: string, + id: string, reportId: string, userId: string, email: string,