Skip to content
Merged
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
98 changes: 98 additions & 0 deletions src/app/data-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
DeleteBinaryDataByFilterResponse,
DeleteBinaryDataByIDsResponse,
DeleteTabularDataResponse,
ExportTabularDataRequest,
ExportTabularDataResponse,
Filter,
GetDatabaseConnectionRequest,
GetDatabaseConnectionResponse,
Expand Down Expand Up @@ -102,6 +104,102 @@ 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* generateResponses() {
await Promise.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 expectedResponse1 = {
...sharedAttributes,
methodParameters: { key: 'param1' },
timeCaptured: timeCaptured1,
payload: { key: 'value1' },
};
const expectedResponse2 = {
...sharedAttributes,
methodParameters: { key: 'param2' },
timeCaptured: timeCaptured2,
payload: { key: 'value2' },
};

expect(data[0]).toMatchObject(expectedResponse1);
expect(data[1]).toMatchObject(expectedResponse2);
});

it('gets tabular data for an interval', async () => {
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<string, returnType>[] = [
Expand Down
80 changes: 80 additions & 0 deletions src/app/data-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ interface TabularData {
timeReceived?: Date;
}

interface TabularDataPoint {
partId: string;
resourceName: string;
resourceSubtype: string;
methodName: string;
timeCaptured: Date | undefined;
organizationId: string;
locationId: string;
robotName: string;
robotId: string;
partName: string;
methodParameters: JsonValue | undefined;
tags: string[];
payload: JsonValue | undefined;
}

export type Dataset = Partial<PBDataset> & {
created?: Date;
};
Expand All @@ -53,6 +69,70 @@ 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,
resourceSubtype: string,
methodName: string,
startTime?: Date,
endTime?: Date
) {
Copy link
Member Author

Choose a reason for hiding this comment

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

@stuqdog Wondering if I should change this to return AsyncIterableIterator<TabularDataPoint>. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm... I suspect we need to be awaiting ourselves either way to convert into a TabularDataPoint (or get into some funky async functional mapping). I guess that might be useful if someone just wants to stream all tabular data as it continues to come in? That seems like it adds more complexity/work to users so my thought is no, but there may be a use case I'm not thinking about (could someone use that to tail all data as it comes in? is that a use case we expect to be common?).

tl;dr weakly I support keeping as-is but open to being convinced otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm thinking of the use case where someone wants to retrieve a lot of data. Possibly even download it as a file

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.
*
Expand Down
Loading