From 6ca571fd72b69f537374116f5f23b59b9b2df1e9 Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Fri, 13 Dec 2024 18:08:39 -0500 Subject: [PATCH 1/7] Add exportTabularData to data client #DATA-3444 --- src/app/data-client.ts | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/app/data-client.ts b/src/app/data-client.ts index c86a0e24f..ed36e21d5 100644 --- a/src/app/data-client.ts +++ b/src/app/data-client.ts @@ -38,6 +38,22 @@ interface TabularData { timeReceived?: Date; } +interface TabularDataPoint { + partId: string; + resourceName: string; + resourceSubtype: string; + methodName: string; + timeCaptured: Date; + organizationId: string; + locationId: string; + robotName: string; + robotId: string; + partName: string; + methodParameters: JsonValue; + tags: string[]; + payload: JsonValue; +} + export type Dataset = Partial & { created?: Date; }; @@ -53,6 +69,55 @@ export class DataClient { this.dataSyncClient = createPromiseClient(DataSyncService, transport); } + async exportTabularData( + partId: string, + resourceName: string, + resourceSubtype: string, + methodName: string, + startTime?: Date, + endTime?: Date + ) { + const interval = new CaptureInterval(); + if (startTime) { + interval.start = Timestamp.fromDate(startTime); + } + if (endTime) { + interval.end = Timestamp.fromDate(endTime); + } + + const req = { + partId, + resourceName, + resourceSubtype, + methodName, + interval, + }; + + const responses = this.dataClient.exportTabularData(req); + + const dataArray: TabularDataPoint[] = []; + + for await (const response of responses) { + dataArray.push({ + partId: response.partId, + resourceName: response.resourceName, + resourceSubtype: response.resourceSubtype, + methodName: response.methodName, + timeCaptured: response.timeCaptured?.toDate(), + organizationId: response.organizationId, + locationId: response.locationId, + robotName: response.robotName, + robotId: response.robotId, + partName: response.partName, + methodParameters: response.methodParameters.toJson(), + tags: response.tags, + payload: response.payload.toJson(), + }); + } + + return dataArray; + } + /** * Obtain unified tabular data and metadata, queried with SQL. * From b932b4e852b91d507d8cf5e300fc55b39b509a31 Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Fri, 13 Dec 2024 18:09:04 -0500 Subject: [PATCH 2/7] Start setting up tests #DATA-3444 --- src/app/data-client.spec.ts | 102 ++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/app/data-client.spec.ts b/src/app/data-client.spec.ts index 217cb865a..002894f79 100644 --- a/src/app/data-client.spec.ts +++ b/src/app/data-client.spec.ts @@ -28,6 +28,8 @@ import { DeleteBinaryDataByFilterResponse, DeleteBinaryDataByIDsResponse, DeleteTabularDataResponse, + ExportTabularDataRequest, + ExportTabularDataResponse, Filter, GetDatabaseConnectionRequest, GetDatabaseConnectionResponse, @@ -102,6 +104,106 @@ describe('DataClient tests', () => { locationId: 'testLocationId', }); + describe('exportTabularData tests', () => { + const sharedAttributes = { + partId: 'partId1', + resourceName: 'resource1', + resourceSubtype: 'resource1:subtype', + methodName: 'Readings', + organizationId: 'orgId1', + locationId: 'locationId1', + robotName: 'robot1', + robotId: 'robotId1', + partName: 'part1', + tags: [], + } + const timeCaptured1 = new Date(2024, 1, 1); + const timeCaptured2 = new Date(2024, 1, 2); + const tabDataResponse1 = new ExportTabularDataResponse({ + ...sharedAttributes, + methodParameters: Struct.fromJson({ key: 'param1' }), + timeCaptured: Timestamp.fromDate(timeCaptured1), + payload: Struct.fromJson({ key: 'value1' }) + }); + const tabDataResponse2 = new ExportTabularDataResponse({ + ...sharedAttributes, + methodParameters: Struct.fromJson({ key: 'param2' }), + timeCaptured: Timestamp.fromDate(timeCaptured2), + payload: Struct.fromJson({ key: 'value2' }) + }); + + let capReq: ExportTabularDataRequest; + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(DataService, { + exportTabularData: (req) => ({ + [Symbol.asyncIterator]: async function* () { + await new Promise((resolve) => { + resolve(''); + }); + capReq = req; + yield tabDataResponse1; + yield tabDataResponse2; + } + }), + }); + }); + }); + + it('gets tabular data', async () => { + const data = await subject().exportTabularData( + 'partId1', + 'resource1', + 'resource1:subtype', + 'Readings', + ); + + expect(data.length).toEqual(2); + const { + methodParameters: methodParameters1, + timeCaptured: timeCaptured1, + payload: payload1, + ...attributes1 + } = data[0]; + const { + methodParameters: methodParameters2, + timeCaptured: timeCaptured2, + payload: payload2, + ...attributes2 + } = data[1]; + expect(attributes1).toMatchObject(sharedAttributes); + expect(methodParameters1).toMatchObject({ key: 'param1' }); + expect(timeCaptured1).toEqual(timeCaptured1); + expect(payload1).toMatchObject({ key: 'value1' }); + expect(attributes2).toMatchObject(sharedAttributes); + expect(methodParameters2).toMatchObject({ key: 'param2' }); + expect(timeCaptured2).toEqual(timeCaptured2); + expect(payload2).toMatchObject({ key: 'value2' }); + }); + + it('gets tabular data for an interval', async () => { + const data = await subject().exportTabularData( + 'partId1', + 'resource1', + 'resource1:subtype', + 'Readings', + timeCaptured1, + timeCaptured2 + ); + const expectedRequest = new ExportTabularDataRequest({ + partId: 'partId1', + resourceName: 'resource1', + resourceSubtype: 'resource1:subtype', + methodName: 'Readings', + interval: { + start: Timestamp.fromDate(timeCaptured1), + end: Timestamp.fromDate(timeCaptured2), + } + }); + expect(capReq).toStrictEqual(expectedRequest); + }); + }); + describe('tabularDataBySQL tests', () => { type returnType = JsonValue | Date; const data: Record[] = [ From 8412db18f63037edc3e7edcc30610a7be50bcc1a Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Mon, 16 Dec 2024 11:06:12 -0500 Subject: [PATCH 3/7] Lint/prettier #DATA-3444 --- src/app/data-client.spec.ts | 38 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/data-client.spec.ts b/src/app/data-client.spec.ts index 002894f79..edeb97583 100644 --- a/src/app/data-client.spec.ts +++ b/src/app/data-client.spec.ts @@ -137,7 +137,7 @@ describe('DataClient tests', () => { mockTransport = createRouterTransport(({ service }) => { service(DataService, { exportTabularData: (req) => ({ - [Symbol.asyncIterator]: async function* () { + [Symbol.asyncIterator]: async function* generateResponses() { await new Promise((resolve) => { resolve(''); }); @@ -159,30 +159,26 @@ describe('DataClient tests', () => { ); expect(data.length).toEqual(2); - const { - methodParameters: methodParameters1, + + const expectedResponse1 = { + ...sharedAttributes, + methodParameters: { key: 'param1' }, timeCaptured: timeCaptured1, - payload: payload1, - ...attributes1 - } = data[0]; - const { - methodParameters: methodParameters2, + payload: { key: 'value1' }, + }; + const expectedResponse2 = { + ...sharedAttributes, + methodParameters: { key: 'param2' }, timeCaptured: timeCaptured2, - payload: payload2, - ...attributes2 - } = data[1]; - expect(attributes1).toMatchObject(sharedAttributes); - expect(methodParameters1).toMatchObject({ key: 'param1' }); - expect(timeCaptured1).toEqual(timeCaptured1); - expect(payload1).toMatchObject({ key: 'value1' }); - expect(attributes2).toMatchObject(sharedAttributes); - expect(methodParameters2).toMatchObject({ key: 'param2' }); - expect(timeCaptured2).toEqual(timeCaptured2); - expect(payload2).toMatchObject({ key: 'value2' }); + payload: { key: 'value2' }, + }; + + expect(data[0]).toMatchObject(expectedResponse1); + expect(data[1]).toMatchObject(expectedResponse2); }); it('gets tabular data for an interval', async () => { - const data = await subject().exportTabularData( + await subject().exportTabularData( 'partId1', 'resource1', 'resource1:subtype', @@ -190,6 +186,7 @@ describe('DataClient tests', () => { timeCaptured1, timeCaptured2 ); + const expectedRequest = new ExportTabularDataRequest({ partId: 'partId1', resourceName: 'resource1', @@ -200,6 +197,7 @@ describe('DataClient tests', () => { end: Timestamp.fromDate(timeCaptured2), } }); + expect(capReq).toStrictEqual(expectedRequest); }); }); From 6047d801f2704a5b36e04f0b512abaa43072a4fd Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Mon, 16 Dec 2024 11:11:29 -0500 Subject: [PATCH 4/7] Improve needed await/resolve #DATA-3444 --- src/app/data-client.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/data-client.spec.ts b/src/app/data-client.spec.ts index edeb97583..46697c5c9 100644 --- a/src/app/data-client.spec.ts +++ b/src/app/data-client.spec.ts @@ -138,9 +138,7 @@ describe('DataClient tests', () => { service(DataService, { exportTabularData: (req) => ({ [Symbol.asyncIterator]: async function* generateResponses() { - await new Promise((resolve) => { - resolve(''); - }); + await Promise.resolve(); capReq = req; yield tabDataResponse1; yield tabDataResponse2; From a45b5f6d2f0044f2b8ea3a03f6928c8411c80ad6 Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Mon, 16 Dec 2024 11:22:17 -0500 Subject: [PATCH 5/7] Add comments for exportTabularData #DATA-3444 --- src/app/data-client.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/app/data-client.ts b/src/app/data-client.ts index ed36e21d5..fca1928da 100644 --- a/src/app/data-client.ts +++ b/src/app/data-client.ts @@ -69,6 +69,19 @@ export class DataClient { this.dataSyncClient = createPromiseClient(DataSyncService, transport); } + /** + * Obtain unified tabular data and metadata from the specified data source. + * + * @param partId The ID of the part that owns the data + * @param resourceName The name of the requested resource that captured the + * data + * @param resourceSubtype The subtype of the requested resource that captured + * the data + * @param methodName The data capture method name + * @param startTime Optional start time (`Date` object) for requesting a specific range of data + * @param endTime Optional end time (`Date` object) for requesting a specific range of data + * @returns An array of unified tabular data and metadata. + */ async exportTabularData( partId: string, resourceName: string, From b1c7f16e6c23fd9559e4b0cc294df54e0b9058d6 Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Mon, 16 Dec 2024 17:49:37 -0500 Subject: [PATCH 6/7] Update type #DATA-3444 --- src/app/data-client.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/data-client.ts b/src/app/data-client.ts index fca1928da..bea288e31 100644 --- a/src/app/data-client.ts +++ b/src/app/data-client.ts @@ -43,15 +43,15 @@ interface TabularDataPoint { resourceName: string; resourceSubtype: string; methodName: string; - timeCaptured: Date; + timeCaptured: Date | undefined; organizationId: string; locationId: string; robotName: string; robotId: string; partName: string; - methodParameters: JsonValue; + methodParameters: JsonValue | undefined; tags: string[]; - payload: JsonValue; + payload: JsonValue | undefined; } export type Dataset = Partial & { @@ -122,9 +122,9 @@ export class DataClient { robotName: response.robotName, robotId: response.robotId, partName: response.partName, - methodParameters: response.methodParameters.toJson(), + methodParameters: response.methodParameters?.toJson(), tags: response.tags, - payload: response.payload.toJson(), + payload: response.payload?.toJson(), }); } From a704ebff6162a71a277d7dcb1bedf9937d1d4d5e Mon Sep 17 00:00:00 2001 From: Katie Peters Date: Tue, 17 Dec 2024 10:52:52 -0500 Subject: [PATCH 7/7] Run prettier #DATA-3444 --- src/app/data-client.spec.ts | 12 ++++++------ src/app/data-client.ts | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/data-client.spec.ts b/src/app/data-client.spec.ts index 46697c5c9..398c9bdc4 100644 --- a/src/app/data-client.spec.ts +++ b/src/app/data-client.spec.ts @@ -116,20 +116,20 @@ describe('DataClient tests', () => { robotId: 'robotId1', partName: 'part1', tags: [], - } + }; const timeCaptured1 = new Date(2024, 1, 1); const timeCaptured2 = new Date(2024, 1, 2); const tabDataResponse1 = new ExportTabularDataResponse({ ...sharedAttributes, methodParameters: Struct.fromJson({ key: 'param1' }), timeCaptured: Timestamp.fromDate(timeCaptured1), - payload: Struct.fromJson({ key: 'value1' }) + payload: Struct.fromJson({ key: 'value1' }), }); const tabDataResponse2 = new ExportTabularDataResponse({ ...sharedAttributes, methodParameters: Struct.fromJson({ key: 'param2' }), timeCaptured: Timestamp.fromDate(timeCaptured2), - payload: Struct.fromJson({ key: 'value2' }) + payload: Struct.fromJson({ key: 'value2' }), }); let capReq: ExportTabularDataRequest; @@ -142,7 +142,7 @@ describe('DataClient tests', () => { capReq = req; yield tabDataResponse1; yield tabDataResponse2; - } + }, }), }); }); @@ -153,7 +153,7 @@ describe('DataClient tests', () => { 'partId1', 'resource1', 'resource1:subtype', - 'Readings', + 'Readings' ); expect(data.length).toEqual(2); @@ -193,7 +193,7 @@ describe('DataClient tests', () => { interval: { start: Timestamp.fromDate(timeCaptured1), end: Timestamp.fromDate(timeCaptured2), - } + }, }); expect(capReq).toStrictEqual(expectedRequest); diff --git a/src/app/data-client.ts b/src/app/data-client.ts index bea288e31..75b3de978 100644 --- a/src/app/data-client.ts +++ b/src/app/data-client.ts @@ -78,8 +78,10 @@ export class DataClient { * @param resourceSubtype The subtype of the requested resource that captured * the data * @param methodName The data capture method name - * @param startTime Optional start time (`Date` object) for requesting a specific range of data - * @param endTime Optional end time (`Date` object) for requesting a specific range of data + * @param startTime Optional start time (`Date` object) for requesting a + * specific range of data + * @param endTime Optional end time (`Date` object) for requesting a specific + * range of data * @returns An array of unified tabular data and metadata. */ async exportTabularData(