From e50f6bf961f5ca9a27d4879090a3659cf0cc5ec2 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Tue, 4 Mar 2025 09:15:31 -0500 Subject: [PATCH 01/18] user defined metadata crud apis --- src/app/app-client.ts | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 5260d1328..9fd3090a8 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -11,6 +11,10 @@ import { CreateModuleResponse, Fragment, FragmentVisibility, + GetLocationMetadataResponse, + GetOrganizationMetadataResponse, + GetRobotMetadataResponse, + GetRobotPartMetadataResponse, GetRobotPartLogsResponse, GetRobotPartResponse, ListOrganizationMembersResponse, @@ -29,6 +33,10 @@ import { RobotPartHistoryEntry, RotateKeyResponse, RoverRentalRobot, + UpdateLocationMetadataResponse, + UpdateOrganizationMetadataResponse, + UpdateRobotMetadataResponse, + UpdateRobotPartMetadataResponse, Visibility, } from '../gen/app/v1/app_pb'; import type { LogEntry } from '../gen/common/v1/common_pb'; @@ -1175,4 +1183,100 @@ export class AppClient { ): Promise { return this.client.createKeyFromExistingKeyAuthorizations({ id }); } + + /** + * Retrieves user-defined metadata for an organization. + * + * @param organizationId The ID of the organization + * @returns The metadata associated with the organization + */ + async getOrganizationMetadata(organizationId: string): Promise { + return this.client.getOrganizationMetadata({ organizationId }); + } + + /** + * Updates user-defined metadata for an organization. + * + * @param organizationId The ID of the organization + * @param data The metadata to update + * @returns Response indicating success or failure + */ + async updateOrganizationMetadata( + organizationId: string, + data: Record + ): Promise { + return this.client.updateOrganizationMetadata({ organizationId, data }); + } + + /** + * Retrieves user-defined metadata for a location. + * + * @param locationId The ID of the location + * @returns The metadata associated with the location + */ + async getLocationMetadata(locationId: string): Promise { + return this.client.getLocationMetadata({ locationId }); + } + + /** + * Updates user-defined metadata for a location. + * + * @param locationId The ID of the location + * @param data The metadata to update + * @returns Response indicating success or failure + */ + async updateLocationMetadata( + locationId: string, + data: Record + ): Promise { + return this.client.updateLocationMetadata({ locationId, data }); + } + + /** + * Retrieves user-defined metadata for a robot. + * + * @param id The ID of the robot + * @returns The metadata associated with the robot + */ + async getRobotMetadata(id: string): Promise { + return this.client.getRobotMetadata({ id }); + } + + /** + * Updates user-defined metadata for a robot. + * + * @param id The ID of the robot + * @param data The metadata to update + * @returns Response indicating success or failure + */ + async updateRobotMetadata( + id: string, + data: Record + ): Promise { + return this.client.updateRobotMetadata({ id, data }); + } + + /** + * Retrieves user-defined metadata for a robot part. + * + * @param id The ID of the robot part + * @returns The metadata associated with the robot part + */ + async getRobotPartMetadata(id: string): Promise { + return this.client.getRobotPartMetadata({ id }); + } + + /** + * Updates user-defined metadata for a robot part. + * + * @param id The ID of the robot part + * @param data The metadata to update + * @returns Response indicating success or failure + */ + async updateRobotPartMetadata( + id: string, + data: Record + ): Promise { + return this.client.updateRobotPartMetadata({ id, data }); + } } From 0c1fbcd8d112e2d7bc9c372e7b18320fc6ecef72 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 11:20:57 -0500 Subject: [PATCH 02/18] Change method signatures to use id as argument --- src/app/app-client.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 9fd3090a8..43c9b7289 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1187,49 +1187,49 @@ export class AppClient { /** * Retrieves user-defined metadata for an organization. * - * @param organizationId The ID of the organization + * @param id The ID of the organization * @returns The metadata associated with the organization */ - async getOrganizationMetadata(organizationId: string): Promise { - return this.client.getOrganizationMetadata({ organizationId }); + async getOrganizationMetadata(id: string): Promise { + return this.client.getOrganizationMetadata({ organizationId: id }); } /** * Updates user-defined metadata for an organization. * - * @param organizationId The ID of the organization + * @param id The ID of the organization * @param data The metadata to update * @returns Response indicating success or failure */ async updateOrganizationMetadata( - organizationId: string, + id: string, data: Record ): Promise { - return this.client.updateOrganizationMetadata({ organizationId, data }); + return this.client.updateOrganizationMetadata({ organizationId: id, data }); } /** * Retrieves user-defined metadata for a location. * - * @param locationId The ID of the location + * @param id The ID of the location * @returns The metadata associated with the location */ - async getLocationMetadata(locationId: string): Promise { - return this.client.getLocationMetadata({ locationId }); + async getLocationMetadata(id: string): Promise { + return this.client.getLocationMetadata({ locationId: id }); } /** * Updates user-defined metadata for a location. * - * @param locationId The ID of the location + * @param id The ID of the location * @param data The metadata to update * @returns Response indicating success or failure */ async updateLocationMetadata( - locationId: string, + id: string, data: Record ): Promise { - return this.client.updateLocationMetadata({ locationId, data }); + return this.client.updateLocationMetadata({ locationId: id, data }); } /** From de976cea9489ca9a574632ddd22496c1eb01caba Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 11:31:50 -0500 Subject: [PATCH 03/18] eslint-disable-next-line @typescript-eslint/no-explicit-any --- src/app/app-client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 43c9b7289..9d0c8e644 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1203,6 +1203,7 @@ export class AppClient { */ async updateOrganizationMetadata( id: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { return this.client.updateOrganizationMetadata({ organizationId: id, data }); @@ -1227,6 +1228,7 @@ export class AppClient { */ async updateLocationMetadata( id: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { return this.client.updateLocationMetadata({ locationId: id, data }); @@ -1251,6 +1253,7 @@ export class AppClient { */ async updateRobotMetadata( id: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { return this.client.updateRobotMetadata({ id, data }); @@ -1275,6 +1278,7 @@ export class AppClient { */ async updateRobotPartMetadata( id: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { return this.client.updateRobotPartMetadata({ id, data }); From 4c889e4670528671cd381a357050a6f3268d4c75 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 12:06:37 -0500 Subject: [PATCH 04/18] Run prettier with write --- src/app/app-client.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 9d0c8e644..8cfa709e1 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1190,7 +1190,9 @@ export class AppClient { * @param id The ID of the organization * @returns The metadata associated with the organization */ - async getOrganizationMetadata(id: string): Promise { + async getOrganizationMetadata( + id: string + ): Promise { return this.client.getOrganizationMetadata({ organizationId: id }); } @@ -1202,7 +1204,7 @@ export class AppClient { * @returns Response indicating success or failure */ async updateOrganizationMetadata( - id: string, + id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { @@ -1227,7 +1229,7 @@ export class AppClient { * @returns Response indicating success or failure */ async updateLocationMetadata( - id: string, + id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { @@ -1252,7 +1254,7 @@ export class AppClient { * @returns Response indicating success or failure */ async updateRobotMetadata( - id: string, + id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { @@ -1265,7 +1267,9 @@ export class AppClient { * @param id The ID of the robot part * @returns The metadata associated with the robot part */ - async getRobotPartMetadata(id: string): Promise { + async getRobotPartMetadata( + id: string + ): Promise { return this.client.getRobotPartMetadata({ id }); } @@ -1277,7 +1281,7 @@ export class AppClient { * @returns Response indicating success or failure */ async updateRobotPartMetadata( - id: string, + id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record ): Promise { From 9c0391680e75f18623513126eb295c1925660046 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 15:44:55 -0500 Subject: [PATCH 05/18] Change robot to machine --- src/app/app-client.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 8cfa709e1..5f2f1e5c8 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1237,23 +1237,23 @@ export class AppClient { } /** - * Retrieves user-defined metadata for a robot. + * Retrieves user-defined metadata for a machine. * - * @param id The ID of the robot - * @returns The metadata associated with the robot + * @param id The ID of the machine + * @returns The metadata associated with the machine */ - async getRobotMetadata(id: string): Promise { + async getMachineMetadata(id: string): Promise { return this.client.getRobotMetadata({ id }); } /** * Updates user-defined metadata for a robot. * - * @param id The ID of the robot + * @param id The ID of the machine * @param data The metadata to update * @returns Response indicating success or failure */ - async updateRobotMetadata( + async updateMachineMetadata( id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record @@ -1262,25 +1262,25 @@ export class AppClient { } /** - * Retrieves user-defined metadata for a robot part. + * Retrieves user-defined metadata for a machine part. * - * @param id The ID of the robot part - * @returns The metadata associated with the robot part + * @param id The ID of the machine part + * @returns The metadata associated with the machine part */ - async getRobotPartMetadata( + async getMachinePartMetadata( id: string ): Promise { return this.client.getRobotPartMetadata({ id }); } /** - * Updates user-defined metadata for a robot part. + * Updates user-defined metadata for a machine part. * - * @param id The ID of the robot part + * @param id The ID of the machine part * @param data The metadata to update * @returns Response indicating success or failure */ - async updateRobotPartMetadata( + async updateMachinePartMetadata( id: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: Record From fef2742eacdb3eb0f228cac5758a6781e76a9047 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 17:05:13 -0500 Subject: [PATCH 06/18] Update any to use protobuf's any --- src/app/app-client.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 5f2f1e5c8..ffe8a5d5e 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -40,6 +40,7 @@ import { Visibility, } from '../gen/app/v1/app_pb'; import type { LogEntry } from '../gen/common/v1/common_pb'; +import { Any } from '@bufbuild/protobuf'; /** * Creates an Authorization object from auth details. @@ -1205,8 +1206,7 @@ export class AppClient { */ async updateOrganizationMetadata( id: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: Record + data: Record ): Promise { return this.client.updateOrganizationMetadata({ organizationId: id, data }); } @@ -1230,8 +1230,7 @@ export class AppClient { */ async updateLocationMetadata( id: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: Record + data: Record ): Promise { return this.client.updateLocationMetadata({ locationId: id, data }); } @@ -1255,8 +1254,7 @@ export class AppClient { */ async updateMachineMetadata( id: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: Record + data: Record ): Promise { return this.client.updateRobotMetadata({ id, data }); } @@ -1282,8 +1280,7 @@ export class AppClient { */ async updateMachinePartMetadata( id: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: Record + data: Record ): Promise { return this.client.updateRobotPartMetadata({ id, data }); } From adef3118cc1c529ec81fe6f04c36ceaae1acf942 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 5 Mar 2025 17:13:30 -0500 Subject: [PATCH 07/18] ran pretty write --- src/app/app-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index ffe8a5d5e..99df27593 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -40,7 +40,7 @@ import { Visibility, } from '../gen/app/v1/app_pb'; import type { LogEntry } from '../gen/common/v1/common_pb'; -import { Any } from '@bufbuild/protobuf'; +import { Any } from '@bufbuild/protobuf'; /** * Creates an Authorization object from auth details. From 90e1a6fffac2340579e91089daf161b7dc2ba9ca Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Thu, 6 Mar 2025 10:16:41 -0500 Subject: [PATCH 08/18] Change update metadata methods to return void --- src/app/app-client.ts | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 99df27593..5b73c9872 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -33,10 +33,6 @@ import { RobotPartHistoryEntry, RotateKeyResponse, RoverRentalRobot, - UpdateLocationMetadataResponse, - UpdateOrganizationMetadataResponse, - UpdateRobotMetadataResponse, - UpdateRobotPartMetadataResponse, Visibility, } from '../gen/app/v1/app_pb'; import type { LogEntry } from '../gen/common/v1/common_pb'; @@ -1202,13 +1198,12 @@ export class AppClient { * * @param id The ID of the organization * @param data The metadata to update - * @returns Response indicating success or failure */ async updateOrganizationMetadata( id: string, data: Record - ): Promise { - return this.client.updateOrganizationMetadata({ organizationId: id, data }); + ): Promise { + await this.client.updateOrganizationMetadata({ organizationId: id, data }); } /** @@ -1226,13 +1221,12 @@ export class AppClient { * * @param id The ID of the location * @param data The metadata to update - * @returns Response indicating success or failure */ async updateLocationMetadata( id: string, data: Record - ): Promise { - return this.client.updateLocationMetadata({ locationId: id, data }); + ): Promise { + await this.client.updateLocationMetadata({ locationId: id, data }); } /** @@ -1250,13 +1244,12 @@ export class AppClient { * * @param id The ID of the machine * @param data The metadata to update - * @returns Response indicating success or failure */ async updateMachineMetadata( id: string, data: Record - ): Promise { - return this.client.updateRobotMetadata({ id, data }); + ): Promise { + await this.client.updateRobotMetadata({ id, data }); } /** @@ -1276,12 +1269,11 @@ export class AppClient { * * @param id The ID of the machine part * @param data The metadata to update - * @returns Response indicating success or failure */ async updateMachinePartMetadata( id: string, data: Record - ): Promise { - return this.client.updateRobotPartMetadata({ id, data }); + ): Promise { + await this.client.updateRobotPartMetadata({ id, data }); } } From 72f92f026a97c6b48733cca45f56e8e48b53eeb0 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Tue, 11 Mar 2025 16:18:07 -0400 Subject: [PATCH 09/18] Update get metadata methods to return maps --- src/app/app-client.spec.ts | 56 +++++++++++++++++++++++++++++++++++- src/app/app-client.ts | 58 ++++++++++++++++++++++++++++---------- 2 files changed, 98 insertions(+), 16 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 8e3870470..94138687c 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1,6 +1,6 @@ import * as pb from '../gen/app/v1/app_pb'; -import { Struct, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; +import { Any, Struct, Value, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; import { createRouterTransport, type Transport } from '@connectrpc/connect'; import { createWritableIterable } from '@connectrpc/connect/protocol'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -1733,4 +1733,58 @@ describe('AppClient tests', () => { expect(response.id).toEqual('id'); }); }); + + describe('getOrganizationMetadata', () => { + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + getOrganizationMetadata: () => new pb.GetOrganizationMetadataResponse(), + }); + }); + }); + + it('should return an empty object if response.data is not an instance of Struct', async () => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + getOrganizationMetadata: () => + new pb.GetOrganizationMetadataResponse({ data: {} }), + }); + }); + + const response = await subject().getOrganizationMetadata('orgId'); + expect(response).toEqual({}); + }); + + it('should return a plain JavaScript object if response.data is a Struct', async () => { + const mockStructData = { + key1: 'value1', + }; + + const structInstance = Struct.fromJson(mockStructData); + console.log('Struct Instance:', structInstance); + + const packedData = Any.pack(structInstance); + console.log('Packed Data Type:', typeof packedData); + console.log('Packed Data Content:', packedData); + + const testResponse = new pb.GetOrganizationMetadataResponse({ + data: packedData, + }); + + console.log('Test Response before Mock:', testResponse); + + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + getOrganizationMetadata: () => testResponse, + }); + }); + + const response = await subject().getOrganizationMetadata('orgId'); + console.log('Test Response:', response); + + expect(response).toEqual({ + key1: 'value1', + }); + }); + }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 5b73c9872..e1d750c6e 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1,4 +1,4 @@ -import type { Struct } from '@bufbuild/protobuf'; +import { Any, Struct } from '@bufbuild/protobuf'; import { createClient, type Client, type Transport } from '@connectrpc/connect'; import { PackageType } from '../gen/app/packages/v1/packages_pb'; import { AppService } from '../gen/app/v1/app_connect'; @@ -12,7 +12,6 @@ import { Fragment, FragmentVisibility, GetLocationMetadataResponse, - GetOrganizationMetadataResponse, GetRobotMetadataResponse, GetRobotPartMetadataResponse, GetRobotPartLogsResponse, @@ -36,7 +35,6 @@ import { Visibility, } from '../gen/app/v1/app_pb'; import type { LogEntry } from '../gen/common/v1/common_pb'; -import { Any } from '@bufbuild/protobuf'; /** * Creates an Authorization object from auth details. @@ -1185,14 +1183,18 @@ export class AppClient { * Retrieves user-defined metadata for an organization. * * @param id The ID of the organization - * @returns The metadata associated with the organization + * @returns The metadata associated with the organization as a plain JS object */ async getOrganizationMetadata( id: string - ): Promise { - return this.client.getOrganizationMetadata({ organizationId: id }); + ): Promise> { + const response = await this.client.getOrganizationMetadata({ organizationId: id }); + if (!(response.data instanceof Struct)) { + return {}; + } + return response.data.toJson() as Record; } - + /** * Updates user-defined metadata for an organization. * @@ -1201,9 +1203,23 @@ export class AppClient { */ async updateOrganizationMetadata( id: string, - data: Record + data: Record ): Promise { - await this.client.updateOrganizationMetadata({ organizationId: id, data }); + const convertedData: Record = {}; + + for (const [key, value] of Object.entries(data)) { + if (value === null || value === undefined) { + continue; + } + + try { + convertedData[key] = Any.pack(value as never); + } catch { + throw new Error(`Invalid metadata value for key "${key}"`); + } + } + + await this.client.updateOrganizationMetadata({ organizationId: id, data: convertedData }); } /** @@ -1212,8 +1228,12 @@ export class AppClient { * @param id The ID of the location * @returns The metadata associated with the location */ - async getLocationMetadata(id: string): Promise { - return this.client.getLocationMetadata({ locationId: id }); + async getLocationMetadata(id: string): Promise> { + const response = await this.client.getLocationMetadata({ locationId: id }); + if (!(response.data instanceof Struct)) { + return {}; + } + return response.data.toJson() as Record; } /** @@ -1235,8 +1255,12 @@ export class AppClient { * @param id The ID of the machine * @returns The metadata associated with the machine */ - async getMachineMetadata(id: string): Promise { - return this.client.getRobotMetadata({ id }); + async getMachineMetadata(id: string): Promise> { + const response = await this.client.getRobotMetadata({ id }); + if (!(response.data instanceof Struct)) { + return {}; + } + return response.data.toJson() as Record; } /** @@ -1260,8 +1284,12 @@ export class AppClient { */ async getMachinePartMetadata( id: string - ): Promise { - return this.client.getRobotPartMetadata({ id }); + ): Promise> { + const response = await this.client.getRobotPartMetadata({ id }); + if (!(response.data instanceof Struct)) { + return {}; + } + return response.data.toJson() as Record; } /** From fbb0976b71978c7b8e7755d4539ba04c2b1bd042 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Tue, 11 Mar 2025 16:49:55 -0400 Subject: [PATCH 10/18] Modify updateOrganizationMetadata and add unit tests --- src/app/app-client.spec.ts | 58 ++++++++++++++++++++++++++++++++++++++ src/app/app-client.ts | 19 ++++--------- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 94138687c..a527509a5 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1787,4 +1787,62 @@ describe('AppClient tests', () => { }); }); }); + + describe('updateOrganizationMetadata', () => { + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateOrganizationMetadata: () => new pb.UpdateOrganizationMetadataResponse(), + }); + }); + }); + + it('should handle empty metadata correctly', async () => { + let capturedRequest; + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateOrganizationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateOrganizationMetadataResponse(); + }, + }); + }); + + await subject().updateOrganizationMetadata('orgId', {}); + + expect(capturedRequest).toEqual({ + organizationId: 'orgId', + data: Struct.fromJson({}), // Should be an empty Struct + }); + }); + + it('should successfully update metadata with valid data', async () => { + const metadata = { + key1: 'value1', + key2: 42, + key3: true, + }; + + const expectedStruct = Struct.fromJson(metadata); + + let capturedRequest; + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateOrganizationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateOrganizationMetadataResponse(); + }, + }); + }); + + await subject().updateOrganizationMetadata('orgId', metadata); + + expect(capturedRequest).toMatchObject({ + organizationId: 'orgId', + data: expect.objectContaining({ + fields: expectedStruct.fields, + }), + }); + }); + }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index e1d750c6e..5a78db3a6 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1205,21 +1205,12 @@ export class AppClient { id: string, data: Record ): Promise { - const convertedData: Record = {}; + const convertedData = Struct.fromJson(data); - for (const [key, value] of Object.entries(data)) { - if (value === null || value === undefined) { - continue; - } - - try { - convertedData[key] = Any.pack(value as never); - } catch { - throw new Error(`Invalid metadata value for key "${key}"`); - } - } - - await this.client.updateOrganizationMetadata({ organizationId: id, data: convertedData }); + await this.client.updateOrganizationMetadata({ + organizationId: id, + data: convertedData, + }); } /** From b16e6900ac057ce137ce8135ad6aac7438a15c50 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 10:14:57 -0400 Subject: [PATCH 11/18] Complete get metadata methods and add unit tests accordingly --- src/app/app-client.spec.ts | 207 ++++++++++++++++++++++++++----------- src/app/app-client.ts | 90 ++++++++++------ 2 files changed, 203 insertions(+), 94 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index a527509a5..1e9475200 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1743,106 +1743,193 @@ describe('AppClient tests', () => { }); }); - it('should return an empty object if response.data is not an instance of Struct', async () => { + it('returns an empty object if there is no Struct', async () => { + const response = await subject().getOrganizationMetadata('orgId'); + expect(response).toEqual({}); + }); + + it('preserves the map key when a Struct is found', async () => { + const testResponse = new pb.GetOrganizationMetadataResponse({ + data: { + myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) + }, + }); + mockTransport = createRouterTransport(({ service }) => { service(AppService, { - getOrganizationMetadata: () => - new pb.GetOrganizationMetadataResponse({ data: {} }), + getOrganizationMetadata: () => testResponse, }); }); const response = await subject().getOrganizationMetadata('orgId'); - expect(response).toEqual({}); + expect(response).toEqual({ myStruct: { key1: 'value1' } }); }); + }); - it('should return a plain JavaScript object if response.data is a Struct', async () => { - const mockStructData = { - key1: 'value1', - }; + // describe('updateOrganizationMetadata', () => { + // beforeEach(() => { + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateOrganizationMetadata: () => new pb.UpdateOrganizationMetadataResponse(), + // }); + // }); + // }); + + // it('should handle empty metadata correctly', async () => { + // let capturedRequest; + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateOrganizationMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateOrganizationMetadataResponse(); + // }, + // }); + // }); + + // await subject().updateOrganizationMetadata('orgId', {}); + + // expect(capturedRequest).toEqual({ + // organizationId: 'orgId', + // data: Struct.fromJson({}), + // }); + // }); + + // it('should successfully update metadata with valid data', async () => { + // const metadata = { + // key1: 'value1', + // key2: 42, + // key3: true, + // }; + + // const expectedStruct = new Struct({ + // fields: { + // key1: Value.fromJson('value1'), + // key2: Value.fromJson(42), + // key3: Value.fromJson(true), + // }, + // }); - const structInstance = Struct.fromJson(mockStructData); - console.log('Struct Instance:', structInstance); + // let capturedRequest; + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateOrganizationMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateOrganizationMetadataResponse(); + // }, + // }); + // }); + + // await subject().updateOrganizationMetadata('orgId', metadata); + + // expect(capturedRequest).toEqual({ + // organizationId: 'orgId', + // data: expect.objectContaining({ + // fields: expectedStruct.fields, + // }), + // }); + // }); + // }); - const packedData = Any.pack(structInstance); - console.log('Packed Data Type:', typeof packedData); - console.log('Packed Data Content:', packedData); + describe('getLocationMetadata', () => { + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + getLocationMetadata: () => new pb.GetLocationMetadataResponse(), + }); + }); + }); - const testResponse = new pb.GetOrganizationMetadataResponse({ - data: packedData, + it('returns an empty object if there is no Struct', async () => { + const response = await subject().getLocationMetadata('orgId'); + expect(response).toEqual({}); + }); + + it('preserves the map key when a Struct is found', async () => { + const testResponse = new pb.GetLocationMetadataResponse({ + data: { + myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) + }, }); - console.log('Test Response before Mock:', testResponse); - mockTransport = createRouterTransport(({ service }) => { service(AppService, { - getOrganizationMetadata: () => testResponse, + getLocationMetadata: () => testResponse, }); }); - const response = await subject().getOrganizationMetadata('orgId'); - console.log('Test Response:', response); - - expect(response).toEqual({ - key1: 'value1', - }); + const response = await subject().getLocationMetadata('orgId'); + expect(response).toEqual({ myStruct: { key1: 'value1' } }); }); }); - describe('updateOrganizationMetadata', () => { + // updateLocationMetadata + + describe('getRobotMetadata', () => { beforeEach(() => { mockTransport = createRouterTransport(({ service }) => { service(AppService, { - updateOrganizationMetadata: () => new pb.UpdateOrganizationMetadataResponse(), + getRobotMetadata: () => new pb.GetRobotMetadataResponse(), }); }); }); - it('should handle empty metadata correctly', async () => { - let capturedRequest; + it('returns an empty object if there is no Struct', async () => { + const response = await subject().getRobotMetadata('orgId'); + expect(response).toEqual({}); + }); + + it('preserves the map key when a Struct is found', async () => { + const testResponse = new pb.GetRobotMetadataResponse({ + data: { + myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) + }, + }); + mockTransport = createRouterTransport(({ service }) => { service(AppService, { - updateOrganizationMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateOrganizationMetadataResponse(); - }, + getRobotMetadata: () => testResponse, }); }); - await subject().updateOrganizationMetadata('orgId', {}); - - expect(capturedRequest).toEqual({ - organizationId: 'orgId', - data: Struct.fromJson({}), // Should be an empty Struct + const response = await subject().getRobotMetadata('orgId'); + expect(response).toEqual({ myStruct: { key1: 'value1' } }); + }); + }); + + // updateRobotMetadata + + describe('getRobotPartMetadata', () => { + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + getRobotPartMetadata: () => new pb.GetRobotPartMetadataResponse(), + }); }); }); - it('should successfully update metadata with valid data', async () => { - const metadata = { - key1: 'value1', - key2: 42, - key3: true, - }; - - const expectedStruct = Struct.fromJson(metadata); - - let capturedRequest; + it('returns an empty object if there is no Struct', async () => { + const response = await subject().getRobotPartMetadata('orgId'); + expect(response).toEqual({}); + }); + + it('preserves the map key when a Struct is found', async () => { + const testResponse = new pb.GetRobotPartMetadataResponse({ + data: { + myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) + }, + }); + mockTransport = createRouterTransport(({ service }) => { service(AppService, { - updateOrganizationMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateOrganizationMetadataResponse(); - }, + getRobotPartMetadata: () => testResponse, }); }); - await subject().updateOrganizationMetadata('orgId', metadata); - - expect(capturedRequest).toMatchObject({ - organizationId: 'orgId', - data: expect.objectContaining({ - fields: expectedStruct.fields, - }), - }); - }); + const response = await subject().getRobotPartMetadata('orgId'); + expect(response).toEqual({ myStruct: { key1: 'value1' } }); + }); }); + + // updateRobotPartMetadata + }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 5a78db3a6..3b5799c35 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1,4 +1,5 @@ -import { Any, Struct } from '@bufbuild/protobuf'; +import type { Any, JsonValue } from '@bufbuild/protobuf'; +import { Struct } from '@bufbuild/protobuf'; import { createClient, type Client, type Transport } from '@connectrpc/connect'; import { PackageType } from '../gen/app/packages/v1/packages_pb'; import { AppService } from '../gen/app/v1/app_connect'; @@ -11,7 +12,6 @@ import { CreateModuleResponse, Fragment, FragmentVisibility, - GetLocationMetadataResponse, GetRobotMetadataResponse, GetRobotPartMetadataResponse, GetRobotPartLogsResponse, @@ -112,6 +112,27 @@ export const createPermission = ( }); }; +/** + * Decodes a map into a + * record of plain JavaScript objects keyed by the same map keys. + * + * @param data A record of string -> google.protobuf.Any + * @returns A record whose keys match `data`, + * with values unpacked from Struct messages + */ +export const decodeMetadataMap = (data: Record): Record => { + const result: Record = {}; + + for (const [key, anyValue] of Object.entries(data)) { + if (anyValue.typeUrl === 'type.googleapis.com/google.protobuf.Struct') { + const structValue = Struct.fromBinary(anyValue.value); + result[key] = structValue.toJson() as JsonValue; + } + } + + return result; +}; + export class AppClient { private client: Client; @@ -1187,12 +1208,9 @@ export class AppClient { */ async getOrganizationMetadata( id: string - ): Promise> { + ): Promise> { const response = await this.client.getOrganizationMetadata({ organizationId: id }); - if (!(response.data instanceof Struct)) { - return {}; - } - return response.data.toJson() as Record; + return decodeMetadataMap(response.data); } /** @@ -1206,6 +1224,19 @@ export class AppClient { data: Record ): Promise { const convertedData = Struct.fromJson(data); + // Create a new Struct with the provided data + // const convertedData = new Struct(); + + // // Manually populate the fields + // const fields = {}; + + // // Convert each value in data to a Value object + // Object.entries(data).forEach(([key, value]) => { + // fields[key] = Value.fromJson(value); + // }); + + // // Set the fields property of the Struct + // convertedData.fields = fields; await this.client.updateOrganizationMetadata({ organizationId: id, @@ -1219,12 +1250,9 @@ export class AppClient { * @param id The ID of the location * @returns The metadata associated with the location */ - async getLocationMetadata(id: string): Promise> { + async getLocationMetadata(id: string): Promise> { const response = await this.client.getLocationMetadata({ locationId: id }); - if (!(response.data instanceof Struct)) { - return {}; - } - return response.data.toJson() as Record; + return decodeMetadataMap(response.data); } /** @@ -1241,26 +1269,23 @@ export class AppClient { } /** - * Retrieves user-defined metadata for a machine. + * Retrieves user-defined metadata for a robot. * - * @param id The ID of the machine - * @returns The metadata associated with the machine + * @param id The ID of the robot + * @returns The metadata associated with the robot */ - async getMachineMetadata(id: string): Promise> { + async getRobotMetadata(id: string): Promise> { const response = await this.client.getRobotMetadata({ id }); - if (!(response.data instanceof Struct)) { - return {}; - } - return response.data.toJson() as Record; + return decodeMetadataMap(response.data); } /** * Updates user-defined metadata for a robot. * - * @param id The ID of the machine + * @param id The ID of the robot * @param data The metadata to update */ - async updateMachineMetadata( + async updateRobotMetadata( id: string, data: Record ): Promise { @@ -1268,28 +1293,25 @@ export class AppClient { } /** - * Retrieves user-defined metadata for a machine part. + * Retrieves user-defined metadata for a robot part. * - * @param id The ID of the machine part - * @returns The metadata associated with the machine part + * @param id The ID of the robot part + * @returns The metadata associated with the robot part */ - async getMachinePartMetadata( + async getRobotPartMetadata( id: string - ): Promise> { + ): Promise> { const response = await this.client.getRobotPartMetadata({ id }); - if (!(response.data instanceof Struct)) { - return {}; - } - return response.data.toJson() as Record; + return decodeMetadataMap(response.data); } /** - * Updates user-defined metadata for a machine part. + * Updates user-defined metadata for a machine robot. * - * @param id The ID of the machine part + * @param id The ID of the machine robot * @param data The metadata to update */ - async updateMachinePartMetadata( + async updateRobotPartMetadata( id: string, data: Record ): Promise { From f4be611b648a1d32d9f8e7adf2e6ba9ae4d790c7 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 11:32:16 -0400 Subject: [PATCH 12/18] Modify the update methods --- src/app/app-client.spec.ts | 93 +++++++++++++------------------------- src/app/app-client.ts | 47 ++++++++++--------- 2 files changed, 56 insertions(+), 84 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 1e9475200..c284028f8 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1,6 +1,6 @@ import * as pb from '../gen/app/v1/app_pb'; -import { Any, Struct, Value, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; +import { Any, Struct, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; import { createRouterTransport, type Transport } from '@connectrpc/connect'; import { createWritableIterable } from '@connectrpc/connect/protocol'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -1766,69 +1766,38 @@ describe('AppClient tests', () => { }); }); - // describe('updateOrganizationMetadata', () => { - // beforeEach(() => { - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateOrganizationMetadata: () => new pb.UpdateOrganizationMetadataResponse(), - // }); - // }); - // }); - - // it('should handle empty metadata correctly', async () => { - // let capturedRequest; - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateOrganizationMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateOrganizationMetadataResponse(); - // }, - // }); - // }); - - // await subject().updateOrganizationMetadata('orgId', {}); - - // expect(capturedRequest).toEqual({ - // organizationId: 'orgId', - // data: Struct.fromJson({}), - // }); - // }); - - // it('should successfully update metadata with valid data', async () => { - // const metadata = { - // key1: 'value1', - // key2: 42, - // key3: true, - // }; - - // const expectedStruct = new Struct({ - // fields: { - // key1: Value.fromJson('value1'), - // key2: Value.fromJson(42), - // key3: Value.fromJson(true), - // }, - // }); - - // let capturedRequest; - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateOrganizationMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateOrganizationMetadataResponse(); - // }, - // }); - // }); + describe('updateOrganizationMetadata', () => { + let capturedRequest: pb.UpdateOrganizationMetadataRequest | undefined; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateOrganizationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateOrganizationMetadataResponse(); + }, + }); + }); + capturedRequest = undefined; + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateOrganizationMetadata('orgId', {}); - // await subject().updateOrganizationMetadata('orgId', metadata); + expect(capturedRequest?.organizationId).toBe('orgId'); + expect(capturedRequest?.data?.fields?.toJson()).toEqual({}); + }); + + it('should successfully update metadata with valid data', async () => { + await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); - // expect(capturedRequest).toEqual({ - // organizationId: 'orgId', - // data: expect.objectContaining({ - // fields: expectedStruct.fields, - // }), - // }); - // }); - // }); + expect(capturedRequest?.organizationId).toBe('orgId'); + + const struct = capturedRequest?.data as Struct; + expect(struct.toJson()).toEqual({ key1: { stringValue: 'value1' } }); + + }); + }); describe('getLocationMetadata', () => { beforeEach(() => { diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 3b5799c35..e453eb9a3 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1,5 +1,5 @@ import type { Any, JsonValue } from '@bufbuild/protobuf'; -import { Struct } from '@bufbuild/protobuf'; +import { Struct, Value } from '@bufbuild/protobuf'; import { createClient, type Client, type Transport } from '@connectrpc/connect'; import { PackageType } from '../gen/app/packages/v1/packages_pb'; import { AppService } from '../gen/app/v1/app_connect'; @@ -1221,22 +1221,12 @@ export class AppClient { */ async updateOrganizationMetadata( id: string, - data: Record + data: Record ): Promise { - const convertedData = Struct.fromJson(data); - // Create a new Struct with the provided data - // const convertedData = new Struct(); - - // // Manually populate the fields - // const fields = {}; - - // // Convert each value in data to a Value object - // Object.entries(data).forEach(([key, value]) => { - // fields[key] = Value.fromJson(value); - // }); - - // // Set the fields property of the Struct - // convertedData.fields = fields; + const convertedData = new Struct({ fields: {} }); + for (const [key, val] of Object.entries(data)) { + convertedData.fields[key] = Value.fromJson(val); + } await this.client.updateOrganizationMetadata({ organizationId: id, @@ -1263,9 +1253,14 @@ export class AppClient { */ async updateLocationMetadata( id: string, - data: Record + data: Record ): Promise { - await this.client.updateLocationMetadata({ locationId: id, data }); + const convertedData = new Struct({ fields: {} }); + for (const [key, val] of Object.entries(data)) { + convertedData.fields[key] = Value.fromJson(val); + } + + await this.client.updateLocationMetadata({ locationId: id, data: convertedData }); } /** @@ -1287,9 +1282,13 @@ export class AppClient { */ async updateRobotMetadata( id: string, - data: Record + data: Record ): Promise { - await this.client.updateRobotMetadata({ id, data }); + const convertedData = new Struct({ fields: {} }); + for (const [key, val] of Object.entries(data)) { + convertedData.fields[key] = Value.fromJson(val); + } + await this.client.updateRobotMetadata({ id, data: convertedData }); } /** @@ -1313,8 +1312,12 @@ export class AppClient { */ async updateRobotPartMetadata( id: string, - data: Record + data: Record ): Promise { - await this.client.updateRobotPartMetadata({ id, data }); + const convertedData = new Struct({ fields: {} }); + for (const [key, val] of Object.entries(data)) { + convertedData.fields[key] = Value.fromJson(val); + } + await this.client.updateRobotPartMetadata({ id, data: convertedData }); } } From fe0866297f448172e57a8c4bb0a1b86961c59c66 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 12:37:38 -0400 Subject: [PATCH 13/18] Add unit tests for update methods --- src/app/app-client.spec.ts | 106 +++++++++++++++++++++++++++++++++---- src/app/app-client.ts | 47 ++++++++-------- 2 files changed, 120 insertions(+), 33 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index c284028f8..078394dcd 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1,6 +1,6 @@ import * as pb from '../gen/app/v1/app_pb'; -import { Any, Struct, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; +import { Any, Struct, Timestamp, Value, type PartialMessage } from '@bufbuild/protobuf'; import { createRouterTransport, type Transport } from '@connectrpc/connect'; import { createWritableIterable } from '@connectrpc/connect/protocol'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -1785,17 +1785,16 @@ describe('AppClient tests', () => { await subject().updateOrganizationMetadata('orgId', {}); expect(capturedRequest?.organizationId).toBe('orgId'); - expect(capturedRequest?.data?.fields?.toJson()).toEqual({}); + expect(capturedRequest?.data).toEqual({}); }); it('should successfully update metadata with valid data', async () => { await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); expect(capturedRequest?.organizationId).toBe('orgId'); - - const struct = capturedRequest?.data as Struct; - expect(struct.toJson()).toEqual({ key1: { stringValue: 'value1' } }); - + expect(capturedRequest?.data).toEqual({ + key1: Any.pack(Value.fromJson('value1')), + }); }); }); @@ -1831,7 +1830,37 @@ describe('AppClient tests', () => { }); }); - // updateLocationMetadata + describe('updateLocationMetadata', () => { + let capturedRequest: pb.UpdateLocationMetadataResponse | undefined; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateLocationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateLocationMetadataResponse(); + }, + }); + }); + capturedRequest = undefined; + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateLocationMetadata('orgId', {}); + + expect(capturedRequest?.locationId).toBe('orgId'); + expect(capturedRequest?.data).toEqual({}); + }); + + it('should successfully update metadata with valid data', async () => { + await subject().updateLocationMetadata('orgId', { key1: 'value1' }); + + expect(capturedRequest?.locationId).toBe('orgId'); + expect(capturedRequest?.data).toEqual({ + key1: Any.pack(Value.fromJson('value1')), + }); + }); + }); describe('getRobotMetadata', () => { beforeEach(() => { @@ -1865,7 +1894,37 @@ describe('AppClient tests', () => { }); }); - // updateRobotMetadata + describe('updateRobotMetadata', () => { + let capturedRequest: pb.UpdateRobotMetadataResponse | undefined; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateRobotMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateRobotMetadataResponse(); + }, + }); + }); + capturedRequest = undefined; + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateRobotMetadata('orgId', {}); + + expect(capturedRequest?.id).toBe('orgId'); + expect(capturedRequest?.data).toEqual({}); + }); + + it('should successfully update metadata with valid data', async () => { + await subject().updateRobotMetadata('orgId', { key1: 'value1' }); + + expect(capturedRequest?.id).toBe('orgId'); + expect(capturedRequest?.data).toEqual({ + key1: Any.pack(Value.fromJson('value1')), + }); + }); + }); describe('getRobotPartMetadata', () => { beforeEach(() => { @@ -1899,6 +1958,35 @@ describe('AppClient tests', () => { }); }); - // updateRobotPartMetadata + describe('updateRobotPartMetadata', () => { + let capturedRequest: pb.UpdateRobotPartMetadataResponse | undefined; + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateRobotPartMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateRobotPartMetadataResponse(); + }, + }); + }); + capturedRequest = undefined; + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateRobotPartMetadata('orgId', {}); + + expect(capturedRequest?.id).toBe('orgId'); + expect(capturedRequest?.data).toEqual({}); + }); + + it('should successfully update metadata with valid data', async () => { + await subject().updateRobotPartMetadata('orgId', { key1: 'value1' }); + + expect(capturedRequest?.id).toBe('orgId'); + expect(capturedRequest?.data).toEqual({ + key1: Any.pack(Value.fromJson('value1')), + }); + }); + }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index e453eb9a3..8b9441bf0 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1,5 +1,5 @@ -import type { Any, JsonValue } from '@bufbuild/protobuf'; -import { Struct, Value } from '@bufbuild/protobuf'; +import type { JsonValue } from '@bufbuild/protobuf'; +import { Any, Struct, Value } from '@bufbuild/protobuf'; import { createClient, type Client, type Transport } from '@connectrpc/connect'; import { PackageType } from '../gen/app/packages/v1/packages_pb'; import { AppService } from '../gen/app/v1/app_connect'; @@ -133,6 +133,22 @@ export const decodeMetadataMap = (data: Record): Record): Record => { + const convertedData: Record = {}; + + for (const [key, val] of Object.entries(data)) { + convertedData[key] = Any.pack(Value.fromJson(val)); + } + + return convertedData; +} + export class AppClient { private client: Client; @@ -1211,6 +1227,7 @@ export class AppClient { ): Promise> { const response = await this.client.getOrganizationMetadata({ organizationId: id }); return decodeMetadataMap(response.data); + // return response.data.toJson(); } /** @@ -1223,14 +1240,9 @@ export class AppClient { id: string, data: Record ): Promise { - const convertedData = new Struct({ fields: {} }); - for (const [key, val] of Object.entries(data)) { - convertedData.fields[key] = Value.fromJson(val); - } - await this.client.updateOrganizationMetadata({ organizationId: id, - data: convertedData, + data: convertToAnyMetadata(data), }); } @@ -1255,12 +1267,7 @@ export class AppClient { id: string, data: Record ): Promise { - const convertedData = new Struct({ fields: {} }); - for (const [key, val] of Object.entries(data)) { - convertedData.fields[key] = Value.fromJson(val); - } - - await this.client.updateLocationMetadata({ locationId: id, data: convertedData }); + await this.client.updateLocationMetadata({ locationId: id, data: convertToAnyMetadata(data) }); } /** @@ -1284,11 +1291,7 @@ export class AppClient { id: string, data: Record ): Promise { - const convertedData = new Struct({ fields: {} }); - for (const [key, val] of Object.entries(data)) { - convertedData.fields[key] = Value.fromJson(val); - } - await this.client.updateRobotMetadata({ id, data: convertedData }); + await this.client.updateRobotMetadata({ id, data: convertToAnyMetadata(data) }); } /** @@ -1314,10 +1317,6 @@ export class AppClient { id: string, data: Record ): Promise { - const convertedData = new Struct({ fields: {} }); - for (const [key, val] of Object.entries(data)) { - convertedData.fields[key] = Value.fromJson(val); - } - await this.client.updateRobotPartMetadata({ id, data: convertedData }); + await this.client.updateRobotPartMetadata({ id, data: convertToAnyMetadata(data) }); } } From 4723a36334acabb65dfe84c89ba387ced365fe36 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 12:51:53 -0400 Subject: [PATCH 14/18] Fix get methods --- src/app/app-client.spec.ts | 248 ++++++++++++++++++------------------- src/app/app-client.ts | 38 ++---- 2 files changed, 130 insertions(+), 156 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 078394dcd..4cb4c206f 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1750,9 +1750,7 @@ describe('AppClient tests', () => { it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetOrganizationMetadataResponse({ - data: { - myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) - }, + data: Struct.fromJson({ key1: 'value1' }), }); mockTransport = createRouterTransport(({ service }) => { @@ -1762,41 +1760,41 @@ describe('AppClient tests', () => { }); const response = await subject().getOrganizationMetadata('orgId'); - expect(response).toEqual({ myStruct: { key1: 'value1' } }); + expect(response).toEqual( { key1: 'value1' } ); }); }); - describe('updateOrganizationMetadata', () => { - let capturedRequest: pb.UpdateOrganizationMetadataRequest | undefined; - - beforeEach(() => { - mockTransport = createRouterTransport(({ service }) => { - service(AppService, { - updateOrganizationMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateOrganizationMetadataResponse(); - }, - }); - }); - capturedRequest = undefined; - }); - - it('should handle empty metadata correctly', async () => { - await subject().updateOrganizationMetadata('orgId', {}); + // describe('updateOrganizationMetadata', () => { + // let capturedRequest: pb.UpdateOrganizationMetadataRequest | undefined; + + // beforeEach(() => { + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateOrganizationMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateOrganizationMetadataResponse(); + // }, + // }); + // }); + // capturedRequest = undefined; + // }); + + // it('should handle empty metadata correctly', async () => { + // await subject().updateOrganizationMetadata('orgId', {}); - expect(capturedRequest?.organizationId).toBe('orgId'); - expect(capturedRequest?.data).toEqual({}); - }); + // expect(capturedRequest?.organizationId).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({}); + // }); - it('should successfully update metadata with valid data', async () => { - await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); + // it('should successfully update metadata with valid data', async () => { + // await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); - expect(capturedRequest?.organizationId).toBe('orgId'); - expect(capturedRequest?.data).toEqual({ - key1: Any.pack(Value.fromJson('value1')), - }); - }); - }); + // expect(capturedRequest?.organizationId).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({ + // key1: Any.pack(Value.fromJson('value1')), + // }); + // }); + // }); describe('getLocationMetadata', () => { beforeEach(() => { @@ -1814,9 +1812,7 @@ describe('AppClient tests', () => { it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetLocationMetadataResponse({ - data: { - myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) - }, + data: Struct.fromJson({ key1: 'value1' }), }); mockTransport = createRouterTransport(({ service }) => { @@ -1826,41 +1822,41 @@ describe('AppClient tests', () => { }); const response = await subject().getLocationMetadata('orgId'); - expect(response).toEqual({ myStruct: { key1: 'value1' } }); + expect(response).toEqual( { key1: 'value1' } ); }); }); - describe('updateLocationMetadata', () => { - let capturedRequest: pb.UpdateLocationMetadataResponse | undefined; - - beforeEach(() => { - mockTransport = createRouterTransport(({ service }) => { - service(AppService, { - updateLocationMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateLocationMetadataResponse(); - }, - }); - }); - capturedRequest = undefined; - }); - - it('should handle empty metadata correctly', async () => { - await subject().updateLocationMetadata('orgId', {}); + // describe('updateLocationMetadata', () => { + // let capturedRequest: pb.UpdateLocationMetadataResponse | undefined; + + // beforeEach(() => { + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateLocationMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateLocationMetadataResponse(); + // }, + // }); + // }); + // capturedRequest = undefined; + // }); + + // it('should handle empty metadata correctly', async () => { + // await subject().updateLocationMetadata('orgId', {}); - expect(capturedRequest?.locationId).toBe('orgId'); - expect(capturedRequest?.data).toEqual({}); - }); + // expect(capturedRequest?.locationId).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({}); + // }); - it('should successfully update metadata with valid data', async () => { - await subject().updateLocationMetadata('orgId', { key1: 'value1' }); + // it('should successfully update metadata with valid data', async () => { + // await subject().updateLocationMetadata('orgId', { key1: 'value1' }); - expect(capturedRequest?.locationId).toBe('orgId'); - expect(capturedRequest?.data).toEqual({ - key1: Any.pack(Value.fromJson('value1')), - }); - }); - }); + // expect(capturedRequest?.locationId).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({ + // key1: Any.pack(Value.fromJson('value1')), + // }); + // }); + // }); describe('getRobotMetadata', () => { beforeEach(() => { @@ -1878,9 +1874,7 @@ describe('AppClient tests', () => { it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetRobotMetadataResponse({ - data: { - myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) - }, + data: Struct.fromJson({ key1: 'value1' }), }); mockTransport = createRouterTransport(({ service }) => { @@ -1890,41 +1884,41 @@ describe('AppClient tests', () => { }); const response = await subject().getRobotMetadata('orgId'); - expect(response).toEqual({ myStruct: { key1: 'value1' } }); + expect(response).toEqual( { key1: 'value1' } ); }); }); - describe('updateRobotMetadata', () => { - let capturedRequest: pb.UpdateRobotMetadataResponse | undefined; - - beforeEach(() => { - mockTransport = createRouterTransport(({ service }) => { - service(AppService, { - updateRobotMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateRobotMetadataResponse(); - }, - }); - }); - capturedRequest = undefined; - }); - - it('should handle empty metadata correctly', async () => { - await subject().updateRobotMetadata('orgId', {}); + // describe('updateRobotMetadata', () => { + // let capturedRequest: pb.UpdateRobotMetadataResponse | undefined; + + // beforeEach(() => { + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateRobotMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateRobotMetadataResponse(); + // }, + // }); + // }); + // capturedRequest = undefined; + // }); + + // it('should handle empty metadata correctly', async () => { + // await subject().updateRobotMetadata('orgId', {}); - expect(capturedRequest?.id).toBe('orgId'); - expect(capturedRequest?.data).toEqual({}); - }); + // expect(capturedRequest?.id).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({}); + // }); - it('should successfully update metadata with valid data', async () => { - await subject().updateRobotMetadata('orgId', { key1: 'value1' }); + // it('should successfully update metadata with valid data', async () => { + // await subject().updateRobotMetadata('orgId', { key1: 'value1' }); - expect(capturedRequest?.id).toBe('orgId'); - expect(capturedRequest?.data).toEqual({ - key1: Any.pack(Value.fromJson('value1')), - }); - }); - }); + // expect(capturedRequest?.id).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({ + // key1: Any.pack(Value.fromJson('value1')), + // }); + // }); + // }); describe('getRobotPartMetadata', () => { beforeEach(() => { @@ -1942,9 +1936,7 @@ describe('AppClient tests', () => { it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetRobotPartMetadataResponse({ - data: { - myStruct: Any.pack(Struct.fromJson({ key1: 'value1' })) - }, + data: Struct.fromJson({ key1: 'value1' }), }); mockTransport = createRouterTransport(({ service }) => { @@ -1954,39 +1946,39 @@ describe('AppClient tests', () => { }); const response = await subject().getRobotPartMetadata('orgId'); - expect(response).toEqual({ myStruct: { key1: 'value1' } }); + expect(response).toEqual( { key1: 'value1' } ); }); }); - describe('updateRobotPartMetadata', () => { - let capturedRequest: pb.UpdateRobotPartMetadataResponse | undefined; - - beforeEach(() => { - mockTransport = createRouterTransport(({ service }) => { - service(AppService, { - updateRobotPartMetadata: (req) => { - capturedRequest = req; - return new pb.UpdateRobotPartMetadataResponse(); - }, - }); - }); - capturedRequest = undefined; - }); - - it('should handle empty metadata correctly', async () => { - await subject().updateRobotPartMetadata('orgId', {}); + // describe('updateRobotPartMetadata', () => { + // let capturedRequest: pb.UpdateRobotPartMetadataResponse | undefined; + + // beforeEach(() => { + // mockTransport = createRouterTransport(({ service }) => { + // service(AppService, { + // updateRobotPartMetadata: (req) => { + // capturedRequest = req; + // return new pb.UpdateRobotPartMetadataResponse(); + // }, + // }); + // }); + // capturedRequest = undefined; + // }); + + // it('should handle empty metadata correctly', async () => { + // await subject().updateRobotPartMetadata('orgId', {}); - expect(capturedRequest?.id).toBe('orgId'); - expect(capturedRequest?.data).toEqual({}); - }); + // expect(capturedRequest?.id).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({}); + // }); - it('should successfully update metadata with valid data', async () => { - await subject().updateRobotPartMetadata('orgId', { key1: 'value1' }); + // it('should successfully update metadata with valid data', async () => { + // await subject().updateRobotPartMetadata('orgId', { key1: 'value1' }); - expect(capturedRequest?.id).toBe('orgId'); - expect(capturedRequest?.data).toEqual({ - key1: Any.pack(Value.fromJson('value1')), - }); - }); - }); + // expect(capturedRequest?.id).toBe('orgId'); + // expect(capturedRequest?.data).toEqual({ + // key1: Any.pack(Value.fromJson('value1')), + // }); + // }); + // }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 8b9441bf0..2cd460481 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -112,27 +112,6 @@ export const createPermission = ( }); }; -/** - * Decodes a map into a - * record of plain JavaScript objects keyed by the same map keys. - * - * @param data A record of string -> google.protobuf.Any - * @returns A record whose keys match `data`, - * with values unpacked from Struct messages - */ -export const decodeMetadataMap = (data: Record): Record => { - const result: Record = {}; - - for (const [key, anyValue] of Object.entries(data)) { - if (anyValue.typeUrl === 'type.googleapis.com/google.protobuf.Struct') { - const structValue = Struct.fromBinary(anyValue.value); - result[key] = structValue.toJson() as JsonValue; - } - } - - return result; -}; - /** * Converts a JavaScript object into a Protobuf-compatible metadata structure. * @@ -1225,9 +1204,9 @@ export class AppClient { async getOrganizationMetadata( id: string ): Promise> { - const response = await this.client.getOrganizationMetadata({ organizationId: id }); - return decodeMetadataMap(response.data); - // return response.data.toJson(); + const response = await this.client.getOrganizationMetadata({ organizationId: id }); + const jsonResponse = response.toJson() as { data?: Record }; + return jsonResponse.data ?? {}; } /** @@ -1254,7 +1233,8 @@ export class AppClient { */ async getLocationMetadata(id: string): Promise> { const response = await this.client.getLocationMetadata({ locationId: id }); - return decodeMetadataMap(response.data); + const jsonResponse = response.toJson() as { data?: Record }; + return jsonResponse.data ?? {}; } /** @@ -1278,7 +1258,8 @@ export class AppClient { */ async getRobotMetadata(id: string): Promise> { const response = await this.client.getRobotMetadata({ id }); - return decodeMetadataMap(response.data); + const jsonResponse = response.toJson() as { data?: Record }; + return jsonResponse.data ?? {}; } /** @@ -1304,8 +1285,9 @@ export class AppClient { id: string ): Promise> { const response = await this.client.getRobotPartMetadata({ id }); - return decodeMetadataMap(response.data); - } + const jsonResponse = response.toJson() as { data?: Record }; + return jsonResponse.data ?? {}; + } /** * Updates user-defined metadata for a machine robot. From 19f966dd3d26b3983f73581be3c940311d612c83 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 13:45:43 -0400 Subject: [PATCH 15/18] Add update metadata unit tests. cleanup --- src/app/app-client.spec.ts | 230 +++++++++++++++++++------------------ src/app/app-client.ts | 28 +---- 2 files changed, 122 insertions(+), 136 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 4cb4c206f..8e81e5f35 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1,6 +1,6 @@ import * as pb from '../gen/app/v1/app_pb'; -import { Any, Struct, Timestamp, Value, type PartialMessage } from '@bufbuild/protobuf'; +import { Struct, Timestamp, type PartialMessage } from '@bufbuild/protobuf'; import { createRouterTransport, type Transport } from '@connectrpc/connect'; import { createWritableIterable } from '@connectrpc/connect/protocol'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -1764,37 +1764,38 @@ describe('AppClient tests', () => { }); }); - // describe('updateOrganizationMetadata', () => { - // let capturedRequest: pb.UpdateOrganizationMetadataRequest | undefined; - - // beforeEach(() => { - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateOrganizationMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateOrganizationMetadataResponse(); - // }, - // }); - // }); - // capturedRequest = undefined; - // }); - - // it('should handle empty metadata correctly', async () => { - // await subject().updateOrganizationMetadata('orgId', {}); + describe('updateOrganizationMetadata', () => { + let capturedRequest: pb.UpdateOrganizationMetadataRequest; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateOrganizationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateOrganizationMetadataResponse(); + }, + }); + }); + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateOrganizationMetadata('orgId', {}); - // expect(capturedRequest?.organizationId).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({}); - // }); + expect(capturedRequest).toEqual({ + organizationId: 'orgId', + data: Struct.fromJson({}), + }); + }); - // it('should successfully update metadata with valid data', async () => { - // await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); + it('should successfully update metadata with valid data', async () => { + await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); - // expect(capturedRequest?.organizationId).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({ - // key1: Any.pack(Value.fromJson('value1')), - // }); - // }); - // }); + expect(capturedRequest).toEqual({ + organizationId: 'orgId', + data: Struct.fromJson( { key1: 'value1'} ), + }); + }); + }); describe('getLocationMetadata', () => { beforeEach(() => { @@ -1826,37 +1827,38 @@ describe('AppClient tests', () => { }); }); - // describe('updateLocationMetadata', () => { - // let capturedRequest: pb.UpdateLocationMetadataResponse | undefined; - - // beforeEach(() => { - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateLocationMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateLocationMetadataResponse(); - // }, - // }); - // }); - // capturedRequest = undefined; - // }); - - // it('should handle empty metadata correctly', async () => { - // await subject().updateLocationMetadata('orgId', {}); + describe('updateLocationMetadata', () => { + let capturedRequest: pb.UpdateLocationMetadataResponse; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateLocationMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateLocationMetadataResponse(); + }, + }); + }); + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateLocationMetadata('locId', {}); - // expect(capturedRequest?.locationId).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({}); - // }); + expect(capturedRequest).toEqual({ + locationId: 'locId', + data: Struct.fromJson({}), + }); + }); - // it('should successfully update metadata with valid data', async () => { - // await subject().updateLocationMetadata('orgId', { key1: 'value1' }); + it('should successfully update metadata with valid data', async () => { + await subject().updateLocationMetadata('locId', { key1: 'value1' }); - // expect(capturedRequest?.locationId).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({ - // key1: Any.pack(Value.fromJson('value1')), - // }); - // }); - // }); + expect(capturedRequest).toEqual({ + locationId: 'locId', + data: Struct.fromJson( { key1: 'value1'} ), + }); + }); + }); describe('getRobotMetadata', () => { beforeEach(() => { @@ -1888,37 +1890,38 @@ describe('AppClient tests', () => { }); }); - // describe('updateRobotMetadata', () => { - // let capturedRequest: pb.UpdateRobotMetadataResponse | undefined; - - // beforeEach(() => { - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateRobotMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateRobotMetadataResponse(); - // }, - // }); - // }); - // capturedRequest = undefined; - // }); - - // it('should handle empty metadata correctly', async () => { - // await subject().updateRobotMetadata('orgId', {}); + describe('updateRobotMetadata', () => { + let capturedRequest: pb.UpdateRobotMetadataResponse; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateRobotMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateLocationMetadataResponse(); + }, + }); + }); + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateRobotMetadata('robotId', {}); - // expect(capturedRequest?.id).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({}); - // }); + expect(capturedRequest).toEqual({ + id: 'robotId', + data: Struct.fromJson({}), + }); + }); - // it('should successfully update metadata with valid data', async () => { - // await subject().updateRobotMetadata('orgId', { key1: 'value1' }); + it('should successfully update metadata with valid data', async () => { + await subject().updateRobotMetadata('robotId', { key1: 'value1' }); - // expect(capturedRequest?.id).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({ - // key1: Any.pack(Value.fromJson('value1')), - // }); - // }); - // }); + expect(capturedRequest).toEqual({ + id: 'robotId', + data: Struct.fromJson( { key1: 'value1'} ), + }); + }); + }); describe('getRobotPartMetadata', () => { beforeEach(() => { @@ -1950,35 +1953,36 @@ describe('AppClient tests', () => { }); }); - // describe('updateRobotPartMetadata', () => { - // let capturedRequest: pb.UpdateRobotPartMetadataResponse | undefined; - - // beforeEach(() => { - // mockTransport = createRouterTransport(({ service }) => { - // service(AppService, { - // updateRobotPartMetadata: (req) => { - // capturedRequest = req; - // return new pb.UpdateRobotPartMetadataResponse(); - // }, - // }); - // }); - // capturedRequest = undefined; - // }); - - // it('should handle empty metadata correctly', async () => { - // await subject().updateRobotPartMetadata('orgId', {}); + describe('updateRobotPartMetadata', () => { + let capturedRequest: pb.UpdateRobotPartMetadataResponse; + + beforeEach(() => { + mockTransport = createRouterTransport(({ service }) => { + service(AppService, { + updateRobotPartMetadata: (req) => { + capturedRequest = req; + return new pb.UpdateRobotPartMetadataResponse(); + }, + }); + }); + }); + + it('should handle empty metadata correctly', async () => { + await subject().updateRobotPartMetadata('robotPartId', {}); - // expect(capturedRequest?.id).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({}); - // }); + expect(capturedRequest).toEqual({ + id: 'robotPartId', + data: Struct.fromJson({}), + }); + }); - // it('should successfully update metadata with valid data', async () => { - // await subject().updateRobotPartMetadata('orgId', { key1: 'value1' }); + it('should successfully update metadata with valid data', async () => { + await subject().updateRobotPartMetadata('robotPartId', { key1: 'value1' }); - // expect(capturedRequest?.id).toBe('orgId'); - // expect(capturedRequest?.data).toEqual({ - // key1: Any.pack(Value.fromJson('value1')), - // }); - // }); - // }); + expect(capturedRequest).toEqual({ + id: 'robotPartId', + data: Struct.fromJson( { key1: 'value1'} ), + }); + }); + }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 2cd460481..e730c6c9a 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1,5 +1,5 @@ import type { JsonValue } from '@bufbuild/protobuf'; -import { Any, Struct, Value } from '@bufbuild/protobuf'; +import { Struct } from '@bufbuild/protobuf'; import { createClient, type Client, type Transport } from '@connectrpc/connect'; import { PackageType } from '../gen/app/packages/v1/packages_pb'; import { AppService } from '../gen/app/v1/app_connect'; @@ -12,8 +12,6 @@ import { CreateModuleResponse, Fragment, FragmentVisibility, - GetRobotMetadataResponse, - GetRobotPartMetadataResponse, GetRobotPartLogsResponse, GetRobotPartResponse, ListOrganizationMembersResponse, @@ -112,22 +110,6 @@ export const createPermission = ( }); }; -/** - * Converts a JavaScript object into a Protobuf-compatible metadata structure. - * - * @param data - An object containing key-value pairs to be converted. - * @returns A record where each key maps to an `Any`-wrapped Protobuf `Value`. - */ -export const convertToAnyMetadata = (data: Record): Record => { - const convertedData: Record = {}; - - for (const [key, val] of Object.entries(data)) { - convertedData[key] = Any.pack(Value.fromJson(val)); - } - - return convertedData; -} - export class AppClient { private client: Client; @@ -1221,7 +1203,7 @@ export class AppClient { ): Promise { await this.client.updateOrganizationMetadata({ organizationId: id, - data: convertToAnyMetadata(data), + data: Struct.fromJson(data), }); } @@ -1247,7 +1229,7 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateLocationMetadata({ locationId: id, data: convertToAnyMetadata(data) }); + await this.client.updateLocationMetadata({ locationId: id, data: Struct.fromJson(data) }); } /** @@ -1272,7 +1254,7 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateRobotMetadata({ id, data: convertToAnyMetadata(data) }); + await this.client.updateRobotMetadata({ id, data: Struct.fromJson(data) }); } /** @@ -1299,6 +1281,6 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateRobotPartMetadata({ id, data: convertToAnyMetadata(data) }); + await this.client.updateRobotPartMetadata({ id, data: Struct.fromJson(data) }); } } From 6ce2a38616b4e2a687a60f85ab3ba604f76425a0 Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 13:49:02 -0400 Subject: [PATCH 16/18] run formatter --- src/app/app-client.spec.ts | 111 +++++++++++++++++++------------------ src/app/app-client.ts | 44 ++++++++++----- 2 files changed, 86 insertions(+), 69 deletions(-) diff --git a/src/app/app-client.spec.ts b/src/app/app-client.spec.ts index 8e81e5f35..07614b282 100644 --- a/src/app/app-client.spec.ts +++ b/src/app/app-client.spec.ts @@ -1738,7 +1738,8 @@ describe('AppClient tests', () => { beforeEach(() => { mockTransport = createRouterTransport(({ service }) => { service(AppService, { - getOrganizationMetadata: () => new pb.GetOrganizationMetadataResponse(), + getOrganizationMetadata: () => + new pb.GetOrganizationMetadataResponse(), }); }); }); @@ -1748,21 +1749,21 @@ describe('AppClient tests', () => { expect(response).toEqual({}); }); - it('preserves the map key when a Struct is found', async () => { + it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetOrganizationMetadataResponse({ data: Struct.fromJson({ key1: 'value1' }), }); - + mockTransport = createRouterTransport(({ service }) => { service(AppService, { getOrganizationMetadata: () => testResponse, }); }); - + const response = await subject().getOrganizationMetadata('orgId'); - expect(response).toEqual( { key1: 'value1' } ); + expect(response).toEqual({ key1: 'value1' }); }); - }); + }); describe('updateOrganizationMetadata', () => { let capturedRequest: pb.UpdateOrganizationMetadataRequest; @@ -1778,24 +1779,24 @@ describe('AppClient tests', () => { }); }); - it('should handle empty metadata correctly', async () => { + it('should handle empty metadata correctly', async () => { await subject().updateOrganizationMetadata('orgId', {}); - + expect(capturedRequest).toEqual({ organizationId: 'orgId', - data: Struct.fromJson({}), + data: Struct.fromJson({}), }); }); - it('should successfully update metadata with valid data', async () => { + it('should successfully update metadata with valid data', async () => { await subject().updateOrganizationMetadata('orgId', { key1: 'value1' }); - + expect(capturedRequest).toEqual({ organizationId: 'orgId', - data: Struct.fromJson( { key1: 'value1'} ), + data: Struct.fromJson({ key1: 'value1' }), }); - }); - }); + }); + }); describe('getLocationMetadata', () => { beforeEach(() => { @@ -1811,21 +1812,21 @@ describe('AppClient tests', () => { expect(response).toEqual({}); }); - it('preserves the map key when a Struct is found', async () => { + it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetLocationMetadataResponse({ data: Struct.fromJson({ key1: 'value1' }), }); - + mockTransport = createRouterTransport(({ service }) => { service(AppService, { getLocationMetadata: () => testResponse, }); }); - + const response = await subject().getLocationMetadata('orgId'); - expect(response).toEqual( { key1: 'value1' } ); + expect(response).toEqual({ key1: 'value1' }); }); - }); + }); describe('updateLocationMetadata', () => { let capturedRequest: pb.UpdateLocationMetadataResponse; @@ -1841,24 +1842,24 @@ describe('AppClient tests', () => { }); }); - it('should handle empty metadata correctly', async () => { + it('should handle empty metadata correctly', async () => { await subject().updateLocationMetadata('locId', {}); - + expect(capturedRequest).toEqual({ locationId: 'locId', - data: Struct.fromJson({}), + data: Struct.fromJson({}), }); }); - it('should successfully update metadata with valid data', async () => { + it('should successfully update metadata with valid data', async () => { await subject().updateLocationMetadata('locId', { key1: 'value1' }); - + expect(capturedRequest).toEqual({ locationId: 'locId', - data: Struct.fromJson( { key1: 'value1'} ), + data: Struct.fromJson({ key1: 'value1' }), }); - }); - }); + }); + }); describe('getRobotMetadata', () => { beforeEach(() => { @@ -1874,21 +1875,21 @@ describe('AppClient tests', () => { expect(response).toEqual({}); }); - it('preserves the map key when a Struct is found', async () => { + it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetRobotMetadataResponse({ data: Struct.fromJson({ key1: 'value1' }), }); - + mockTransport = createRouterTransport(({ service }) => { service(AppService, { getRobotMetadata: () => testResponse, }); }); - + const response = await subject().getRobotMetadata('orgId'); - expect(response).toEqual( { key1: 'value1' } ); + expect(response).toEqual({ key1: 'value1' }); }); - }); + }); describe('updateRobotMetadata', () => { let capturedRequest: pb.UpdateRobotMetadataResponse; @@ -1904,24 +1905,24 @@ describe('AppClient tests', () => { }); }); - it('should handle empty metadata correctly', async () => { + it('should handle empty metadata correctly', async () => { await subject().updateRobotMetadata('robotId', {}); - + expect(capturedRequest).toEqual({ id: 'robotId', - data: Struct.fromJson({}), + data: Struct.fromJson({}), }); }); - it('should successfully update metadata with valid data', async () => { + it('should successfully update metadata with valid data', async () => { await subject().updateRobotMetadata('robotId', { key1: 'value1' }); - + expect(capturedRequest).toEqual({ id: 'robotId', - data: Struct.fromJson( { key1: 'value1'} ), + data: Struct.fromJson({ key1: 'value1' }), }); - }); - }); + }); + }); describe('getRobotPartMetadata', () => { beforeEach(() => { @@ -1937,21 +1938,21 @@ describe('AppClient tests', () => { expect(response).toEqual({}); }); - it('preserves the map key when a Struct is found', async () => { + it('preserves the map key when a Struct is found', async () => { const testResponse = new pb.GetRobotPartMetadataResponse({ data: Struct.fromJson({ key1: 'value1' }), }); - + mockTransport = createRouterTransport(({ service }) => { service(AppService, { getRobotPartMetadata: () => testResponse, }); }); - + const response = await subject().getRobotPartMetadata('orgId'); - expect(response).toEqual( { key1: 'value1' } ); + expect(response).toEqual({ key1: 'value1' }); }); - }); + }); describe('updateRobotPartMetadata', () => { let capturedRequest: pb.UpdateRobotPartMetadataResponse; @@ -1967,22 +1968,24 @@ describe('AppClient tests', () => { }); }); - it('should handle empty metadata correctly', async () => { + it('should handle empty metadata correctly', async () => { await subject().updateRobotPartMetadata('robotPartId', {}); - + expect(capturedRequest).toEqual({ id: 'robotPartId', - data: Struct.fromJson({}), + data: Struct.fromJson({}), }); }); - it('should successfully update metadata with valid data', async () => { - await subject().updateRobotPartMetadata('robotPartId', { key1: 'value1' }); - + it('should successfully update metadata with valid data', async () => { + await subject().updateRobotPartMetadata('robotPartId', { + key1: 'value1', + }); + expect(capturedRequest).toEqual({ id: 'robotPartId', - data: Struct.fromJson( { key1: 'value1'} ), + data: Struct.fromJson({ key1: 'value1' }), }); - }); - }); + }); + }); }); diff --git a/src/app/app-client.ts b/src/app/app-client.ts index e730c6c9a..95857974d 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1186,11 +1186,15 @@ export class AppClient { async getOrganizationMetadata( id: string ): Promise> { - const response = await this.client.getOrganizationMetadata({ organizationId: id }); - const jsonResponse = response.toJson() as { data?: Record }; + const response = await this.client.getOrganizationMetadata({ + organizationId: id, + }); + const jsonResponse = response.toJson() as { + data?: Record; + }; return jsonResponse.data ?? {}; } - + /** * Updates user-defined metadata for an organization. * @@ -1201,8 +1205,8 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateOrganizationMetadata({ - organizationId: id, + await this.client.updateOrganizationMetadata({ + organizationId: id, data: Struct.fromJson(data), }); } @@ -1215,7 +1219,9 @@ export class AppClient { */ async getLocationMetadata(id: string): Promise> { const response = await this.client.getLocationMetadata({ locationId: id }); - const jsonResponse = response.toJson() as { data?: Record }; + const jsonResponse = response.toJson() as { + data?: Record; + }; return jsonResponse.data ?? {}; } @@ -1229,7 +1235,10 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateLocationMetadata({ locationId: id, data: Struct.fromJson(data) }); + await this.client.updateLocationMetadata({ + locationId: id, + data: Struct.fromJson(data), + }); } /** @@ -1240,7 +1249,9 @@ export class AppClient { */ async getRobotMetadata(id: string): Promise> { const response = await this.client.getRobotMetadata({ id }); - const jsonResponse = response.toJson() as { data?: Record }; + const jsonResponse = response.toJson() as { + data?: Record; + }; return jsonResponse.data ?? {}; } @@ -1263,13 +1274,13 @@ export class AppClient { * @param id The ID of the robot part * @returns The metadata associated with the robot part */ - async getRobotPartMetadata( - id: string - ): Promise> { + async getRobotPartMetadata(id: string): Promise> { const response = await this.client.getRobotPartMetadata({ id }); - const jsonResponse = response.toJson() as { data?: Record }; - return jsonResponse.data ?? {}; - } + const jsonResponse = response.toJson() as { + data?: Record; + }; + return jsonResponse.data ?? {}; + } /** * Updates user-defined metadata for a machine robot. @@ -1281,6 +1292,9 @@ export class AppClient { id: string, data: Record ): Promise { - await this.client.updateRobotPartMetadata({ id, data: Struct.fromJson(data) }); + await this.client.updateRobotPartMetadata({ + id, + data: Struct.fromJson(data), + }); } } From 3f85a846eb1bbfff59682498ac4fded2a1b7023f Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 14:38:17 -0400 Subject: [PATCH 17/18] update comment for getOrganizationMetadata --- src/app/app-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 95857974d..4abc0adc5 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1181,7 +1181,7 @@ export class AppClient { * Retrieves user-defined metadata for an organization. * * @param id The ID of the organization - * @returns The metadata associated with the organization as a plain JS object + * @returns The metadata associated with the organization */ async getOrganizationMetadata( id: string From 86b2331bb3f1953f64adcda0005ac62a25f6c02b Mon Sep 17 00:00:00 2001 From: JosephBorodach Date: Wed, 12 Mar 2025 14:39:12 -0400 Subject: [PATCH 18/18] Update comment for updateRobotPartMetadata --- src/app/app-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/app-client.ts b/src/app/app-client.ts index 4abc0adc5..ec0602635 100644 --- a/src/app/app-client.ts +++ b/src/app/app-client.ts @@ -1283,9 +1283,9 @@ export class AppClient { } /** - * Updates user-defined metadata for a machine robot. + * Updates user-defined metadata for a robot part. * - * @param id The ID of the machine robot + * @param id The ID of the robot part * @param data The metadata to update */ async updateRobotPartMetadata(