Skip to content

Commit

Permalink
Do partial robot updates with SignalR
Browse files Browse the repository at this point in the history
  • Loading branch information
andchiind committed Jan 7, 2025
1 parent 36cdfeb commit b78340d
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 32 deletions.
31 changes: 31 additions & 0 deletions backend/api/Controllers/Models/RobotAttributeResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
using Api.Database.Models;

namespace Api.Controllers.Models
{
public class RobotAttributeResponse
{
public string Id { get; set; }

public string PropertyName { get; set; }

public object? Value { get; set; }

[JsonConstructor]
#nullable disable
public RobotAttributeResponse() { }

#nullable enable

public RobotAttributeResponse(string robotId, string propertyName, object? robotProperty)
{
Id = robotId;
PropertyName = propertyName;
Value = robotProperty;
if (!typeof(RobotResponse).GetProperties().Any(property => property.Name == propertyName))
{
throw new ArgumentException($"Property {robotProperty} does not match any attributes in the RobotAttributeResponse class");
}
}
}
}
89 changes: 62 additions & 27 deletions backend/api/Services/RobotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,8 @@ public async Task Update(Robot robot)

context.Update(robot);
await ApplyDatabaseUpdate(robot.CurrentInstallation);
_ = signalRService.SendMessageAsync(
"Robot updated",
robot?.CurrentInstallation,
robot != null ? new RobotResponse(robot) : null
);
if (robot.CurrentInstallation != null)
NotifySignalROfUpdatedRobot(robot!, robot!.CurrentInstallation!);
DetachTracking(robot!);
}

Expand Down Expand Up @@ -421,33 +418,46 @@ private async Task UpdateRobotProperty(
throw new RobotNotFoundException(errorMessage);
}

foreach (var property in typeof(Robot).GetProperties())
var updatedProperty = typeof(Robot).GetProperties().FirstOrDefault((property) => property.Name == propertyName);

if (updatedProperty == null)
{
if (property.Name == propertyName)
{
if (isLogLevelDebug)
logger.LogDebug(
"Setting {robotName} field {propertyName} from {oldValue} to {NewValue}",
robot.Name,
propertyName,
property.GetValue(robot),
value
);
else
logger.LogInformation(
"Setting {robotName} field {propertyName} from {oldValue} to {NewValue}",
robot.Name,
propertyName,
property.GetValue(robot),
value
);
property.SetValue(robot, value);
}
logger.LogError("Failed to update {robotName} as it did not have the property {property}", robot.Name, propertyName);
DetachTracking(robot);
return;
}

if (isLogLevelDebug)
logger.LogDebug(
"Setting {robotName} field {propertyName} from {oldValue} to {NewValue}",
robot.Name,
propertyName,
updatedProperty.GetValue(robot),
value
);
else
logger.LogInformation(
"Setting {robotName} field {propertyName} from {oldValue} to {NewValue}",
robot.Name,
propertyName,
updatedProperty.GetValue(robot),
value
);

updatedProperty.SetValue(robot, value);

try
{
await Update(robot);
if (robot.CurrentInspectionArea is not null)
context.Entry(robot.CurrentInspectionArea).State = EntityState.Unchanged;
context.Entry(robot.Model).State = EntityState.Unchanged;

context.Update(robot);
await ApplyDatabaseUpdate(robot.CurrentInstallation);
if (robot.CurrentInstallation != null)
NotifySignalROfUpdatedRobot(robot!, robot!.CurrentInstallation!, propertyName, value);

DetachTracking(robot!);
}
catch (InvalidOperationException e)
{
Expand Down Expand Up @@ -491,6 +501,31 @@ private async Task VerifyThatUserIsAuthorizedToUpdateDataForInstallation(
);
}

