From 7c590b92b0e578621a2f6fe0a53cb82fb4b26232 Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Wed, 12 Oct 2022 21:41:28 -0400 Subject: [PATCH 1/2] feat: add summary client --- .../table-data-form-data-builder.ts | 4 +- .../crashes-api-row/crashes-api-row.ts | 13 ++-- src/index.ts | 1 + src/summary/index.ts | 2 + .../summary-api-client.e2e.ts | 58 ++++++++++++++++++ .../summary-api-client.spec.ts | 61 +++++++++++++++++++ .../summary-api-client/summary-api-client.ts | 39 ++++++++++++ .../summary-table-data-client.ts | 48 +++++++++++++++ .../summary-table-data-request.ts | 6 ++ .../summary-api-row/summary-api-row.ts | 41 +++++++++++++ 10 files changed, 266 insertions(+), 7 deletions(-) create mode 100644 src/summary/index.ts create mode 100644 src/summary/summary-api-client/summary-api-client.e2e.ts create mode 100644 src/summary/summary-api-client/summary-api-client.spec.ts create mode 100644 src/summary/summary-api-client/summary-api-client.ts create mode 100644 src/summary/summary-api-client/summary-table-data-client.ts create mode 100644 src/summary/summary-api-client/summary-table-data-request.ts create mode 100644 src/summary/summary-api-row/summary-api-row.ts diff --git a/src/common/data/table-data/table-data-form-data-builder/table-data-form-data-builder.ts b/src/common/data/table-data/table-data-form-data-builder/table-data-form-data-builder.ts index 97b2b36..2a2da24 100644 --- a/src/common/data/table-data/table-data-form-data-builder/table-data-form-data-builder.ts +++ b/src/common/data/table-data/table-data-form-data-builder/table-data-form-data-builder.ts @@ -78,14 +78,14 @@ export class TableDataFormDataBuilder { return this; } - withApplications(applications: Array): TableDataFormDataBuilder { + withApplications(applications: Array | undefined): TableDataFormDataBuilder { if (applications && applications.length) { this._formParts.appNames = applications.join(','); } return this; } - withVersions(versions: Array): TableDataFormDataBuilder { + withVersions(versions: Array | undefined): TableDataFormDataBuilder { if (versions && versions.length) { this._formParts.versions = versions.join(','); } diff --git a/src/crashes/crashes-api-row/crashes-api-row.ts b/src/crashes/crashes-api-row/crashes-api-row.ts index 9af95c7..509f04f 100644 --- a/src/crashes/crashes-api-row/crashes-api-row.ts +++ b/src/crashes/crashes-api-row/crashes-api-row.ts @@ -1,9 +1,6 @@ interface CrashData { - id: string; groupByCount: number; stackKey: string; - stackId: string; - stackKeyId: string; appName: string; appVersion: string; appDescription: string; @@ -22,17 +19,23 @@ interface CrashData { exceptionMessage: string; } -export interface CrashDataWithMappedProperties extends CrashData { +interface CrashDataWithMappedProperties extends CrashData { + id: number; + stackId: number; + stackKeyId: number; comments: string; ipAddress: string; } export interface CrashesApiResponseRow extends CrashData { + id: string; + stackId: string; + stackKeyId: string; Comments: string; IpAddress: string; } -export class CrashesApiRow { +export class CrashesApiRow implements CrashDataWithMappedProperties { public id: number; public groupByCount: number; public stackKey: string; diff --git a/src/index.ts b/src/index.ts index e7b924d..735d06d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,4 +3,5 @@ export * from './crash'; export * from './crashes'; export * from './events'; export * from './post'; +export * from './summary'; export * from './versions'; \ No newline at end of file diff --git a/src/summary/index.ts b/src/summary/index.ts new file mode 100644 index 0000000..82bbd98 --- /dev/null +++ b/src/summary/index.ts @@ -0,0 +1,2 @@ +export { SummaryApiClient } from './summary-api-client/summary-api-client'; +export { SummaryApiRow } from './summary-api-row/summary-api-row'; diff --git a/src/summary/summary-api-client/summary-api-client.e2e.ts b/src/summary/summary-api-client/summary-api-client.e2e.ts new file mode 100644 index 0000000..feda8d8 --- /dev/null +++ b/src/summary/summary-api-client/summary-api-client.e2e.ts @@ -0,0 +1,58 @@ +import { BugSplatApiClient } from '@common'; +import { config } from '@spec/config'; +import { postNativeCrashAndSymbols } from '@spec/files/native/post-native-crash'; +import { firstValueFrom, timer } from 'rxjs'; +import { SummaryApiClient } from './summary-api-client'; + +describe('SummaryApiClient', () => { + let summaryClient: SummaryApiClient; + let application; + let version; + + beforeEach(async () => { + const { host, email, password } = config; + const bugsplat = await BugSplatApiClient.createAuthenticatedClientForNode(email, password, host); + summaryClient = new SummaryApiClient(bugsplat); + application = 'myConsoleCrasher'; + version = `${Math.random() * 1000000}`; + await postNativeCrashAndSymbols( + bugsplat, + config.database, + application, + version + ); + }); + + describe('getSummary', () => { + it('should return 200 and array of stack keys', async () => { + const stackKey = 'myConsoleCrasher!MemoryException(150)'; + const database = config.database; + const applications = [application]; + const versions = [version]; + const pageSize = 1; + + let result; + for (let i = 0; i < 150; i++) { + result = await summaryClient.getSummary({ + database, + applications, + versions, + pageSize + }); + + const isProcessed = result.rows[0].stackKeyId > 0; + if (isProcessed) { + break; + } + + await firstValueFrom(timer(2000)); + } + + const row = result.rows.find(row => row.stackKey === stackKey); + expect(result.rows).toBeTruthy(); + expect(result.rows.length).toEqual(pageSize); + expect(row?.stackKeyId).toBeGreaterThanOrEqual(1); + expect(row?.stackKey).toEqual(stackKey); + }); + }); +}); diff --git a/src/summary/summary-api-client/summary-api-client.spec.ts b/src/summary/summary-api-client/summary-api-client.spec.ts new file mode 100644 index 0000000..697e011 --- /dev/null +++ b/src/summary/summary-api-client/summary-api-client.spec.ts @@ -0,0 +1,61 @@ +import { createFakeBugSplatApiClient } from '@spec/fakes/common/bugsplat-api-client'; +import { createFakeFormData } from '@spec/fakes/common/form-data'; +import { createFakeResponseBody } from '@spec/fakes/common/response'; +import * as SummaryTableDataClientModule from '../../summary/summary-api-client/summary-table-data-client'; +import { SummaryApiClient } from './summary-api-client'; + +describe('SummaryApiClient', () => { + let sut: SummaryApiClient; + + let apiClient; + let apiClientResponse; + let database; + let fakeFormData; + let pageData; + let rows; + let tableDataClient; + let tableDataClientResponse; + let stackKeyId; + let subKeyDepth; + let userSum; + + beforeEach(() => { + fakeFormData = createFakeFormData(); + apiClientResponse = createFakeResponseBody(200); + apiClient = createFakeBugSplatApiClient(fakeFormData, apiClientResponse); + + database = '☕️'; + stackKeyId = '9001'; + subKeyDepth = '7'; + userSum = '123'; + pageData = { coffee: 'black rifle' }; + rows = [{ stackKeyId, subKeyDepth, userSum }]; + tableDataClientResponse = createFakeResponseBody(200, { pageData, rows }); + tableDataClient = jasmine.createSpyObj('SummaryTableDataClient', ['postGetData']); + tableDataClient.postGetData.and.resolveTo(tableDataClientResponse); + spyOn(SummaryTableDataClientModule, 'SummaryTableDataClient').and.returnValue(tableDataClient); + + sut = new SummaryApiClient(apiClient); + }); + + describe('getSummary', () => { + let result; + let request; + + beforeEach(async () => { + request = { database }; + result = await sut.getSummary(request); + }); + + it('should call postGetData with request', () => { + expect(tableDataClient.postGetData).toHaveBeenCalledWith(request); + }); + + it('should return value with stackKeyId, subKeyDepth, and userSum values mapped to numbers', () => { + expect(result.pageData).toEqual(pageData); + expect(result.rows[0].stackKeyId).toEqual(Number(stackKeyId)); + expect(result.rows[0].subKeyDepth).toEqual(Number(subKeyDepth)); + expect(result.rows[0].userSum).toEqual(Number(userSum)); + }); + }); +}); \ No newline at end of file diff --git a/src/summary/summary-api-client/summary-api-client.ts b/src/summary/summary-api-client/summary-api-client.ts new file mode 100644 index 0000000..b3209db --- /dev/null +++ b/src/summary/summary-api-client/summary-api-client.ts @@ -0,0 +1,39 @@ +import { ApiClient, TableDataResponse } from '@common'; +import { SummaryApiResponseRow, SummaryApiRow } from '../summary-api-row/summary-api-row'; +import { SummaryTableDataClient } from './summary-table-data-client'; +import { SummaryTableDataRequest } from './summary-table-data-request'; + +export class SummaryApiClient { + + private _tableDataClient: SummaryTableDataClient; + + constructor(private _client: ApiClient) { + this._tableDataClient = new SummaryTableDataClient(this._client, '/summary?data'); + } + + async getSummary(request: SummaryTableDataRequest): Promise> { + const response = await this._tableDataClient.postGetData(request); + const json = await response.json(); + const pageData = json.pageData; + const rows = json.rows.map((row: SummaryApiResponseRow) => new SummaryApiRow( + row.stackKey, + Number(row.stackKeyId), + row.firstReport, + row.lastReport, + row.crashSum, + row.techSupportSubject, + row.stackKeyDefectId, + row.stackKeyDefectUrl, + row.stackKeyDefectLabel, + row.comments, + Number(row.subKeyDepth), + Number(row.userSum) + ) + ); + + return { + rows, + pageData + }; + } +} diff --git a/src/summary/summary-api-client/summary-table-data-client.ts b/src/summary/summary-api-client/summary-table-data-client.ts new file mode 100644 index 0000000..4c4ba39 --- /dev/null +++ b/src/summary/summary-api-client/summary-table-data-client.ts @@ -0,0 +1,48 @@ +import { ApiClient, BugSplatResponse, TableDataFormDataBuilder } from '@common'; +import { SummaryTableDataRequest } from './summary-table-data-request'; + +export class SummaryTableDataClient { + + constructor(private _apiClient: ApiClient, private _url: string) { } + + // We use POST to get data in most cases because it supports longer queries + async postGetData(request: SummaryTableDataRequest): Promise { + const factory = () => this._apiClient.createFormData(); + const formData = new TableDataFormDataBuilder(factory) + .withDatabase(request.database) + .withApplications(request.applications) + .withVersions(request.versions) + .withFilterGroups(request.filterGroups) + .withColumnGroups(request.columnGroups) + .withPage(request.page) + .withPageSize(request.pageSize) + .withSortColumn(request.sortColumn) + .withSortOrder(request.sortOrder) + .build(); + const init = { + method: 'POST', + body: formData, + cache: 'no-cache', + credentials: 'include', + redirect: 'follow' + }; + return this.makeRequest(this._url, init); + } + + private async makeRequest(url: string, init: RequestInit): Promise { + const response = await this._apiClient.fetch(url, init); + const responseData = await response.json(); + const rows = responseData ? responseData[0]?.Rows : []; + const pageData = responseData ? responseData[0]?.PageData : {}; + + const status = response.status; + const json = async () => ({ rows, pageData }); + return { + status, + json + }; + } +} + + + diff --git a/src/summary/summary-api-client/summary-table-data-request.ts b/src/summary/summary-api-client/summary-table-data-request.ts new file mode 100644 index 0000000..fd2d8e9 --- /dev/null +++ b/src/summary/summary-api-client/summary-table-data-request.ts @@ -0,0 +1,6 @@ +import { TableDataRequest } from '@common'; + +export interface SummaryTableDataRequest extends TableDataRequest { + applications?: Array; + versions?: Array; +} \ No newline at end of file diff --git a/src/summary/summary-api-row/summary-api-row.ts b/src/summary/summary-api-row/summary-api-row.ts new file mode 100644 index 0000000..dac16ee --- /dev/null +++ b/src/summary/summary-api-row/summary-api-row.ts @@ -0,0 +1,41 @@ +interface SummaryData { + stackKey: string, + firstReport: string, + lastReport: string, + crashSum: string, + techSupportSubject: string, + stackKeyDefectId: string, + stackKeyDefectUrl: string, + stackKeyDefectLabel: string, + comments: string, +} + +export interface SummaryApiResponseRow extends SummaryData { + stackKeyId: string; + subKeyDepth: string; + userSum: string; +} + +interface SummaryDataWithMappedProperties extends SummaryData { + stackKeyId: number; + subKeyDepth: number; + userSum: number; +} + + +export class SummaryApiRow implements SummaryDataWithMappedProperties { + constructor( + public stackKey: string, + public stackKeyId: number, + public firstReport: string, + public lastReport: string, + public crashSum: string, + public techSupportSubject: string, + public stackKeyDefectId: string, + public stackKeyDefectUrl: string, + public stackKeyDefectLabel: string, + public comments: string, + public subKeyDepth: number, + public userSum: number + ) { } +} From 64394d1ebab977681181bf0a8b6f7d73a57a93fc Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Wed, 12 Oct 2022 21:56:37 -0400 Subject: [PATCH 2/2] fix: check in missing files --- src/summary/index.ts | 1 + src/summary/summary-api-client/summary-api-client.spec.ts | 2 +- src/summary/summary-api-client/summary-api-client.ts | 4 ++-- .../summary-table-data-client.ts | 0 .../summary-table-data-request.ts | 0 5 files changed, 4 insertions(+), 3 deletions(-) rename src/summary/{summary-api-client => summary-table-data}/summary-table-data-client.ts (100%) rename src/summary/{summary-api-client => summary-table-data}/summary-table-data-request.ts (100%) diff --git a/src/summary/index.ts b/src/summary/index.ts index 82bbd98..34bf3d3 100644 --- a/src/summary/index.ts +++ b/src/summary/index.ts @@ -1,2 +1,3 @@ export { SummaryApiClient } from './summary-api-client/summary-api-client'; export { SummaryApiRow } from './summary-api-row/summary-api-row'; +export { SummaryTableDataRequest } from './summary-table-data/summary-table-data-request'; diff --git a/src/summary/summary-api-client/summary-api-client.spec.ts b/src/summary/summary-api-client/summary-api-client.spec.ts index 697e011..2768951 100644 --- a/src/summary/summary-api-client/summary-api-client.spec.ts +++ b/src/summary/summary-api-client/summary-api-client.spec.ts @@ -1,7 +1,7 @@ import { createFakeBugSplatApiClient } from '@spec/fakes/common/bugsplat-api-client'; import { createFakeFormData } from '@spec/fakes/common/form-data'; import { createFakeResponseBody } from '@spec/fakes/common/response'; -import * as SummaryTableDataClientModule from '../../summary/summary-api-client/summary-table-data-client'; +import * as SummaryTableDataClientModule from '../summary-table-data/summary-table-data-client'; import { SummaryApiClient } from './summary-api-client'; describe('SummaryApiClient', () => { diff --git a/src/summary/summary-api-client/summary-api-client.ts b/src/summary/summary-api-client/summary-api-client.ts index b3209db..af55ef6 100644 --- a/src/summary/summary-api-client/summary-api-client.ts +++ b/src/summary/summary-api-client/summary-api-client.ts @@ -1,7 +1,7 @@ import { ApiClient, TableDataResponse } from '@common'; import { SummaryApiResponseRow, SummaryApiRow } from '../summary-api-row/summary-api-row'; -import { SummaryTableDataClient } from './summary-table-data-client'; -import { SummaryTableDataRequest } from './summary-table-data-request'; +import { SummaryTableDataClient } from '../summary-table-data/summary-table-data-client'; +import { SummaryTableDataRequest } from '../summary-table-data/summary-table-data-request'; export class SummaryApiClient { diff --git a/src/summary/summary-api-client/summary-table-data-client.ts b/src/summary/summary-table-data/summary-table-data-client.ts similarity index 100% rename from src/summary/summary-api-client/summary-table-data-client.ts rename to src/summary/summary-table-data/summary-table-data-client.ts diff --git a/src/summary/summary-api-client/summary-table-data-request.ts b/src/summary/summary-table-data/summary-table-data-request.ts similarity index 100% rename from src/summary/summary-api-client/summary-table-data-request.ts rename to src/summary/summary-table-data/summary-table-data-request.ts