Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ lib/
dev/
clean/
.gemini/
.env
20 changes: 9 additions & 11 deletions src/crashlytics/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ import { FirebaseError } from "../error";

export const ApplicationIdSchema = z
.string()
.describe(
"Firebase app id. For an Android application, read the " +
"mobilesdk_app_id value specified in the google-services.json file for " +
"the current package name. For an iOS Application, read the GOOGLE_APP_ID " +
"from GoogleService-Info.plist. If neither is available, ask the user to " +
"provide the app id.",
);
.describe("Firebase App Id. Strictly required for all API calls.");

export const IssueIdSchema = z.string().describe("Crashlytics issue id, as hexidecimal uuid");
export const IssueIdSchema = z.string().describe("Crashlytics issue id, as hexidecimal UUID");

export const EventFilterSchema = z
.object({
Expand Down Expand Up @@ -109,12 +103,15 @@ export function filterToUrlSearchParams(filter: EventFilter): URLSearchParams {
const displayNamePattern = /^[^()]+\s+\([^()]+\)$/; // Regular expression like "xxxx (yyy)"

/**
* Perform some simplistic validation on filters.
* Perform some simplistic validation on filters and fill missing values.
* @param filter filters to validate
* @throws FirebaseError if any of the filters are invalid.
*/
export function validateEventFilters(filter: EventFilter): void {
if (!filter) return;
export function validateEventFilters(filter: EventFilter = {}): EventFilter {
if (!!filter.intervalStartTime && !filter.intervalEndTime) {
// interval.end_time is required if interval.start_time is set but the agent likes to forget it
filter.intervalEndTime = new Date().toISOString();
}
const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
if (filter.intervalStartTime && new Date(filter.intervalStartTime) < ninetyDaysAgo) {
throw new FirebaseError("intervalStartTime must be less than 90 days in the past");
Expand All @@ -140,4 +137,5 @@ export function validateEventFilters(filter: EventFilter): void {
}
});
}
return filter;
}
4 changes: 2 additions & 2 deletions src/crashlytics/reports.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ describe("getReport", () => {
.reply(200, mockResponse);

const result = await getReport(
CrashlyticsReport.TopIssues,
CrashlyticsReport.TOP_ISSUES,
appId,
{ issueErrorTypes: [issueType] },
pageSize,
Expand All @@ -153,7 +153,7 @@ describe("getReport", () => {
it("should throw a FirebaseError if the appId is invalid", async () => {
const invalidAppId = "invalid-app-id";

await expect(getReport(CrashlyticsReport.TopIssues, invalidAppId, {})).to.be.rejectedWith(
await expect(getReport(CrashlyticsReport.TOP_ISSUES, invalidAppId, {})).to.be.rejectedWith(
FirebaseError,
"Unable to get the projectId from the AppId.",
);
Expand Down
31 changes: 19 additions & 12 deletions src/crashlytics/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@
EventFilterSchema,
filterToUrlSearchParams,
} from "./filters";
import { FirebaseError } from "../error";

const DEFAULT_PAGE_SIZE = 10;

export enum CrashlyticsReport {
TOP_ISSUES = "topIssues",
TOP_VARIANTS = "topVariants",
TOP_VERSIONS = "topVersions",
TOP_OPERATING_SYSTEMS = "topOperatingSystems",
TOP_APPLE_DEVICES = "topAppleDevices",
TOP_ANDROID_DEVICES = "topAndroidDevices",
}

export const CrashlyticsReportSchema = z.nativeEnum(CrashlyticsReport);

export const ReportInputSchema = z.object({
appId: ApplicationIdSchema,
report: CrashlyticsReportSchema,
filter: EventFilterSchema,
pageSize: z.number().optional().describe("Number of rows to return").default(DEFAULT_PAGE_SIZE),
});

export type ReportInput = z.infer<typeof ReportInputSchema>;

export enum CrashlyticsReport {
TopIssues = "topIssues",
TopVariants = "topVariants",
TopVersions = "topVersions",
TopOperatingSystems = "topOperatingSystems",
TopAppleDevices = "topAppleDevices",
TopAndroidDevices = "topAndroidDevices",
}

/**
* Returns a report for Crashlytics events.
* @param report One of the supported reports in the CrashlyticsReport enum
Expand Down Expand Up @@ -61,24 +65,27 @@
return simplifiedReport;
}

export async function getReport(

Check warning on line 68 in src/crashlytics/reports.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
report: CrashlyticsReport,
reportName: CrashlyticsReport,
appId: string,
filter: EventFilter,
pageSize = DEFAULT_PAGE_SIZE,
): Promise<Report> {
if (!reportName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default reportName that might be applied??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything I tried always passes a correct report name. Default or empty would be an error.

throw new FirebaseError("Invalid Crashlytics report " + reportName);

Check warning on line 75 in src/crashlytics/reports.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Operands of '+' operation must either be both strings or both numbers. Consider using a template literal
}
const requestProjectNumber = parseProjectNumber(appId);
const queryParams = filterToUrlSearchParams(filter);
queryParams.set("page_size", `${pageSize}`);
logger.debug(
`[crashlytics] report ${report} called with appId: ${appId} filter: ${queryParams.toString()}, page_size: ${pageSize}`,
`[crashlytics] report ${reportName} called with appId: ${appId} filter: ${queryParams.toString()}, page_size: ${pageSize}`,
);
const response = await CRASHLYTICS_API_CLIENT.request<void, Report>({
method: "GET",
headers: {
"Content-Type": "application/json",
},
path: `/projects/${requestProjectNumber}/apps/${appId}/reports/${report}`,
path: `/projects/${requestProjectNumber}/apps/${appId}/reports/${reportName}`,
queryParams: queryParams,
timeout: TIMEOUT,
});
Expand Down
Loading
Loading