Skip to content

Commit

Permalink
RSDK-7217 - Data wrappers (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
stuqdog authored May 8, 2024
1 parent 88f76c4 commit 05e2994
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 0 deletions.
145 changes: 145 additions & 0 deletions lib/src/app/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:async/async.dart';
import 'package:collection/collection.dart';
import 'package:fixnum/fixnum.dart';
import 'package:viam_sdk/protos/app/data.dart';
import 'package:viam_sdk/src/gen/google/protobuf/any.pb.dart';
import 'package:viam_sdk/src/utils.dart';

Expand All @@ -13,6 +14,8 @@ import '../gen/app/datasync/v1/data_sync.pbgrpc.dart' hide CaptureInterval;
import '../gen/google/protobuf/timestamp.pb.dart';
import '../media/image.dart';

typedef DatabaseConnection = GetDatabaseConnectionResponse;

/// gRPC client for the [DataClient]. Used for retrieving stored data from app.viam.com.
///
/// All calls must be authenticated.
Expand Down Expand Up @@ -133,6 +136,148 @@ class DataClient {
return response.data.map((e) => e.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.
Future<int> deleteTabularData(String organizationId, int olderThanDays) async {
final request = DeleteTabularDataRequest()
..organizationId = organizationId
..deleteOlderThanDays = olderThanDays;
final response = await _dataClient.deleteTabularData(request);
return response.deletedCount.toInt();
}

/// Delete binary data based on an optionally provided filter.
/// If a [filter] is not provided, all data will be deleted.
///
/// Returns the number of pieces of data that were deleted.
Future<int> deleteBinaryDataByFilter(Filter? filter, {bool includeInternalData = false}) async {
final request = DeleteBinaryDataByFilterRequest()
..includeInternalData = includeInternalData
..filter = filter ?? Filter();
final response = await _dataClient.deleteBinaryDataByFilter(request);
return response.deletedCount.toInt();
}

/// Delete binary data based on data ID.
///
/// Returns the number of pieces of data that were deleted.
Future<int> deleteBinaryDataByIds(List<BinaryID> binaryIds) async {
final request = DeleteBinaryDataByIDsRequest()..binaryIds.addAll(binaryIds);
final response = await _dataClient.deleteBinaryDataByIDs(request);
return response.deletedCount.toInt();
}

/// Adds tags to binary data based on IDs.
Future<void> addTagsToBinaryDataByIds(List<String> tags, List<BinaryID> binaryIds) async {
final request = AddTagsToBinaryDataByIDsRequest()
..tags.addAll(tags)
..binaryIds.addAll(binaryIds);
await _dataClient.addTagsToBinaryDataByIDs(request);
}

/// Adds tags to binary data based on a filter.
/// If no [filter] is provided, all binary data will be tagged.
Future<void> addTagsToBinaryDataByFilter(List<String> tags, Filter? filter) async {
final request = AddTagsToBinaryDataByFilterRequest()
..tags.addAll(tags)
..filter = filter ?? Filter();
await _dataClient.addTagsToBinaryDataByFilter(request);
}

/// Remove tags from binary data based on filter.
/// If a [filter] is not provided, the tags will be removed from all data.
///
/// Returns the number of tags deleted.
Future<int> removeTagsFromBinaryDataByFilter(List<String> tags, Filter? filter) async {
final request = RemoveTagsFromBinaryDataByFilterRequest()
..tags.addAll(tags)
..filter = filter ?? Filter();
final response = await _dataClient.removeTagsFromBinaryDataByFilter(request);
return response.deletedCount.toInt();
}

/// Remove tags from binary data based on IDs.
///
/// Returns the number of tags deleted.
Future<int> removeTagsFromBinaryDataByIds(List<String> tags, List<BinaryID> binaryIds) async {
final request = RemoveTagsFromBinaryDataByIDsRequest()
..tags.addAll(tags)
..binaryIds.addAll(binaryIds);
final response = await _dataClient.removeTagsFromBinaryDataByIDs(request);
return response.deletedCount.toInt();
}

/// Add a bounding box to an image by ID, with x and y coordinates normalized from 0 to 1.
///
/// Returns the bounding box ID.
Future<String> addBoundingBoxToImageById(
String label, BinaryID binaryId, double xMinNormalized, double yMinNormalized, double xMaxNormalized, double yMaxNormalized) async {
final request = AddBoundingBoxToImageByIDRequest()
..label = label
..binaryId = binaryId
..xMinNormalized = xMinNormalized
..yMinNormalized = yMinNormalized
..xMaxNormalized = xMaxNormalized
..yMaxNormalized = yMaxNormalized;
final response = await _dataClient.addBoundingBoxToImageByID(request);
return response.bboxId;
}

/// Removes a bounding box from an image based on bbox ID and image ID.
Future<void> removeBoundingBoxFromImageById(String bboxId, BinaryID binaryId) async {
final request = RemoveBoundingBoxFromImageByIDRequest()
..bboxId = bboxId
..binaryId = binaryId;
await _dataClient.removeBoundingBoxFromImageByID(request);
}

/// Returns a list of tags based on a filter.
/// If no [filter] is provided, all tags will be returned.
Future<List<String>> tagsByFilter(Filter? filter) async {
final request = TagsByFilterRequest()..filter = filter ?? Filter();
final response = await _dataClient.tagsByFilter(request);
return response.tags;
}

/// Returns a list of bounding box labels based on a filter.
/// If no [filter] is provided, all labels will be returned.
Future<List<String>> boundingBoxLabelsByFilter(Filter? filter) async {
final request = BoundingBoxLabelsByFilterRequest()..filter = filter ?? Filter();
final response = await _dataClient.boundingBoxLabelsByFilter(request);
return response.labels;
}

/// Returns a database connection to access a MongoDB Atlas Data Federation instance.
Future<DatabaseConnection> getDatabaseConnection(String organizationId) async {
final request = GetDatabaseConnectionRequest()..organizationId = organizationId;
return await _dataClient.getDatabaseConnection(request);
}

/// Configures a database user for Viam's MongoDB Atlas Data Federation instance.
Future<void> configureDatabaseUser(String organizationId, String password) async {
final request = ConfigureDatabaseUserRequest()
..password = password
..organizationId = organizationId;
await _dataClient.configureDatabaseUser(request);
}

/// Adds binary data to a dataset based on IDs.
Future<void> addBinaryDataToDatasetByIds(List<BinaryID> binaryIds, String datasetId) async {
final request = AddBinaryDataToDatasetByIDsRequest()
..binaryIds.addAll(binaryIds)
..datasetId = datasetId;
await _dataClient.addBinaryDataToDatasetByIDs(request);
}

/// Removes binary data from a dataset based on IDs.
Future<void> removeBinaryDataFromDatasetByIds(List<BinaryID> binaryIds, String datasetId) async {
final request = RemoveBinaryDataFromDatasetByIDsRequest()
..binaryIds.addAll(binaryIds)
..datasetId = datasetId;
await _dataClient.removeBinaryDataFromDatasetByIDs(request);
}

/// Upload an image to Viam's Data Manager
///
/// If no name is provided, the current timestamp will be used as the filename.
Expand Down
119 changes: 119 additions & 0 deletions test/unit_test/app/data_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import 'package:mockito/mockito.dart';
import 'package:viam_sdk/protos/app/data.dart';
import 'package:viam_sdk/protos/app/data_sync.dart' hide CaptureInterval;
import 'package:viam_sdk/src/app/data.dart';
import 'package:viam_sdk/src/gen/app/data/v1/data.pb.dart';
import 'package:viam_sdk/src/gen/app/data/v1/data.pbgrpc.dart';
import 'package:viam_sdk/src/gen/app/data/v1/data.pbjson.dart';
import 'package:viam_sdk/src/media/image.dart';
import 'package:viam_sdk/src/utils.dart';

Expand Down Expand Up @@ -150,6 +153,122 @@ void main() {
final response = await dataClient.tabularDataByMql('some_org_id', [Uint8List.fromList('some_query'.codeUnits)]);
expect(response, equals(data));
});

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

final response = await dataClient.deleteTabularData('some_org_id', 5);
expect(response, equals(12));
});

