Skip to content

Commit

Permalink
DATA-3446 Add exportTabularData to DataClient (#316)
Browse files Browse the repository at this point in the history
* Add exportTabularData to DataClient + basic test #DATA-3446

* Simplify exportTabularData response handling; use Timestamp.fromDateTime #DATA-3446

* Change return type to a list #DATA-3446

* Update returns comment #DATA-3446
  • Loading branch information
katiepeters authored Dec 19, 2024
1 parent 7d5bbb2 commit 8fc978f
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 0 deletions.
108 changes: 108 additions & 0 deletions lib/src/app/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,66 @@ import '../utils.dart';
/// {@category Viam SDK}
typedef DatabaseConnection = GetDatabaseConnectionResponse;

/// Represents a tabular data point and its associated metadata.
class TabularDataPoint {
/// The robot part ID
final String partId;

/// The resource name
final String resourceName;

/// The resource subtype
/// Example: `rdk:component:sensor`
final String resourceSubtype;

/// The method used for data capture
/// Example: `Readings`
final String methodName;

/// The time at which the data point was captured
final DateTime timeCaptured;

/// The organization ID
final String organizationId;

/// The location ID
final String locationId;

/// The robot name
final String robotName;

/// The robot ID
final String robotId;

/// The robot part name
final String partName;

/// Additional parameters associated with the data capture method
final dynamic methodParameters;

/// A list of tags associated with the data point
final List<String> tags;

/// The captured data
final Map<String, dynamic> payload;

TabularDataPoint({
required this.partId,
required this.resourceName,
required this.resourceSubtype,
required this.methodName,
required this.timeCaptured,
required this.organizationId,
required this.locationId,
required this.robotName,
required this.robotId,
required this.partName,
required this.methodParameters,
required this.tags,
required this.payload,
});
}

/// gRPC client used for retrieving, uploading, and modifying stored data from app.viam.com.
///
/// All calls must be authenticated.
Expand Down Expand Up @@ -103,6 +163,54 @@ class DataClient {
return response.rawData.map((e) => BsonCodec.deserialize(BsonBinary.from(e))).toList();
}

/// Obtain unified tabular data and metadata from the specified data source.
///
/// Returns a list of unified tabular data and metadata.
///
/// For more information, see [Data Client API](https://docs.viam.com/appendix/apis/data-client/).
Future<List<TabularDataPoint>> exportTabularData(
String partId,
String resourceName,
String resourceSubtype,
String methodName,
DateTime? startTime,
DateTime? endTime,
) async {
final interval = CaptureInterval();
if (startTime != null) {
interval.start = Timestamp.fromDateTime(startTime);
}
if (endTime != null) {
interval.end = Timestamp.fromDateTime(endTime);
}

final request = ExportTabularDataRequest()
..partId = partId
..resourceName = resourceName
..resourceSubtype = resourceSubtype
..methodName = methodName
..interval = interval;

return _dataClient
.exportTabularData(request)
.map((response) => TabularDataPoint(
partId: response.partId,
resourceName: response.resourceName,
resourceSubtype: response.resourceSubtype,
methodName: response.methodName,
timeCaptured: response.timeCaptured.toDateTime(),
organizationId: response.organizationId,
locationId: response.locationId,
robotName: response.robotName,
robotId: response.robotId,
partName: response.partName,
methodParameters: response.methodParameters.toMap(),
tags: response.tags,
payload: response.payload.toMap(),
))
.toList();
}

/// Delete tabular data older than a provided number of days from an organization.
///
/// Returns the number of pieces of data that were deleted.
Expand Down
94 changes: 94 additions & 0 deletions test/unit_test/app/data_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,100 @@ void main() {
expect(response, equals(data));
});

test('exportTabularData', () async {
final timeCaptured1 = DateTime.utc(2023, 1, 1);
final timeCaptured2 = DateTime.utc(2023, 1, 2);
final Map<String, dynamic> methodParams1 = {};
final Map<String, dynamic> methodParams2 = {'key': 'method2'};
final Map<String, dynamic> payload1 = {'key': 'value1'};
final Map<String, dynamic> payload2 = {'key': 'value2'};

final List<TabularDataPoint> data = [
TabularDataPoint(
partId: 'partId1',
resourceName: 'resourceName1',
resourceSubtype: 'resourceSubtype1',
methodName: 'Readings',
timeCaptured: timeCaptured1,
organizationId: 'orgId1',
locationId: 'locationId1',
robotName: 'robot1',
robotId: 'robotId1',
partName: 'part1',
methodParameters: methodParams1,
tags: [],
payload: payload1,
),
TabularDataPoint(
partId: 'partId1',
resourceName: 'resourceName1',
resourceSubtype: 'resourceSubtype1',
methodName: 'Readings',
timeCaptured: timeCaptured2,
organizationId: 'orgId1',
locationId: 'locationId1',
robotName: 'robot1',
robotId: 'robotId1',
partName: 'part1',
methodParameters: methodParams2,
tags: [],
payload: payload2,
),
];

when(serviceClient.exportTabularData(any)).thenAnswer((_) => MockResponseStream.list([
ExportTabularDataResponse(
partId: 'partId1',
resourceName: 'resourceName1',
resourceSubtype: 'resourceSubtype1',
methodName: 'Readings',
timeCaptured: Timestamp.fromDateTime(timeCaptured1),
organizationId: 'orgId1',
locationId: 'locationId1',
robotName: 'robot1',
robotId: 'robotId1',
partName: 'part1',
methodParameters: methodParams1.toStruct(),
tags: [],
payload: payload1.toStruct(),
),
ExportTabularDataResponse(
partId: 'partId1',
resourceName: 'resourceName1',
resourceSubtype: 'resourceSubtype1',
methodName: 'Readings',
timeCaptured: Timestamp.fromDateTime(timeCaptured2),
organizationId: 'orgId1',
locationId: 'locationId1',
robotName: 'robot1',
robotId: 'robotId1',
partName: 'part1',
methodParameters: methodParams2.toStruct(),
tags: [],
payload: payload2.toStruct(),
),
]));

final response = await dataClient.exportTabularData('partId1', 'resourceName1', 'resourceSubtype1', 'methodName', null, null);
var index = 0;
for (final point in response) {
expect(point.partId, equals(data[index].partId));
expect(point.resourceName, equals(data[index].resourceName));
expect(point.resourceSubtype, equals(data[index].resourceSubtype));
expect(point.methodName, equals(data[index].methodName));
expect(point.timeCaptured, equals(data[index].timeCaptured));
expect(point.organizationId, equals(data[index].organizationId));
expect(point.locationId, equals(data[index].locationId));
expect(point.robotName, equals(data[index].robotName));
expect(point.robotId, equals(data[index].robotId));
expect(point.partName, equals(data[index].partName));
expect(point.methodParameters, equals(data[index].methodParameters));
expect(point.tags, equals(data[index].tags));
expect(point.payload, equals(data[index].payload));
index++;
}
});

test('deleteTabularData', () async {
when(serviceClient.deleteTabularData(any))
.thenAnswer((_) => MockResponseFuture.value(DeleteTabularDataResponse()..deletedCount = Int64(12)));
Expand Down

0 comments on commit 8fc978f

Please sign in to comment.