-
-
Notifications
You must be signed in to change notification settings - Fork 76
Develop #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #240
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| namespace Resgrid.Config | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Resgrid.Config | ||
| { | ||
| public static class InfoConfig | ||
| { | ||
|
|
@@ -21,5 +23,39 @@ public static class InfoConfig | |
| public static string RelayAppKey = "RelayAppKey"; | ||
|
|
||
| public static string EmailProcessorKey = "EmailProcessorKey"; | ||
|
|
||
| public static List<ResgridSystemLocation> Locations = new List<ResgridSystemLocation>() | ||
| { | ||
| new ResgridSystemLocation() | ||
| { | ||
| Name = "US-West", | ||
| DisplayName = "Resgrid North America (Global)", | ||
| LocationInfo = | ||
| "This is the Resgrid system hosted in the Western United States (private datacenter). This system services most Resgrid customers.", | ||
| IsDefault = true, | ||
| ApiUrl = "https://api.resgrid.com", | ||
| AllowsFreeAccounts = true | ||
| }, | ||
| new ResgridSystemLocation() | ||
| { | ||
| Name = "EU-Central", | ||
| DisplayName = "Resgrid Europe", | ||
| LocationInfo = | ||
| "This is the Resgrid system hosted in Central Europe (on OVH). This system services Resgrid customers in the European Union to help with data compliance requirements.", | ||
| IsDefault = false, | ||
| ApiUrl = "https://api.eu.resgrid.com", | ||
| AllowsFreeAccounts = false | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public class ResgridSystemLocation | ||
| { | ||
| public string Name { get; set; } | ||
| public string DisplayName { get; set; } | ||
| public string LocationInfo { get; set; } | ||
| public bool IsDefault { get; set; } | ||
| public string ApiUrl { get; set; } | ||
| public bool AllowsFreeAccounts { get; set; } | ||
|
Comment on lines
+52
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use a record with init-only semantics for config state. Records with positional parameters make the configuration immutable and concise, matching the guideline to separate state from behavior. - public class ResgridSystemLocation
- {
- public string Name { get; set; }
- public string DisplayName { get; set; }
- public string LocationInfo { get; set; }
- public bool IsDefault { get; set; }
- public string ApiUrl { get; set; }
- public bool AllowsFreeAccounts { get; set; }
- }
+ public sealed record ResgridSystemLocation(
+ string Name,
+ string DisplayName,
+ string LocationInfo,
+ bool IsDefault,
+ string ApiUrl,
+ bool AllowsFreeAccounts
+ );If other code uses object initializers, convert those call sites accordingly. Also consider adding a helper to fetch the default location: public static ResgridSystemLocation DefaultLocation => Locations.First(l => l.IsDefault);🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,48 +49,84 @@ public async Task<Call> GenerateCall(CallEmail email, string managingUser, List< | |
| if (String.IsNullOrEmpty(email.Subject)) | ||
| return null; | ||
|
|
||
| string[] sections = email.TextBody.Split(new[] {" ALPHA 512 "}, StringSplitOptions.None); | ||
| string[] sectionOneParts = sections[0].Split(new[] {" "}, StringSplitOptions.None); | ||
|
|
||
| Call c = new Call(); | ||
| c.Notes = email.TextBody; | ||
| c.Name = sections[1].Trim(); | ||
| c.LoggedOn = DateTime.UtcNow; | ||
| c.Priority = priority; | ||
| c.ReportingUserId = managingUser; | ||
| c.Dispatches = new Collection<CallDispatch>(); | ||
| c.CallSource = (int)CallSources.EmailImport; | ||
| c.SourceIdentifier = email.MessageId; | ||
| c.NatureOfCall = sections[1].Trim(); | ||
| c.IncidentNumber = sectionOneParts[0].Trim(); | ||
| c.ExternalIdentifier = sectionOneParts[0].Trim(); | ||
|
|
||
| if (users != null && users.Any()) | ||
| try | ||
| { | ||
| foreach (var u in users) | ||
| string[] sections = email.TextBody.Split(new[] { " ALPHA 512 " }, StringSplitOptions.None); | ||
| string[] sectionOneParts = sections[0].Split(new[] { " " }, StringSplitOptions.None); | ||
|
|
||
| Call c = new Call(); | ||
| c.Notes = email.TextBody; | ||
| c.Name = sections[1].Trim(); | ||
| c.LoggedOn = DateTime.UtcNow; | ||
| c.Priority = priority; | ||
| c.ReportingUserId = managingUser; | ||
| c.Dispatches = new Collection<CallDispatch>(); | ||
| c.CallSource = (int)CallSources.EmailImport; | ||
| c.SourceIdentifier = email.MessageId; | ||
| c.NatureOfCall = sections[1].Trim(); | ||
| c.IncidentNumber = sectionOneParts[0].Trim(); | ||
| c.ExternalIdentifier = sectionOneParts[0].Trim(); | ||
|
|
||
| if (users != null && users.Any()) | ||
| { | ||
| CallDispatch cd = new CallDispatch(); | ||
| cd.UserId = u.UserId; | ||
| foreach (var u in users) | ||
| { | ||
| CallDispatch cd = new CallDispatch(); | ||
| cd.UserId = u.UserId; | ||
|
|
||
| c.Dispatches.Add(cd); | ||
| c.Dispatches.Add(cd); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Search for an active call | ||
| if (activeCalls != null && activeCalls.Any()) | ||
| // Search for an active call | ||
| if (activeCalls != null && activeCalls.Any()) | ||
| { | ||
| var activeCall = activeCalls.FirstOrDefault(x => x.IncidentNumber == c.IncidentNumber); | ||
|
|
||
| if (activeCall != null) | ||
| { | ||
| activeCall.Notes = c.Notes; | ||
| activeCall.LastDispatchedOn = DateTime.UtcNow; | ||
|
|
||
| return activeCall; | ||
| } | ||
| } | ||
|
|
||
| return c; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var activeCall = activeCalls.FirstOrDefault(x => x.IncidentNumber == c.IncidentNumber); | ||
| Call c = new Call(); | ||
| c.Name = email.Subject; | ||
| c.NatureOfCall = $"ERROR PROCESSING DISPATCH EMAIL, Unprocessed email body: {email.TextBody}"; | ||
|
|
||
| if (activeCall != null) | ||
| if (users != null && users.Any()) | ||
| { | ||
| activeCall.Notes = c.Notes; | ||
| activeCall.LastDispatchedOn = DateTime.UtcNow; | ||
| foreach (var u in users) | ||
| { | ||
| CallDispatch cd = new CallDispatch(); | ||
| cd.UserId = u.UserId; | ||
|
|
||
| return activeCall; | ||
| c.Dispatches.Add(cd); | ||
| } | ||
| } | ||
|
Comment on lines
+99
to
112
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix NullReference and complete fallback Call construction in catch block c.Dispatches isn't initialized before Add, causing a NullReferenceException inside catch. Also key fields (Notes, LoggedOn, Priority, ReportingUserId, CallSource, SourceIdentifier) aren't set, and active-call lookup uses a null IncidentNumber. Apply this diff: - catch (Exception ex)
+ catch (Exception ex)
{
- Call c = new Call();
- c.Name = email.Subject;
- c.NatureOfCall = $"ERROR PROCESSING DISPATCH EMAIL, Unprocessed email body: {email.TextBody}";
+ Call c = new Call();
+ c.Name = email.Subject ?? "Email-to-Call Error";
+ c.Notes = email.TextBody;
+ c.NatureOfCall = $"ERROR PROCESSING DISPATCH EMAIL, Unprocessed email body: {email.TextBody}";
+ c.LoggedOn = DateTime.UtcNow;
+ c.Priority = priority;
+ c.ReportingUserId = managingUser;
+ c.Dispatches = new Collection<CallDispatch>();
+ c.CallSource = (int)CallSources.EmailImport;
+ c.SourceIdentifier = email.MessageId;
if (users != null && users.Any())
{
foreach (var u in users)
{
CallDispatch cd = new CallDispatch();
cd.UserId = u.UserId;
c.Dispatches.Add(cd);
}
}
- // Search for an active call
- if (activeCalls != null && activeCalls.Any())
+ // Search for an active call only if we have an incident number
+ if (!string.IsNullOrWhiteSpace(c.IncidentNumber) && activeCalls != null && activeCalls.Any())
{
var activeCall = activeCalls.FirstOrDefault(x => x.IncidentNumber == c.IncidentNumber);
if (activeCall != null)
{
activeCall.Notes = c.Notes;
activeCall.LastDispatchedOn = DateTime.UtcNow;
return activeCall;
}
}
return c;
}Also applies to: 114-129 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| return c; | ||
| // Search for an active call | ||
| if (activeCalls != null && activeCalls.Any()) | ||
| { | ||
| var activeCall = activeCalls.FirstOrDefault(x => x.IncidentNumber == c.IncidentNumber); | ||
|
|
||
| if (activeCall != null) | ||
| { | ||
| activeCall.Notes = c.Notes; | ||
| activeCall.LastDispatchedOn = DateTime.UtcNow; | ||
|
|
||
| return activeCall; | ||
| } | ||
| } | ||
|
|
||
| return c; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| using System.Collections.Generic; | ||
| using Resgrid.Config; | ||
|
|
||
| namespace Resgrid.Web.Services.Models.v4.Configs | ||
| { | ||
| /// <summary> | ||
| /// Gets Configuration Information for the Resgrid System | ||
| /// </summary> | ||
| public class GetSystemConfigResult : StandardApiResponseV4Base | ||
| { | ||
| /// <summary> | ||
| /// Response Data | ||
| /// </summary> | ||
| public GetSystemConfigResultData Data { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Default constructor | ||
| /// </summary> | ||
| public GetSystemConfigResult() | ||
| { | ||
| Data = new GetSystemConfigResultData(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Information about the Resgrid System | ||
| /// </summary> | ||
| public class GetSystemConfigResultData | ||
| { | ||
| /// <summary> | ||
| /// Resgrid Datacenter Locations | ||
| /// </summary> | ||
| public List<ResgridSystemLocation> Locations { get; set; } | ||
|
|
||
| public GetSystemConfigResultData() | ||
| { | ||
| Locations = InfoConfig.Locations; | ||
| } | ||
|
Comment on lines
+35
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Don’t return a reference to global mutable state (deep-copy Locations). Assigning using System.Collections.Generic;
+using System.Linq;
using Resgrid.Config;
@@
public GetSystemConfigResultData()
{
- Locations = InfoConfig.Locations;
+ Locations = InfoConfig.Locations
+ .Select(l => new ResgridSystemLocation
+ {
+ Name = l.Name,
+ DisplayName = l.DisplayName,
+ LocationInfo = l.LocationInfo,
+ IsDefault = l.IsDefault,
+ ApiUrl = l.ApiUrl,
+ AllowsFreeAccounts = l.AllowsFreeAccounts
+ })
+ .ToList();
}Also applies to: 1-1 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Expose Locations as read-only to avoid global mutation; initialize immutably.
The config list is public and mutable, which is risky. Prefer IReadOnlyList and immutable initialization.
🤖 Prompt for AI Agents