test('deleteBinaryDataByFilter', () async {
when(serviceClient.deleteBinaryDataByFilter(any))
.thenAnswer((_) => MockResponseFuture.value(DeleteBinaryDataByFilterResponse()..deletedCount = Int64(12)));

final response = await dataClient.deleteBinaryDataByFilter(Filter(), includeInternalData: true);
expect(response, equals(12));
});

test('deleteBinaryDataByIds', () async {
when(serviceClient.deleteBinaryDataByIDs(any))
.thenAnswer((_) => MockResponseFuture.value(DeleteBinaryDataByIDsResponse()..deletedCount = Int64(12)));

final response = await dataClient.deleteBinaryDataByIds([BinaryID(fileId: 'file', organizationId: 'orgId', locationId: 'locId')]);
expect(response, equals(12));
});

test('addTagsToBinaryDataByIds', () async {
when(serviceClient.addTagsToBinaryDataByIDs(any)).thenAnswer((_) => MockResponseFuture.value(AddTagsToBinaryDataByIDsResponse()));
await dataClient.addTagsToBinaryDataByIds(['tags'], [BinaryID(fileId: 'file', organizationId: 'orgId', locationId: 'locId')]);
verify(serviceClient.addTagsToBinaryDataByIDs(any)).called(1);
});