private void NotifySignalROfUpdatedRobot(Robot robot, Installation installation, string propertyName, object? propertyValue)
{
try
{
if (propertyName == typeof(InspectionArea).Name)
{
if (propertyValue != null)
propertyValue = new InspectionAreaResponse((InspectionArea)propertyValue);
propertyValue = (InspectionAreaResponse?) propertyValue;
}

var responseObject = new RobotAttributeResponse(robot.Id, propertyName, propertyName);

_ = signalRService.SendMessageAsync(
"Robot attribute updated",
installation,
robot != null ? responseObject : null
);
}
catch (ArgumentException)
{
}

}

private void NotifySignalROfUpdatedRobot(Robot robot, Installation installation)
{
_ = signalRService.SendMessageAsync(
Expand Down
37 changes: 32 additions & 5 deletions frontend/src/components/Contexts/RobotContext.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { createContext, useContext, useState, FC, useEffect } from 'react'
import { BackendAPICaller } from 'api/ApiCaller'
import { Robot } from 'models/Robot'
import { Robot, RobotAttributeUpdate } from 'models/Robot'
import { SignalREventLabels, useSignalRContext } from './SignalRContext'
import { useLanguageContext } from './LanguageContext'
import { AlertType, useAlertContext } from './AlertContext'
import { FailedRequestAlertContent, FailedRequestAlertListContent } from 'components/Alerts/FailedRequestAlert'
import { AlertCategory } from 'components/Alerts/AlertsBanner'
import { useInstallationContext } from './InstallationContext'

const upsertRobotList = (list: Robot[], mission: Robot) => {
const upsertRobotList = (list: Robot[], robot: Robot) => {
const newList = [...list]
const i = newList.findIndex((e) => e.id === mission.id)
if (i > -1) newList[i] = mission
else newList.push(mission)
const i = newList.findIndex((e) => e.id === robot.id)
if (i > -1) newList[i] = robot
else newList.push(robot)
return newList
}

const updateRobotInList = (list: Robot[], robotId: string, mapping: (old: Robot) => Robot) => {
const newList = [...list]
const i = newList.findIndex((e) => e.id === robotId)
if (i > -1) newList[i] = mapping(newList[i])
return newList
}

Expand Down Expand Up @@ -60,6 +67,26 @@ export const RobotProvider: FC<Props> = ({ children }) => {
return [...oldRobotListCopy]
})
})
registerEvent(SignalREventLabels.robotAttributeUpdated, (username: string, message: string) => {
const updatedRobot: RobotAttributeUpdate = JSON.parse(message)
// The check below makes it so that it is not treated as null in the code.

const updatedProperty = updatedRobot.propertyName
if (!updatedProperty) return;

const updatedValue = updatedRobot.value

const updateFunction = (oldRobot: Robot): Robot => {
if (Object.keys(oldRobot).includes(updatedProperty))
oldRobot = {...oldRobot, [updatedProperty]: updatedValue }
return oldRobot
}
setEnabledRobots((oldRobotList) => {
let oldRobotListCopy = [...oldRobotList]
oldRobotListCopy = updateRobotInList(oldRobotListCopy, updatedRobot.id, updateFunction)
return [...oldRobotListCopy]
})
})
registerEvent(SignalREventLabels.robotDeleted, (username: string, message: string) => {
const updatedRobot: Robot = JSON.parse(message)
setEnabledRobots((oldRobotList) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Contexts/SignalRContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export enum SignalREventLabels {
inspectionAreaDeleted = 'InspectionArea deleted',
robotAdded = 'Robot added',
robotUpdated = 'Robot updated',
robotAttributeUpdated = 'Robot attribute updated',
robotDeleted = 'Robot deleted',
inspectionUpdated = 'Inspection updated',
alert = 'Alert',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/models/Robot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export enum RobotFlotillaStatus {
Recharging = 'Recharging',
}

export interface RobotAttributeUpdate {
id: string
propertyName: string
value: any
}

export interface Robot {
id: string
name?: string
Expand Down

0 comments on commit b78340d

Please sign in to comment.