diff --git a/src/api.ts b/src/api.ts index 511d6e4..ed6c973 100644 --- a/src/api.ts +++ b/src/api.ts @@ -130,6 +130,23 @@ import { UserMetadata, } from "./user" import { validateOrgApiKey, validatePersonalApiKey } from "./validators" +import { + AttritionReportInterval, + ChampionReportInterval, + ChartData, + ChartMetric, + ChartMetricCadence, + ChurnReportInterval, + GrowthReportInterval, + OrgReport, + OrgReportType, + ReengagementReportInterval, + ReportPagination, + TopInviterReportInterval, + UserReport, + UserReportType +} from "./userInsights" +import { fetchChartMetricData, fetchOrgReport, fetchUserReport } from "./api/userInsights" export function getApis(authUrl: URL, integrationApiKey: string) { function fetchTokenVerificationMetadataWrapper(): Promise { @@ -462,6 +479,71 @@ 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) + } + + function fetchChartMetricDataWrapper( + chartMetric: ChartMetric, + cadence: ChartMetricCadence, + startDate?: Date, + endDate?: Date, + ): Promise { + return fetchChartMetricData(authUrl, integrationApiKey, chartMetric, cadence, startDate, endDate) + } + return { // fetching functions fetchTokenVerificationMetadata: fetchTokenVerificationMetadataWrapper, @@ -536,5 +618,15 @@ 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, + fetchChartMetricData: fetchChartMetricDataWrapper, } } diff --git a/src/api/userInsights.ts b/src/api/userInsights.ts new file mode 100644 index 0000000..4854d63 --- /dev/null +++ b/src/api/userInsights.ts @@ -0,0 +1,100 @@ +import { RateLimitedException } from "../exceptions" +import { httpRequest } from "../http" +import { + ChartData, + ChartMetric, + ChartMetricCadence, + OrgReport, + OrgReportType, + ReportPagination, + UserReport, + UserReportType, +} from "../userInsights" +import { formatQueryParameters, parseSnakeCaseToCamelCase, } from "../utils" + +const USER_REPORTS_PATH = "/api/backend/v1/user_report" +const ORG_REPORTS_PATH = "/api/backend/v1/org_report" +const CHART_METRICS_PATH = "/api/backend/v1/chart_metrics" + +// 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 && httpResponse.statusCode >= 400) { + throw new Error("Unknown error when fetching the user report") + } + + 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 && httpResponse.statusCode >= 400) { + throw new Error("Unknown error when fetching the org report") + } + + return parseSnakeCaseToCamelCase(httpResponse.response) + }) +} + +export function fetchChartMetricData( + authUrl: URL, + integrationApiKey: string, + chartMetric: ChartMetric, + cadence?: ChartMetricCadence, + startDate?: Date, + endDate?: Date, +): Promise { + const request = { + cadence: cadence, + start_date: startDate?.toISOString().slice(0, 10), // format to YYYY-MM-DD + end_date: endDate?.toISOString().slice(0, 10), // format to YYYY-MM-DD + } + const queryString = formatQueryParameters(request) + + return httpRequest(authUrl, integrationApiKey, `${CHART_METRICS_PATH}/${chartMetric}?${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 && httpResponse.statusCode >= 400) { + throw new Error("Unknown error when fetching the chart metric data") + } + + return parseSnakeCaseToCamelCase(httpResponse.response) + }) +} diff --git a/src/index.ts b/src/index.ts index 4f43c66..ce24293 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,6 +79,24 @@ 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, + ChartData, + ChartDataPoint, + ChartMetric, + ChartMetricCadence, +} from './userInsights' export type { UserProperties, User, diff --git a/src/userInsights.ts b/src/userInsights.ts new file mode 100644 index 0000000..90a3565 --- /dev/null +++ b/src/userInsights.ts @@ -0,0 +1,129 @@ +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", +} + +// chart data types + +export enum ChartMetric { + SIGNUPS = "signups", + ORGS_CREATED = "orgs_created", + ACTIVE_USERS = "active_users", + ACTIVE_ORGS = "active_orgs", +} + +export enum ChartMetricCadence { + DAILY = "Daily", + WEEKLY = "Weekly", + MONTHLY = "Monthly", +} + +export type ChartDataPoint = { + result: number, + date: string, // YYYY-MM-DD format date + cadenceCompleted: boolean, +} + +export type ChartData = { + chartType: string, + cadence: ChartMetricCadence, + metrics: ChartDataPoint[], +} \ No newline at end of file