test('addTagsToBinaryDataByFilter', () async {
when(serviceClient.addTagsToBinaryDataByFilter(any))
.thenAnswer((_) => MockResponseFuture.value(AddTagsToBinaryDataByFilterResponse()));
await dataClient.addTagsToBinaryDataByFilter(['tags'], Filter());
verify(serviceClient.addTagsToBinaryDataByFilter(any)).called(1);
});

test('removeTagsFromBinaryDataByFilter', () async {
when(serviceClient.removeTagsFromBinaryDataByFilter(any))
.thenAnswer((_) => MockResponseFuture.value(RemoveTagsFromBinaryDataByFilterResponse(deletedCount: Int64(15))));

final response = await dataClient.removeTagsFromBinaryDataByFilter(['tags'], Filter());
expect(response, equals(15));
});

test('removeTagsFromBinaryDataByIds', () async {
when(serviceClient.removeTagsFromBinaryDataByIDs(any))
.thenAnswer((_) => MockResponseFuture.value(RemoveTagsFromBinaryDataByIDsResponse(deletedCount: Int64(18))));

final response = await dataClient
.removeTagsFromBinaryDataByIds(['tags'], [BinaryID(organizationId: 'orgId', locationId: 'locId', fileId: 'fileId')]);
expect(response, equals(18));
});

test('addBoundingBoxToImageById', () async {
when(serviceClient.addBoundingBoxToImageByID(any))
.thenAnswer((_) => MockResponseFuture.value(AddBoundingBoxToImageByIDResponse(bboxId: 'bboxId')));

final response = await dataClient.addBoundingBoxToImageById(
'label', BinaryID(organizationId: 'orgId', locationId: 'locId', fileId: 'fileId'), 0.1, 0.2, 0.3, 0.4);
expect(response, equals('bboxId'));
});

test('removeBoundingBoxFromImageById', () async {
when(serviceClient.removeBoundingBoxFromImageByID(any))
.thenAnswer((_) => MockResponseFuture.value(RemoveBoundingBoxFromImageByIDResponse()));
await dataClient.removeBoundingBoxFromImageById('bboxId', BinaryID(organizationId: 'orgId', locationId: 'locId', fileId: 'fileId'));
verify(serviceClient.removeBoundingBoxFromImageByID(any)).called(1);
});

test('tagsByFilter', () async {
when(serviceClient.tagsByFilter(any)).thenAnswer((_) => MockResponseFuture.value(TagsByFilterResponse(tags: ['tags'])));

final response = await dataClient.tagsByFilter(Filter());
expect(response, equals(['tags']));
});

test('boundingBoxLabelsByFilter', () async {
when(serviceClient.boundingBoxLabelsByFilter(any))
.thenAnswer((_) => MockResponseFuture.value(BoundingBoxLabelsByFilterResponse(labels: ['label'])));

final response = await dataClient.boundingBoxLabelsByFilter(Filter());
expect(response, equals(['label']));
});

test('getDatabaseConnection', () async {
when(serviceClient.getDatabaseConnection(any)).thenAnswer((_) =>
MockResponseFuture.value(GetDatabaseConnectionResponse(hostname: 'hostname', mongodbUri: 'mongo', hasDatabaseUser: true)));

final response = await dataClient.getDatabaseConnection('orgId');
expect(response.hostname, equals('hostname'));
expect(response.mongodbUri, equals('mongo'));
expect(response.hasDatabaseUser, equals(true));
});

test('configureDatabaseUser', () async {
when(serviceClient.configureDatabaseUser(any)).thenAnswer((_) => MockResponseFuture.value(ConfigureDatabaseUserResponse()));
await dataClient.configureDatabaseUser('orgId', 'password');
verify(serviceClient.configureDatabaseUser(any)).called(1);
});

test('addBinaryDataToDatasetByIds', () async {
when(serviceClient.addBinaryDataToDatasetByIDs(any))
.thenAnswer((_) => MockResponseFuture.value(AddBinaryDataToDatasetByIDsResponse()));
await dataClient.addBinaryDataToDatasetByIds([BinaryID(fileId: 'fileId', organizationId: 'orgId', locationId: 'locId')], 'dataset');
verify(serviceClient.addBinaryDataToDatasetByIDs(any)).called(1);
});

test('removeBinaryDataFromDatasetByIds', () async {
when(serviceClient.removeBinaryDataFromDatasetByIDs(any))
.thenAnswer((_) => MockResponseFuture.value(RemoveBinaryDataFromDatasetByIDsResponse()));
await dataClient
.removeBinaryDataFromDatasetByIds([BinaryID(fileId: 'fileId', organizationId: 'orgId', locationId: 'locId')], 'dataset');
verify(serviceClient.removeBinaryDataFromDatasetByIDs(any)).called(1);
});
});

group('DataSync Tests', () {
Expand Down

0 comments on commit 05e2994

Please sign in to comment.