diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateLogJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateLogJob.cs deleted file mode 100644 index b7a48f98a..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateLogJob.cs +++ /dev/null @@ -1,29 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs -{ - public record CreateLogJob : Job - { - public LogObject LogObject { get; init; } - - public override string Description() - { - return $"Create Log - WellUid: {LogObject.WellUid}; WellboreUid: {LogObject.WellboreUid}; LogUid: {LogObject.Uid};"; - } - - public override string GetObjectName() - { - return LogObject.Name; - } - - public override string GetWellboreName() - { - return LogObject.WellboreName; - } - - public override string GetWellName() - { - return LogObject.WellName; - } - } -} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateMudLogJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateMudLogJob.cs deleted file mode 100644 index 283bf0ede..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateMudLogJob.cs +++ /dev/null @@ -1,29 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs -{ - public record CreateMudLogJob : Job - { - public MudLog MudLog { get; init; } - - public override string Description() - { - return $"Create MudLog - WellUid: {MudLog.WellUid}; WellboreUid: {MudLog.WellboreUid}; MudLogUid: {MudLog.Uid};"; - } - - public override string GetObjectName() - { - return MudLog.Name; - } - - public override string GetWellboreName() - { - return MudLog.WellboreName; - } - - public override string GetWellName() - { - return MudLog.WellName; - } - } -} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateObjectOnWellboreJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateObjectOnWellboreJob.cs new file mode 100644 index 000000000..e01984d29 --- /dev/null +++ b/Src/WitsmlExplorer.Api/Jobs/CreateObjectOnWellboreJob.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +using WitsmlExplorer.Api.Models; + +namespace WitsmlExplorer.Api.Jobs +{ + public record CreateObjectOnWellboreJob : Job + { + [JsonConverter(typeof(ObjectOnWellboreConverter))] + public ObjectOnWellbore Object { get; init; } + public EntityType ObjectType { get; init; } + + public override string Description() + { + return $"To Create - Type: {ObjectType}, Uid: {Object.Uid}."; + } + + public override string GetObjectName() + { + return Object.Name; + } + + public override string GetWellboreName() + { + return Object.WellboreName; + } + + public override string GetWellName() + { + return Object.WellName; + } + } +} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs deleted file mode 100644 index 08dd4b103..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs +++ /dev/null @@ -1,50 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs; - -/// -/// Job for create rig with jobInfo. -/// -public record CreateRigJob : Job -{ - /// - /// Rig API model. - /// - public Rig Rig { get; init; } - - /// - /// Getting description of created rig. - /// - /// String of job info which provide WellUid, WellboreUid and RigUid. - public override string Description() - { - return $"Create Rig - Uid: {Rig.Uid}; Name: {Rig.Name}; WellUid: {Rig.WellUid}; WellboreUid: {Rig.WellboreUid};"; - } - - /// - /// Getting name of rig. - /// - /// String of rig name. - public override string GetObjectName() - { - return Rig.Name; - } - - /// - /// Getting name of wellbore. - /// - /// String of wellbore name. - public override string GetWellboreName() - { - return Rig.WellboreName; - } - - /// - /// Getting name of well. - /// - /// String of well name. - public override string GetWellName() - { - return Rig.WellName; - } -} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateRiskJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateRiskJob.cs deleted file mode 100644 index 767d98ab3..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateRiskJob.cs +++ /dev/null @@ -1,29 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs -{ - public record CreateRiskJob : Job - { - public Risk Risk { get; init; } - - public override string Description() - { - return $"Create Risk - WellUid: {Risk.WellUid}; WellboreUid: {Risk.WellboreUid}; RiskUid: {Risk.Uid};"; - } - - public override string GetObjectName() - { - return Risk.Name; - } - - public override string GetWellboreName() - { - return Risk.WellboreName; - } - - public override string GetWellName() - { - return Risk.WellName; - } - } -} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateTrajectoryJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateTrajectoryJob.cs deleted file mode 100644 index 2ba40901a..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateTrajectoryJob.cs +++ /dev/null @@ -1,50 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs; - -/// -/// Job for create trajectory with jobInfo. -/// -public record CreateTrajectoryJob : Job -{ - /// - /// Trajectory API model. - /// - public Trajectory Trajectory { get; init; } - - /// - /// Getting description of created trajectory. - /// - /// String of job info which provide Trajectory Uid and Name, WellUid, WellboreUid. - public override string Description() - { - return $"Create Trajectory - Uid: {Trajectory.Uid}; Name: {Trajectory.Name}; WellUid: {Trajectory.WellUid}; WellboreUid: {Trajectory.WellboreUid};"; - } - - /// - /// Getting name of trajectory. - /// - /// String of trajectory name. - public override string GetObjectName() - { - return Trajectory.Name; - } - - /// - /// Getting name of wellbore. - /// - /// String of wellbore name. - public override string GetWellboreName() - { - return Trajectory.WellboreName; - } - - /// - /// Getting name of well. - /// - /// String of well name. - public override string GetWellName() - { - return Trajectory.WellName; - } -} diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateWbGeometryJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateWbGeometryJob.cs deleted file mode 100644 index 7e8d12a35..000000000 --- a/Src/WitsmlExplorer.Api/Jobs/CreateWbGeometryJob.cs +++ /dev/null @@ -1,29 +0,0 @@ -using WitsmlExplorer.Api.Models; - -namespace WitsmlExplorer.Api.Jobs -{ - public record CreateWbGeometryJob : Job - { - public WbGeometry WbGeometry { get; init; } - - public override string Description() - { - return $"Create WbGeometry - WellUid: {WbGeometry.WellUid}; WellboreUid: {WbGeometry.WellboreUid}; WbGeometryUid: {WbGeometry.Uid};"; - } - - public override string GetObjectName() - { - return WbGeometry.Name; - } - - public override string GetWellboreName() - { - return WbGeometry.WellboreName; - } - - public override string GetWellName() - { - return WbGeometry.WellName; - } - } -} diff --git a/Src/WitsmlExplorer.Api/Models/JobType.cs b/Src/WitsmlExplorer.Api/Models/JobType.cs index 5abeabf67..43f956661 100644 --- a/Src/WitsmlExplorer.Api/Models/JobType.cs +++ b/Src/WitsmlExplorer.Api/Models/JobType.cs @@ -25,14 +25,9 @@ public enum JobType ModifyWbGeometrySection, ModifyWell, ModifyWellbore, - CreateLogObject, CreateWell, CreateWellbore, - CreateRisk, - CreateMudLog, - CreateRig, - CreateTrajectory, - CreateWbGeometry, + CreateObjectOnWellbore, BatchModifyWell, ImportLogData, ReplaceComponents, diff --git a/Src/WitsmlExplorer.Api/Models/LogObject.cs b/Src/WitsmlExplorer.Api/Models/LogObject.cs index 346fa14f6..5523f91dc 100644 --- a/Src/WitsmlExplorer.Api/Models/LogObject.cs +++ b/Src/WitsmlExplorer.Api/Models/LogObject.cs @@ -9,7 +9,7 @@ public class LogObject : ObjectOnWellbore public string IndexType { get; set; } public string StartIndex { get; set; } public string EndIndex { get; set; } - public bool ObjectGrowing { get; init; } + public bool? ObjectGrowing { get; init; } public string ServiceCompany { get; init; } public string RunNumber { get; init; } public string IndexCurve { get; init; } diff --git a/Src/WitsmlExplorer.Api/Models/Measure/DayMeasure.cs b/Src/WitsmlExplorer.Api/Models/Measure/DayMeasure.cs index 82304a39e..d6cb6e693 100644 --- a/Src/WitsmlExplorer.Api/Models/Measure/DayMeasure.cs +++ b/Src/WitsmlExplorer.Api/Models/Measure/DayMeasure.cs @@ -1,7 +1,20 @@ +using System.Globalization; + +using Witsml.Data.Measures; + namespace WitsmlExplorer.Api.Models.Measure { public class DayMeasure : Measure { public int Value { get; init; } + + public WitsmlDayMeasure ToWitsml() + { + return new() + { + Uom = Uom, + Value = Value.ToString(CultureInfo.InvariantCulture) + }; + } } } diff --git a/Src/WitsmlExplorer.Api/Models/Wellbore.cs b/Src/WitsmlExplorer.Api/Models/Wellbore.cs index 335996463..3ea6aa2b2 100644 --- a/Src/WitsmlExplorer.Api/Models/Wellbore.cs +++ b/Src/WitsmlExplorer.Api/Models/Wellbore.cs @@ -11,8 +11,8 @@ public class Wellbore public string WellborePurpose { get; set; } public string WellboreParentUid { get; set; } public string WellboreParentName { get; set; } - public string WellStatus { get; set; } - public string WellType { get; set; } + public string WellboreStatus { get; set; } + public string WellboreType { get; set; } public bool? IsActive { get; set; } public string DateTimeCreation { get; set; } public string DateTimeLastChange { get; set; } diff --git a/Src/WitsmlExplorer.Api/Query/FluidsReportQueries.cs b/Src/WitsmlExplorer.Api/Query/FluidsReportQueries.cs index a7089b51c..193103186 100644 --- a/Src/WitsmlExplorer.Api/Query/FluidsReportQueries.cs +++ b/Src/WitsmlExplorer.Api/Query/FluidsReportQueries.cs @@ -25,7 +25,9 @@ public static WitsmlFluidsReports QueryByWellbore(string wellUid, string wellbor DTimLastChange = "", ItemState = "", Comments = "", - DefaultDatum = "" + DefaultDatum = "", + SourceName = "", + ServiceCategory = "" } }.AsItemInWitsmlList(); } diff --git a/Src/WitsmlExplorer.Api/Query/RigQueries.cs b/Src/WitsmlExplorer.Api/Query/RigQueries.cs index d7bba277d..0ad6182bd 100644 --- a/Src/WitsmlExplorer.Api/Query/RigQueries.cs +++ b/Src/WitsmlExplorer.Api/Query/RigQueries.cs @@ -23,6 +23,8 @@ public static WitsmlRigs GetWitsmlRig(string wellUid, string wellboreUid, string IsOffshore = "", Manufacturer = "", Name = "", + NameWell = "", + NameWellbore = "", NameContact = "", Owner = "", Registration = "", diff --git a/Src/WitsmlExplorer.Api/Query/RiskQueries.cs b/Src/WitsmlExplorer.Api/Query/RiskQueries.cs index 6dd37a4ef..805b3f922 100644 --- a/Src/WitsmlExplorer.Api/Query/RiskQueries.cs +++ b/Src/WitsmlExplorer.Api/Query/RiskQueries.cs @@ -16,6 +16,8 @@ public static WitsmlRisks GetWitsmlRiskByWellbore(string wellUid, string wellbor UidWell = wellUid, UidWellbore = wellboreUid, Name = "", + NameWell = "", + NameWellbore = "", Type = "", Category = "", SubCategory = "", diff --git a/Src/WitsmlExplorer.Api/Query/WellboreQueries.cs b/Src/WitsmlExplorer.Api/Query/WellboreQueries.cs index 96009a1c9..c76fe5ce1 100644 --- a/Src/WitsmlExplorer.Api/Query/WellboreQueries.cs +++ b/Src/WitsmlExplorer.Api/Query/WellboreQueries.cs @@ -143,35 +143,44 @@ public static WitsmlWellbores UpdateWitsmlWellbore(Wellbore wellbore) public static WitsmlWellbores CreateWitsmlWellbore(Wellbore wellbore) { - return !string.IsNullOrEmpty(wellbore.WellboreParentUid) - ? new WitsmlWellbores + WitsmlParentWellbore parentWellbore = (wellbore.WellboreParentUid == null && wellbore.WellboreParentName == null) ? null + : new WitsmlParentWellbore { - Wellbores = new WitsmlWellbore - { - Uid = wellbore.Uid, - Name = wellbore.Name, - UidWell = wellbore.WellUid, - NameWell = wellbore.WellName, - ParentWellbore = new WitsmlParentWellbore - { - UidRef = wellbore.WellboreParentUid, - Value = wellbore.WellboreParentName - }, - PurposeWellbore = wellbore.WellborePurpose - - }.AsItemInList() - } - : new WitsmlWellbores + UidRef = wellbore.WellboreParentUid, + Value = wellbore.WellboreParentName + }; + return new WitsmlWellbores + { + Wellbores = new WitsmlWellbore { - Wellbores = new WitsmlWellbore + Uid = wellbore.Uid, + Name = wellbore.Name, + UidWell = wellbore.WellUid, + NameWell = wellbore.WellName, + ParentWellbore = parentWellbore, + StatusWellbore = wellbore.WellboreStatus, + TypeWellbore = wellbore.WellboreType, + Number = wellbore.Number, + SuffixAPI = wellbore.SuffixAPI, + NumGovt = wellbore.NumGovt, + Shape = wellbore.Shape, + DTimKickoff = wellbore.DTimeKickoff, + PurposeWellbore = wellbore.WellborePurpose, + Md = wellbore.Md?.ToWitsml(), + Tvd = wellbore.Tvd?.ToWitsml(), + MdKickoff = wellbore.MdKickoff?.ToWitsml(), + TvdKickoff = wellbore.TvdKickoff?.ToWitsml(), + MdPlanned = wellbore.MdPlanned?.ToWitsml(), + TvdPlanned = wellbore.TvdPlanned?.ToWitsml(), + MdSubSeaPlanned = wellbore.MdSubSeaPlanned?.ToWitsml(), + TvdSubSeaPlanned = wellbore.TvdSubSeaPlanned?.ToWitsml(), + DayTarget = wellbore.DayTarget?.ToWitsml(), + CommonData = wellbore.Comments == null ? null : new WitsmlCommonData { - Uid = wellbore.Uid, - Name = wellbore.Name, - UidWell = wellbore.WellUid, - NameWell = wellbore.WellName, - PurposeWellbore = wellbore.WellborePurpose - }.AsItemInList() - }; + Comments = wellbore.Comments + } + }.AsItemInList() + }; } public static WitsmlWellbores DeleteWitsmlWellbore(string wellUid, string wellboreUid) diff --git a/Src/WitsmlExplorer.Api/Services/BhaRunService.cs b/Src/WitsmlExplorer.Api/Services/BhaRunService.cs index 096dd4abc..9edd88db8 100644 --- a/Src/WitsmlExplorer.Api/Services/BhaRunService.cs +++ b/Src/WitsmlExplorer.Api/Services/BhaRunService.cs @@ -45,10 +45,10 @@ private static BhaRun WitsmlToBhaRun(WitsmlBhaRun bhaRun) WellboreName = bhaRun.NameWellbore, WellboreUid = bhaRun.UidWellbore, NumStringRun = bhaRun.NumStringRun, - Tubular = new RefNameString + Tubular = (bhaRun.Tubular == null) ? null : new RefNameString { - UidRef = bhaRun.Tubular?.UidRef, - Value = bhaRun.Tubular?.Value + UidRef = bhaRun.Tubular.UidRef, + Value = bhaRun.Tubular.Value }, StatusBha = bhaRun.StatusBha ?? null, NumBitRun = bhaRun.NumBitRun, diff --git a/Src/WitsmlExplorer.Api/Services/FluidsReportService.cs b/Src/WitsmlExplorer.Api/Services/FluidsReportService.cs index ce602b79f..e491fccfa 100644 --- a/Src/WitsmlExplorer.Api/Services/FluidsReportService.cs +++ b/Src/WitsmlExplorer.Api/Services/FluidsReportService.cs @@ -141,6 +141,8 @@ private static FluidsReport WitsmlToFluidsReport(WitsmlFluidsReport fluidsReport ItemState = fluidsReport.CommonData.ItemState, Comments = fluidsReport.CommonData.Comments, DefaultDatum = fluidsReport.CommonData.DefaultDatum, + SourceName = fluidsReport.CommonData.SourceName, + ServiceCategory = fluidsReport.CommonData.ServiceCategory } }; } diff --git a/Src/WitsmlExplorer.Api/Services/MudLogService.cs b/Src/WitsmlExplorer.Api/Services/MudLogService.cs index 5987352f4..3ce66117e 100644 --- a/Src/WitsmlExplorer.Api/Services/MudLogService.cs +++ b/Src/WitsmlExplorer.Api/Services/MudLogService.cs @@ -74,8 +74,8 @@ private static List GetGeologyIntervals(List GetWellbore(string wellUid, string wellboreUid) WitsmlWellbore witsmlWellbore = result.Wellbores.FirstOrDefault(); return witsmlWellbore == null ? null - : new Wellbore - { - Uid = witsmlWellbore.Uid, - Name = witsmlWellbore.Name, - WellUid = witsmlWellbore.UidWell, - WellName = witsmlWellbore.NameWell, - Number = witsmlWellbore.Number, - SuffixAPI = witsmlWellbore.SuffixAPI, - NumGovt = witsmlWellbore.NumGovt, - WellStatus = witsmlWellbore.StatusWellbore, - IsActive = StringHelpers.ToBoolean(witsmlWellbore.IsActive), - WellborePurpose = witsmlWellbore.PurposeWellbore, - WellboreParentUid = witsmlWellbore.ParentWellbore?.UidRef, - WellboreParentName = witsmlWellbore.ParentWellbore?.Value, - WellType = witsmlWellbore.TypeWellbore, - Shape = witsmlWellbore.Shape, - DTimeKickoff = witsmlWellbore.DTimKickoff, - Md = (witsmlWellbore.Md == null) ? null : new LengthMeasure { Uom = witsmlWellbore.Md.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.Md.Value) }, - Tvd = (witsmlWellbore.Tvd == null) ? null : new LengthMeasure { Uom = witsmlWellbore.Tvd.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.Tvd.Value) }, - MdKickoff = (witsmlWellbore.MdKickoff == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdKickoff.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdKickoff.Value) }, - TvdKickoff = (witsmlWellbore.TvdKickoff == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdKickoff.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdKickoff.Value) }, - MdPlanned = (witsmlWellbore.MdPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdPlanned.Value) }, - TvdPlanned = (witsmlWellbore.TvdPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdPlanned.Value) }, - MdSubSeaPlanned = (witsmlWellbore.MdSubSeaPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdSubSeaPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdSubSeaPlanned.Value) }, - TvdSubSeaPlanned = (witsmlWellbore.TvdSubSeaPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdSubSeaPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdSubSeaPlanned.Value) }, - DayTarget = (witsmlWellbore.DayTarget == null) ? null : new DayMeasure { Uom = witsmlWellbore.DayTarget.Uom, Value = int.Parse(witsmlWellbore.DayTarget.Value) }, - DateTimeCreation = witsmlWellbore.CommonData.DTimCreation, - DateTimeLastChange = witsmlWellbore.CommonData.DTimLastChange, - ItemState = witsmlWellbore.CommonData.ItemState, - Comments = witsmlWellbore.CommonData.Comments - }; + : FromWitsml(witsmlWellbore); } public async Task> GetWellbores(string wellUid = "") @@ -68,25 +38,48 @@ public async Task> GetWellbores(string wellUid = "") return await MeasurementHelper.MeasureExecutionTimeAsync(async (timeMeasurer) => { WitsmlWellbores query = WellboreQueries.GetWitsmlWellboreByWell(wellUid); - WitsmlWellbores result = await _witsmlClient.GetFromStoreAsync(query, new OptionsIn(ReturnElements.Requested)); + WitsmlWellbores result = await _witsmlClient.GetFromStoreAsync(query, new OptionsIn(ReturnElements.All)); List wellbores = result.Wellbores - .Select(witsmlWellbore => - new Wellbore - { - Uid = witsmlWellbore.Uid, - Name = witsmlWellbore.Name, - WellUid = witsmlWellbore.UidWell, - WellName = witsmlWellbore.NameWell, - WellStatus = witsmlWellbore.StatusWellbore, - WellType = witsmlWellbore.TypeWellbore, - IsActive = StringHelpers.ToBoolean(witsmlWellbore.IsActive), - DateTimeLastChange = witsmlWellbore.CommonData.DTimLastChange, - DateTimeCreation = witsmlWellbore.CommonData.DTimCreation - }) + .Select(FromWitsml) .OrderBy(wellbore => wellbore.Name).ToList(); timeMeasurer.LogMessage = executionTime => $"Fetched {wellbores.Count} wellbores in {executionTime} ms."; return wellbores; }); } + + private Wellbore FromWitsml(WitsmlWellbore witsmlWellbore) + { + return new Wellbore + { + Uid = witsmlWellbore.Uid, + Name = witsmlWellbore.Name, + WellUid = witsmlWellbore.UidWell, + WellName = witsmlWellbore.NameWell, + Number = witsmlWellbore.Number, + SuffixAPI = witsmlWellbore.SuffixAPI, + NumGovt = witsmlWellbore.NumGovt, + WellboreStatus = witsmlWellbore.StatusWellbore, + IsActive = StringHelpers.ToBoolean(witsmlWellbore.IsActive), + WellborePurpose = witsmlWellbore.PurposeWellbore, + WellboreParentUid = witsmlWellbore.ParentWellbore?.UidRef, + WellboreParentName = witsmlWellbore.ParentWellbore?.Value, + WellboreType = witsmlWellbore.TypeWellbore, + Shape = witsmlWellbore.Shape, + DTimeKickoff = witsmlWellbore.DTimKickoff, + Md = (witsmlWellbore.Md == null) ? null : new LengthMeasure { Uom = witsmlWellbore.Md.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.Md.Value) }, + Tvd = (witsmlWellbore.Tvd == null) ? null : new LengthMeasure { Uom = witsmlWellbore.Tvd.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.Tvd.Value) }, + MdKickoff = (witsmlWellbore.MdKickoff == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdKickoff.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdKickoff.Value) }, + TvdKickoff = (witsmlWellbore.TvdKickoff == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdKickoff.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdKickoff.Value) }, + MdPlanned = (witsmlWellbore.MdPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdPlanned.Value) }, + TvdPlanned = (witsmlWellbore.TvdPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdPlanned.Value) }, + MdSubSeaPlanned = (witsmlWellbore.MdSubSeaPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.MdSubSeaPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.MdSubSeaPlanned.Value) }, + TvdSubSeaPlanned = (witsmlWellbore.TvdSubSeaPlanned == null) ? null : new LengthMeasure { Uom = witsmlWellbore.TvdSubSeaPlanned.Uom, Value = StringHelpers.ToDecimal(witsmlWellbore.TvdSubSeaPlanned.Value) }, + DayTarget = (witsmlWellbore.DayTarget == null) ? null : new DayMeasure { Uom = witsmlWellbore.DayTarget.Uom, Value = int.Parse(witsmlWellbore.DayTarget.Value) }, + DateTimeCreation = witsmlWellbore.CommonData.DTimCreation, + DateTimeLastChange = witsmlWellbore.CommonData.DTimLastChange, + ItemState = witsmlWellbore.CommonData.ItemState, + Comments = witsmlWellbore.CommonData.Comments + }; + } } } diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateLogWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateLogWorker.cs deleted file mode 100644 index 7c2288fa0..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateLogWorker.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data; -using Witsml.Data.Curves; -using Witsml.Extensions; -using Witsml.ServiceReference; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Query; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create -{ - public class CreateLogWorker : BaseWorker, IWorker - { - public JobType JobType => JobType.CreateLogObject; - - public CreateLogWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateLogJob job, CancellationToken? cancellationToken = null) - { - WitsmlWellbore targetWellbore = await GetWellbore(GetTargetWitsmlClientOrThrow(), job.LogObject); - WitsmlLogs copyLogQuery = CreateLogQuery(job, targetWellbore); - QueryResult createLogResult = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(copyLogQuery); - if (!createLogResult.IsSuccessful) - { - string errorMessage = "Failed to create log."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, createLogResult.Reason), null); - } - - Logger.LogInformation("Log object created. {jobDescription}", job.Description()); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), job.LogObject.WellUid, job.LogObject.WellboreUid, EntityType.Log); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"Log object {job.LogObject.Name} created for {targetWellbore.Name}"); - - return (workerResult, refreshAction); - } - - private static WitsmlLogs CreateLogQuery(CreateLogJob job, WitsmlWellbore targetWellbore) - { - IndexType indexType = job.LogObject.IndexCurve == IndexType.Depth.ToString() ? IndexType.Depth : IndexType.Time; - string unit = indexType == IndexType.Depth ? DepthUnit.Meter.ToString() : Unit.TimeUnit.ToString(); - return new WitsmlLogs - { - Logs = new WitsmlLog - { - UidWell = targetWellbore.UidWell, - NameWell = targetWellbore.NameWell, - UidWellbore = targetWellbore.Uid, - NameWellbore = targetWellbore.Name, - Uid = job.LogObject.Uid, - Name = job.LogObject.Name, - RunNumber = job.LogObject.RunNumber, - ServiceCompany = job.LogObject.ServiceCompany, - IndexType = indexType == IndexType.Depth ? WitsmlLog.WITSML_INDEX_TYPE_MD : WitsmlLog.WITSML_INDEX_TYPE_DATE_TIME, - IndexCurve = new WitsmlIndexCurve() - { - Value = indexType.ToString() - }, - LogCurveInfo = new WitsmlLogCurveInfo - { - Uid = Guid.NewGuid().ToString(), - Mnemonic = indexType.ToString(), - Unit = unit, - TypeLogData = indexType == IndexType.Depth ? WitsmlLogCurveInfo.LogDataTypeDouble : WitsmlLogCurveInfo.LogDataTypeDatetime - }.AsItemInList() - }.AsItemInList() - }; - } - - private enum IndexType - { - Depth, - Time - } - - private static async Task GetWellbore(IWitsmlClient client, LogObject logObject) - { - WitsmlWellbores query = WellboreQueries.GetWitsmlWellboreByUid(logObject.WellUid, logObject.WellboreUid); - WitsmlWellbores wellbores = await client.GetFromStoreAsync(query, new OptionsIn(ReturnElements.Requested)); - return wellbores.Wellbores.FirstOrDefault(); - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateMudLogWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateMudLogWorker.cs deleted file mode 100644 index ceee3a87b..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateMudLogWorker.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data.MudLog; -using Witsml.ServiceReference; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Query; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create -{ - public class CreateMudLogWorker : BaseWorker, IWorker - { - public JobType JobType => JobType.CreateMudLog; - - public CreateMudLogWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateMudLogJob job, CancellationToken? cancellationToken = null) - { - MudLog mudLog = job.MudLog; - Verify(mudLog); - - WitsmlMudLogs mudLogToCreate = mudLog.ToWitsml(); - - QueryResult result = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(mudLogToCreate); - if (result.IsSuccessful) - { - await WaitUntilMudLogHasBeenCreated(mudLog); - Logger.LogInformation("MudLog created. {jobDescription}", job.Description()); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"MudLog created ({mudLog.Name} [{mudLog.Uid}])"); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), mudLog.WellUid, mudLog.WellboreUid, EntityType.MudLog); - return (workerResult, refreshAction); - } - - EntityDescription description = new() { WellboreName = mudLog.WellboreName }; - string errorMessage = "Failed to create mudLog."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, result.Reason, description), null); - } - - private async Task WaitUntilMudLogHasBeenCreated(MudLog mudLog) - { - bool isMudLogCreated = false; - WitsmlMudLogs query = MudLogQueries.QueryById(mudLog.WellUid, mudLog.WellboreUid, new string[] { mudLog.Uid }); - int maxRetries = 30; - while (!isMudLogCreated) - { - if (--maxRetries == 0) - { - throw new InvalidOperationException($"Not able to read newly created MudLog with name {mudLog.Name} (id={mudLog.Uid})"); - } - Thread.Sleep(1000); - WitsmlMudLogs mudLogResult = await GetTargetWitsmlClientOrThrow().GetFromStoreAsync(query, new OptionsIn(ReturnElements.IdOnly)); - isMudLogCreated = mudLogResult.MudLogs.Any(); - } - } - - private static void Verify(MudLog mudLog) - { - if (string.IsNullOrEmpty(mudLog.Uid)) - { - throw new InvalidOperationException($"{nameof(mudLog.Uid)} cannot be empty"); - } - - if (string.IsNullOrEmpty(mudLog.Name)) - { - throw new InvalidOperationException($"{nameof(mudLog.Name)} cannot be empty"); - } - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateObjectOnWellboreWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateObjectOnWellboreWorker.cs new file mode 100644 index 000000000..3c0bc06c7 --- /dev/null +++ b/Src/WitsmlExplorer.Api/Workers/Create/CreateObjectOnWellboreWorker.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; + +using Witsml; +using Witsml.Data; +using Witsml.Data.Curves; +using Witsml.Extensions; + +using WitsmlExplorer.Api.Jobs; +using WitsmlExplorer.Api.Models; +using WitsmlExplorer.Api.Services; +using WitsmlExplorer.Api.Workers.Modify; + +namespace WitsmlExplorer.Api.Workers.Create +{ + public class CreateObjectOnWellboreWorker : BaseWorker, IWorker + { + public JobType JobType => JobType.CreateObjectOnWellbore; + + public CreateObjectOnWellboreWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } + + public override async Task<(WorkerResult, RefreshAction)> Execute(CreateObjectOnWellboreJob job, CancellationToken? cancellationToken = null) + { + ObjectOnWellbore obj = job.Object; + EntityType objectType = job.ObjectType; + + Logger.LogInformation("Started {JobType}. {jobDescription}", JobType, job.Description()); + + try + { + ModifyUtils.VerifyCreationValues(obj); + } + catch (Exception e) + { + Logger.LogError("{JobType} - Job validation failed", JobType); + return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, e.Message, ""), null); + } + + IWitsmlQueryType query = obj.ToWitsml(); + + if (job.ObjectType == EntityType.Log) + { + LogObject log = (LogObject)job.Object; + ((WitsmlLogs)query).Logs.First().LogCurveInfo = new WitsmlLogCurveInfo + { + Uid = Guid.NewGuid().ToString(), + Mnemonic = log.IndexCurve, + Unit = log.IndexType == WitsmlLog.WITSML_INDEX_TYPE_MD ? DepthUnit.Meter.ToString() : Unit.TimeUnit.ToString(), + TypeLogData = log.IndexType == WitsmlLog.WITSML_INDEX_TYPE_MD ? WitsmlLogCurveInfo.LogDataTypeDouble : WitsmlLogCurveInfo.LogDataTypeDatetime + }.AsItemInList(); + } + + QueryResult createResult = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(query); + + if (!createResult.IsSuccessful) + { + string errorMessage = $"Failed to create {objectType}"; + Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); + return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, createResult.Reason), null); + } + + Logger.LogInformation("{objectType} created. {jobDescription}", objectType, job.Description()); + RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), obj.WellUid, obj.WellboreUid, objectType); + WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"{objectType} {obj.Name} updated for {obj.WellboreName}"); + + return (workerResult, refreshAction); + } + } +} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs deleted file mode 100644 index eeb7d2758..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data.Rig; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create; - -/// -/// Worker for creating new rig by specific well and wellbore. -/// -public class CreateRigWorker : BaseWorker, IWorker -{ - public CreateRigWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - public JobType JobType => JobType.CreateRig; - - /// - /// Create new rig on wellbore for witsml client. - /// - /// Job info of created rig. - /// Task of workerResult with refresh objects. - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateRigJob job, CancellationToken? cancellationToken = null) - { - Verify(job.Rig); - - WitsmlRigs rigToCreate = job.Rig.ToWitsml(); - - QueryResult addToStoreResult = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(rigToCreate); - - if (!addToStoreResult.IsSuccessful) - { - string errorMessage = "Failed to create rig."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, addToStoreResult.Reason), null); - } - - Logger.LogInformation("Rig created. {jobDescription}", job.Description()); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), job.Rig.WellUid, job.Rig.WellboreUid, EntityType.Rig); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"Rig {job.Rig.Name} add for {job.Rig.WellboreName}"); - - return (workerResult, refreshAction); - } - - private static void Verify(Rig rig) - { - if (string.IsNullOrEmpty(rig.Uid)) - { - throw new InvalidOperationException($"{nameof(rig.Uid)} cannot be empty"); - } - - if (string.IsNullOrEmpty(rig.Name)) - { - throw new InvalidOperationException($"{nameof(rig.Name)} cannot be empty"); - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateRiskWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateRiskWorker.cs deleted file mode 100644 index 26553e6b5..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateRiskWorker.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data; -using Witsml.ServiceReference; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Query; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create -{ - - public class CreateRiskWorker : BaseWorker, IWorker - { - public JobType JobType => JobType.CreateRisk; - - public CreateRiskWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateRiskJob job, CancellationToken? cancellationToken = null) - { - Risk risk = job.Risk; - Verify(risk); - - WitsmlRisks riskToCreate = risk.ToWitsml(); - - QueryResult result = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(riskToCreate); - if (result.IsSuccessful) - { - await WaitUntilRiskHasBeenCreated(risk); - Logger.LogInformation("Risk created. {jobDescription}", job.Description()); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"Risk created ({risk.Name} [{risk.Uid}])"); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), risk.WellUid, risk.WellboreUid, EntityType.Risk); - return (workerResult, refreshAction); - } - - EntityDescription description = new() { WellboreName = risk.WellboreName }; - string errorMessage = "Failed to create Risk."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, result.Reason, description), null); - } - private async Task WaitUntilRiskHasBeenCreated(Risk risk) - { - bool isCreated = false; - WitsmlRisks query = RiskQueries.QueryById(risk.WellUid, risk.WellboreUid, risk.Uid); - int maxRetries = 30; - while (!isCreated) - { - if (--maxRetries == 0) - { - throw new InvalidOperationException($"Not able to read newly created Risk with name {risk.Name} (id={risk.Uid})"); - } - Thread.Sleep(1000); - WitsmlRisks riskResult = await GetTargetWitsmlClientOrThrow().GetFromStoreAsync(query, new OptionsIn(ReturnElements.IdOnly)); - isCreated = riskResult.Risks.Any(); - } - } - - private static void Verify(Risk risk) - { - if (string.IsNullOrEmpty(risk.Uid)) - { - throw new InvalidOperationException($"{nameof(risk.Uid)} cannot be empty"); - } - - if (string.IsNullOrEmpty(risk.Name)) - { - throw new InvalidOperationException($"{nameof(risk.Name)} cannot be empty"); - } - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateTrajectoryWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateTrajectoryWorker.cs deleted file mode 100644 index 302d295f5..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateTrajectoryWorker.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create; - -/// -/// Worker for creating new trajectory by specific well and wellbore. -/// -public class CreateTrajectoryWorker : BaseWorker, IWorker -{ - public CreateTrajectoryWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - public JobType JobType => JobType.CreateTrajectory; - - /// - /// Create new trajectory on wellbore for witsml client. - /// - /// Job info of created trajectory. - /// Task of workerResult with refresh objects. - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateTrajectoryJob job, CancellationToken? cancellationToken = null) - { - Verify(job.Trajectory); - - WitsmlTrajectories trajectoryToCreate = job.Trajectory.ToWitsml(); - - QueryResult addToStoreResult = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(trajectoryToCreate); - - if (!addToStoreResult.IsSuccessful) - { - string errorMessage = "Failed to create trajectory."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, addToStoreResult.Reason), null); - } - - Logger.LogInformation("Trajectory created. {jobDescription}", job.Description()); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), job.Trajectory.WellUid, job.Trajectory.WellboreUid, EntityType.Trajectory); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"Trajectory {job.Trajectory.Name} add for {job.Trajectory.WellboreName}"); - - return (workerResult, refreshAction); - } - - private static void Verify(Trajectory trajectory) - { - if (string.IsNullOrEmpty(trajectory.Uid)) - { - throw new InvalidOperationException($"{nameof(trajectory.Uid)} cannot be empty"); - } - - if (string.IsNullOrEmpty(trajectory.Name)) - { - throw new InvalidOperationException($"{nameof(trajectory.Name)} cannot be empty"); - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateWbGeometryWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateWbGeometryWorker.cs deleted file mode 100644 index 5f23fd23e..000000000 --- a/Src/WitsmlExplorer.Api/Workers/Create/CreateWbGeometryWorker.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using Microsoft.Extensions.Logging; - -using Witsml; -using Witsml.Data; -using Witsml.Data.Measures; -using Witsml.Extensions; -using Witsml.ServiceReference; - -using WitsmlExplorer.Api.Jobs; -using WitsmlExplorer.Api.Models; -using WitsmlExplorer.Api.Query; -using WitsmlExplorer.Api.Services; - -namespace WitsmlExplorer.Api.Workers.Create -{ - public class CreateWbGeometryWorker : BaseWorker, IWorker - { - public JobType JobType => JobType.CreateWbGeometry; - - public CreateWbGeometryWorker(ILogger logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { } - - public override async Task<(WorkerResult, RefreshAction)> Execute(CreateWbGeometryJob job, CancellationToken? cancellationToken = null) - { - WbGeometry wbGeometry = job.WbGeometry; - Verify(wbGeometry); - - WitsmlWbGeometrys wbGeometryToCreate = SetupWbGeometryToCreate(wbGeometry); - - QueryResult result = await GetTargetWitsmlClientOrThrow().AddToStoreAsync(wbGeometryToCreate); - if (result.IsSuccessful) - { - await WaitUntilWbGeometryHasBeenCreated(wbGeometry); - Logger.LogInformation("WbGeometry created. {jobDescription}", job.Description()); - WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"WbGeometry created ({wbGeometry.Name} [{wbGeometry.Uid}])"); - RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), wbGeometry.WellUid, wbGeometry.WellboreUid, EntityType.WbGeometry); - return (workerResult, refreshAction); - } - - EntityDescription description = new() { WellboreName = wbGeometry.WellboreName }; - string errorMessage = "Failed to create WbGeometry."; - Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description()); - return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, result.Reason, description), null); - - } - private async Task WaitUntilWbGeometryHasBeenCreated(WbGeometry wbGeometry) - { - bool isCreated = false; - WitsmlWbGeometrys query = WbGeometryQueries.GetWitsmlWbGeometryIdOnly(wbGeometry.WellUid, wbGeometry.WellboreUid, wbGeometry.Uid); - int maxRetries = 30; - while (!isCreated) - { - if (--maxRetries == 0) - { - throw new InvalidOperationException($"Not able to read newly created WbGeometry with name {wbGeometry.Name} (id={wbGeometry.Uid})"); - } - Thread.Sleep(1000); - WitsmlWbGeometrys wbGeometryResult = await GetTargetWitsmlClientOrThrow().GetFromStoreAsync(query, new OptionsIn(ReturnElements.IdOnly)); - isCreated = wbGeometryResult.WbGeometrys.Any(); //Or WbGeometry - } - } - - private static WitsmlWbGeometrys SetupWbGeometryToCreate(WbGeometry wbGeometry) - { - return new WitsmlWbGeometrys - { - WbGeometrys = new WitsmlWbGeometry - { - UidWell = wbGeometry.WellUid, - UidWellbore = wbGeometry.WellboreUid, - Uid = wbGeometry.Name, - Name = wbGeometry.Name, - NameWell = wbGeometry.WellName, - NameWellbore = wbGeometry.WellboreName, - DTimReport = wbGeometry.DTimReport, - MdBottom = wbGeometry.MdBottom != null ? new WitsmlMeasuredDepthCoord { Uom = wbGeometry.MdBottom.Uom, Value = wbGeometry.MdBottom.Value.ToString(CultureInfo.InvariantCulture) } : null, - GapAir = wbGeometry.GapAir != null ? new WitsmlLengthMeasure { Uom = wbGeometry.GapAir.Uom, Value = wbGeometry.GapAir.Value.ToString(CultureInfo.InvariantCulture) } : null, - DepthWaterMean = wbGeometry.DepthWaterMean != null ? new WitsmlLengthMeasure { Uom = wbGeometry.DepthWaterMean.Uom, Value = wbGeometry.DepthWaterMean.Value.ToString(CultureInfo.InvariantCulture) } : null, - CommonData = new WitsmlCommonData - { - ItemState = wbGeometry.CommonData.ItemState, - SourceName = wbGeometry.CommonData.SourceName, - Comments = wbGeometry.CommonData.Comments, - }, - }.AsItemInList() - }; - } - - private static void Verify(WbGeometry wbGeometry) - { - if (string.IsNullOrEmpty(wbGeometry.Uid)) - { - throw new InvalidOperationException($"{nameof(wbGeometry.Uid)} cannot be empty"); - } - - if (string.IsNullOrEmpty(wbGeometry.Name)) - { - throw new InvalidOperationException($"{nameof(wbGeometry.Name)} cannot be empty"); - } - } - } -} diff --git a/Src/WitsmlExplorer.Api/Workers/Modify/BatchModifyObjectsOnWellboreWorker.cs b/Src/WitsmlExplorer.Api/Workers/Modify/BatchModifyObjectsOnWellboreWorker.cs index 2e850a26a..3f2d659fa 100644 --- a/Src/WitsmlExplorer.Api/Workers/Modify/BatchModifyObjectsOnWellboreWorker.cs +++ b/Src/WitsmlExplorer.Api/Workers/Modify/BatchModifyObjectsOnWellboreWorker.cs @@ -29,11 +29,10 @@ public BatchModifyObjectsOnWellboreWorker(ILogger ModifyUtils.PrepareModification(obj, objectType, Logger)).ToList(); - try { - objects.ForEach(ModifyUtils.VerifyModification); + objects.ForEach(obj => ModifyUtils.VerifyModificationProperties(obj, objectType, Logger)); + objects.ForEach(ModifyUtils.VerifyModificationValues); } catch (Exception e) { diff --git a/Src/WitsmlExplorer.Api/Workers/Modify/ModifyObjectOnWellboreWorker.cs b/Src/WitsmlExplorer.Api/Workers/Modify/ModifyObjectOnWellboreWorker.cs index b34c7f684..c5db362a4 100644 --- a/Src/WitsmlExplorer.Api/Workers/Modify/ModifyObjectOnWellboreWorker.cs +++ b/Src/WitsmlExplorer.Api/Workers/Modify/ModifyObjectOnWellboreWorker.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; using Witsml; using Witsml.Data; @@ -26,11 +27,10 @@ public ModifyObjectOnWellboreWorker(ILogger logger, I Logger.LogInformation("Started {JobType}. {jobDescription}", JobType, job.Description()); - obj = ModifyUtils.PrepareModification(obj, objectType, Logger); - try { - ModifyUtils.VerifyModification(obj); + ModifyUtils.VerifyModificationProperties(obj, objectType, Logger); + ModifyUtils.VerifyModificationValues(obj); } catch (Exception e) { @@ -38,6 +38,8 @@ public ModifyObjectOnWellboreWorker(ILogger logger, I return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, e.Message, ""), null); } + await PreModifyModifications(job); + IWitsmlQueryType query = obj.ToWitsml(); QueryResult modifyResult = await GetTargetWitsmlClientOrThrow().UpdateInStoreAsync(query); @@ -54,5 +56,25 @@ public ModifyObjectOnWellboreWorker(ILogger logger, I return (workerResult, refreshAction); } + + private async Task PreModifyModifications(ModifyObjectOnWellboreJob job) + { + if (job.ObjectType == EntityType.Risk) + { + Risk risk = (Risk)job.Object; + if (!risk.AffectedPersonnel.IsNullOrEmpty()) + { + // AffectedPersonnel can't be modified. So we delete it first, and then run the modification as usual. + WitsmlRisks test = new WitsmlRisk + { + Uid = risk.Uid, + UidWell = risk.WellUid, + UidWellbore = risk.WellboreUid, + AffectedPersonnel = [""] // Warning: The empty string must be included to ensure that AffectedPersonnel is serialized correctly and added to the query. Otherwise we risk deleting the entire risk object! + }.AsItemInWitsmlList(); + await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(test); + } + } + } } } diff --git a/Src/WitsmlExplorer.Api/Workers/Modify/ModifyUtils.cs b/Src/WitsmlExplorer.Api/Workers/Modify/ModifyUtils.cs index 1d3122c24..cfdff8622 100644 --- a/Src/WitsmlExplorer.Api/Workers/Modify/ModifyUtils.cs +++ b/Src/WitsmlExplorer.Api/Workers/Modify/ModifyUtils.cs @@ -11,6 +11,14 @@ namespace WitsmlExplorer.Api.Workers.Modify { public static class ModifyUtils { + public static void VerifyNotNull(object value, string name) + { + if (value == null) + { + throw new InvalidOperationException($"{name} cannot be null"); + } + } + public static void VerifyMeasure(Measure measure, string name) { if (measure == null) @@ -47,6 +55,13 @@ public static void VerifyAllowedValues(string value, List allowedValues, } } + private static readonly List ObjectReferenceProperties = new List + { + nameof(ObjectOnWellbore.Uid), + nameof(ObjectOnWellbore.WellUid), + nameof(ObjectOnWellbore.WellboreUid) + }; + // Properties not used when converting to WITSML can safely be added here. private static readonly Dictionary> AllowedPropertiesToChange = new Dictionary> { @@ -74,6 +89,10 @@ public static void VerifyAllowedValues(string value, List allowedValues, EntityType.FluidsReport, new HashSet { nameof(FluidsReport.Name), + nameof(FluidsReport.DTim), + nameof(FluidsReport.Md), + nameof(FluidsReport.Tvd), + nameof(FluidsReport.NumReport), nameof(FormationMarker.CommonData) } }, @@ -161,6 +180,7 @@ public static void VerifyAllowedValues(string value, List allowedValues, nameof(Risk.DTimEnd), nameof(Risk.MdBitStart), nameof(Risk.MdBitEnd), + nameof(Risk.SeverityLevel), nameof(Risk.ProbabilityLevel), nameof(Risk.Summary), nameof(Risk.Details), @@ -196,53 +216,43 @@ public static void VerifyAllowedValues(string value, List allowedValues, }, }; - public static ObjectOnWellbore PrepareModification(ObjectOnWellbore obj, EntityType objectType, ILogger logger) + public static void VerifyModificationProperties(ObjectOnWellbore obj, EntityType objectType, ILogger logger) { if (!AllowedPropertiesToChange.TryGetValue(objectType, out var allowedProperties)) { throw new NotSupportedException($"ObjectType '{objectType}' is not supported"); } - return SetNotAllowedPropertiesToNull(obj, allowedProperties, logger); - } - - private static ObjectOnWellbore SetNotAllowedPropertiesToNull(ObjectOnWellbore obj, HashSet allowedPropertiesToChange, ILogger logger) - { - // The uids should not be changed, but are needed to identify the object - allowedPropertiesToChange.Add(nameof(obj.WellUid)); - allowedPropertiesToChange.Add(nameof(obj.WellboreUid)); - allowedPropertiesToChange.Add(nameof(obj.Uid)); - foreach (var property in obj.GetType().GetProperties()) { - if (!allowedPropertiesToChange.Contains(property.Name) && property.GetValue(obj) != null) + if (!allowedProperties.Contains(property.Name) && !ObjectReferenceProperties.Contains(property.Name) && property.GetValue(obj) != null) { - logger.LogWarning("Property '{propertyName}' should not be changed and will be set to null. If the change is intended, please update the AllowedPropertiesToChange list in ModifyUtils.cs", property.Name); - property.SetValue(obj, null); + throw new ArgumentException($"Modifying {property.Name} for a {objectType} is prohibited"); } } - - return obj; } - public static void VerifyModification(ObjectOnWellbore obj) + public static void VerifyCreationValues(ObjectOnWellbore obj) { - if (obj == null) - { - throw new InvalidOperationException("Object cannot be null"); - } - if (string.IsNullOrEmpty(obj.WellUid)) - { - throw new InvalidOperationException("WellUid cannot be empty"); - } - if (string.IsNullOrEmpty(obj.WellboreUid)) - { - throw new InvalidOperationException("WellboreUid cannot be empty"); - } - if (string.IsNullOrEmpty(obj.Uid)) + VerifyModificationValues(obj); + VerifyNotNull(obj.Name, nameof(obj.Name)); + switch (obj) { - throw new InvalidOperationException("Uid cannot be empty"); + case LogObject log: + VerifyLogCreation(log); + break; } + } + + public static void VerifyModificationValues(ObjectOnWellbore obj) + { + VerifyNotNull(obj, obj.GetType().Name); + VerifyNotNull(obj.WellUid, nameof(obj.WellUid)); + VerifyNotNull(obj.WellboreUid, nameof(obj.WellboreUid)); + VerifyNotNull(obj.Uid, nameof(obj.Uid)); + VerifyString(obj.WellUid, nameof(obj.WellUid)); + VerifyString(obj.WellboreUid, nameof(obj.WellboreUid)); + VerifyString(obj.Uid, nameof(obj.Uid)); VerifyString(obj.Name, nameof(obj.Name)); switch (obj) @@ -326,11 +336,19 @@ private static void VerifyFormationMarker(FormationMarker formationMarker) private static void VerifyLog(LogObject log) { + VerifyString(log.IndexType, nameof(log.IndexType)); + VerifyString(log.IndexCurve, nameof(log.IndexCurve)); VerifyString(log.ServiceCompany, nameof(log.ServiceCompany)); VerifyString(log.RunNumber, nameof(log.RunNumber), 16); VerifyAllowedValues(log.CommonData?.ItemState, _allowedItemStates, "CommonData.ItemState"); } + private static void VerifyLogCreation(LogObject log) + { + VerifyNotNull(log.IndexType, nameof(log.IndexType)); + VerifyNotNull(log.IndexCurve, nameof(log.IndexCurve)); + } + private static void VerifyMessage(MessageObject message) { VerifyAllowedValues(message.CommonData?.ItemState, _allowedItemStates, "CommonData.ItemState"); diff --git a/Src/WitsmlExplorer.Frontend/components/Constants.tsx b/Src/WitsmlExplorer.Frontend/components/Constants.tsx index 102ca5f4c..3954dcd87 100644 --- a/Src/WitsmlExplorer.Frontend/components/Constants.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Constants.tsx @@ -1,5 +1,8 @@ export const WITSML_INDEX_TYPE_MD = "measured depth"; export const WITSML_INDEX_TYPE_DATE_TIME = "date time"; +export type WITSML_INDEX_TYPE = + | typeof WITSML_INDEX_TYPE_MD + | typeof WITSML_INDEX_TYPE_DATE_TIME; export const WITSML_LOG_ORDERTYPE_DECREASING = "decreasing"; export const DateFormat = { diff --git a/Src/WitsmlExplorer.Frontend/components/ContentViews/WellboresListView.tsx b/Src/WitsmlExplorer.Frontend/components/ContentViews/WellboresListView.tsx index 34f63e763..633ae83c5 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContentViews/WellboresListView.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContentViews/WellboresListView.tsx @@ -50,9 +50,13 @@ export default function WellboresListView() { const columns: ContentTableColumn[] = [ { property: "name", label: "name", type: ContentType.String }, - { property: "wellType", label: "typeWellbore", type: ContentType.String }, { - property: "wellStatus", + property: "wellboreType", + label: "typeWellbore", + type: ContentType.String + }, + { + property: "wellboreStatus", label: "statusWellbore", type: ContentType.String }, diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/BatchModifyMenuItem.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/BatchModifyMenuItem.tsx index 961c38129..4915c35d5 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/BatchModifyMenuItem.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/BatchModifyMenuItem.tsx @@ -1,18 +1,14 @@ import { Typography } from "@equinor/eds-core-react"; import { MenuItem } from "@mui/material"; +import { getBatchModifyObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/BatchModifyObjectOnWellboreProperties"; +import { PropertiesModal } from "components/Modals/PropertiesModal/PropertiesModal"; import { useOperationState } from "hooks/useOperationState"; import { ReactElement, forwardRef } from "react"; import OperationType from "../../contexts/operationType"; import ObjectOnWellbore from "../../models/objectOnWellbore"; -import { ObjectType } from "../../models/objectType"; -import { rigType } from "../../models/rigType"; +import { ObjectType, ObjectTypeToModel } from "../../models/objectType"; import JobService, { JobType } from "../../services/jobService"; import { colors } from "../../styles/Colors"; -import { - BatchModifyPropertiesModal, - BatchModifyProperty -} from "../Modals/BatchModifyPropertiesModal"; -import { validText } from "../Modals/ModalParts"; import { ReportModal } from "../Modals/ReportModal"; import { StyledIcon, menuItemText } from "./ContextMenuUtils"; @@ -25,11 +21,13 @@ export const BatchModifyMenuItem = forwardRef( (props: BatchModifyMenuItemProps, ref: React.Ref): ReactElement => { const { checkedObjects, objectType } = props; const { dispatchOperation } = useOperationState(); - const batchModifyProperties = objectBatchModifyProperties[objectType]; + const batchModifyProperties = + getBatchModifyObjectOnWellboreProperties(objectType); const onSubmitBatchModify = async (batchUpdates: { [key: string]: string; }) => { + dispatchOperation({ type: OperationType.HideModal }); const objectsToModify = checkedObjects.map((object) => ({ uid: object.uid, wellboreUid: object.wellboreUid, @@ -59,12 +57,13 @@ export const BatchModifyMenuItem = forwardRef( dispatchOperation({ type: OperationType.HideContextMenu }); const batchModifyModalProps = { title: menuItemText("Batch update", objectType, checkedObjects), + object: {} as ObjectTypeToModel[typeof objectType], properties: batchModifyProperties, onSubmit: onSubmitBatchModify }; dispatchOperation({ type: OperationType.DisplayModal, - payload: + payload: }); }; @@ -85,65 +84,3 @@ export const BatchModifyMenuItem = forwardRef( ); BatchModifyMenuItem.displayName = "BatchModifyMenuItem"; - -// Note: Only add properties that can be updated directly (without having to create a new object and delete the old one) -export const objectBatchModifyProperties: { - [key in ObjectType]?: BatchModifyProperty[]; -} = { - [ObjectType.BhaRun]: [], - [ObjectType.ChangeLog]: [], - [ObjectType.FluidsReport]: [], - [ObjectType.FormationMarker]: [], - [ObjectType.Log]: [ - { - property: "name", - validator: (value: string) => validText(value, 0, 64), - helperText: "Name must be less than 64 characters" - }, - { - property: "runNumber", - validator: (value: string) => validText(value, 0, 16), - helperText: "Run number must be less than 16 characters" - }, - { - property: "commonData.comments" - } - ], - [ObjectType.Message]: [], - [ObjectType.MudLog]: [], - [ObjectType.Rig]: [ - { - property: "owner", - validator: (value: string) => validText(value, 0, 32), - helperText: "Owner must be less than 32 characters" - }, - { - property: "typeRig", - options: rigType - }, - { - property: "manufacturer", - validator: (value: string) => validText(value, 0, 64), - helperText: "Owner must be less than 64 characters" - }, - { - property: "classRig", - validator: (value: string) => validText(value, 0, 32), - helperText: "Owner must be less than 32 characters" - }, - { - property: "approvals", - validator: (value: string) => validText(value, 0, 64), - helperText: "Owner must be less than 64 characters" - }, - { - property: "registration", - validator: (value: string) => validText(value, 0, 32), - helperText: "Owner must be less than 32 characters" - } - ], - [ObjectType.Risk]: [], - [ObjectType.Trajectory]: [], - [ObjectType.Tubular]: [], - [ObjectType.WbGeometry]: [] -}; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/BhaRunContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/BhaRunContextMenu.tsx index c77c4d863..a1b76305f 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/BhaRunContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/BhaRunContextMenu.tsx @@ -7,12 +7,8 @@ import { ObjectContextMenuProps, ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; -import BhaRunPropertiesModal, { - BhaRunPropertiesModalProps -} from "components/Modals/BhaRunPropertiesModal"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -31,20 +27,6 @@ const BhaRunContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyBhaRunProps: BhaRunPropertiesModalProps = { - mode, - bhaRun: checkedObjects[0] as BhaRun, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - return ( , + openObjectOnWellboreProperties( + ObjectType.BhaRun, + checkedObjects?.[0] as BhaRun, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > { dispatchOperation({ type: OperationType.HideContextMenu }); let url = ""; - if (objectType === ObjectType.Log && indexCurve) { + if (objectType === ObjectType.Log && indexType) { const logTypePath = - indexCurve === IndexCurve.Depth + indexType === WITSML_INDEX_TYPE_MD ? RouterLogType.DEPTH : RouterLogType.TIME; url = getLogObjectsViewPath( diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/FluidsReportContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/FluidsReportContextMenu.tsx index aa1106357..37a3f0065 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/FluidsReportContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/FluidsReportContextMenu.tsx @@ -1,4 +1,4 @@ -import { Typography } from "@equinor/eds-core-react"; +import { Divider, Typography } from "@equinor/eds-core-react"; import { MenuItem } from "@mui/material"; import { useQueryClient } from "@tanstack/react-query"; import ContextMenu from "components/ContextMenus/ContextMenu"; @@ -12,11 +12,13 @@ import { ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; import { ComponentType } from "models/componentType"; +import FluidsReport from "models/fluidsReport"; import { ObjectType } from "models/objectType"; import React from "react"; import { colors } from "styles/Colors"; @@ -68,7 +70,25 @@ const FluidsReportContextMenu = ( queryClient, openInQueryView, extraMenuItems() - ) + ), + , + + openObjectOnWellboreProperties( + ObjectType.FluidsReport, + checkedObjects?.[0] as FluidsReport, + dispatchOperation + ) + } + disabled={checkedObjects.length !== 1} + > + + Properties + ]} /> ); diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/FormationMarkerContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/FormationMarkerContextMenu.tsx index 2a8c6eb74..1b80500ff 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/FormationMarkerContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/FormationMarkerContextMenu.tsx @@ -7,11 +7,8 @@ import { ObjectContextMenuProps, ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; -import FormationMarkerPropertiesModal, { - FormationMarkerPropertiesModalProps -} from "components/Modals/FormationMarkerPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -30,25 +27,18 @@ const FormationMarkerContextMenu = ( const queryClient = useQueryClient(); const { servers } = useGetServers(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const modifyFormationMarkerProps: FormationMarkerPropertiesModalProps = { - formationMarker: checkedObjects[0] as FormationMarker - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: ( - - ) - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.FormationMarker, + checkedObjects?.[0] as FormationMarker, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/GeologyIntervalContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/GeologyIntervalContextMenu.tsx index ee8c768b2..cfe7bd9af 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/GeologyIntervalContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/GeologyIntervalContextMenu.tsx @@ -12,7 +12,12 @@ import { pasteComponents } from "components/ContextMenus/CopyUtils"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import GeologyIntervalPropertiesModal from "components/Modals/GeologyIntervalPropertiesModal"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getGeologyIntervalProperties } from "components/Modals/PropertiesModal/Properties/GeologyIntervalProperties"; +import { + PropertiesModal, + PropertiesModalProps +} from "components/Modals/PropertiesModal/PropertiesModal"; import { useConnectedServer } from "contexts/connectedServerContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; @@ -20,9 +25,11 @@ import { useOperationState } from "hooks/useOperationState"; import { ComponentType } from "models/componentType"; import GeologyInterval from "models/geologyInterval"; import { createComponentReferences } from "models/jobs/componentReferences"; +import ObjectReference from "models/jobs/objectReference"; import MudLog from "models/mudLog"; +import { toObjectReference } from "models/objectOnWellbore"; import React from "react"; -import { JobType } from "services/jobService"; +import JobService, { JobType } from "services/jobService"; import { colors } from "styles/Colors"; export interface GeologyIntervalContextMenuProps { @@ -43,17 +50,30 @@ const GeologyIntervalContextMenu = ( const onClickProperties = async () => { dispatchOperation({ type: OperationType.HideContextMenu }); - const geologyIntervalPropertiesModalProps = { - geologyInterval: checkedGeologyIntervals[0], - mudLog - }; + const geologyIntervalPropertiesModalProps: PropertiesModalProps = + { + title: `Edit properties for ${checkedGeologyIntervals[0].uid}`, + object: checkedGeologyIntervals[0], + properties: getGeologyIntervalProperties(PropertiesModalMode.Edit), + onSubmit: async (updates: Partial) => { + dispatchOperation({ type: OperationType.HideModal }); + const mudLogReference: ObjectReference = toObjectReference(mudLog); + const modifyGeologyIntervalJob = { + geologyInterval: { + uid: checkedGeologyIntervals[0].uid, + ...updates + }, + mudLogReference + }; + await JobService.orderJob( + JobType.ModifyGeologyInterval, + modifyGeologyIntervalJob + ); + } + }; dispatchOperation({ type: OperationType.DisplayModal, - payload: ( - - ) + payload: }); }; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogCurveInfoContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogCurveInfoContextMenu.tsx index 60d0cfb1d..acaaf7bf4 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogCurveInfoContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogCurveInfoContextMenu.tsx @@ -18,16 +18,17 @@ import AnalyzeGapModal, { import CopyRangeModal, { CopyRangeModalProps } from "components/Modals/CopyRangeModal"; -import LogCurveInfoPropertiesModal from "components/Modals/LogCurveInfoPropertiesModal"; import { LogCurvePriorityModal, LogCurvePriorityModalProps } from "components/Modals/LogCurvePriorityModal"; -import { IndexCurve } from "components/Modals/LogPropertiesModal"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; import { OffsetLogCurveModal, OffsetLogCurveModalProps } from "components/Modals/OffsetLogCurveModal"; +import { getLogCurveInfoProperties } from "components/Modals/PropertiesModal/Properties/LogCurveInfoProperties"; +import { PropertiesModal } from "components/Modals/PropertiesModal/PropertiesModal"; import SelectIndexToDisplayModal from "components/Modals/SelectIndexToDisplayModal"; import { DisplayModalAction, @@ -36,12 +37,16 @@ import { } from "contexts/operationStateReducer"; import OperationType from "contexts/operationType"; import { ComponentType } from "models/componentType"; +import { IndexCurve } from "models/indexCurve"; import { createComponentReferences } from "models/jobs/componentReferences"; +import ModifyLogCurveInfoJob from "models/jobs/modifyLogCurveInfoJob"; +import LogCurveInfo from "models/logCurveInfo"; import LogObject from "models/logObject"; +import { toObjectReference } from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; import React from "react"; -import { JobType } from "services/jobService"; +import JobService, { JobType } from "services/jobService"; import LogCurvePriorityService from "services/logCurvePriorityService"; import { colors } from "styles/Colors"; import LogCurveInfoBatchUpdateModal from "../Modals/LogCurveInfoBatchUpdateModal"; @@ -114,15 +119,27 @@ const LogCurveInfoContextMenu = ( dispatchOperation({ type: OperationType.HideContextMenu }); const logCurveInfo = checkedLogCurveInfoRows[0].logCurveInfo; const logCurveInfoPropertiesModalProps = { - logCurveInfo, - dispatchOperation, - selectedLog + title: `Edit properties for LogCurve: ${logCurveInfo.mnemonic}`, + properties: getLogCurveInfoProperties( + PropertiesModalMode.Edit, + logCurveInfo?.mnemonic === selectedLog?.indexCurve + ), + object: logCurveInfo, + onSubmit: async (updates: Partial) => { + dispatchOperation({ type: OperationType.HideModal }); + const job: ModifyLogCurveInfoJob = { + logReference: toObjectReference(selectedLog), + logCurveInfo: { + ...logCurveInfo, + ...updates + } + }; + await JobService.orderJob(JobType.ModifyLogCurveInfo, job); + } }; dispatchOperation({ type: OperationType.DisplayModal, - payload: ( - - ) + payload: }); }; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogObjectContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogObjectContextMenu.tsx index 1fa929a2f..91e7aeebf 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogObjectContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogObjectContextMenu.tsx @@ -28,11 +28,10 @@ import LogComparisonModal, { import LogDataImportModal, { LogDataImportModalProps } from "components/Modals/LogDataImportModal"; -import LogPropertiesModal from "components/Modals/LogPropertiesModal"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; import ObjectPickerModal, { ObjectPickerProps } from "components/Modals/ObjectPickerModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { ReportModal } from "components/Modals/ReportModal"; import SpliceLogsModal from "components/Modals/SpliceLogsModal"; import TrimLogObjectModal from "components/Modals/TrimLogObject/TrimLogObjectModal"; @@ -78,20 +77,6 @@ const LogObjectContextMenu = ( const queryClient = useQueryClient(); const navigate = useNavigate(); - const onClickProperties = () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const logObject = checkedObjects[0]; - const logPropertiesModalProps = { - mode: PropertiesModalMode.Edit, - logObject, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const onClickTrimLogObject = () => { const logObject = checkedObjects[0]; dispatchOperation({ @@ -458,7 +443,13 @@ const LogObjectContextMenu = ( , + openObjectOnWellboreProperties( + ObjectType.Log, + checkedObjects?.[0] as LogObject, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogsContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogsContextMenu.tsx index a2181b41f..7cd2e3e23 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogsContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/LogsContextMenu.tsx @@ -1,6 +1,11 @@ import { Typography } from "@equinor/eds-core-react"; import { MenuItem } from "@mui/material"; import { useQueryClient } from "@tanstack/react-query"; +import { + WITSML_INDEX_TYPE, + WITSML_INDEX_TYPE_DATE_TIME, + WITSML_INDEX_TYPE_MD +} from "components/Constants"; import { StoreFunction, TemplateObjects @@ -15,19 +20,16 @@ import { import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; -import LogPropertiesModal, { - IndexCurve, - LogPropertiesModalInterface -} from "components/Modals/LogPropertiesModal"; import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; import { DisplayModalAction, HideContextMenuAction, HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; +import { IndexCurve } from "models/indexCurve"; import { toWellboreReference } from "models/jobs/wellboreReference"; import LogObject from "models/logObject"; import { ObjectType } from "models/objectType"; @@ -43,11 +45,11 @@ export interface LogsContextMenuProps { ) => void; wellbore: Wellbore; servers: Server[]; - indexCurve?: IndexCurve; + indexType?: WITSML_INDEX_TYPE; } const LogsContextMenu = (props: LogsContextMenuProps): React.ReactElement => { - const { dispatchOperation, wellbore, servers, indexCurve } = props; + const { dispatchOperation, wellbore, servers, indexType } = props; const logReferences = useClipboardReferencesOfType(ObjectType.Log); const openInQueryView = useOpenInQueryView(); const { connectedServer } = useConnectedServer(); @@ -61,19 +63,18 @@ const LogsContextMenu = (props: LogsContextMenuProps): React.ReactElement => { wellName: wellbore.wellName, wellboreUid: wellbore.uid, wellboreName: wellbore.name, + indexType: indexType ?? WITSML_INDEX_TYPE_MD, indexCurve: - indexCurve === IndexCurve.Time ? IndexCurve.Time : IndexCurve.Depth - }; - const logPropertiesModalProps: LogPropertiesModalInterface = { - mode: PropertiesModalMode.New, - logObject: newLog, - dispatchOperation - }; - const action: DisplayModalAction = { - type: OperationType.DisplayModal, - payload: + indexType === WITSML_INDEX_TYPE_DATE_TIME + ? IndexCurve.Time + : IndexCurve.Depth }; - dispatchOperation(action); + openObjectOnWellboreProperties( + ObjectType.Log, + newLog, + dispatchOperation, + PropertiesModalMode.New + ); }; return ( @@ -129,7 +130,7 @@ const LogsContextMenu = (props: LogsContextMenuProps): React.ReactElement => { server, wellbore, ObjectType.Log, - indexCurve + indexType ) } > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/MessageObjectContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/MessageObjectContextMenu.tsx index 928e1ce53..4ece3dbc9 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/MessageObjectContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/MessageObjectContextMenu.tsx @@ -13,13 +13,10 @@ import { import MessageComparisonModal, { MessageComparisonModalProps } from "components/Modals/MessageComparisonModal"; -import MessagePropertiesModal, { - MessagePropertiesModalProps -} from "components/Modals/MessagePropertiesModal"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; import ObjectPickerModal, { ObjectPickerProps } from "components/Modals/ObjectPickerModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; @@ -42,20 +39,6 @@ const MessageObjectContextMenu = ( const queryClient = useQueryClient(); const { servers } = useGetServers(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyMessageObjectProps: MessagePropertiesModalProps = { - mode, - messageObject: checkedObjects[0] as MessageObject, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const onClickCompare = () => { dispatchOperation({ type: OperationType.HideContextMenu }); const onPicked = (targetObject: ObjectOnWellbore, targetServer: Server) => { @@ -99,7 +82,13 @@ const MessageObjectContextMenu = ( , + openObjectOnWellboreProperties( + ObjectType.Message, + checkedObjects?.[0] as MessageObject, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/MudLogContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/MudLogContextMenu.tsx index 3601d829a..6dfd6a3d8 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/MudLogContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/MudLogContextMenu.tsx @@ -12,11 +12,8 @@ import { ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import MudLogPropertiesModal, { - MudLogPropertiesModalProps -} from "components/Modals/MudLogPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -39,17 +36,6 @@ const MudLogContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const modifyMudLogProps: MudLogPropertiesModalProps = { - mudLog: checkedObjects[0] as MudLog - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.MudLog, + checkedObjects?.[0] as MudLog, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectMenuItems.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectMenuItems.tsx index a71719875..6e07a0001 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectMenuItems.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectMenuItems.tsx @@ -20,9 +20,9 @@ import { } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; -import { IndexCurve } from "components/Modals/LogPropertiesModal"; import { DispatchOperation } from "contexts/operationStateReducer"; import { OpenInQueryView } from "hooks/useOpenInQueryView"; +import { IndexCurve } from "models/indexCurve"; import LogObject from "models/logObject"; import ObjectOnWellbore from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectsSidebarContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectsSidebarContextMenu.tsx index 431f2a14f..84331d19d 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectsSidebarContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/ObjectsSidebarContextMenu.tsx @@ -16,11 +16,14 @@ import { import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; import { toWellboreReference } from "models/jobs/wellboreReference"; +import ObjectOnWellbore from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; import Wellbore from "models/wellbore"; @@ -44,6 +47,23 @@ const ObjectsSidebarContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); + const onClickNewObject = () => { + const newObject: ObjectOnWellbore = { + uid: uuid(), + name: "", + wellUid: wellbore.wellUid, + wellName: wellbore.wellName, + wellboreUid: wellbore.uid, + wellboreName: wellbore.name + }; + openObjectOnWellboreProperties( + objectType, + newObject, + dispatchOperation, + PropertiesModalMode.New + ); + }; + return ( , + + + New {objectType} + , diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigContextMenu.tsx index 940d073a0..5c47e3753 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigContextMenu.tsx @@ -8,12 +8,8 @@ import { ObjectContextMenuProps, ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; -import RigPropertiesModal, { - RigPropertiesModalProps -} from "components/Modals/RigPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -30,20 +26,6 @@ const RigContextMenu = (props: ObjectContextMenuProps): React.ReactElement => { const queryClient = useQueryClient(); const { servers } = useGetServers(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyRigObjectProps: RigPropertiesModalProps = { - mode, - rig: checkedObjects[0] as Rig, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , @@ -54,7 +36,13 @@ const RigContextMenu = (props: ObjectContextMenuProps): React.ReactElement => { />, + openObjectOnWellboreProperties( + ObjectType.Rig, + checkedObjects?.[0] as Rig, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx index bc51ad669..8c003d947 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx @@ -15,12 +15,8 @@ import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; import { PropertiesModalMode } from "components/Modals/ModalParts"; -import RigPropertiesModal, { - RigPropertiesModalProps -} from "components/Modals/RigPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import { DisplayModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; import { toWellboreReference } from "models/jobs/wellboreReference"; @@ -71,16 +67,12 @@ const RigsContextMenu = (props: RigsContextMenuProps): React.ReactElement => { typeRig: "unknown", yearEntService: null }; - const rigPropertiesModalProps: RigPropertiesModalProps = { - mode: PropertiesModalMode.New, - rig: newRig, - dispatchOperation - }; - const action: DisplayModalAction = { - type: OperationType.DisplayModal, - payload: - }; - dispatchOperation(action); + openObjectOnWellboreProperties( + ObjectType.Rig, + newRig, + dispatchOperation, + PropertiesModalMode.New + ); }; return ( diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RiskContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RiskContextMenu.tsx index 2aa72aaff..913a80f88 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/RiskContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RiskContextMenu.tsx @@ -7,12 +7,8 @@ import { ObjectContextMenuProps, ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; -import RiskPropertiesModal, { - RiskPropertiesModalProps -} from "components/Modals/RiskPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -31,26 +27,18 @@ const RiskObjectContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyRiskObjectProps: RiskPropertiesModalProps = { - mode, - riskObject: checkedObjects[0] as RiskObject, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.Risk, + checkedObjects?.[0] as RiskObject, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoriesContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoriesContextMenu.tsx index 62aeafe0b..a31e6231c 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoriesContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoriesContextMenu.tsx @@ -15,12 +15,8 @@ import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; import { PropertiesModalMode } from "components/Modals/ModalParts"; -import TrajectoryPropertiesModal, { - TrajectoryPropertiesModalProps -} from "components/Modals/TrajectoryPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import { DisplayModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; import { toWellboreReference } from "models/jobs/wellboreReference"; @@ -66,16 +62,12 @@ const TrajectoriesContextMenu = ( trajectoryStations: [], commonData: null }; - const trajectoryPropertiesModalProps: TrajectoryPropertiesModalProps = { - mode: PropertiesModalMode.New, - trajectory: newTrajectory, - dispatchOperation - }; - const action: DisplayModalAction = { - type: OperationType.DisplayModal, - payload: - }; - dispatchOperation(action); + openObjectOnWellboreProperties( + ObjectType.Trajectory, + newTrajectory, + dispatchOperation, + PropertiesModalMode.New + ); }; return ( diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryContextMenu.tsx index e0e38edd0..7d8f7e336 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryContextMenu.tsx @@ -12,12 +12,8 @@ import { ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; -import TrajectoryPropertiesModal, { - TrajectoryPropertiesModalProps -} from "components/Modals/TrajectoryPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -40,20 +36,6 @@ const TrajectoryContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyObjectProps: TrajectoryPropertiesModalProps = { - mode, - trajectory: checkedObjects[0] as Trajectory, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.Trajectory, + checkedObjects?.[0] as Trajectory, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryStationContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryStationContextMenu.tsx index b2727e980..f72a09841 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryStationContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TrajectoryStationContextMenu.tsx @@ -15,18 +15,23 @@ import { } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import TrajectoryStationPropertiesModal from "components/Modals/TrajectoryStationPropertiesModal"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getTrajectoryStationProperties } from "components/Modals/PropertiesModal/Properties/TrajectoryStationProperties"; +import { PropertiesModal } from "components/Modals/PropertiesModal/PropertiesModal"; import { useConnectedServer } from "contexts/connectedServerContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOperationState } from "hooks/useOperationState"; import { ComponentType } from "models/componentType"; import { createComponentReferences } from "models/jobs/componentReferences"; +import ObjectReference from "models/jobs/objectReference"; +import { toObjectReference } from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; import Trajectory from "models/trajectory"; +import TrajectoryStation from "models/trajectoryStation"; import React from "react"; -import { JobType } from "services/jobService"; +import JobService, { JobType } from "services/jobService"; import { colors } from "styles/Colors"; export interface TrajectoryStationContextMenuProps { @@ -47,18 +52,31 @@ const TrajectoryStationContextMenu = ( const onClickProperties = async () => { dispatchOperation({ type: OperationType.HideContextMenu }); + const trajectoryStation = checkedTrajectoryStations[0].trajectoryStation; const trajectoryStationPropertiesModalProps = { - trajectoryStation: checkedTrajectoryStations[0].trajectoryStation, - trajectory, - dispatchOperation + title: `Edit properties for Trajectory Station for Trajectory ${trajectoryStation.uid} - ${trajectoryStation.typeTrajStation}`, + properties: getTrajectoryStationProperties(PropertiesModalMode.Edit), + object: trajectoryStation, + onSubmit: async (updates: Partial) => { + dispatchOperation({ type: OperationType.HideModal }); + const trajectoryReference: ObjectReference = + toObjectReference(trajectory); + const modifyTrajectoryStationJob = { + trajectoryStation: { + ...trajectoryStation, + ...updates + }, + trajectoryReference + }; + await JobService.orderJob( + JobType.ModifyTrajectoryStation, + modifyTrajectoryStationJob + ); + } }; dispatchOperation({ type: OperationType.DisplayModal, - payload: ( - - ) + payload: }); }; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularComponentContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularComponentContextMenu.tsx index 0855bf7ca..8952d31dc 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularComponentContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularComponentContextMenu.tsx @@ -17,18 +17,23 @@ import { } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import TubularComponentPropertiesModal from "components/Modals/TubularComponentPropertiesModal"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getTubularComponentProperties } from "components/Modals/PropertiesModal/Properties/TubularComponentProperties"; +import { PropertiesModal } from "components/Modals/PropertiesModal/PropertiesModal"; import { useConnectedServer } from "contexts/connectedServerContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOperationState } from "hooks/useOperationState"; import { ComponentType } from "models/componentType"; import { createComponentReferences } from "models/jobs/componentReferences"; +import ObjectReference from "models/jobs/objectReference"; +import { toObjectReference } from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; import Tubular from "models/tubular"; +import TubularComponent from "models/tubularComponent"; import React from "react"; -import { JobType } from "services/jobService"; +import JobService, { JobType } from "services/jobService"; import { colors } from "styles/Colors"; export interface TubularComponentContextMenuProps { @@ -50,18 +55,30 @@ const TubularComponentContextMenu = ( const onClickProperties = async () => { dispatchOperation({ type: OperationType.HideContextMenu }); + const tubularComponent = checkedTubularComponents[0].tubularComponent; const tubularComponentPropertiesModalProps = { - tubularComponent: checkedTubularComponents[0].tubularComponent, - tubular, - dispatchOperation + title: `Edit properties for Sequence ${tubularComponent.sequence} - ${tubularComponent.typeTubularComponent} - ${tubularComponent.uid}`, + properties: getTubularComponentProperties(PropertiesModalMode.Edit), + object: tubularComponent, + onSubmit: async (updates: Partial) => { + dispatchOperation({ type: OperationType.HideModal }); + const tubularReference: ObjectReference = toObjectReference(tubular); + const modifyTubularComponentJob = { + tubularComponent: { + ...tubularComponent, + ...updates + }, + tubularReference + }; + await JobService.orderJob( + JobType.ModifyTubularComponent, + modifyTubularComponentJob + ); + } }; dispatchOperation({ type: OperationType.DisplayModal, - payload: ( - - ) + payload: }); }; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularContextMenu.tsx index 17f5c1b00..3451b8d7f 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularContextMenu.tsx @@ -12,10 +12,8 @@ import { ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; -import TubularPropertiesModal from "components/Modals/TubularPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -38,19 +36,6 @@ const TubularContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickProperties = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const tubularPropertiesModalProps = { - mode: PropertiesModalMode.Edit, - tubular: checkedObjects[0] as Tubular, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.Tubular, + checkedObjects?.[0] as Tubular, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularsContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularsContextMenu.tsx index 2792adcca..8691a362a 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularsContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/TubularsContextMenu.tsx @@ -14,12 +14,15 @@ import { import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardReferencesOfType } from "components/ContextMenus/UseClipboardReferences"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; import { toWellboreReference } from "models/jobs/wellboreReference"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; +import Tubular from "models/tubular"; import Wellbore from "models/wellbore"; import React from "react"; import { colors } from "styles/Colors"; @@ -40,6 +43,25 @@ const TubularsContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); + const onClickNewTubular = () => { + const newTubular: Tubular = { + uid: uuid(), + name: "", + wellUid: wellbore.wellUid, + wellName: wellbore.wellName, + wellboreUid: wellbore.uid, + wellboreName: wellbore.name, + typeTubularAssy: null, + commonData: null + }; + openObjectOnWellboreProperties( + ObjectType.Tubular, + newTubular, + dispatchOperation, + PropertiesModalMode.New + ); + }; + return ( Refresh tubulars , + + + New Tubular + , diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometryContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometryContextMenu.tsx index ddc1fbdf4..5b8e11987 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometryContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometryContextMenu.tsx @@ -12,12 +12,8 @@ import { ObjectMenuItems } from "components/ContextMenus/ObjectMenuItems"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import { PropertiesModalMode } from "components/Modals/ModalParts"; -import WbGeometryPropertiesModal, { - WbGeometryPropertiesModalProps -} from "components/Modals/WbGeometryPropertiesModal"; +import { openObjectOnWellboreProperties } from "components/Modals/PropertiesModal/openPropertiesHelpers"; import { useConnectedServer } from "contexts/connectedServerContext"; -import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOpenInQueryView } from "hooks/useOpenInQueryView"; import { useOperationState } from "hooks/useOperationState"; @@ -40,20 +36,6 @@ const WbGeometryObjectContextMenu = ( const { connectedServer } = useConnectedServer(); const queryClient = useQueryClient(); - const onClickModify = async () => { - dispatchOperation({ type: OperationType.HideContextMenu }); - const mode = PropertiesModalMode.Edit; - const modifyWbGeometryObjectProps: WbGeometryPropertiesModalProps = { - mode, - wbGeometryObject: checkedObjects[0] as WbGeometryObject, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const extraMenuItems = (): React.ReactElement[] => { return [ , + openObjectOnWellboreProperties( + ObjectType.WbGeometry, + checkedObjects?.[0] as WbGeometryObject, + dispatchOperation + ) + } disabled={checkedObjects.length !== 1} > diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometrySectionContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometrySectionContextMenu.tsx index e73caf1c1..f0db1151e 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometrySectionContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WbGeometrySectionContextMenu.tsx @@ -14,19 +14,23 @@ import { } from "components/ContextMenus/CopyUtils"; import NestedMenuItem from "components/ContextMenus/NestedMenuItem"; import { useClipboardComponentReferencesOfType } from "components/ContextMenus/UseClipboardComponentReferences"; -import WbGeometrySectionPropertiesModal from "components/Modals/WbGeometrySectionPropertiesModal"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getWbGeometrySectionProperties } from "components/Modals/PropertiesModal/Properties/WbGeometrySectionProperties"; +import { PropertiesModal } from "components/Modals/PropertiesModal/PropertiesModal"; import { useConnectedServer } from "contexts/connectedServerContext"; import OperationType from "contexts/operationType"; import { useGetServers } from "hooks/query/useGetServers"; import { useOperationState } from "hooks/useOperationState"; import { ComponentType } from "models/componentType"; import { createComponentReferences } from "models/jobs/componentReferences"; +import ObjectReference from "models/jobs/objectReference"; +import { toObjectReference } from "models/objectOnWellbore"; import { ObjectType } from "models/objectType"; import { Server } from "models/server"; import WbGeometry from "models/wbGeometry"; import WbGeometrySection from "models/wbGeometrySection"; import React from "react"; -import { JobType } from "services/jobService"; +import JobService, { JobType } from "services/jobService"; import { colors } from "styles/Colors"; export interface WbGeometrySectionContextMenuProps { @@ -48,17 +52,29 @@ const WbGeometrySectionContextMenu = ( const onClickProperties = async () => { dispatchOperation({ type: OperationType.HideContextMenu }); const wbGeometrySectionPropertiesModalProps = { - wbGeometrySection: checkedWbGeometrySections[0], - wbGeometry, - dispatchOperation + title: `Edit properties for ${checkedWbGeometrySections[0].uid}`, + properties: getWbGeometrySectionProperties(PropertiesModalMode.Edit), + object: checkedWbGeometrySections[0], + onSubmit: async (updates: Partial) => { + dispatchOperation({ type: OperationType.HideModal }); + const wbGeometryReference: ObjectReference = + toObjectReference(wbGeometry); + const modifyWbGeometrySectionJob = { + wbGeometrySection: { + ...checkedWbGeometrySections[0], + ...updates + }, + wbGeometryReference + }; + await JobService.orderJob( + JobType.ModifyWbGeometrySection, + modifyWbGeometrySectionJob + ); + } }; dispatchOperation({ type: OperationType.DisplayModal, - payload: ( - - ) + payload: }); }; diff --git a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WellContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WellContextMenu.tsx index ae18bece4..724a97f6b 100644 --- a/Src/WitsmlExplorer.Frontend/components/ContextMenus/WellContextMenu.tsx +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/WellContextMenu.tsx @@ -17,15 +17,13 @@ import MissingDataAgentModal, { MissingDataAgentModalProps } from "components/Modals/MissingDataAgentModal"; import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { + openWellProperties, + openWellboreProperties +} from "components/Modals/PropertiesModal/openPropertiesHelpers"; import WellBatchUpdateModal, { WellBatchUpdateModalProps } from "components/Modals/WellBatchUpdateModal"; -import WellPropertiesModal, { - WellPropertiesModalProps -} from "components/Modals/WellPropertiesModal"; -import WellborePropertiesModal, { - WellborePropertiesModalProps -} from "components/Modals/WellborePropertiesModal"; import { useConnectedServer } from "contexts/connectedServerContext"; import { DisplayModalAction, @@ -73,15 +71,7 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => { country: "", timeZone: "" }; - const wellPropertiesModalProps: WellPropertiesModalProps = { - mode: PropertiesModalMode.New, - well: newWell, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); + openWellProperties(newWell, dispatchOperation, PropertiesModalMode.New); }; const onClickRefresh = async () => { @@ -100,22 +90,18 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => { name: "", wellUid: well.uid, wellName: well.name, - wellStatus: "", - wellType: "", + wellboreStatus: "", + wellboreType: "", isActive: false, wellboreParentUid: "", wellboreParentName: "", wellborePurpose: "unknown" }; - const wellborePropertiesModalProps: WellborePropertiesModalProps = { - mode: PropertiesModalMode.New, - wellbore: newWellbore, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); + openWellboreProperties( + newWellbore, + dispatchOperation, + PropertiesModalMode.New + ); }; const deleteWell = async () => { @@ -183,18 +169,6 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => { }); }; - const onClickProperties = () => { - const wellPropertiesModalProps: WellPropertiesModalProps = { - mode: PropertiesModalMode.Edit, - well, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const onClickShowOnServer = async (server: Server) => { dispatchOperation({ type: OperationType.HideContextMenu }); const wellboresViewPath = getWellboresViewPath(server.url, well.uid); @@ -332,7 +306,10 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => { Missing Data Agent , , - + openWellProperties(well, dispatchOperation)} + > - }; - dispatchOperation(action); + openWellboreProperties( + newWellbore, + dispatchOperation, + PropertiesModalMode.New + ); }; const onClickNewLog = () => { @@ -102,18 +96,15 @@ const WellboreContextMenu = ( wellName: wellbore.wellName, wellboreUid: wellbore.uid, wellboreName: wellbore.name, + indexType: WITSML_INDEX_TYPE_MD, indexCurve: IndexCurve.Depth }; - const logPropertiesModalProps: LogPropertiesModalInterface = { - mode: PropertiesModalMode.New, - logObject: newLog, - dispatchOperation - }; - const action: DisplayModalAction = { - type: OperationType.DisplayModal, - payload: - }; - dispatchOperation(action); + openObjectOnWellboreProperties( + ObjectType.Log, + newLog, + dispatchOperation, + PropertiesModalMode.New + ); }; const deleteWellbore = async () => { @@ -197,18 +188,6 @@ const WellboreContextMenu = ( }); }; - const onClickProperties = async () => { - const wellborePropertiesModalProps: WellborePropertiesModalProps = { - mode: PropertiesModalMode.Edit, - wellbore, - dispatchOperation - }; - dispatchOperation({ - type: OperationType.DisplayModal, - payload: - }); - }; - const onClickShowOnServer = async (server: Server) => { dispatchOperation({ type: OperationType.HideContextMenu }); const objectGroupsViewPath = getObjectGroupsViewPath( @@ -367,7 +346,10 @@ const WellboreContextMenu = ( Missing Data Agent , , - + openWellboreProperties(wellbore, dispatchOperation)} + > boolean; - helperText?: string; -} - -export interface BatchModifyModalProps { - title: string; - properties: BatchModifyProperty[]; - onSubmit: (batchUpdates: { [key: string]: string }) => void; -} - -export const BatchModifyPropertiesModal = ( - props: BatchModifyModalProps -): ReactElement => { - const { title, properties, onSubmit } = props; - const { dispatchOperation } = useOperationState(); - const [batchUpdates, setBatchUpdates] = useState<{ [key: string]: string }>( - properties.reduce((acc, prop) => ({ ...acc, [prop.property]: "" }), {}) - ); - const allValid = properties.every( - (prop) => - !prop.validator || - !batchUpdates[prop.property] || - prop.validator(batchUpdates[prop.property]) - ); - const allEmpty = properties.every((prop) => !batchUpdates[prop.property]); - - const onChangeProperty = (property: string, value: string) => { - setBatchUpdates({ - ...batchUpdates, - [property]: value - }); - }; - - const onInternalSubmit = async () => { - dispatchOperation({ type: OperationType.HideModal }); - // Remove empty properties as they should not be updated - const filteredBatchUpdates = Object.fromEntries( - Object.entries(batchUpdates).filter(([, value]) => value !== "") - ); - - // Create nested objects for properties with . in the name (e.g. commonData.source.name) - const nestedBatchUpdates = Object.entries(filteredBatchUpdates).reduce<{ - [key: string]: any; - }>((acc, [property, value]) => { - const keys = property.split("."); - keys.reduce((obj, key, index) => { - if (index === keys.length - 1) { - obj[key] = value; - } else { - obj[key] = obj[key] || {}; - } - return obj[key]; - }, acc); - return acc; - }, {}); - - onSubmit(nestedBatchUpdates); - }; - - return ( - - {properties.length === 0 &&

No properties to update.

} - {properties.map((property) => - property.options ? ( - - onChangeProperty(property.property, selectedItems?.[0] ?? "") - } - /> - ) : ( - ) => - onChangeProperty(property.property, e.target.value) - } - /> - ) - )} - - } - confirmDisabled={!allValid || allEmpty} - onSubmit={onInternalSubmit} - isLoading={false} - /> - ); -}; - -const Layout = styled.div` - margin-top: 12px; - margin-bottom: 12px; - display: flex; - flex-direction: column; - gap: 8px; -`; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/BhaRunPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/BhaRunPropertiesModal.tsx deleted file mode 100644 index 809f4b1d7..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/BhaRunPropertiesModal.tsx +++ /dev/null @@ -1,449 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import { DateTimeField } from "components/Modals/DateTimeField"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { - DateTimeFormat, - HideModalAction -} from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import BhaRun from "models/bhaRun"; -import { itemStateTypes } from "models/itemStateTypes"; -import { ObjectType } from "models/objectType"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -const typesOfBhaStatus = ["final", "progress", "plan", "unknown"]; - -export interface BhaRunPropertiesModalProps { - mode: PropertiesModalMode; - bhaRun: BhaRun; - dispatchOperation: (action: HideModalAction) => void; -} - -const BhaRunPropertiesModal = ( - props: BhaRunPropertiesModalProps -): React.ReactElement => { - const { mode, bhaRun, dispatchOperation } = props; - const { - operationState: { timeZone } - } = useOperationState(); - const [editableBhaRun, setEditableBhaRun] = useState(null); - const [dTimStartValid, setDTimStartValid] = useState(true); - const [dTimStopValid, setDTimStopValid] = useState(true); - const [dTimStartDrillingValid, setDTimStartDrillingValid] = - useState(true); - const [dTimStopDrillingValid, setDTimStopDrillingValid] = - useState(true); - const [isLoading, setIsLoading] = useState(false); - const editMode = mode === PropertiesModalMode.Edit; - - useEffect(() => { - setEditableBhaRun({ - ...bhaRun, - dTimStart: bhaRun.dTimStart - ? formatDateString(bhaRun.dTimStart, timeZone, DateTimeFormat.Raw) - : null, - dTimStop: bhaRun.dTimStop - ? formatDateString(bhaRun.dTimStop, timeZone, DateTimeFormat.Raw) - : null, - dTimStartDrilling: bhaRun.dTimStartDrilling - ? formatDateString( - bhaRun.dTimStartDrilling, - timeZone, - DateTimeFormat.Raw - ) - : null, - dTimStopDrilling: bhaRun.dTimStopDrilling - ? formatDateString( - bhaRun.dTimStopDrilling, - timeZone, - DateTimeFormat.Raw - ) - : null, - commonData: { - ...bhaRun.commonData, - dTimCreation: bhaRun.commonData.dTimCreation - ? formatDateString( - bhaRun.commonData.dTimCreation, - timeZone, - DateTimeFormat.Raw - ) - : null, - dTimLastChange: bhaRun.commonData.dTimLastChange - ? formatDateString( - bhaRun.commonData.dTimLastChange, - timeZone, - DateTimeFormat.Raw - ) - : null - } - }); - }, [bhaRun]); - - const onSubmit = async (updatedBhaRun: BhaRun) => { - setIsLoading(true); - const modifyJob = { - object: { ...updatedBhaRun, objectType: ObjectType.BhaRun }, - objectType: ObjectType.BhaRun - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const validBhaRunName = validText(editableBhaRun?.name, 1, 64); - - return ( - <> - {editableBhaRun && ( - - - - - - - ) => - setEditableBhaRun({ ...editableBhaRun, name: e.target.value }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - tubular: { - ...editableBhaRun.tubular, - value: e.target.value - } - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - tubular: { - ...editableBhaRun.tubular, - uidRef: e.target.value - } - }) - } - /> - { - setEditableBhaRun({ ...editableBhaRun, dTimStart: dateTime }); - setDTimStartValid(valid); - }} - timeZone={timeZone} - /> - { - setEditableBhaRun({ ...editableBhaRun, dTimStop: dateTime }); - setDTimStopValid(valid); - }} - timeZone={timeZone} - /> - { - setEditableBhaRun({ - ...editableBhaRun, - dTimStartDrilling: dateTime - }); - setDTimStartDrillingValid(valid); - }} - timeZone={timeZone} - /> - { - setEditableBhaRun({ - ...editableBhaRun, - dTimStopDrilling: dateTime - }); - setDTimStopDrillingValid(valid); - }} - timeZone={timeZone} - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - planDogleg: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableBhaRun.planDogleg.uom - } - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - actDogleg: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableBhaRun.actDogleg.uom - } - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - actDoglegMx: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableBhaRun.actDoglegMx.uom - } - }) - } - /> - { - setEditableBhaRun({ - ...editableBhaRun, - statusBha: selectedItems[0] - }); - }} - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - numBitRun: e.target.value - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - numStringRun: e.target.value - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - reasonTrip: e.target.value - }) - } - /> - ) => - setEditableBhaRun({ - ...editableBhaRun, - objectiveBha: e.target.value - }) - } - /> - { - const commonData = { - ...editableBhaRun.commonData, - itemState: selectedItems[0] ?? null - }; - setEditableBhaRun({ ...editableBhaRun, commonData }); - }} - /> - - - ) => { - const commonData = { - ...editableBhaRun.commonData, - sourceName: e.target.value - }; - setEditableBhaRun({ ...editableBhaRun, commonData }); - }} - /> - ) => { - const commonData = { - ...editableBhaRun.commonData, - serviceCategory: e.target.value - }; - setEditableBhaRun({ ...editableBhaRun, commonData }); - }} - /> - ) => { - const commonData = { - ...editableBhaRun.commonData, - comments: e.target.value - }; - setEditableBhaRun({ ...editableBhaRun, commonData }); - }} - /> - ) => { - const commonData = { - ...editableBhaRun.commonData, - defaultDatum: e.target.value - }; - setEditableBhaRun({ ...editableBhaRun, commonData }); - }} - /> - - } - confirmDisabled={ - !validBhaRunName || - !dTimStopValid || - !dTimStartValid || - !dTimStartDrillingValid || - !dTimStopDrillingValid - } - onSubmit={() => onSubmit(editableBhaRun)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default BhaRunPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/FormationMarkerPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/FormationMarkerPropertiesModal.tsx deleted file mode 100644 index 1fa9ba384..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/FormationMarkerPropertiesModal.tsx +++ /dev/null @@ -1,352 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import ModalDialog from "components/Modals/ModalDialog"; -import { - invalidMeasureInput, - invalidStringInput, - undefinedOnUnchagedEmptyString -} from "components/Modals/PropertiesModalUtils"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import FormationMarker from "models/formationMarker"; -import { itemStateTypes } from "models/itemStateTypes"; -import MaxLength from "models/maxLength"; -import Measure from "models/measure"; -import MeasureWithDatum from "models/measureWithDatum"; -import { ObjectType } from "models/objectType"; -import StratigraphicStruct from "models/stratigraphicStruct"; -import React, { ChangeEvent, Dispatch, SetStateAction, useState } from "react"; -import JobService, { JobType } from "services/jobService"; -import { Layout } from "../StyledComponents/Layout"; - -export interface FormationMarkerPropertiesModalProps { - formationMarker: FormationMarker; -} - -type PropertyFlags = { - [Property in keyof Type]: boolean; -}; - -interface EditableFormationMarker { - name?: string; - mdPrognosed?: MeasureWithDatum; - tvdPrognosed?: MeasureWithDatum; - mdTopSample?: MeasureWithDatum; - tvdTopSample?: MeasureWithDatum; - thicknessBed?: Measure; - thicknessApparent?: Measure; - thicknessPerpen?: Measure; - mdLogSample?: MeasureWithDatum; - tvdLogSample?: MeasureWithDatum; - dip?: Measure; - dipDirection?: Measure; - lithostratigraphic?: StratigraphicStruct; - chronostratigraphic?: StratigraphicStruct; - description?: string; - commonData?: { - itemState: string; - }; -} - -type InvalidProperties = PropertyFlags; - -/** - * Takes in the input to modify a formation marker by filling out an EditableFormationMarker object. - * For strings, an empty string represents an invalid value (on deletion of existing value), while undefined represents no change when the value was empty to begin with. - * For Measures, measure.value being NaN represents an invalid value. Only existing measures can be edited. - * @param props FormationMarker to modify - * @returns - */ -const FormationMarkerPropertiesModal = ( - props: FormationMarkerPropertiesModalProps -): React.ReactElement => { - const { formationMarker } = props; - const { dispatchOperation } = useOperationState(); - const [editable, setEditable] = useState({}); - const [isLoading, setIsLoading] = useState(false); - - const onSubmit = async () => { - setIsLoading(true); - const modifyJob = { - object: { - ...editable, - uid: formationMarker.uid, - wellboreUid: formationMarker.wellboreUid, - wellUid: formationMarker.wellUid, - objectType: ObjectType.FormationMarker - }, - objectType: ObjectType.FormationMarker - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const invalid: InvalidProperties = { - name: invalidStringInput( - formationMarker?.name, - editable.name, - MaxLength.Name - ), - mdPrognosed: invalidMeasureInput(editable.mdPrognosed), - tvdPrognosed: invalidMeasureInput(editable.tvdPrognosed), - mdTopSample: invalidMeasureInput(editable.mdTopSample), - tvdTopSample: invalidMeasureInput(editable.tvdTopSample), - thicknessBed: invalidMeasureInput(editable.thicknessBed), - thicknessApparent: invalidMeasureInput(editable.thicknessApparent), - thicknessPerpen: invalidMeasureInput(editable.thicknessPerpen), - mdLogSample: invalidMeasureInput(editable.mdLogSample), - tvdLogSample: invalidMeasureInput(editable.tvdLogSample), - dip: invalidMeasureInput(editable.dip), - dipDirection: invalidMeasureInput(editable.dipDirection), - lithostratigraphic: invalidStringInput( - formationMarker?.lithostratigraphic?.value, - editable.lithostratigraphic?.value, - MaxLength.Name - ), - chronostratigraphic: invalidStringInput( - formationMarker?.chronostratigraphic?.value, - editable.chronostratigraphic?.value, - MaxLength.Name - ), - description: invalidStringInput( - formationMarker?.description, - editable.description, - MaxLength.Comment - ) - }; - - return ( - <> - {editable && ( - - - ) => - setEditable({ ...editable, name: e.target.value }) - } - /> - { - setEditable({ - ...editable, - commonData: { itemState: selectedItems[0] } - }); - }} - hideClearButton={true} - /> - - - - - - - - - - - - - - ) => - setEditable({ - ...editable, - description: undefinedOnUnchagedEmptyString( - formationMarker.description, - e.target.value - ) - }) - } - multiline - /> - - } - confirmDisabled={ - Object.values(invalid).findIndex((value) => value === true) !== -1 - } - onSubmit={() => onSubmit()} - isLoading={isLoading} - /> - )} - - ); -}; - -interface StratigraphicFieldProps { - editable: EditableFormationMarker; - originalStruct: StratigraphicStruct; - invalid: InvalidProperties; - property: keyof InvalidProperties & keyof EditableFormationMarker; - setResult: Dispatch>; -} - -const StratigraphicField = ( - props: StratigraphicFieldProps -): React.ReactElement => { - const { editable, originalStruct, invalid, property, setResult } = props; - return ( - ) => { - setResult({ - ...editable, - [property]: { - ...originalStruct, - value: e.target.value - } - }); - }} - /> - ); -}; - -interface MeasureFieldProps { - editable: EditableFormationMarker; - originalMeasure: Measure; - invalid: InvalidProperties; - property: keyof InvalidProperties & keyof EditableFormationMarker; - setResult: Dispatch>; -} - -const MeasureField = (props: MeasureFieldProps): React.ReactElement => { - const { editable, originalMeasure, invalid, property, setResult } = props; - return ( - ) => { - setResult({ - ...editable, - [property]: { - ...originalMeasure, - value: parseFloat(e.target.value) - } - }); - }} - /> - ); -}; - -export default FormationMarkerPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/GeologyIntervalPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/GeologyIntervalPropertiesModal.tsx deleted file mode 100644 index 30390ebf9..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/GeologyIntervalPropertiesModal.tsx +++ /dev/null @@ -1,430 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import { Typography } from "@mui/material"; -import ModalDialog from "components/Modals/ModalDialog"; -import { - invalidMeasureInput, - invalidNumberInput, - invalidStringInput, - undefinedOnUnchagedEmptyString -} from "components/Modals/PropertiesModalUtils"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import GeologyInterval from "models/geologyInterval"; -import ObjectReference from "models/jobs/objectReference"; -import { lithologySources } from "models/lithologySources"; -import { lithologyTypes } from "models/lithologyTypes"; -import MaxLength from "models/maxLength"; -import Measure from "models/measure"; -import MeasureWithDatum from "models/measureWithDatum"; -import MudLog from "models/mudLog"; -import { toObjectReference } from "models/objectOnWellbore"; -import React, { - ChangeEvent, - Dispatch, - SetStateAction, - useEffect, - useState -} from "react"; -import JobService, { JobType } from "services/jobService"; -import { Layout } from "../StyledComponents/Layout"; - -export interface GeologyIntervalPropertiesModalInterface { - geologyInterval: GeologyInterval; - mudLog: MudLog; -} - -type PropertyFlags = { - [Property in keyof Type]: boolean; -}; - -interface EditableLithology { - type?: string; - codeLith?: string; - lithPc?: number; -} - -interface EditableGeologyInterval { - typeLithology?: string; - description?: string; - mdTop?: MeasureWithDatum; - mdBottom?: MeasureWithDatum; - tvdTop?: MeasureWithDatum; - tvdBase?: MeasureWithDatum; - ropAv?: Measure; - wobAv?: Measure; - tqAv?: Measure; - currentAv?: Measure; - rpmAv?: Measure; - wtMudAv?: Measure; - ecdTdAv?: Measure; - dxcAv?: number; -} - -type InvalidProperties = PropertyFlags; - -/** - * Takes in the input to modify a geology interval by filling out an EditableGeologyInterval object. - * For strings, an empty string represents an invalid value (on deletion of existing value), while undefined represents no change when the value was empty to begin with. - * For Measures, measure.value being NaN represents an invalid value. Only existing measures can be edited. - * For numbers, NaN represents an invalid value (empty input field on deletion). - * @param props GeologyInterval to modify - * @returns - */ -const GeologyIntervalPropertiesModal = ( - props: GeologyIntervalPropertiesModalInterface -): React.ReactElement => { - const { geologyInterval, mudLog: selectedMudLog } = props; - const { dispatchOperation } = useOperationState(); - const [editable, setEditable] = useState({}); - const [editableLithologies, setEditableLithologies] = useState< - Record - >({}); - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - if (geologyInterval != null) { - //the following properties are required by WITSML to be included in the update geology interval query - setEditable({ - typeLithology: geologyInterval.typeLithology, - mdTop: { ...geologyInterval.mdTop }, - mdBottom: { ...geologyInterval.mdBottom } - }); - } - }, [geologyInterval]); - - const onSubmit = async () => { - setIsLoading(true); - const mudLogReference: ObjectReference = toObjectReference(selectedMudLog); - const modifyGeologyIntervalJob = { - geologyInterval: { - ...editable, - uid: geologyInterval.uid, - dxcAv: editable.dxcAv?.toString(), - lithologies: Object.entries(editableLithologies).map((entry) => { - return { - ...entry[1], - uid: entry[0], - lithPc: entry[1].lithPc?.toString() - }; - }) - }, - mudLogReference - }; - await JobService.orderJob( - JobType.ModifyGeologyInterval, - modifyGeologyIntervalJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const invalid: InvalidProperties = { - description: invalidStringInput( - geologyInterval?.description, - editable.description, - MaxLength.Comment - ), - mdTop: invalidMeasureInput(editable.mdTop), - mdBottom: invalidMeasureInput(editable.mdBottom), - tvdTop: invalidMeasureInput(editable.tvdTop), - tvdBase: invalidMeasureInput(editable.tvdBase), - ropAv: invalidMeasureInput(editable.ropAv), - wobAv: invalidMeasureInput(editable.wobAv), - tqAv: invalidMeasureInput(editable.tqAv), - currentAv: invalidMeasureInput(editable.currentAv), - rpmAv: invalidMeasureInput(editable.rpmAv), - wtMudAv: invalidMeasureInput(editable.wtMudAv), - ecdTdAv: invalidMeasureInput(editable.ecdTdAv), - dxcAv: invalidNumberInput(geologyInterval?.dxcAv, editable.dxcAv) - }; - - const invalidLithologies: Record< - string, - { lithPc: boolean; codeLith: boolean } - > = {}; - Object.entries(editableLithologies).forEach((entry) => { - const lithUid = entry[0]; - const lithology = entry[1]; - const originalLithology = geologyInterval.lithologies.find( - (lith) => lith.uid == lithUid - ); - invalidLithologies[lithUid] = { - lithPc: invalidNumberInput(originalLithology.lithPc, lithology.lithPc), - codeLith: invalidStringInput( - originalLithology.codeLith, - lithology.codeLith, - MaxLength.Str16 - ) - }; - }); - - const setLithology = ( - value: any, - property: keyof EditableLithology, - uid: string - ) => { - const editableLithology = - editableLithologies[uid] == null - ? { - type: geologyInterval.lithologies.find((lith) => lith.uid === uid) - .type - } // lithology.type is required by update query - : editableLithologies[uid]; - setEditableLithologies({ - ...editableLithologies, - [uid]: { - ...editableLithology, - [property]: value - } - }); - }; - - return ( - <> - {editable && ( - - - { - setEditable({ ...editable, typeLithology: selectedItems[0] }); - }} - hideClearButton={true} - onFocus={(e) => e.preventDefault()} - /> - ) => - setEditable({ - ...editable, - description: undefinedOnUnchagedEmptyString( - geologyInterval.description, - e.target.value - ) - }) - } - multiline - /> - - - - - - - - - - - - ) => - setEditable({ - ...editable, - dxcAv: parseFloat(e.target.value) - }) - } - /> - {geologyInterval?.lithologies?.map((lithology) => { - return ( - - - Lithology {lithology.uid} - - - setLithology(selectedItems[0], "type", lithology.uid) - } - hideClearButton={true} - /> - ) => - setLithology(e.target.value, "codeLith", lithology.uid) - } - /> - ) => - setLithology( - parseFloat(e.target.value), - "lithPc", - lithology.uid - ) - } - /> - - ); - })} - - } - confirmDisabled={ - Object.values(invalid).findIndex((value) => value === true) !== - -1 || - Object.values(invalidLithologies).findIndex( - (l) => l.lithPc || l.codeLith - ) !== -1 - } - onSubmit={() => onSubmit()} - isLoading={isLoading} - /> - )} - - ); -}; - -interface MeasureFieldProps { - editable: EditableGeologyInterval; - originalMeasure: Measure; - invalid: InvalidProperties; - property: keyof InvalidProperties & keyof EditableGeologyInterval; - setResult: Dispatch>; -} - -const MeasureField = (props: MeasureFieldProps): React.ReactElement => { - const { editable, originalMeasure, invalid, property, setResult } = props; - return ( - ) => { - setResult({ - ...editable, - [property]: { - ...originalMeasure, - value: parseFloat(e.target.value) - } - }); - }} - /> - ); -}; - -export default GeologyIntervalPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/LogCurveInfoPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/LogCurveInfoPropertiesModal.tsx deleted file mode 100644 index b5cbe975e..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/LogCurveInfoPropertiesModal.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { TextField } from "@equinor/eds-core-react"; -import { Typography } from "@mui/material"; -import ModalDialog from "components/Modals/ModalDialog"; -import { validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import ModifyLogCurveInfoJob from "models/jobs/modifyLogCurveInfoJob"; -import LogCurveInfo from "models/logCurveInfo"; -import LogObject from "models/logObject"; -import { toObjectReference } from "models/objectOnWellbore"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; -import { Layout } from "../StyledComponents/Layout"; - -export interface LogCurveInfoPropertiesModalProps { - logCurveInfo: LogCurveInfo; - dispatchOperation: (action: HideModalAction) => void; - selectedLog: LogObject; -} - -const LogCurveInfoPropertiesModal = ( - props: LogCurveInfoPropertiesModalProps -): React.ReactElement => { - const { logCurveInfo, dispatchOperation, selectedLog } = props; - const [editableLogCurveInfo, setEditableLogCurveInfo] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const isIndexCurve = logCurveInfo?.mnemonic === selectedLog?.indexCurve; - - const onSubmit = async () => { - setIsLoading(true); - const job: ModifyLogCurveInfoJob = { - logReference: toObjectReference(selectedLog), - logCurveInfo: editableLogCurveInfo - }; - await JobService.orderJob(JobType.ModifyLogCurveInfo, job); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableLogCurveInfo(logCurveInfo); - }, [logCurveInfo]); - - const validMnemonic = validText(editableLogCurveInfo?.mnemonic, 1, 64); - const validUnit = validText(editableLogCurveInfo?.unit, 1, 64); - - return ( - <> - {editableLogCurveInfo && ( - - - ) => - setEditableLogCurveInfo({ - ...editableLogCurveInfo, - mnemonic: e.target.value - }) - } - /> - ) => - setEditableLogCurveInfo({ - ...editableLogCurveInfo, - unit: e.target.value - }) - } - /> - ) => - setEditableLogCurveInfo({ - ...editableLogCurveInfo, - curveDescription: e.target.value - }) - } - /> - - - {logCurveInfo?.axisDefinitions?.map((axisDefinition) => { - return ( - - - AxisDefinition {axisDefinition.uid} - - - - - - ); - })} - - } - confirmDisabled={ - logCurveInfo.mnemonic == editableLogCurveInfo.mnemonic && - logCurveInfo.unit == editableLogCurveInfo.unit && - logCurveInfo.curveDescription == - editableLogCurveInfo.curveDescription - } - onSubmit={() => onSubmit()} - isLoading={isLoading} - /> - )} - - ); -}; - -export default LogCurveInfoPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/LogHeaderDateTimeField.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/LogHeaderDateTimeField.tsx index 85dc34a84..057773658 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/LogHeaderDateTimeField.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/LogHeaderDateTimeField.tsx @@ -14,6 +14,7 @@ interface DateTimeFieldProps { updateObject: (dateTime: string) => void; minValue?: string; maxValue?: string; + disabled?: boolean; } /** @@ -30,7 +31,7 @@ interface DateTimeFieldProps { export const LogHeaderDateTimeField = ( props: DateTimeFieldProps ): React.ReactElement => { - const { value, label, updateObject, minValue, maxValue } = props; + const { disabled, value, label, updateObject, minValue, maxValue } = props; const { operationState: { timeZone } } = useOperationState(); @@ -85,11 +86,12 @@ export const LogHeaderDateTimeField = ( disabled style={{ fontFeatureSettings: '"tnum"', - width: "92px" + width: "94px" }} /> diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/LogPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/LogPropertiesModal.tsx deleted file mode 100644 index da04b1035..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/LogPropertiesModal.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import { WITSML_INDEX_TYPE_DATE_TIME } from "components/Constants"; -import formatDateString from "components/DateFormatter"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import LogObject from "models/logObject"; -import { ObjectType } from "models/objectType"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export enum IndexCurve { - Depth = "Depth", - Time = "Time" -} - -export interface LogPropertiesModalInterface { - mode: PropertiesModalMode; - logObject: LogObject; - dispatchOperation: (action: HideModalAction) => void; -} - -const LogPropertiesModal = ( - props: LogPropertiesModalInterface -): React.ReactElement => { - const { mode, logObject, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableLogObject, setEditableLogObject] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const editMode = mode === PropertiesModalMode.Edit; - const validIndexCurves = [IndexCurve.Depth, IndexCurve.Time]; - - const validServiceCompany = () => { - if (mode === PropertiesModalMode.New) { - return validText(editableLogObject.serviceCompany, 0, 64); - } else if (mode === PropertiesModalMode.Edit) { - if ( - logObject.serviceCompany === null && - editableLogObject.serviceCompany === null - ) - return true; - return validText(editableLogObject.serviceCompany, 1, 64); - } - }; - - const getServiceCompanyHelperText = () => { - if (mode === PropertiesModalMode.New) { - return "A service company must be 0-64 characters"; - } else if (mode === PropertiesModalMode.Edit) { - return "A service company must be 1-64 characters"; - } - }; - - const validRunNumber = () => { - if (mode === PropertiesModalMode.New) { - return validText(editableLogObject.runNumber, 0, 16); - } else if (mode === PropertiesModalMode.Edit) { - if (logObject.runNumber === null && editableLogObject.runNumber === null) - return true; - return validText(editableLogObject.runNumber, 1, 16); - } - }; - - const getRunNumberHelperText = () => { - if (mode === PropertiesModalMode.New) { - return "A run number must be 0-16 characters"; - } else if (mode === PropertiesModalMode.Edit) { - return "A run number must be 1-16 characters"; - } - }; - - const onSubmit = async (updatedLog: LogObject) => { - setIsLoading(true); - if (editMode) { - const modifyJob = { - object: { ...updatedLog, objectType: ObjectType.Log }, - objectType: ObjectType.Log - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - } else { - const wellboreLogJob = { - logObject: updatedLog - }; - await JobService.orderJob(JobType.CreateLogObject, wellboreLogJob); - } - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const onChangeCurve = async (event: any) => { - const indexCurve = - event.selectedItems[0] === IndexCurve.Time - ? IndexCurve.Time - : IndexCurve.Depth; - setEditableLogObject({ ...editableLogObject, indexCurve }); - }; - - useEffect(() => { - const isTimeIndexed = logObject.indexType === WITSML_INDEX_TYPE_DATE_TIME; - setEditableLogObject({ - ...logObject, - startIndex: isTimeIndexed - ? formatDateString(logObject.startIndex, timeZone, dateTimeFormat) - : logObject.startIndex, - endIndex: isTimeIndexed - ? formatDateString(logObject.endIndex, timeZone, dateTimeFormat) - : logObject.endIndex - }); - }, [logObject]); - - return ( - <> - {editableLogObject && ( - - ) => - setEditableLogObject({ - ...editableLogObject, - uid: e.target.value - }) - } - /> - ) => - setEditableLogObject({ - ...editableLogObject, - name: e.target.value - }) - } - /> - - ) => - setEditableLogObject({ - ...editableLogObject, - serviceCompany: - e.target.value === "" ? null : e.target.value - }) - } - /> - ) => - setEditableLogObject({ - ...editableLogObject, - runNumber: e.target.value === "" ? null : e.target.value - }) - } - /> - - - - - - - - {mode !== PropertiesModalMode.New && ( - <> - - - - )} - - } - confirmDisabled={ - !validText(editableLogObject.uid) || - !validText(editableLogObject.name) || - !validText(editableLogObject.indexCurve) || - !validServiceCompany() || - !validRunNumber() - } - onSubmit={() => onSubmit(editableLogObject)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default LogPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/MessagePropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/MessagePropertiesModal.tsx deleted file mode 100644 index 6043a47f3..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/MessagePropertiesModal.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import MessageObject from "models/messageObject"; -import { ObjectType } from "models/objectType"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface MessagePropertiesModalProps { - mode: PropertiesModalMode; - messageObject: MessageObject; - dispatchOperation: (action: HideModalAction) => void; -} - -const MessagePropertiesModal = ( - props: MessagePropertiesModalProps -): React.ReactElement => { - const { mode, messageObject, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableMessageObject, setEditableMessageObject] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const editMode = mode === PropertiesModalMode.Edit; - - useEffect(() => { - setEditableMessageObject({ - ...messageObject, - commonData: { - ...messageObject.commonData, - dTimCreation: formatDateString( - messageObject.commonData.dTimCreation, - timeZone, - dateTimeFormat - ), - dTimLastChange: formatDateString( - messageObject.commonData.dTimLastChange, - timeZone, - dateTimeFormat - ) - } - }); - }, [messageObject]); - - const onSubmit = async (updatedMessage: MessageObject) => { - setIsLoading(true); - const modifyJob = { - object: { ...updatedMessage, objectType: ObjectType.Message }, - objectType: ObjectType.Message - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const validName = validText(editableMessageObject?.name, 1, 64); - const validMessageText = validText( - editableMessageObject?.messageText, - 1, - 4000 - ); - - return ( - <> - {editableMessageObject && ( - - - - - ) => - setEditableMessageObject({ - ...editableMessageObject, - name: e.target.value - }) - } - /> - ) => - setEditableMessageObject({ - ...editableMessageObject, - messageText: e.target.value - }) - } - /> - - - - - - } - confirmDisabled={!validName || !validMessageText} - onSubmit={() => onSubmit(editableMessageObject)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default MessagePropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/ModalDialog.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/ModalDialog.tsx index d7a6c86a9..1e855b0a5 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/ModalDialog.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/ModalDialog.tsx @@ -213,14 +213,14 @@ const Content = styled(Dialog.CustomContent)<{ color: ${(props) => props.colors.text.staticIconsDefault}; div[class*="InputWrapper__Container"] { - label.dHhldd { + label { color: ${(props) => props.colors.text.staticTextLabel}; } } div[class*="Input__Container"][disabled] { background: ${(props) => props.colors.text.staticTextFieldDefault}; - border-bottom: 1px solid #9ca6ac; + border-bottom: 1px solid #575d63; } div[class*="Input__Container"] { diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/ModalParts.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/ModalParts.tsx index 36cc3693e..7d2c84b3c 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/ModalParts.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/ModalParts.tsx @@ -1,4 +1,9 @@ -export enum PropertiesModalMode { +import MaxLength from "models/maxLength"; +import Measure from "models/measure"; +import RefNameString from "models/refNameString"; +import StratigraphicStruct from "models/stratigraphicStruct"; + +export enum PropertiesModalMode { New, Edit } @@ -22,7 +27,7 @@ export const validTimeZone = (timeZone: string): boolean => { return timeZoneValidator.test(timeZone); }; -const validNumber = (num: string): boolean => { +export const validInteger = (num: string): boolean => { let result = true; if (num) { const arr: Array = num.split(""); @@ -35,10 +40,50 @@ const validNumber = (num: string): boolean => { return result; }; +export const validNumber = (num: string): boolean => { + return !isNaN(parseFloat(num)) && isFinite(parseFloat(num)); +}; + export const validPhoneNumber = (telnum: string): boolean => { - return validNumber(telnum) && validText(telnum, 8, 16); + return validInteger(telnum) && validText(telnum, 1, MaxLength.String32); +}; + +export const validMeasure = (measure: Measure): boolean => { + return ( + typeof measure.value === "number" && + !isNaN(measure.value) && + validText(measure.uom, 1, MaxLength.UomEnum) + ); +}; + +export const validBoolean = (value: any): boolean => { + return typeof value === "boolean"; +}; + +export const validStratigraphicStruct = ( + stratigraphicStruct: StratigraphicStruct +): boolean => { + return ( + validText(stratigraphicStruct.value, 1, MaxLength.Name) && + validText(stratigraphicStruct.kind, 1, MaxLength.Name) + ); +}; + +export const validRefNameString = (refNameString: RefNameString): boolean => { + return ( + validText(refNameString.value, 1, MaxLength.Name) && + validText(refNameString.uidRef, 1, MaxLength.Uid) + ); +}; + +export const validPositiveInteger = (num: string): boolean => { + return /^\+?(0|[1-9]\d*)$/.test(num); +}; + +export const validOption = (option: string, validOptions: string[]) => { + return validOptions.includes(option); }; -export const validFaxNumber = (faxnum: string): boolean => { - return validNumber(faxnum) && validText(faxnum, 0, 16); +export const validMultiOption = (options: string, validOptions: string[]) => { + return options.split(", ").every((option) => validOptions.includes(option)); }; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/MudLogPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/MudLogPropertiesModal.tsx deleted file mode 100644 index 98574170b..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/MudLogPropertiesModal.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import ModalDialog from "components/Modals/ModalDialog"; -import { - invalidStringInput, - undefinedOnUnchagedEmptyString -} from "components/Modals/PropertiesModalUtils"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import { itemStateValues } from "models/commonData"; -import MaxLength from "models/maxLength"; -import MudLog from "models/mudLog"; -import ObjectOnWellbore, { toObjectReference } from "models/objectOnWellbore"; -import { ObjectType } from "models/objectType"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; -import { Layout } from "../StyledComponents/Layout"; - -export interface MudLogPropertiesModalProps { - mudLog: MudLog; -} - -interface EditableMudLog extends ObjectOnWellbore { - mudLogCompany?: string; - mudLogEngineers?: string; - itemState?: string; -} - -const MudLogPropertiesModal = ( - props: MudLogPropertiesModalProps -): React.ReactElement => { - const { mudLog } = props; - const { - operationState: { timeZone, dateTimeFormat }, - dispatchOperation - } = useOperationState(); - const [editableMudLog, setEditableMudLog] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - const onSubmit = async (updatedMudLog: EditableMudLog) => { - setIsLoading(true); - const modifyMudLogJob = { - object: { - ...updatedMudLog, - commonData: updatedMudLog.itemState - ? { itemState: updatedMudLog.itemState } - : null, - objectType: ObjectType.MudLog - }, - objectType: ObjectType.MudLog - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyMudLogJob); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - if (mudLog != null) { - setEditableMudLog(toObjectReference(mudLog)); - } - }, [mudLog]); - - const invalidName = invalidStringInput( - mudLog?.name, - editableMudLog?.name, - MaxLength.Name - ); - const invalidMudLogCompany = invalidStringInput( - mudLog?.mudLogCompany, - editableMudLog?.mudLogCompany, - MaxLength.Name - ); - const invalidMudLogEngineers = invalidStringInput( - mudLog?.mudLogEngineers, - editableMudLog?.mudLogEngineers, - MaxLength.Description - ); - return ( - <> - {editableMudLog && ( - - - - - - - - - - - - { - setEditableMudLog({ - ...editableMudLog, - itemState: selectedItems[0] - }); - }} - /> - - } - confirmDisabled={ - invalidName || invalidMudLogCompany || invalidMudLogEngineers - } - onSubmit={() => onSubmit(editableMudLog)} - isLoading={isLoading} - /> - )} - - ); -}; - -type Key = keyof EditableMudLog & keyof MudLog; -export interface EditableTextFieldProps { - property: Key; - invalid: boolean; - maxLength: number; - setter: React.Dispatch>; - originalObject: MudLog; - editableObject: EditableMudLog; -} - -const EditableTextField = ( - props: EditableTextFieldProps -): React.ReactElement => { - const { - property, - invalid, - maxLength, - setter, - originalObject, - editableObject - } = props; - const originalValue = originalObject[property]; - const value = editableObject[property]; - return ( - ) => - setter({ - ...editableObject, - [property]: undefinedOnUnchagedEmptyString( - originalValue, - e.target.value - ) - }) - } - /> - ); -}; - -export default MudLogPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/NestedPropertyHelpers.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/NestedPropertyHelpers.ts new file mode 100644 index 000000000..7d8b83b33 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/NestedPropertyHelpers.ts @@ -0,0 +1,38 @@ +export const getNestedValue = (obj: any, path: string): any => { + return path.split(".").reduce((acc, key) => acc && acc[key], obj); +}; + +export const setNestedValue = (obj: any, path: string, value: any): any => { + const keys = path.split("."); + let currentLevel = obj; + + for (let i = 0; i < keys.length - 1; i++) { + if (!currentLevel[keys[i]]) { + currentLevel[keys[i]] = {}; + } + currentLevel = currentLevel[keys[i]]; + } + + currentLevel[keys[keys.length - 1]] = value; + return obj; +}; + +export const deleteNestedValue = (obj: any, path: string) => { + const keys = path.split("."); + let currentLevel = obj; + let deepestLevel = obj; + let deepestLevelProperty = keys[0]; + + for (let i = 0; i < keys.length - 1; i++) { + if (!currentLevel[keys[i]]) { + break; + } + if (Object.keys(currentLevel[keys[i]]).length > 1) { + deepestLevel = currentLevel[keys[i]]; + deepestLevelProperty = keys[i + 1]; + } + currentLevel = currentLevel[keys[i]]; + } + delete deepestLevel[deepestLevelProperty]; + return obj; +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BatchModifyObjectOnWellboreProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BatchModifyObjectOnWellboreProperties.ts new file mode 100644 index 000000000..8efa2211c --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BatchModifyObjectOnWellboreProperties.ts @@ -0,0 +1,40 @@ +import { getBatchModifyLogObjectProperties } from "components/Modals/PropertiesModal/Properties/LogObjectProperties"; +import { getBatchModifyRigProperties } from "components/Modals/PropertiesModal/Properties/RigProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { ObjectType, ObjectTypeToModel } from "models/objectType"; + +// Note: Only add properties that can be updated directly (without having to create a new object and delete the old one) +export const getBatchModifyObjectOnWellboreProperties = ( + objectType: T +): PropertiesModalProperty[] => { + switch (objectType) { + case ObjectType.BhaRun: + return []; + case ObjectType.ChangeLog: + return []; + case ObjectType.FluidsReport: + return []; + case ObjectType.FormationMarker: + return []; + case ObjectType.Log: + return getBatchModifyLogObjectProperties() as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Message: + return []; + case ObjectType.MudLog: + return []; + case ObjectType.Rig: + return getBatchModifyRigProperties() as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Risk: + return []; + case ObjectType.Trajectory: + return []; + case ObjectType.Tubular: + return []; + case ObjectType.WbGeometry: + return []; + } +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BhaRunProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BhaRunProperties.ts new file mode 100644 index 000000000..4ba7211d6 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/BhaRunProperties.ts @@ -0,0 +1,140 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validPositiveInteger, + validRefNameString, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText, + getRefNameStringHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import BhaRun from "models/bhaRun"; +import { bhaStatusTypes } from "models/bhaStatusTypes"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; + +export const getBhaRunProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "tubular", + propertyType: PropertyType.RefNameString, + validator: validRefNameString, + helperText: getRefNameStringHelperText("tubular") + }, + { + property: "dTimStart", + propertyType: PropertyType.DateTime + }, + { + property: "dTimStop", + propertyType: PropertyType.DateTime + }, + { + property: "dTimStartDrilling", + propertyType: PropertyType.DateTime + }, + { + property: "dTimStopDrilling", + propertyType: PropertyType.DateTime + }, + { + property: "planDogleg", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("planDogleg") + }, + { + property: "actDogleg", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("actDogleg") + }, + { + property: "actDoglegMx", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("actDoglegMx") + }, + { + property: "statusBha", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, bhaStatusTypes), + helperText: getOptionHelperText("statusBha"), + options: bhaStatusTypes + }, + { + property: "numBitRun", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("numBitRun", MaxLength.Name) + }, + { + property: "numStringRun", + propertyType: PropertyType.StringNumber, + validator: validPositiveInteger, + helperText: "numStringRun must be a positive integer" + }, + { + property: "reasonTrip", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("numBitRun", MaxLength.Comment) + }, + { + property: "objectiveBha", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("objectiveBha", MaxLength.Comment) + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + }, + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.sourceName", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("sourceName", MaxLength.Name) + }, + { + property: "commonData.serviceCategory", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Enum), + helperText: getMaxLengthHelperText("serviceCategory", MaxLength.Enum) + }, + { + property: "commonData.comments", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("comments", MaxLength.Comment), + multiline: true + }, + { + property: "commonData.defaultDatum", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("defaultDatum", MaxLength.Name) + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties.ts new file mode 100644 index 000000000..aa1b907e3 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties.ts @@ -0,0 +1,45 @@ +import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { getMaxLengthHelperText } from "components/Modals/PropertiesModal/ValidationHelpers"; +import MaxLength from "models/maxLength"; +import ObjectOnWellbore from "models/objectOnWellbore"; + +export const getCommonObjectOnWellboreProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "wellUid", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "wellName", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "wellboreUid", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "wellboreName", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "name", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("name", MaxLength.Name), + required: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/FormationMarkerProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/FormationMarkerProperties.ts new file mode 100644 index 000000000..e5a52bff3 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/FormationMarkerProperties.ts @@ -0,0 +1,110 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validStratigraphicStruct, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText, + getStratigraphicStructHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import FormationMarker from "models/formationMarker"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; + +export const getFormationMarkerProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + }, + { + property: "mdPrognosed", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdPrognosed") + }, + { + property: "tvdPrognosed", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdPrognosed") + }, + { + property: "mdTopSample", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdTopSample") + }, + { + property: "tvdTopSample", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdTopSample") + }, + { + property: "thicknessBed", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("thicknessBed") + }, + { + property: "thicknessPerpen", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("thicknessPerpen") + }, + { + property: "mdLogSample", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdLogSample") + }, + { + property: "tvdLogSample", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdLogSample") + }, + { + property: "dip", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("dip") + }, + { + property: "dipDirection", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("dipDirection") + }, + { + property: "lithostratigraphic", + propertyType: PropertyType.StratigraphicStruct, + validator: validStratigraphicStruct, + helperText: getStratigraphicStructHelperText("lithostratigraphic") + }, + { + property: "chronostratigraphic", + propertyType: PropertyType.StratigraphicStruct, + validator: validStratigraphicStruct, + helperText: getStratigraphicStructHelperText("chronostratigraphic") + }, + { + property: "description", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText("description", MaxLength.Description) + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/GeologyIntervalProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/GeologyIntervalProperties.ts new file mode 100644 index 000000000..d7e870c48 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/GeologyIntervalProperties.ts @@ -0,0 +1,157 @@ +import { + PropertiesModalMode, + validMeasure, + validNumber, + validOption, + validText +} from "components/Modals/ModalParts"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getNumberHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import GeologyInterval from "models/geologyInterval"; +import Lithology from "models/lithology"; +import { lithologySources } from "models/lithologySources"; +import { lithologyTypes } from "models/lithologyTypes"; +import MaxLength from "models/maxLength"; + +export const getGeologyIntervalProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "typeLithology", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, lithologySources), + helperText: getOptionHelperText("typeLithology"), + options: lithologySources, + required: true + }, + { + property: "description", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("description", MaxLength.Comment) + }, + { + property: "mdTop", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdTop"), + required: true + }, + { + property: "mdBottom", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdBottom"), + required: true + }, + { + property: "tvdTop", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdTop") + }, + { + property: "tvdBase", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdBase") + }, + { + property: "ropAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("ropAv") + }, + { + property: "wobAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("wobAv") + }, + { + property: "tqAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tqAv") + }, + { + property: "currentAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("currentAv") + }, + { + property: "rpmAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("rpmAv") + }, + { + property: "wtMudAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("wtMudAv") + }, + { + property: "ecdTdAv", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("ecdTdAv") + }, + { + property: "dxcAv", + propertyType: PropertyType.StringNumber, + validator: validNumber, + helperText: getNumberHelperText("dxcAv") + }, + { + property: "lithologies", + propertyType: PropertyType.List, + subProps: getLithologyProps(mode), + itemPrefix: "Lithology " + } +]; + +export const getLithologyProps = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "type", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, lithologyTypes), + helperText: getOptionHelperText("type"), + options: lithologyTypes + }, + { + property: "codeLith", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Str16), + helperText: getMaxLengthHelperText("codeLith", MaxLength.Str16) + }, + { + property: "lithPc", + propertyType: PropertyType.StringNumber, + validator: validNumber, + helperText: getNumberHelperText("lithPc") + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogCurveInfoProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogCurveInfoProperties.ts new file mode 100644 index 000000000..3b790db73 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogCurveInfoProperties.ts @@ -0,0 +1,85 @@ +import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { getMaxLengthHelperText } from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import AxisDefinition from "models/AxisDefinition"; +import LogCurveInfo from "models/logCurveInfo"; +import MaxLength from "models/maxLength"; + +export const getLogCurveInfoProperties = ( + mode: PropertiesModalMode, + isIndexCurve: boolean +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "mnemonic", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("mnemonic", MaxLength.String32), + disabled: isIndexCurve + }, + { + property: "unit", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.UomEnum), + helperText: getMaxLengthHelperText("unit", MaxLength.UomEnum) + }, + { + property: "curveDescription", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText( + "curveDescription", + MaxLength.Description + ) + }, + { + property: "typeLogData", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "mnemAlias", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "axisDefinitions", + propertyType: PropertyType.List, + subProps: getAxisDefinitionProps(mode), + itemPrefix: "Axis Definition " + } +]; + +export const getAxisDefinitionProps = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "order", + propertyType: PropertyType.Number, + disabled: true + }, + { + property: "count", + propertyType: PropertyType.Number, + disabled: true + }, + { + property: "doubleValues", + propertyType: PropertyType.String, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogObjectProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogObjectProperties.ts new file mode 100644 index 000000000..5dc306c1a --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/LogObjectProperties.ts @@ -0,0 +1,109 @@ +import { + WITSML_INDEX_TYPE_DATE_TIME, + WITSML_INDEX_TYPE_MD +} from "components/Constants"; +import { + PropertiesModalMode, + validOption, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { IndexCurve } from "models/indexCurve"; +import LogObject from "models/logObject"; +import MaxLength from "models/maxLength"; + +const indexTypeOptions = [WITSML_INDEX_TYPE_MD, WITSML_INDEX_TYPE_DATE_TIME]; + +export const getLogObjectProperties = ( + mode: PropertiesModalMode, + indexType: string +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "indexCurve", + propertyType: PropertyType.Options, + helperText: "indexCurve cannot be empty", + options: Object.values(IndexCurve), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "indexType", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, indexTypeOptions), + helperText: getOptionHelperText("indexType"), + options: indexTypeOptions, + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "runNumber", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Str16), + helperText: getMaxLengthHelperText("runNumber", MaxLength.Str16) + }, + { + property: "serviceCompany", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("serviceCompany", MaxLength.Name) + }, + { + property: "objectGrowing", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "startIndex", + propertyType: + indexType === WITSML_INDEX_TYPE_DATE_TIME + ? PropertyType.DateTime + : PropertyType.String, + disabled: true + }, + { + property: "endIndex", + propertyType: + indexType === WITSML_INDEX_TYPE_DATE_TIME + ? PropertyType.DateTime + : PropertyType.String, + disabled: true + }, + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + } +]; + +export const getBatchModifyLogObjectProperties = + (): PropertiesModalProperty[] => [ + { + property: "name", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("name", MaxLength.Name) + }, + { + property: "runNumber", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Str16), + helperText: getMaxLengthHelperText("runNumber", MaxLength.Str16) + }, + { + property: "commonData.comments", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("comments", MaxLength.Comment), + multiline: true + } + ]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MessageProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MessageProperties.ts new file mode 100644 index 000000000..1abaec62a --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MessageProperties.ts @@ -0,0 +1,30 @@ +import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { getMaxLengthHelperText } from "components/Modals/PropertiesModal/ValidationHelpers"; +import MaxLength from "models/maxLength"; +import MessageObject from "models/messageObject"; + +export const getMessageProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "messageText", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("messageText", MaxLength.Comment), + multiline: true + }, + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MudLogProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MudLogProperties.ts new file mode 100644 index 000000000..164363793 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/MudLogProperties.ts @@ -0,0 +1,65 @@ +import { + PropertiesModalMode, + validOption, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; +import MudLog from "models/mudLog"; + +export const getMudLogProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "mudLogCompany", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("mudLogCompany", MaxLength.Name) + }, + { + property: "mudLogEngineers", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText("mudLogEngineers", MaxLength.Description) + }, + { + property: "objectGrowing", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "startMd", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "endMd", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + }, + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/ObjectOnWellboreProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/ObjectOnWellboreProperties.ts new file mode 100644 index 000000000..4aa0f5cb2 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/ObjectOnWellboreProperties.ts @@ -0,0 +1,70 @@ +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getBhaRunProperties } from "components/Modals/PropertiesModal/Properties/BhaRunProperties"; +import { getFormationMarkerProperties } from "components/Modals/PropertiesModal/Properties/FormationMarkerProperties"; +import { getLogObjectProperties } from "components/Modals/PropertiesModal/Properties/LogObjectProperties"; +import { getMessageProperties } from "components/Modals/PropertiesModal/Properties/MessageProperties"; +import { getMudLogProperties } from "components/Modals/PropertiesModal/Properties/MudLogProperties"; +import { getRigProperties } from "components/Modals/PropertiesModal/Properties/RigProperties"; +import { getRiskProperties } from "components/Modals/PropertiesModal/Properties/RiskProperties"; +import { getTrajectoryProperties } from "components/Modals/PropertiesModal/Properties/TrajectoryProperties"; +import { getTubularProperties } from "components/Modals/PropertiesModal/Properties/TubularProperties"; +import { getWbGeometryProperties } from "components/Modals/PropertiesModal/Properties/WbGeometryProperties"; +import { getFluidsReportProperties } from "components/Modals/PropertiesModal/Properties/getFluidsReportProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { ObjectType, ObjectTypeToModel } from "models/objectType"; + +// Note: Only add properties that can be updated directly (without having to create a new object and delete the old one) +export const getObjectOnWellboreProperties = ( + objectType: T, + mode: PropertiesModalMode, + indexType: string = null +): PropertiesModalProperty[] => { + switch (objectType) { + case ObjectType.BhaRun: + return getBhaRunProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.ChangeLog: + return []; + case ObjectType.FluidsReport: + return getFluidsReportProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.FormationMarker: + return getFormationMarkerProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Log: + return getLogObjectProperties(mode, indexType) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Message: + return getMessageProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.MudLog: + return getMudLogProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Rig: + return getRigProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Risk: + return getRiskProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Trajectory: + return getTrajectoryProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.Tubular: + return getTubularProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + case ObjectType.WbGeometry: + return getWbGeometryProperties(mode) as PropertiesModalProperty< + ObjectTypeToModel[T] + >[]; + } +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RigProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RigProperties.ts new file mode 100644 index 000000000..ae628527c --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RigProperties.ts @@ -0,0 +1,169 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validPhoneNumber, + validPositiveInteger, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText, + getPhoneNumberHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; +import Rig from "models/rig"; +import { rigType } from "models/rigType"; + +export const getRigProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "typeRig", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, rigType), + helperText: getOptionHelperText("typeRig"), + options: rigType + }, + { + property: "dTimStartOp", + propertyType: PropertyType.DateTime + }, + { + property: "dTimEndOp", + propertyType: PropertyType.DateTime + }, + { + property: "yearEntService", + propertyType: PropertyType.StringNumber, + validator: (num: string) => + validPositiveInteger(num) && validText(num, 4, 4), + helperText: "yearEntService must be a 4 digit positive integer" + }, + { + property: "telNumber", + propertyType: PropertyType.String, + validator: validPhoneNumber, + helperText: getPhoneNumberHelperText("telNumber") + }, + { + property: "faxNumber", + propertyType: PropertyType.String, + validator: validPhoneNumber, + helperText: getPhoneNumberHelperText("faxNumber") + }, + { + property: "emailAddress", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("emailAddress", MaxLength.Name) + }, + { + property: "nameContact", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("nameContact", MaxLength.Name) + }, + { + property: "ratingDrillDepth", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("ratingDrillDepth") + }, + { + property: "ratingWaterDepth", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("ratingWaterDepth") + }, + { + property: "airGap", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("airGap") + }, + { + property: "owner", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("owner", MaxLength.String32) + }, + { + property: "manufacturer", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("manufacturer", MaxLength.Name) + }, + { + property: "classRig", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("classRig", MaxLength.String32) + }, + { + property: "approvals", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("approvals", MaxLength.Name) + }, + { + property: "registration", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("registration", MaxLength.String32) + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + } +]; + +export const getBatchModifyRigProperties = + (): PropertiesModalProperty[] => [ + { + property: "owner", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 0, MaxLength.String32), + helperText: getMaxLengthHelperText("owner", MaxLength.String32) + }, + { + property: "typeRig", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, rigType), + helperText: getOptionHelperText("typeRig"), + options: rigType + }, + { + property: "manufacturer", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 0, MaxLength.Name), + helperText: getMaxLengthHelperText("manufacturer", MaxLength.Name) + }, + { + property: "classRig", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 0, MaxLength.String32), + helperText: getMaxLengthHelperText("classRig", MaxLength.String32) + }, + { + property: "approvals", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 0, MaxLength.Name), + helperText: getMaxLengthHelperText("approvals", MaxLength.Name) + }, + { + property: "registration", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 0, MaxLength.String32), + helperText: getMaxLengthHelperText("registration", MaxLength.String32) + } + ]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RiskProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RiskProperties.ts new file mode 100644 index 000000000..ce1660058 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/RiskProperties.ts @@ -0,0 +1,134 @@ +import { + PropertiesModalMode, + validMeasure, + validMultiOption, + validOption, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { itemStateTypes } from "models/itemStateTypes"; +import { levelIntegerCode } from "models/levelIntegerCode"; +import MaxLength from "models/maxLength"; +import { riskAffectedPersonnel } from "models/riskAffectedPersonnel"; +import { riskCategory } from "models/riskCategory"; +import RiskObject from "models/riskObject"; +import { riskSubCategory } from "models/riskSubCategory"; +import { riskType } from "models/riskType"; + +export const getRiskProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "type", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, riskType), + helperText: getOptionHelperText("type"), + options: riskType + }, + { + property: "category", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, riskCategory), + helperText: getOptionHelperText("category"), + options: riskCategory + }, + { + property: "subCategory", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, riskSubCategory), + helperText: getOptionHelperText("subCategory"), + options: riskSubCategory + }, + { + property: "extendCategory", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Enum), + helperText: getMaxLengthHelperText("extendCategory", MaxLength.Enum) + }, + { + property: "affectedPersonnel", + propertyType: PropertyType.Options, + validator: (value: string) => + validMultiOption(value, riskAffectedPersonnel), + helperText: getOptionHelperText("affectedPersonnel"), + options: riskAffectedPersonnel, + multiSelect: true + }, + { + property: "dTimStart", + propertyType: PropertyType.DateTime + }, + { + property: "dTimEnd", + propertyType: PropertyType.DateTime + }, + { + property: "mdBitStart", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdBitStart") + }, + { + property: "mdBitEnd", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdBitEnd") + }, + { + property: "severityLevel", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, levelIntegerCode), + helperText: getOptionHelperText("severityLevel"), + options: levelIntegerCode + }, + { + property: "probabilityLevel", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, levelIntegerCode), + helperText: getOptionHelperText("severityLevel"), + options: levelIntegerCode + }, + { + property: "summary", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText("summary", MaxLength.Description) + }, + { + property: "details", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText("details", MaxLength.Description) + }, + { + property: "commonData.sourceName", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("sourceName", MaxLength.Name) + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryProperties.ts new file mode 100644 index 000000000..881041f93 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryProperties.ts @@ -0,0 +1,68 @@ +import { + PropertiesModalMode, + validOption, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; +import Trajectory, { aziRefValues } from "models/trajectory"; + +export const getTrajectoryProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "dTimTrajStart", + propertyType: PropertyType.DateTime, + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "dTimTrajEnd", + propertyType: PropertyType.DateTime, + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "serviceCompany", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("serviceCompany", MaxLength.Name) + }, + { + property: "mdMin", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "mdMax", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "aziRef", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, aziRefValues), + helperText: getOptionHelperText("aziRef"), + options: aziRefValues + }, + { + property: "commonData.sourceName", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("sourceName", MaxLength.Name), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryStationProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryStationProperties.ts new file mode 100644 index 000000000..10b36ce7d --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TrajectoryStationProperties.ts @@ -0,0 +1,228 @@ +import { + PropertiesModalMode, + validMeasure, + validText +} from "components/Modals/ModalParts"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import MaxLength from "models/maxLength"; +import TrajectoryStation from "models/trajectoryStation"; + +export const getTrajectoryStationProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "typeTrajStation", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "dTimStn", + propertyType: PropertyType.DateTime + }, + { + property: "md", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("md") + }, + { + property: "tvd", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvd") + }, + { + property: "azi", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("azi") + }, + { + property: "incl", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("incl") + }, + { + property: "mtf", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "gtf", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "dispNs", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "dispEw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "vertSect", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "dls", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "rateTurn", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "rateBuild", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "gravTotalUncert", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "dipAngleUncert", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "magTotalUncert", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "gravTotalFieldReference", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "magTotalFieldReference", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "magDipAngleReference", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "statusTrajStation", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "gravAxialRaw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "gravTran1Raw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "gravTran2Raw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "magAxialRaw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "rawData.magTran1Raw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "rawData.magTran2Raw", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.gravAxialAccelCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.gravTran1AccelCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.gravTran2AccelCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.magAxialDrlstrCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.magTran1DrlstrCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.magTran2DrlstrCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.sagIncCor", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.stnMagDeclUsed", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.stnGridCorUsed", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "corUsed.dirSensorOffset", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "valid.magTotalFieldCalc", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "valid.magDipAngleCalc", + propertyType: PropertyType.Measure, + disabled: true + }, + { + property: "valid.gravTotalFieldCalc", + propertyType: PropertyType.Measure, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularComponentProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularComponentProperties.ts new file mode 100644 index 000000000..f6ac4f50a --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularComponentProperties.ts @@ -0,0 +1,107 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validPositiveInteger, + validText +} from "components/Modals/ModalParts"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { boxPinConfigTypes } from "models/boxPinConfigTypes"; +import { materialTypes } from "models/materialTypes"; +import MaxLength from "models/maxLength"; +import TubularComponent from "models/tubularComponent"; +import { tubularComponentTypes } from "models/tubularComponentTypes"; + +export const getTubularComponentProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "sequence", + propertyType: PropertyType.Number, + validator: (value: string) => + validPositiveInteger(value) && parseInt(value) > 0, + helperText: "sequence must be a positive non-zero integer" + }, + { + property: "description", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Description), + helperText: getMaxLengthHelperText("description", MaxLength.Description) + }, + { + property: "typeTubularComponent", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, tubularComponentTypes), + helperText: getOptionHelperText("typeTubularComponent"), + options: tubularComponentTypes + }, + { + property: "id", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("id") + }, + { + property: "od", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("od") + }, + { + property: "len", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("len") + }, + { + property: "wtPerLen", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("wtPerLen") + }, + { + property: "numJointStand", + propertyType: PropertyType.Number, + validator: validPositiveInteger, + helperText: "numJointStand must be a positive integer" + }, + { + property: "configCon", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, boxPinConfigTypes), + helperText: getOptionHelperText("configCon"), + options: boxPinConfigTypes + }, + { + property: "typeMaterial", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, materialTypes), + helperText: getOptionHelperText("typeMaterial"), + options: materialTypes + }, + { + property: "vendor", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("vendor", MaxLength.Name) + }, + { + property: "model", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("model", MaxLength.Name) + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularProperties.ts new file mode 100644 index 000000000..61cfd4297 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/TubularProperties.ts @@ -0,0 +1,21 @@ +import { PropertiesModalMode, validOption } from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { getOptionHelperText } from "components/Modals/PropertiesModal/ValidationHelpers"; +import Tubular from "models/tubular"; +import { typeTubularAssy } from "models/typeTubularAssy"; + +export const getTubularProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "typeTubularAssy", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, typeTubularAssy), + helperText: getOptionHelperText("typeTubularAssy"), + options: typeTubularAssy, + required: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometryProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometryProperties.ts new file mode 100644 index 000000000..5dc1e9b9f --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometryProperties.ts @@ -0,0 +1,75 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; +import WbGeometryObject from "models/wbGeometry"; + +export const getWbGeometryProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "dTimReport", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.sourceName", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "mdBottom", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdBottom") + }, + { + property: "gapAir", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("gapAir") + }, + { + property: "depthWaterMean", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("depthWaterMean") + }, + { + property: "commonData.comments", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("comments", MaxLength.Comment), + multiline: true + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometrySectionProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometrySectionProperties.ts new file mode 100644 index 000000000..0a6f640ee --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WbGeometrySectionProperties.ts @@ -0,0 +1,105 @@ +import { + PropertiesModalMode, + validBoolean, + validMeasure, + validNumber, + validOption, + validText +} from "components/Modals/ModalParts"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getBooleanHelperText, + getMaxLengthHelperText, + getMeasureHelperText, + getNumberHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { holeCasingTypes } from "models/holeCasingTypes"; +import MaxLength from "models/maxLength"; +import WbGeometrySection from "models/wbGeometrySection"; + +export const getWbGeometrySectionProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "typeHoleCasing", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, holeCasingTypes), + helperText: getOptionHelperText("typeHoleCasing"), + options: holeCasingTypes + }, + { + property: "mdTop", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdTop") + }, + { + property: "mdBottom", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdBottom") + }, + { + property: "tvdTop", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdTop") + }, + { + property: "tvdBottom", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdBottom") + }, + { + property: "idSection", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("idSection") + }, + { + property: "odSection", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("odSection") + }, + { + property: "wtPerLen", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("wtPerLen") + }, + { + property: "curveConductor", + propertyType: PropertyType.Boolean, + validator: validBoolean, + helperText: getBooleanHelperText("curveConductor") + }, + { + property: "diaDrift", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("diaDrift") + }, + { + property: "grade", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("grade", MaxLength.String32) + }, + { + property: "factFric", + propertyType: PropertyType.Number, + validator: validNumber, + helperText: getNumberHelperText("factFric") + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellProperties.ts new file mode 100644 index 000000000..157fdc7be --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellProperties.ts @@ -0,0 +1,73 @@ +import { + PropertiesModalMode, + validText, + validTimeZone +} from "components/Modals/ModalParts"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getTimeZoneHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import MaxLength from "models/maxLength"; +import Well from "models/well"; + +export const getWellProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "name", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("name", MaxLength.Name), + required: true + }, + { + property: "field", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("field", MaxLength.Name) + }, + { + property: "country", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("country", MaxLength.String32) + }, + { + property: "operator", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("operator", MaxLength.Name) + }, + { + property: "timeZone", + propertyType: PropertyType.String, + validator: (value: string) => validTimeZone(value), + helperText: getTimeZoneHelperText("timeZone"), + required: true + }, + { + property: "numLicense", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("numLicense", MaxLength.Name) + }, + { + property: "dateTimeCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "dateTimeLastChange", + propertyType: PropertyType.DateTime, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellboreProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellboreProperties.ts new file mode 100644 index 000000000..d5bc49cae --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/WellboreProperties.ts @@ -0,0 +1,150 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validText +} from "components/Modals/ModalParts"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import MaxLength from "models/maxLength"; +import Wellbore from "models/wellbore"; +import { wellborePurposeValues } from "models/wellborePurposeValues"; + +export const getWellboreProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + { + property: "wellUid", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "wellName", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "wellboreParentName", + propertyType: PropertyType.String, + disabled: true + }, + { + property: "uid", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Uid), + helperText: getMaxLengthHelperText("uid", MaxLength.Uid), + disabled: mode === PropertiesModalMode.Edit + }, + { + property: "name", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("name", MaxLength.Name), + required: true + }, + { + property: "wellborePurpose", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, wellborePurposeValues), + helperText: getOptionHelperText("wellborePurpose"), + options: wellborePurposeValues + }, + { + property: "number", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.String32), + helperText: getMaxLengthHelperText("number", MaxLength.String32) + }, + { + property: "suffixAPI", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("suffixAPI", MaxLength.Name) + }, + { + property: "numGovt", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("numGovt", MaxLength.Name) + }, + { + property: "dTimeKickoff", + propertyType: PropertyType.DateTime + }, + { + property: "md", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("md") + }, + { + property: "tvd", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvd") + }, + { + property: "mdKickoff", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdKickoff") + }, + { + property: "tvdKickoff", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdKickoff") + }, + { + property: "mdPlanned", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdPlanned") + }, + { + property: "tvdPlanned", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdPlanned") + }, + { + property: "mdSubSeaPlanned", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("mdSubSeaPlanned") + }, + { + property: "tvdSubSeaPlanned", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvdSubSeaPlanned") + }, + { + property: "dayTarget", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("dayTarget") + }, + { + property: "dateTimeCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "dateTimeLastChange", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "comments", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("comments", MaxLength.Comment), + multiline: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/getFluidsReportProperties.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/getFluidsReportProperties.ts new file mode 100644 index 000000000..85f390cf2 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/Properties/getFluidsReportProperties.ts @@ -0,0 +1,82 @@ +import { + PropertiesModalMode, + validMeasure, + validOption, + validPositiveInteger, + validText +} from "components/Modals/ModalParts"; +import { getCommonObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/CommonObjectOnWellboreProperties"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import FluidsReport from "models/fluidsReport"; +import { itemStateTypes } from "models/itemStateTypes"; +import MaxLength from "models/maxLength"; + +export const getFluidsReportProperties = ( + mode: PropertiesModalMode +): PropertiesModalProperty[] => [ + ...getCommonObjectOnWellboreProperties(mode), + { + property: "dTim", + propertyType: PropertyType.DateTime + }, + { + property: "md", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("md") + }, + { + property: "tvd", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("tvd") + }, + { + property: "numReport", + propertyType: PropertyType.StringNumber, + validator: validPositiveInteger, + helperText: "numReport must be a positive integer" + }, + { + property: "commonData.sourceName", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("sourceName", MaxLength.Name) + }, + { + property: "commonData.itemState", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, itemStateTypes), + helperText: getOptionHelperText("itemState"), + options: itemStateTypes + }, + { + property: "commonData.serviceCategory", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Enum), + helperText: getMaxLengthHelperText("serviceCategory", MaxLength.Enum) + }, + { + property: "commonData.comments", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Comment), + helperText: getMaxLengthHelperText("comments", MaxLength.Comment), + multiline: true + }, + { + property: "commonData.dTimCreation", + propertyType: PropertyType.DateTime, + disabled: true + }, + { + property: "commonData.dTimLastChange", + propertyType: PropertyType.DateTime, + disabled: true + } +]; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesModal.tsx new file mode 100644 index 000000000..11e5bc3c1 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesModal.tsx @@ -0,0 +1,105 @@ +import { getNestedValue } from "components/Modals/PropertiesModal/NestedPropertyHelpers"; +import { PropertiesRenderer } from "components/Modals/PropertiesModal/PropertiesRenderer"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { isPropertyValid } from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { cloneDeep } from "lodash"; +import { ReactElement, useState } from "react"; +import styled from "styled-components"; +import ModalDialog, { ModalWidth } from "../ModalDialog"; + +export interface PropertiesModalProps { + title: string; + object: T; + properties: PropertiesModalProperty[]; + onSubmit: (updates: Partial) => void; +} + +/** + * PropertiesModal component + * + * A modal dialog for editing properties of a given object. It renders a list of properties, + * validates the input, and submits only the modified properties via the onSubmit callback. + * + * @template T - The type of the object whose properties are being edited. + * + * @param {PropertiesModalProps} props - The props for the PropertiesModal component. + * @param {string} props.title - The title of the modal dialog. + * @param {T} props.object - The object whose properties are to be edited. + * @param {PropertiesModalProperty[]} props.properties - The list of properties available for editing. + * @param {(updates: Partial) => void} props.onSubmit - Callback for submitting the modified properties. + * + * @returns {ReactElement} A React element representing the properties modal dialog. + * + * @remarks + * - The `onSubmit` callback only receives modified properties. + * - The modal validates each property before enabling the submit button. + */ + +export const PropertiesModal = ( + props: PropertiesModalProps +): ReactElement => { + const { title, object, properties, onSubmit } = props; + const [updates, setUpdates] = useState>({}); + const allValid = properties.every((prop) => + isPropertyValid(prop, object, updates) + ); + const anyUpdates = Object.keys(updates).length > 0; + + const getFullUpdateObject = () => { + const notPartialProperties = [ + PropertyType.Measure, + PropertyType.RefNameString, + PropertyType.StratigraphicStruct + ]; + const fullUpdates = cloneDeep(updates); + Object.keys(fullUpdates).forEach((property) => { + const propertyType = properties.find( + (prop) => prop.property === property + )?.propertyType; + if (notPartialProperties.includes(propertyType)) { + const originalValue = getNestedValue(object, property); + fullUpdates[property as keyof T] = { + ...originalValue, + ...updates[property as keyof T] + }; + } + }); + return fullUpdates; + }; + + const onInternalSubmit = async () => { + // Some datatypes like measures needs both the value and uom in order to update, so make sure the original is used if only partially modified by the user. + const fullUpdates = getFullUpdateObject(); + onSubmit(fullUpdates); + }; + + return ( + + {properties.length === 0 &&

No properties to update.

} + + + } + confirmDisabled={!allValid || !anyUpdates} + onSubmit={onInternalSubmit} + isLoading={false} + /> + ); +}; + +const Layout = styled.div` + margin-top: 12px; + margin-bottom: 12px; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +`; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesRenderer.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesRenderer.tsx new file mode 100644 index 000000000..134cecd5e --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertiesRenderer.tsx @@ -0,0 +1,295 @@ +import { Autocomplete, TextField, Typography } from "@equinor/eds-core-react"; +import { Stack } from "@mui/material"; +import { LogHeaderDateTimeField } from "components/Modals/LogHeaderDateTimeField"; +import { + deleteNestedValue, + getNestedValue, + setNestedValue +} from "components/Modals/PropertiesModal/NestedPropertyHelpers"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + formatPropertyValue, + getHelperText, + getVariant, + hasPropertyChanged +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { cloneDeep } from "lodash"; +import { ChangeEvent, Fragment, KeyboardEvent, ReactElement } from "react"; +import styled from "styled-components"; + +interface PropertiesRendererProps { + properties: PropertiesModalProperty[]; + object: T; + updates: Partial; + onChange: (updates: Partial) => void; +} + +export const PropertiesRenderer = ({ + properties, + object, + updates, + onChange +}: PropertiesRendererProps): ReactElement => { + const getInitialSelectedOptions = (prop: PropertiesModalProperty) => { + const optionsString = getNestedValue(object, prop.property)?.toString(); + if (!optionsString) return []; + return prop.multiSelect ? optionsString.split(", ") : [optionsString]; + }; + + const onOptionsChange = ( + prop: PropertiesModalProperty, + selectedItems: string[] + ) => { + const updatedValue = + (prop.multiSelect + ? selectedItems.sort().join(", ") + : selectedItems?.[0]) ?? ""; + onChangeProperty(prop.property, prop.propertyType, updatedValue); + }; + + const onChangeProperty = ( + property: string, + propertyType: PropertyType, + value: string + ) => { + const originalValue = getNestedValue(object, property); + const formattedValue = formatPropertyValue(property, propertyType, value); + if (hasPropertyChanged(propertyType, formattedValue, originalValue)) { + onChange(setNestedValue(cloneDeep(updates), property, formattedValue)); + } else { + onChange(deleteNestedValue(cloneDeep(updates), property)); + } + }; + + return ( + <> + {properties.map((prop) => { + switch (prop.propertyType) { + case PropertyType.List: { + const subObjectList = getNestedValue(object, prop.property) ?? []; + const subUpdatesList = getNestedValue(updates, prop.property) ?? []; + const onSubObjectChange = ( + subObject: any, + updates: Partial + ) => { + const fullSubUpdate = { ...cloneDeep(subObject), ...updates }; + const updateIndex = subUpdatesList.findIndex( + (update: any) => update.uid === subObject.uid + ); + let fullSubUpdateList = cloneDeep(subUpdatesList); + if (updateIndex >= 0) { + fullSubUpdateList[updateIndex] = fullSubUpdate; + } else { + fullSubUpdateList = [...fullSubUpdateList, fullSubUpdate]; + } + onChangeProperty( + prop.property, + prop.propertyType, + fullSubUpdateList + ); + }; + return ( + + {subObjectList.map((subObject: any, i: number) => ( + + + + {prop.itemPrefix + + (subObject.name || + subObject.mnemonic || + subObject.uid || + i.toString())} + + + subUpdate.uid === subObject.uid + ) ?? {} + } + onChange={(updates) => + onSubObjectChange(subObject, updates) + } + /> + + ))} + + ); + } + case PropertyType.Boolean: + case PropertyType.Options: { + const options = + (prop.propertyType === PropertyType.Boolean + ? ["true", "false"] + : prop.options) ?? []; + return ( + + onOptionsChange(prop, selectedItems) + } + onInputChange={(text: string) => { + if (!prop.multiSelect) { + onChangeProperty(prop.property, prop.propertyType, text); + } + }} + hideClearButton={!prop.multiSelect} + multiple={prop.multiSelect} + /> + ); + } + case PropertyType.DateTime: + return ( + { + onChangeProperty(prop.property, prop.propertyType, dateTime); + }} + /> + ); + case PropertyType.Measure: + return ( + + ) => + onChangeProperty( + `${prop.property}.value`, + prop.propertyType, + e.target.value + ) + } + /> + ) => + onChangeProperty( + `${prop.property}.uom`, + prop.propertyType, + e.target.value + ) + } + style={{ + width: "300px" + }} + /> + + ); + case PropertyType.RefNameString: + case PropertyType.StratigraphicStruct: { + const secondaryProperty = + prop.propertyType === PropertyType.RefNameString + ? "uidRef" + : "kind"; + return ( + + ) => + onChangeProperty( + `${prop.property}.value`, + prop.propertyType, + e.target.value + ) + } + /> + ) => + onChangeProperty( + `${prop.property}.${secondaryProperty}`, + prop.propertyType, + e.target.value + ) + } + style={{ + width: "300px" + }} + /> + + ); + } + case PropertyType.String: + case PropertyType.StringNumber: + case PropertyType.Number: + return ( + ) => { + if (prop.multiline && e.key === "Enter") e.stopPropagation(); + }} + disabled={prop.disabled} + defaultValue={getNestedValue(object, prop.property)} + helperText={getHelperText(prop, object, updates)} + variant={getVariant(prop, object, updates)} + onChange={(e: ChangeEvent) => + onChangeProperty( + prop.property, + prop.propertyType, + e.target.value + ) + } + /> + ); + } + })} + + ); +}; + +const ListHeaderLayout = styled.div` + grid-column: 1 / -1; + margin-top: 12px; +`; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertyTypes.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertyTypes.ts new file mode 100644 index 000000000..1b82c760c --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/PropertyTypes.ts @@ -0,0 +1,49 @@ +/** + * PropertyType options and details: + * + * PropertyType.String: + * - A simple text input. + * - Can be multi-line if `multiline` is set to true. + * + * PropertyType.StringNumber: + * - A numerical input that returns the number as a string. + * + * PropertyType.Number: + * - A numerical input. + * + * PropertyType.DateTime: + * - An input for date and time. + * + * PropertyType.Measure: + * - Combines a value and unit of measure. + * + * PropertyType.Options: + * - A dropdown or multi-select input. + * - Requires an `options` array. + * - Can be multi-select if `multiSelect` is set to true. + * + * PropertyType.RefNameString: + * - A reference name input, often used for linked data. + * + * PropertyType.StratigraphicStruct: + * - For complex stratigraphic structure inputs. + * + * PropertyType.Boolean: + * - A dropdown input for boolean values. + * + * PropertyType.List: + * - An input for a list of items. + * - Requires `subProps` defining the properties of list items. + */ +export enum PropertyType { + String, + StringNumber, + Number, + DateTime, + Measure, + Options, + RefNameString, + StratigraphicStruct, + Boolean, + List +} diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/ValidationHelpers.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/ValidationHelpers.ts new file mode 100644 index 000000000..6e7411698 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/ValidationHelpers.ts @@ -0,0 +1,157 @@ +import { Variants } from "@equinor/eds-core-react/dist/types/components/types"; +import { getNestedValue } from "components/Modals/PropertiesModal/NestedPropertyHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; + +const getActualValue = ( + propertyType: PropertyType, + value: any, + originalValue: any +) => { + switch (propertyType) { + case PropertyType.RefNameString: + case PropertyType.StratigraphicStruct: + case PropertyType.Measure: + return !value && !originalValue ? null : { ...originalValue, ...value }; + default: + return value === undefined ? originalValue : value; + } +}; + +export const isPropertyValid = ( + prop: PropertiesModalProperty, + originalObject: T, + updates: Partial +): boolean => { + const originalValue = getNestedValue(originalObject, prop.property); + const updatedValue = getNestedValue(updates, prop.property); + const actualValue = getActualValue( + prop.propertyType, + updatedValue, + originalValue + ); + if (prop.propertyType === PropertyType.List) { + if (updatedValue === undefined) return true; + return (updatedValue as any[]).every((subValue) => { + const originalSubObject = (originalValue as any[]).find( + (oV) => oV.uid === subValue.uid + ); + return prop.subProps.every((subProp) => + isPropertyValid(subProp, originalSubObject, subValue) + ); + }); + } + if (prop.validator && (actualValue || typeof actualValue === "boolean")) { + return prop.validator(actualValue, originalValue); + } + const isRequired = prop.required || !!originalValue; + if (isRequired && !actualValue) { + return false; + } + return true; +}; + +export const getHelperText = ( + prop: PropertiesModalProperty, + originalObject: T, + updates: Partial +) => { + return isPropertyValid(prop, originalObject, updates) ? "" : prop.helperText; +}; + +export const getVariant = ( + prop: PropertiesModalProperty, + originalObject: T, + updates: Partial +): Variants => { + return isPropertyValid(prop, originalObject, updates) ? undefined : "error"; +}; + +export const hasPropertyChanged = ( + propertyType: PropertyType, + value: any, + originalValue: any +) => { + switch (propertyType) { + case PropertyType.List: { + const updatedUids = (value as any[]).map((v) => v.uid); + const originalFiltered = (originalValue as any[]) + .map((obj) => + Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) + ) + .filter((obj) => updatedUids.includes(obj.uid)); + const valueFiltered = (value as any[]).map((obj) => + Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)) + ); + return JSON.stringify(originalFiltered) !== JSON.stringify(valueFiltered); + } + case PropertyType.DateTime: + return Date.parse(originalValue) !== Date.parse(value); + default: + return ( + !( + value === "" && + (originalValue === null || originalValue === undefined) + ) && value !== originalValue + ); + } +}; + +export const formatPropertyValue = ( + property: string, + propertyType: PropertyType, + value: string +) => { + switch (propertyType) { + case PropertyType.Boolean: + if (!["true", "false"].includes(value)) return "null"; + return value === "true"; + case PropertyType.Number: + return parseFloat(value); + case PropertyType.Measure: + if ( + propertyType === PropertyType.Measure && + property.endsWith(".value") && + value !== "" + ) { + return parseFloat(value); + } + } + return value; +}; + +export const getMaxLengthHelperText = (property: string, maxLength: number) => { + return `${property} must be 1-${maxLength} characters`; +}; + +export const getTimeZoneHelperText = (property: string) => { + return `${property} has to be 'Z' or in the format -hh:mm or +hh:mm within the range (-12:00 to +14:00) and minutes has to be 00, 30 or 45`; +}; + +export const getPhoneNumberHelperText = (property: string) => { + return `${property} must be an integer of 1-32 characters. Whitespace, dash and plus is accepted`; +}; + +export const getNumberHelperText = (property: string) => { + return `${property} must be a valid number`; +}; + +export const getMeasureHelperText = (property: string) => { + return `${property} must have a valid number and unit`; +}; + +export const getBooleanHelperText = (property: string) => { + return `${property} must be true or false`; +}; + +export const getStratigraphicStructHelperText = (property: string) => { + return `${property} must have a valid value and kind`; +}; + +export const getRefNameStringHelperText = (property: string) => { + return `${property} must have a valid value and uidRef`; +}; + +export const getOptionHelperText = (property: string) => { + return `${property} must have a valid option`; +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/LogCurveInfoPropertiesModal.test.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/LogCurveInfoPropertiesModal.test.tsx new file mode 100644 index 000000000..48b318d6d --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/LogCurveInfoPropertiesModal.test.tsx @@ -0,0 +1,103 @@ +import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { mockEdsCoreReact } from "__testUtils__/mocks/EDSMocks"; +import { + getAxisDefinition, + getLogCurveInfo, + renderWithContexts +} from "__testUtils__/testUtils"; +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getLogCurveInfoProperties } from "components/Modals/PropertiesModal/Properties/LogCurveInfoProperties"; +import { + PropertiesModal, + PropertiesModalProps +} from "components/Modals/PropertiesModal/PropertiesModal"; +import LogCurveInfo from "models/logCurveInfo"; +import { vi } from "vitest"; + +vi.mock("@equinor/eds-core-react", () => mockEdsCoreReact()); + +const simpleProps: PropertiesModalProps = { + title: `Edit LogCurveInfo properties`, + object: getLogCurveInfo(), + properties: getLogCurveInfoProperties(PropertiesModalMode.Edit, false), + onSubmit: () => {} +}; + +const propsWithAxisDefinition: PropertiesModalProps = { + ...simpleProps, + object: getLogCurveInfo({ + axisDefinitions: [getAxisDefinition()] + }) +}; + +describe("Tests for PropertiesModal for LogCurveInfo", () => { + it("Properties of a LogCurve should be shown in the modal", async () => { + const expectedLogCurveInfo = simpleProps.object; + + renderWithContexts(); + + const uidInput = screen.getByRole("textbox", { name: /uid/i }); + const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); + + expect(uidInput).toHaveValue(expectedLogCurveInfo.uid); + expect(mnemonicInput).toHaveValue(expectedLogCurveInfo.mnemonic); + + expect(uidInput).toBeDisabled(); + expect(mnemonicInput).toBeEnabled(); + }); + + it("AxisDefinition should be shown disabled in the LogCurveInfo modal when included in the props", async () => { + const expectedLogCurveInfo = propsWithAxisDefinition.object; + const expectedAxisDefinition = expectedLogCurveInfo.axisDefinitions[0]; + + renderWithContexts(); + + const uidInput = screen.getByRole("textbox", { name: /uid/i }); + const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); + const axisDefinitionLabel = screen.getByText(/axisdefinition/i); + const orderInput = screen.getByRole("spinbutton", { name: /order/i }); + const countInput = screen.getByRole("spinbutton", { name: /count/i }); + const doubleValuesInput = screen.getByRole("textbox", { + name: /doubleValues/i + }); + + expect(uidInput).toHaveValue(expectedLogCurveInfo.uid); + expect(mnemonicInput).toHaveValue(expectedLogCurveInfo.mnemonic); + expect(axisDefinitionLabel).toHaveTextContent(expectedAxisDefinition.uid); + expect(orderInput).toHaveValue(expectedAxisDefinition.order); + expect(countInput).toHaveValue(expectedAxisDefinition.count); + expect(doubleValuesInput).toHaveValue(expectedAxisDefinition.doubleValues); + + expect(uidInput).toBeDisabled(); + expect(mnemonicInput).toBeEnabled(); + expect(orderInput).toBeDisabled(); + expect(countInput).toBeDisabled(); + expect(doubleValuesInput).toBeDisabled(); + }); + + it("Saving edited properties of a LogCurve should call onSubmit with the changed parts of the object", async () => { + const user = userEvent.setup(); + const onSubmitMock = vi.fn(); + + const props = { + ...simpleProps, + onSubmit: onSubmitMock + }; + + renderWithContexts(); + + const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + + await user.clear(mnemonicInput); + await user.type(mnemonicInput, "editedMnemonic"); + + expect(saveButton).toBeEnabled(); + + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledTimes(1); + expect(onSubmitMock).toHaveBeenCalledWith({ mnemonic: "editedMnemonic" }); + }); +}); diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/PropertiesModal.test.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/PropertiesModal.test.tsx new file mode 100644 index 000000000..16e07ab74 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/__tests__/PropertiesModal.test.tsx @@ -0,0 +1,406 @@ +import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { mockEdsCoreReact } from "__testUtils__/mocks/EDSMocks"; +import { + MockResizeObserver, + renderWithContexts +} from "__testUtils__/testUtils"; +import { + validBoolean, + validMeasure, + validOption, + validPositiveInteger, + validText +} from "components/Modals/ModalParts"; +import { + PropertiesModal, + PropertiesModalProps +} from "components/Modals/PropertiesModal/PropertiesModal"; +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; +import { + getBooleanHelperText, + getMaxLengthHelperText, + getMeasureHelperText, + getOptionHelperText +} from "components/Modals/PropertiesModal/ValidationHelpers"; +import { PropertiesModalProperty } from "components/Modals/PropertiesModal/propertiesModalProperty"; +import MaxLength from "models/maxLength"; +import Measure from "models/measure"; +import { Mock, vi } from "vitest"; + +vi.mock("@equinor/eds-core-react", () => mockEdsCoreReact()); + +interface TestObject { + stringProperty: string; + numericStringProperty: string; + numberProperty: number; + measureProperty: Measure; + optionsProperty: string; + booleanProperty: boolean; + listProperty: SubObject[]; +} + +interface SubObject { + stringSubProperty: string; +} + +const initialObject: TestObject = { + stringProperty: "stringValue", + numericStringProperty: "3", + numberProperty: 5, + measureProperty: { + value: 3.2, + uom: "m" + }, + optionsProperty: "option 2", + booleanProperty: false, + listProperty: [ + { + stringSubProperty: "subStringValue" + } + ] +}; + +const testOptions = ["option 1", "option 2", "option 3"]; + +const subProperties: PropertiesModalProperty[] = [ + { + property: "stringSubProperty", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("stringSubProperty", MaxLength.Name) + } +]; + +const testProperties: PropertiesModalProperty[] = [ + { + property: "stringProperty", + propertyType: PropertyType.String, + validator: (value: string) => validText(value, 1, MaxLength.Name), + helperText: getMaxLengthHelperText("stringProperty", MaxLength.Name) + }, + { + property: "numericStringProperty", + propertyType: PropertyType.StringNumber, + validator: validPositiveInteger, + helperText: "numericStringProperty must be a positive integer" + }, + { + property: "numberProperty", + propertyType: PropertyType.Number, + validator: validPositiveInteger, + helperText: "numberProperty must be a positive integer" + }, + { + property: "measureProperty", + propertyType: PropertyType.Measure, + validator: validMeasure, + helperText: getMeasureHelperText("measureProperty") + }, + { + property: "optionsProperty", + propertyType: PropertyType.Options, + validator: (value: string) => validOption(value, testOptions), + helperText: getOptionHelperText("optionsProperty"), + options: testOptions + }, + { + property: "booleanProperty", + propertyType: PropertyType.Boolean, + validator: validBoolean, + helperText: getBooleanHelperText("booleanProperty") + }, + { + property: "listProperty", + propertyType: PropertyType.List, + subProps: subProperties, + itemPrefix: "ListItem " + } +]; + +describe("Tests for PropertiesModal", () => { + window.ResizeObserver = MockResizeObserver; + let testProps: PropertiesModalProps; + let onSubmitMock: Mock; + + beforeEach(() => { + onSubmitMock = vi.fn(); + testProps = { + title: `Edit TestObject properties`, + object: initialObject, + properties: testProperties, + onSubmit: onSubmitMock + }; + }); + + it("Should show all properties", async () => { + renderWithContexts(); + + const stringInput = screen.getByRole("textbox", { + name: /stringProperty/i + }); + const numericStringInput = screen.getByRole("spinbutton", { + name: /numericStringProperty/i + }); + const numberInput = screen.getByRole("spinbutton", { + name: /numberProperty/i + }); + const measureValueInput = screen.getByRole("spinbutton", { + name: /measureProperty/i + }); + const measureUnitInput = screen.getByRole("textbox", { name: /unit/i }); + const optionsInput = screen.getByRole("combobox", { + name: /optionsProperty/i + }); + const booleanInput = screen.getByRole("combobox", { + name: /booleanProperty/i + }); + const listHeader = screen.getByText(/ListItem/i); + const subStringInput = screen.getByRole("textbox", { + name: /stringSubProperty/i + }); + + expect(stringInput).toHaveValue(initialObject.stringProperty); + expect(stringInput).toBeEnabled(); + expect(numericStringInput).toHaveValue( + parseInt(initialObject.numericStringProperty) + ); + expect(numericStringInput).toBeEnabled(); + expect(numberInput).toHaveValue(initialObject.numberProperty); + expect(numberInput).toBeEnabled(); + expect(measureValueInput).toHaveValue(initialObject.measureProperty.value); + expect(measureValueInput).toBeEnabled(); + expect(measureUnitInput).toHaveValue(initialObject.measureProperty.uom); + expect(measureUnitInput).toBeEnabled(); + expect(optionsInput).toHaveValue(initialObject.optionsProperty); + expect(optionsInput).toBeEnabled(); + expect(booleanInput).toHaveValue(initialObject.booleanProperty.toString()); + expect(booleanInput).toBeEnabled(); + expect(listHeader).toBeInTheDocument(); + expect(subStringInput).toHaveValue( + initialObject.listProperty[0].stringSubProperty + ); + expect(subStringInput).toBeEnabled(); + }); + + it("Should disable save when no values are changed", async () => { + renderWithContexts(); + + const saveButton = screen.getByRole("button", { name: /save/i }); + + expect(saveButton).toBeDisabled(); + }); + + it("Should enable save when a value is valid and changed", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = "newStringValue"; + + const input = screen.getByRole("textbox", { name: /stringProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue); + + expect(input).toHaveValue(newValue); + expect(saveButton).toBeEnabled(); + }); + + it("Should disable save when all properties are reverted to their original value as no values are changed", async () => { + const user = userEvent.setup(); + renderWithContexts(); + + const stringInput = screen.getByRole("textbox", { + name: /stringProperty/i + }); + const measureValueInput = screen.getByRole("spinbutton", { + name: /measureProperty/i + }); + const subStringInput = screen.getByRole("textbox", { + name: /stringSubProperty/i + }); + const saveButton = screen.getByRole("button", { name: /save/i }); + + // Change the input fields + await user.type(stringInput, "a"); + await user.type(measureValueInput, "5"); + await user.type(subStringInput, "b"); + + // Revert changes + await user.type(stringInput, "{backspace}"); + await user.type(measureValueInput, "{backspace}"); + await user.type(subStringInput, "{backspace}"); + + expect(stringInput).toHaveValue(initialObject.stringProperty); + expect(measureValueInput).toHaveValue(initialObject.measureProperty.value); + expect(subStringInput).toHaveValue( + initialObject.listProperty[0].stringSubProperty + ); + expect(saveButton).toBeDisabled(); + }); + + it("Clicking cancel should not call the callback", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = "newStringValue"; + + const input = screen.getByRole("textbox", { name: /stringProperty/i }); + const cancelButton = screen.getByRole("button", { name: /cancel/i }); + await user.clear(input); + await user.type(input, newValue); + await user.click(cancelButton); + + expect(onSubmitMock).not.toHaveBeenCalled(); + }); + + it("Saving edited string should call callback with the changed property", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = "newStringValue"; + + const input = screen.getByRole("textbox", { name: /stringProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue); + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledOnce(); + expect(onSubmitMock).toHaveBeenCalledWith({ stringProperty: newValue }); + }); + + it("Saving edited stringNumber should call callback with the changed number as a string", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = "9"; + + const input = screen.getByRole("spinbutton", { + name: /numericStringProperty/i + }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue); + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledOnce(); + expect(onSubmitMock).toHaveBeenCalledWith({ + numericStringProperty: newValue + }); + }); + + it("Saving edited number should call callback with the changed number as a number", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = 9; + + const input = screen.getByRole("spinbutton", { name: /numberProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue.toString()); + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledOnce(); + expect(onSubmitMock).toHaveBeenCalledWith({ numberProperty: newValue }); + }); + + it("Saving edited boolean should call callback with the changed boolean", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = true; + + const input = screen.getByRole("combobox", { name: /booleanProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue.toString()); + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledOnce(); + expect(onSubmitMock).toHaveBeenCalledWith({ booleanProperty: newValue }); + }); + + it("Saving partially edited measure should call callback with the full measure", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newValue = 9.99; + + const input = screen.getByRole("spinbutton", { name: /measureProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newValue.toString()); + await user.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalledOnce(); + expect(onSubmitMock).toHaveBeenCalledWith({ + measureProperty: { + value: newValue, + uom: initialObject.measureProperty.uom + } + }); + }); + + it("Invalid edited string should give an error", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const newText = "editedInputWithTooLongText".repeat(10); + const expectedHelperText = testProps.properties.find( + (p) => p.property === "stringProperty" + ).helperText; + + const input = screen.getByRole("textbox", { name: /stringProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, newText); + const helperText = screen.getByText(expectedHelperText); + + expect(input).toHaveValue(newText); + expect(helperText).toBeInTheDocument(); + expect(saveButton).toBeDisabled(); + }); + + it("Clearing the measure value should give an error", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const expectedHelperText = testProps.properties.find( + (p) => p.property === "measureProperty" + ).helperText; + + const input = screen.getByRole("spinbutton", { name: /measureProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + const helperText = screen.getByText(expectedHelperText); + + expect(helperText).toBeInTheDocument(); + expect(saveButton).toBeDisabled(); + }); + + it("Clearing the measure unit should give an error", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const expectedHelperText = testProps.properties.find( + (p) => p.property === "measureProperty" + ).helperText; + + const input = screen.getByRole("textbox", { name: /unit/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + const helperText = screen.getByText(expectedHelperText); + + expect(helperText).toBeInTheDocument(); + expect(saveButton).toBeDisabled(); + }); + + it("Invalid option should give an error", async () => { + const user = userEvent.setup(); + renderWithContexts(); + const expectedHelperText = testProps.properties.find( + (p) => p.property === "optionsProperty" + ).helperText; + + const input = screen.getByRole("combobox", { name: /optionsProperty/i }); + const saveButton = screen.getByRole("button", { name: /save/i }); + await user.clear(input); + await user.type(input, "option 4"); + const helperText = screen.getByText(expectedHelperText); + + expect(helperText).toBeInTheDocument(); + expect(saveButton).toBeDisabled(); + }); +}); diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/openPropertiesHelpers.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/openPropertiesHelpers.tsx new file mode 100644 index 000000000..d07904513 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/openPropertiesHelpers.tsx @@ -0,0 +1,109 @@ +import { PropertiesModalMode } from "components/Modals/ModalParts"; +import { getObjectOnWellboreProperties } from "components/Modals/PropertiesModal/Properties/ObjectOnWellboreProperties"; +import { getWellProperties } from "components/Modals/PropertiesModal/Properties/WellProperties"; +import { getWellboreProperties } from "components/Modals/PropertiesModal/Properties/WellboreProperties"; +import { + PropertiesModal, + PropertiesModalProps +} from "components/Modals/PropertiesModal/PropertiesModal"; +import { + orderCreateObjectOnWellboreJob, + orderCreateWellJob, + orderCreateWellboreJob, + orderModifyObjectOnWellboreJob, + orderModifyWellJob, + orderModifyWellboreJob +} from "components/Modals/PropertiesModal/orderPropertyJobHelpers"; +import { DispatchOperation } from "contexts/operationStateReducer"; +import OperationType from "contexts/operationType"; +import LogObject from "models/logObject"; +import { ObjectType, ObjectTypeToModel } from "models/objectType"; +import Well from "models/well"; +import Wellbore from "models/wellbore"; + +export const openObjectOnWellboreProperties = async ( + objectType: T, + object: ObjectTypeToModel[T], + dispatchOperation: DispatchOperation, + mode: PropertiesModalMode = PropertiesModalMode.Edit +) => { + dispatchOperation({ type: OperationType.HideContextMenu }); + const indexType = + objectType === ObjectType.Log ? (object as LogObject).indexType : null; + const properties = getObjectOnWellboreProperties(objectType, mode, indexType); + + const propertyModalProps: PropertiesModalProps = { + title: + mode === PropertiesModalMode.Edit + ? `Edit properties for ${object.name}` + : `Create new ${objectType}`, + object, + properties, + onSubmit: async (updates) => { + dispatchOperation({ type: OperationType.HideModal }); + mode === PropertiesModalMode.Edit + ? orderModifyObjectOnWellboreJob(objectType, object, updates) + : orderCreateObjectOnWellboreJob(objectType, object, updates); + } + }; + dispatchOperation({ + type: OperationType.DisplayModal, + payload: + }); +}; + +export const openWellProperties = async ( + well: Well, + dispatchOperation: DispatchOperation, + mode: PropertiesModalMode = PropertiesModalMode.Edit +) => { + dispatchOperation({ type: OperationType.HideContextMenu }); + const properties = getWellProperties(mode); + + const propertyModalProps: PropertiesModalProps = { + title: + mode === PropertiesModalMode.Edit + ? `Edit properties for ${well.name}` + : "Create new Well", + object: well, + properties, + onSubmit: async (updates) => { + dispatchOperation({ type: OperationType.HideModal }); + mode === PropertiesModalMode.Edit + ? orderModifyWellJob(well, updates) + : orderCreateWellJob(well, updates); + } + }; + dispatchOperation({ + type: OperationType.DisplayModal, + payload: + }); +}; + +export const openWellboreProperties = async ( + wellbore: Wellbore, + dispatchOperation: DispatchOperation, + mode: PropertiesModalMode = PropertiesModalMode.Edit +) => { + dispatchOperation({ type: OperationType.HideContextMenu }); + const properties = getWellboreProperties(mode); + + const propertyModalProps: PropertiesModalProps = { + title: + mode === PropertiesModalMode.Edit + ? `Edit properties for ${wellbore.name}` + : "Create new Wellbore", + object: wellbore, + properties, + onSubmit: async (updates) => { + dispatchOperation({ type: OperationType.HideModal }); + mode === PropertiesModalMode.Edit + ? orderModifyWellboreJob(wellbore, updates) + : orderCreateWellboreJob(wellbore, updates); + } + }; + dispatchOperation({ + type: OperationType.DisplayModal, + payload: + }); +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/orderPropertyJobHelpers.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/orderPropertyJobHelpers.ts new file mode 100644 index 000000000..f6bd19236 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/orderPropertyJobHelpers.ts @@ -0,0 +1,94 @@ +import { ObjectType, ObjectTypeToModel } from "models/objectType"; +import Well from "models/well"; +import Wellbore from "models/wellbore"; +import JobService, { JobType } from "services/jobService"; + +export const orderModifyObjectOnWellboreJob = async ( + objectType: T, + object: ObjectTypeToModel[T], + updates: Partial +) => { + const modifyJob = { + object: { + // updates only contains modified properties, so we need to add uids for a correct reference to the object. + uid: object.uid, + wellUid: object.wellUid, + wellboreUid: object.wellboreUid, + ...updates, + objectType: objectType + }, + objectType: objectType + }; + await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); +}; + +export const orderCreateObjectOnWellboreJob = async ( + objectType: T, + object: ObjectTypeToModel[T], + updates: Partial +) => { + const createJob = { + object: { + ...object, + ...updates, + objectType: objectType + }, + objectType: objectType + }; + await JobService.orderJob(JobType.CreateObjectOnWellbore, createJob); +}; + +export const orderModifyWellJob = async ( + object: Well, + updates: Partial +) => { + const modifyJob = { + well: { + // updates only contains modified properties, so we need to add uids for a correct reference to the object. + uid: object.uid, + ...updates + } + }; + await JobService.orderJob(JobType.ModifyWell, modifyJob); +}; + +export const orderCreateWellJob = async ( + object: Well, + updates: Partial +) => { + const createJob = { + well: { + ...object, + ...updates + } + }; + await JobService.orderJob(JobType.CreateWell, createJob); +}; + +export const orderModifyWellboreJob = async ( + object: Wellbore, + updates: Partial +) => { + const modifyJob = { + wellbore: { + // updates only contains modified properties, so we need to add uids for a correct reference to the object. + uid: object.uid, + wellUid: object.wellUid, + ...updates + } + }; + await JobService.orderJob(JobType.ModifyWellbore, modifyJob); +}; + +export const orderCreateWellboreJob = async ( + object: Wellbore, + updates: Partial +) => { + const createJob = { + wellbore: { + ...object, + ...updates + } + }; + await JobService.orderJob(JobType.CreateWellbore, createJob); +}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/propertiesModalProperty.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/propertiesModalProperty.ts new file mode 100644 index 000000000..78c6611f2 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModal/propertiesModalProperty.ts @@ -0,0 +1,71 @@ +import { PropertyType } from "components/Modals/PropertiesModal/PropertyTypes"; + +// Helper-type to verify that a (nested) property exists on the given type, limited to depth D +type NestedKeys = D extends 0 + ? never + : { + [K in keyof T]: T[K] extends (infer U)[] + ? K extends string + ? `${K}` | `${K}[number]` | `${K}[number].${NestedKeys}` + : never + : T[K] extends object + ? K extends string + ? `${K}` | `${K}.${NestedKeys}` + : never + : K; + }[keyof T & string]; + +// Prev is used to limit NestedKeys to a max depth to avoid infinite typescript checks. +type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + +interface CommonProps { + property: NestedKeys; + propertyType: PropertyType; + validator?: (value: any, originalValue: string) => boolean; + helperText?: string; + required?: boolean; + disabled?: boolean; +} + +// options are only allowed for PropertyType.Options +type OptionsPropertyProps = + | { propertyType: PropertyType.Options; options: string[] } + | { + propertyType: Exclude; + options?: never; + }; + +// multiSelect is only allowed for PropertyType.Options +type MultiSelectOptionProps = + | { propertyType: PropertyType.Options; multiSelect?: boolean } + | { + propertyType: Exclude; + multiSelect?: never; + }; + +// multiline is only allowed for PropertyType.String +type MultiLineProps = + | { propertyType: PropertyType.String; multiline?: boolean } + | { + propertyType: Exclude; + multiline?: never; + }; + +// subproperties for list types +type SubProps = + | { + propertyType: PropertyType.List; + subProps: PropertiesModalProperty[]; + itemPrefix?: string; + } + | { + propertyType: Exclude; + subProperties?: never; + itemPrefix?: never; + }; + +export type PropertiesModalProperty = CommonProps & + OptionsPropertyProps & + MultiSelectOptionProps & + MultiLineProps & + SubProps; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModalUtils.ts b/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModalUtils.ts deleted file mode 100644 index 33e8c316f..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/PropertiesModalUtils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import Measure from "models/measure"; - -export const undefinedOnUnchagedEmptyString = ( - original?: string, - edited?: string -): string | null => { - if (edited?.length > 0) { - return edited; - } - if (original == null || original.length == 0) { - return undefined; - } - return ""; -}; - -export const invalidStringInput = ( - original: string, - edited: string, - maxLength: number -): boolean => { - return ( - errorOnDeletion(original, edited) || - (edited != null && edited.length > maxLength) - ); -}; - -const errorOnDeletion = (original: string, edited: string): boolean => { - if (original == null || original.length == 0) { - return false; - } - return edited != null && edited.length == 0; -}; - -export const invalidMeasureInput = (edited: Measure): boolean => { - return edited != null && isNaN(edited.value); -}; - -export const invalidNumberInput = (original: any, edited: number): boolean => { - return original != null && edited != null && isNaN(edited); -}; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx deleted file mode 100644 index fe9651e53..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx +++ /dev/null @@ -1,396 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import { DateTimeField } from "components/Modals/DateTimeField"; -import ModalDialog from "components/Modals/ModalDialog"; -import { - PropertiesModalMode, - validFaxNumber, - validPhoneNumber, - validText -} from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import { itemStateTypes } from "models/itemStateTypes"; -import { ObjectType } from "models/objectType"; -import Rig from "models/rig"; -import { rigType } from "models/rigType"; -import React, { ChangeEvent, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface RigPropertiesModalProps { - mode: PropertiesModalMode; - rig: Rig; - dispatchOperation: (action: HideModalAction) => void; -} - -const RigPropertiesModal = ( - props: RigPropertiesModalProps -): React.ReactElement => { - const { mode, rig, dispatchOperation } = props; - const { - operationState: { timeZone } - } = useOperationState(); - const [editableRig, setEditableRig] = useState({ ...rig }); - const [isLoading, setIsLoading] = useState(false); - const [dTimStartOpValid, setDTimStartOpValid] = useState(true); - const [dTimEndOpValid, setDTimEndOpValid] = useState(true); - const editMode = mode === PropertiesModalMode.Edit; - - const onSubmit = async (updatedRig: Rig) => { - setIsLoading(true); - if (editMode) { - const modifyJob = { - object: { ...updatedRig, objectType: ObjectType.Rig }, - objectType: ObjectType.Rig - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - } else { - const wellboreRigJob = { - rig: updatedRig - }; - await JobService.orderJob(JobType.CreateRig, wellboreRigJob); - } - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const yearEntServiceValid = - (!rig.yearEntService && !editableRig?.yearEntService) || - editableRig?.yearEntService?.length == 4; - const validTelNumber = - (!rig.telNumber && !editableRig?.telNumber) || - validPhoneNumber(editableRig.telNumber); - const faxNumberValid = - (!rig.faxNumber && !editableRig?.faxNumber) || - validFaxNumber(editableRig.faxNumber); - const validEmailAddress = - (!rig.emailAddress && !editableRig?.emailAddress) || - validText(editableRig.emailAddress, 1, 128); - const validNameContact = - (!rig.nameContact && !editableRig?.nameContact) || - validText(editableRig?.nameContact, 1, 64); - - const validRigUid = validText(editableRig?.uid, 1, 64); - const validRigName = validText(editableRig?.name, 1, 64); - - return ( - <> - {editableRig && ( - - ) => - setEditableRig({ ...editableRig, uid: e.target.value }) - } - /> - - - - - ) => - setEditableRig({ ...editableRig, name: e.target.value }) - } - /> - { - setEditableRig({ ...editableRig, typeRig: selectedItems[0] }); - }} - /> - { - setEditableRig({ ...editableRig, dTimStartOp: dateTime }); - setDTimStartOpValid(valid); - }} - timeZone={timeZone} - /> - { - setEditableRig({ ...editableRig, dTimEndOp: dateTime }); - setDTimEndOpValid(valid); - }} - timeZone={timeZone} - /> - ) => - setEditableRig({ - ...editableRig, - yearEntService: e.target.value - }) - } - /> - ) => - setEditableRig({ ...editableRig, telNumber: e.target.value }) - } - /> - ) => - setEditableRig({ ...editableRig, faxNumber: e.target.value }) - } - /> - ) => - setEditableRig({ - ...editableRig, - emailAddress: e.target.value - }) - } - /> - ) => - setEditableRig({ - ...editableRig, - nameContact: e.target.value - }) - } - /> - ) => - setEditableRig({ - ...editableRig, - ratingDrillDepth: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableRig.ratingDrillDepth.uom - } - }) - } - /> - ) => - setEditableRig({ - ...editableRig, - ratingWaterDepth: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableRig.ratingWaterDepth.uom - } - }) - } - /> - ) => { - const uom = - editableRig.airGap !== null ? editableRig.airGap.uom : "m"; - setEditableRig({ - ...editableRig, - airGap: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: uom - } - }); - }} - /> - ) => { - setEditableRig({ - ...editableRig, - owner: e.target.value - }); - }} - /> - ) => { - setEditableRig({ - ...editableRig, - manufacturer: e.target.value - }); - }} - /> - ) => { - setEditableRig({ - ...editableRig, - classRig: e.target.value - }); - }} - /> - ) => { - setEditableRig({ - ...editableRig, - approvals: e.target.value - }); - }} - /> - ) => { - setEditableRig({ - ...editableRig, - registration: e.target.value - }); - }} - /> - { - const commonData = { - ...editableRig.commonData, - itemState: selectedItems[0] ?? null - }; - setEditableRig({ ...editableRig, commonData }); - }} - /> - - } - confirmDisabled={ - !validRigUid || - !validRigName || - !dTimStartOpValid || - !dTimEndOpValid - } - onSubmit={() => onSubmit(editableRig)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default RigPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/RiskPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/RiskPropertiesModal.tsx deleted file mode 100644 index 01d9c2d56..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/RiskPropertiesModal.tsx +++ /dev/null @@ -1,433 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import { DateTimeField } from "components/Modals/DateTimeField"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import { itemStateTypes } from "models/itemStateTypes"; -import { ObjectType } from "models/objectType"; -import { riskAffectedPersonnel } from "models/riskAffectedPersonnel"; -import { riskCategory } from "models/riskCategory"; -import RiskObject from "models/riskObject"; -import { riskSubCategory } from "models/riskSubCategory"; -import { riskType } from "models/riskType"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface RiskPropertiesModalProps { - mode: PropertiesModalMode; - riskObject: RiskObject; - dispatchOperation: (action: HideModalAction) => void; -} - -const RiskPropertiesModal = ( - props: RiskPropertiesModalProps -): React.ReactElement => { - const { mode, riskObject, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableRiskObject, setEditableRiskObject] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const [dTimStartValid, setDTimStartValid] = useState(true); - const [dTimEndValid, setDTimEndValid] = useState(true); - const editMode = mode === PropertiesModalMode.Edit; - - useEffect(() => { - setEditableRiskObject({ - ...riskObject, - dTimStart: formatDateString( - riskObject.dTimStart, - timeZone, - dateTimeFormat - ), - dTimEnd: formatDateString(riskObject.dTimEnd, timeZone, dateTimeFormat), - commonData: { - ...riskObject.commonData, - dTimCreation: formatDateString( - riskObject.commonData.dTimCreation, - timeZone, - dateTimeFormat - ), - dTimLastChange: formatDateString( - riskObject.commonData.dTimLastChange, - timeZone, - dateTimeFormat - ) - } - }); - }, [riskObject]); - - const onSubmit = async (updatedRisk: RiskObject) => { - setIsLoading(true); - const modifyJob = { - object: { ...updatedRisk, objectType: ObjectType.Risk }, - objectType: ObjectType.Risk - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const validRiskName = validText(editableRiskObject?.name, 1, 64); - const validRiskDetails = validText(editableRiskObject?.details, 1, 256); - - return ( - <> - {editableRiskObject && ( - - - - - - - - - ) => - setEditableRiskObject({ - ...editableRiskObject, - name: e.target.value - }) - } - /> - { - setEditableRiskObject({ - ...editableRiskObject, - type: selectedItems[0] - }); - }} - /> - { - setEditableRiskObject({ - ...editableRiskObject, - category: selectedItems[0] - }); - }} - /> - - { - setEditableRiskObject({ - ...editableRiskObject, - subCategory: selectedItems[0] - }); - }} - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - extendCategory: e.target.value - }) - } - /> - { - setEditableRiskObject({ - ...editableRiskObject, - affectedPersonnel: selectedItems.join(", ") - }); - }} - /> - { - setEditableRiskObject({ - ...editableRiskObject, - dTimStart: dateTime - }); - setDTimStartValid(valid); - }} - timeZone={timeZone} - /> - { - setEditableRiskObject({ - ...editableRiskObject, - dTimEnd: dateTime - }); - setDTimEndValid(valid); - }} - timeZone={timeZone} - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - mdBitStart: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableRiskObject.mdBitStart.uom - } - }) - } - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - mdBitEnd: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableRiskObject.mdBitEnd.uom - } - }) - } - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - severityLevel: e.target.value - }) - } - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - probabilityLevel: e.target.value - }) - } - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - summary: e.target.value - }) - } - /> - ) => - setEditableRiskObject({ - ...editableRiskObject, - details: e.target.value - }) - } - /> - ) => { - const commonData = { - ...editableRiskObject.commonData, - sourceName: e.target.value - }; - setEditableRiskObject({ ...editableRiskObject, commonData }); - }} - /> - { - const commonData = { - ...editableRiskObject.commonData, - itemState: selectedItems[0] ?? null - }; - setEditableRiskObject({ ...editableRiskObject, commonData }); - }} - /> - - } - confirmDisabled={!validRiskName || !dTimStartValid || !dTimEndValid} - onSubmit={() => onSubmit(editableRiskObject)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default RiskPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/ServerModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/ServerModal.tsx index 6739b093b..497a0ecd8 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/ServerModal.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/ServerModal.tsx @@ -222,7 +222,7 @@ const ServerModal = (props: ServerModalProps): React.ReactElement => { style={labelStyle} htmlFor="creds" /> - + void; -} - -const TrajectoryPropertiesModal = ( - props: TrajectoryPropertiesModalProps -): React.ReactElement => { - const { mode, trajectory, dispatchOperation } = props; - const { - operationState: { timeZone } - } = useOperationState(); - const [editableTrajectory, setEditableTrajectory] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const [, setDTimTrajStartValid] = useState(true); - const [, setDTimTrajEndValid] = useState(true); - const editMode = mode === PropertiesModalMode.Edit; - - useEffect(() => { - setEditableTrajectory({ - ...trajectory, - commonData: { - ...trajectory.commonData - } - }); - }, [trajectory]); - - const onSubmit = async (updatedTrajectory: Trajectory) => { - setIsLoading(true); - if (editMode) { - const modifyJob = { - object: { ...updatedTrajectory, objectType: ObjectType.Trajectory }, - objectType: ObjectType.Trajectory - }; - await JobService.orderJob(JobType.ModifyObjectOnWellbore, modifyJob); - } else { - const wellboreTrajectoryJob = { - trajectory: updatedTrajectory - }; - await JobService.orderJob( - JobType.CreateTrajectory, - wellboreTrajectoryJob - ); - } - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const validTrajectoryUid = validText(editableTrajectory?.uid, 1, 64); - const validTrajectoryName = validText(editableTrajectory?.name, 1, 64); - const validTrajectoryServiceCompany = editMode - ? validText(editableTrajectory?.serviceCompany, 1, 64) - : validText(editableTrajectory?.serviceCompany, 0, 64); - - return ( - <> - {editableTrajectory && ( - - ) => - setEditableTrajectory({ - ...editableTrajectory, - uid: e.target.value - }) - } - /> - - - - - ) => - setEditableTrajectory({ - ...editableTrajectory, - name: e.target.value - }) - } - /> - { - setEditableTrajectory({ - ...editableTrajectory, - dTimTrajStart: dateTime - }); - setDTimTrajStartValid(valid); - }} - timeZone={timeZone} - disabled={editMode} - /> - { - setEditableTrajectory({ - ...editableTrajectory, - dTimTrajEnd: dateTime - }); - setDTimTrajEndValid(valid); - }} - timeZone={timeZone} - disabled={editMode} - /> - ) => - setEditableTrajectory({ - ...editableTrajectory, - serviceCompany: e.target.value - }) - } - /> - ) => - setEditableTrajectory({ - ...editableTrajectory, - mdMin: { - ...editableTrajectory.mdMin, - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value) - } - }) - } - /> - ) => - setEditableTrajectory({ - ...editableTrajectory, - mdMax: { - ...editableTrajectory.mdMax, - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value) - } - }) - } - /> - { - setEditableTrajectory({ - ...editableTrajectory, - aziRef: selectedItems[0] - }); - }} - onFocus={(e) => e.preventDefault()} - /> - ) => { - const commonData = { - ...editableTrajectory.commonData, - sourceName: e.target.value - }; - setEditableTrajectory({ ...editableTrajectory, commonData }); - }} - /> - { - const commonData = { - ...editableTrajectory.commonData, - itemState: selectedItems[0] ?? null - }; - setEditableTrajectory({ ...editableTrajectory, commonData }); - }} - /> - - } - confirmDisabled={ - !validTrajectoryUid || - !validTrajectoryName || - !validTrajectoryServiceCompany - } - onSubmit={() => onSubmit(editableTrajectory)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default TrajectoryPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/TrajectoryStationPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/TrajectoryStationPropertiesModal.tsx deleted file mode 100644 index e7f6178e3..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/TrajectoryStationPropertiesModal.tsx +++ /dev/null @@ -1,453 +0,0 @@ -import { TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import { DateTimeField } from "components/Modals/DateTimeField"; -import ModalDialog from "components/Modals/ModalDialog"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import ObjectReference from "models/jobs/objectReference"; -import { measureToString } from "models/measure"; -import { toObjectReference } from "models/objectOnWellbore"; -import Trajectory from "models/trajectory"; -import TrajectoryStation from "models/trajectoryStation"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface TrajectoryStationPropertiesModalInterface { - trajectoryStation: TrajectoryStation; - trajectory: Trajectory; - dispatchOperation: (action: HideModalAction) => void; -} - -const TrajectoryStationPropertiesModal = ( - props: TrajectoryStationPropertiesModalInterface -): React.ReactElement => { - const { trajectoryStation, trajectory, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableTrajectoryStation, setEditableTrajectoryStation] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const [dTimStnValid, setDTimStnValid] = useState(true); - - const onSubmit = async (updatedTrajectoryStation: TrajectoryStation) => { - setIsLoading(true); - const trajectoryReference: ObjectReference = toObjectReference(trajectory); - const modifyTrajectoryStationJob = { - trajectoryStation: updatedTrajectoryStation, - trajectoryReference - }; - await JobService.orderJob( - JobType.ModifyTrajectoryStation, - modifyTrajectoryStationJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableTrajectoryStation({ - ...trajectoryStation, - dTimStn: formatDateString( - trajectoryStation.dTimStn, - timeZone, - dateTimeFormat - ) - }); - }, [trajectoryStation]); - return ( - <> - {editableTrajectoryStation && ( - - - - { - setEditableTrajectoryStation({ - ...editableTrajectoryStation, - dTimStn: dateTime - }); - setDTimStnValid(valid); - }} - timeZone={timeZone} - /> - ) => - setEditableTrajectoryStation({ - ...editableTrajectoryStation, - md: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableTrajectoryStation.md.uom - } - }) - } - /> - ) => - setEditableTrajectoryStation({ - ...editableTrajectoryStation, - tvd: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableTrajectoryStation.tvd.uom - } - }) - } - /> - ) => - setEditableTrajectoryStation({ - ...editableTrajectoryStation, - azi: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableTrajectoryStation.azi.uom - } - }) - } - /> - ) => - setEditableTrajectoryStation({ - ...editableTrajectoryStation, - incl: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableTrajectoryStation.incl.uom - } - }) - } - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } - confirmDisabled={!dTimStnValid} - onSubmit={() => onSubmit(editableTrajectoryStation)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default TrajectoryStationPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/TubularComponentPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/TubularComponentPropertiesModal.tsx deleted file mode 100644 index f74e660e9..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/TubularComponentPropertiesModal.tsx +++ /dev/null @@ -1,357 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import ModalDialog from "components/Modals/ModalDialog"; -import { validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { isInteger } from "lodash"; -import { boxPinConfigTypes } from "models/boxPinConfigTypes"; -import ObjectReference from "models/jobs/objectReference"; -import { materialTypes } from "models/materialTypes"; -import MaxLength from "models/maxLength"; -import { toObjectReference } from "models/objectOnWellbore"; -import Tubular from "models/tubular"; -import TubularComponent from "models/tubularComponent"; -import { tubularComponentTypes } from "models/tubularComponentTypes"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface TubularComponentPropertiesModalInterface { - tubularComponent: TubularComponent; - tubular: Tubular; - dispatchOperation: (action: HideModalAction) => void; -} - -const isInvalidSequence = (sequence: number) => { - return Number.isNaN(sequence) || sequence < 1 || !isInteger(sequence); -}; - -const TubularComponentPropertiesModal = ( - props: TubularComponentPropertiesModalInterface -): React.ReactElement => { - const { tubularComponent, tubular, dispatchOperation } = props; - const [editableTubularComponent, setEditableTubularComponent] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - - const onSubmit = async (updatedTubularComponent: TubularComponent) => { - setIsLoading(true); - const tubularReference: ObjectReference = toObjectReference(tubular); - const modifyTubularComponentJob = { - tubularComponent: updatedTubularComponent, - tubularReference - }; - await JobService.orderJob( - JobType.ModifyTubularComponent, - modifyTubularComponentJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableTubularComponent(tubularComponent); - }, [tubularComponent]); - - return ( - <> - {editableTubularComponent && ( - - - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - sequence: parseFloat(e.target.value) - }) - } - variant={ - isInvalidSequence(editableTubularComponent.sequence) - ? "error" - : undefined - } - helperText={ - isInvalidSequence(editableTubularComponent.sequence) && - "Sequence must be a positive non-zero integer" - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - description: e.target.value - }) - } - variant={ - tubularComponent.description && - !validText( - editableTubularComponent.description, - 1, - MaxLength.Comment - ) - ? "error" - : undefined - } - helperText={ - tubularComponent.description && - !validText( - editableTubularComponent.description, - 1, - MaxLength.Comment - ) && - `Description must be 1-${MaxLength.Comment} characters` - } - /> - { - setEditableTubularComponent({ - ...editableTubularComponent, - typeTubularComponent: selectedItems[0] - }); - }} - hideClearButton={ - !!editableTubularComponent.typeTubularComponent - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - id: { - value: parseFloat(e.target.value), - uom: editableTubularComponent.id.uom - } - }) - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - od: { - value: parseFloat(e.target.value), - uom: editableTubularComponent.od.uom - } - }) - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - len: { - value: parseFloat(e.target.value), - uom: editableTubularComponent.len.uom - } - }) - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - wtPerLen: { - value: parseFloat(e.target.value), - uom: editableTubularComponent.wtPerLen.uom - } - }) - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - numJointStand: parseFloat(e.target.value) - }) - } - variant={ - Number.isNaN(editableTubularComponent.numJointStand) - ? "error" - : undefined - } - helperText={ - Number.isNaN(editableTubularComponent.numJointStand) && - "numJointStand must be a positive non-zero integer" - } - /> - { - setEditableTubularComponent({ - ...editableTubularComponent, - configCon: selectedItems[0] - }); - }} - hideClearButton={!!editableTubularComponent.configCon} - /> - { - setEditableTubularComponent({ - ...editableTubularComponent, - typeMaterial: selectedItems[0] - }); - }} - hideClearButton={!!editableTubularComponent.typeMaterial} - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - vendor: e.target.value - }) - } - variant={ - tubularComponent.vendor && - !validText(editableTubularComponent.vendor, 1, MaxLength.Name) - ? "error" - : undefined - } - helperText={ - tubularComponent.vendor && - !validText( - editableTubularComponent.vendor, - 1, - MaxLength.Name - ) && - `Vendor must be 1-${MaxLength.Name} characters` - } - /> - ) => - setEditableTubularComponent({ - ...editableTubularComponent, - model: e.target.value - }) - } - variant={ - tubularComponent.model && - !validText(editableTubularComponent.model, 1, MaxLength.Name) - ? "error" - : undefined - } - helperText={ - tubularComponent.model && - !validText( - editableTubularComponent.model, - 1, - MaxLength.Name - ) && - `Model must be 1-${MaxLength.Name} characters` - } - /> - - } - confirmDisabled={ - !validText(editableTubularComponent.typeTubularComponent) || - isInvalidSequence(editableTubularComponent.sequence) || - Number.isNaN(editableTubularComponent.numJointStand) || - Number.isNaN(editableTubularComponent.id.value) || - Number.isNaN(editableTubularComponent.od.value) || - Number.isNaN(editableTubularComponent.len.value) || - Number.isNaN(editableTubularComponent.wtPerLen.value) || - (tubularComponent.description && - !validText( - editableTubularComponent.description, - 1, - MaxLength.Comment - )) || - (tubularComponent.configCon && - !validText( - editableTubularComponent.configCon, - 1, - MaxLength.Enum - )) || - (tubularComponent.typeMaterial && - !validText( - editableTubularComponent.typeMaterial, - 1, - MaxLength.Enum - )) || - (tubularComponent.vendor && - !validText(editableTubularComponent.vendor, 1, MaxLength.Name)) || - (tubularComponent.model && - !validText(editableTubularComponent.model, 1, MaxLength.Name)) - } - onSubmit={() => onSubmit(editableTubularComponent)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default TubularComponentPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/TubularPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/TubularPropertiesModal.tsx deleted file mode 100644 index 0c8af0346..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/TubularPropertiesModal.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import ModalDialog from "components/Modals/ModalDialog"; -import { validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { ObjectType } from "models/objectType"; -import Tubular from "models/tubular"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -const typeTubularAssy = [ - "drilling", - "directional drilling", - "fishing", - "condition mud", - "tubing conveyed logging", - "cementing", - "casing", - "clean out", - "completion or testing", - "coring", - "hole opening or underreaming", - "milling or dressing or cutting", - "wiper or check or reaming", - "unknown" -]; - -export interface TubularPropertiesModalInterface { - tubular: Tubular; - dispatchOperation: (action: HideModalAction) => void; -} - -const TubularPropertiesModal = ( - props: TubularPropertiesModalInterface -): React.ReactElement => { - const { tubular, dispatchOperation } = props; - const [editableTubular, setEditableTubular] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - const onSubmit = async (updatedTubular: Tubular) => { - setIsLoading(true); - const wellboreTubularJob = { - object: { ...updatedTubular, objectType: ObjectType.Tubular }, - objectType: ObjectType.Tubular - }; - await JobService.orderJob( - JobType.ModifyObjectOnWellbore, - wellboreTubularJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableTubular(tubular); - }, [tubular]); - - return ( - <> - {editableTubular && ( - - - ) => - setEditableTubular({ - ...editableTubular, - name: e.target.value - }) - } - /> - { - setEditableTubular({ - ...editableTubular, - typeTubularAssy: selectedItems[0] - }); - }} - hideClearButton={true} - /> - - } - confirmDisabled={ - !validText(editableTubular.uid) || - !validText(editableTubular.name) || - !validText(editableTubular.typeTubularAssy) - } - onSubmit={() => onSubmit(editableTubular)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default TubularPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometryPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometryPropertiesModal.tsx deleted file mode 100644 index c968f8be1..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometryPropertiesModal.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import { itemStateTypes } from "models/itemStateTypes"; -import { ObjectType } from "models/objectType"; -import WbGeometryObject from "models/wbGeometry"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface WbGeometryPropertiesModalProps { - mode: PropertiesModalMode; - wbGeometryObject: WbGeometryObject; - dispatchOperation: (action: HideModalAction) => void; -} - -const WbGeometryPropertiesModal = ( - props: WbGeometryPropertiesModalProps -): React.ReactElement => { - const { mode, wbGeometryObject, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableWbGeometryObject, setEditableWbGeometryObject] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - const editMode = mode === PropertiesModalMode.Edit; - - useEffect(() => { - setEditableWbGeometryObject({ - ...wbGeometryObject, - dTimReport: formatDateString( - wbGeometryObject.dTimReport, - timeZone, - dateTimeFormat - ), - commonData: { - ...wbGeometryObject.commonData, - dTimCreation: formatDateString( - wbGeometryObject.commonData.dTimCreation, - timeZone, - dateTimeFormat - ), - dTimLastChange: formatDateString( - wbGeometryObject.commonData.dTimLastChange, - timeZone, - dateTimeFormat - ) - } - }); - }, [wbGeometryObject]); - - const onSubmit = async (updatedWbGeometry: WbGeometryObject) => { - setIsLoading(true); - const wellboreWbGeometryJob = { - object: { ...updatedWbGeometry, objectType: ObjectType.WbGeometry }, - objectType: ObjectType.WbGeometry - }; - await JobService.orderJob( - JobType.ModifyObjectOnWellbore, - wellboreWbGeometryJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - const validWbGeometryName = validText(editableWbGeometryObject?.name, 1, 64); - - return ( - <> - {editableWbGeometryObject && ( - - - - - - - - - - - ) => - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - name: e.target.value - }) - } - /> - ) => - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - mdBottom: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWbGeometryObject.mdBottom.uom - } - }) - } - /> - ) => - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - gapAir: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWbGeometryObject.gapAir.uom - } - }) - } - /> - ) => - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - depthWaterMean: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWbGeometryObject.depthWaterMean.uom - } - }) - } - /> - ) => { - const commonData = { - ...editableWbGeometryObject.commonData, - comments: e.target.value - }; - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - commonData - }); - }} - /> - { - const commonData = { - ...editableWbGeometryObject.commonData, - itemState: selectedItems[0] ?? null - }; - setEditableWbGeometryObject({ - ...editableWbGeometryObject, - commonData - }); - }} - /> - - } - confirmDisabled={!validWbGeometryName} - onSubmit={() => onSubmit(editableWbGeometryObject)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default WbGeometryPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometrySectionPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometrySectionPropertiesModal.tsx deleted file mode 100644 index 3a0de52ed..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/WbGeometrySectionPropertiesModal.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import ModalDialog from "components/Modals/ModalDialog"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { holeCasingTypes } from "models/holeCasingTypes"; -import ObjectReference from "models/jobs/objectReference"; -import Measure from "models/measure"; -import { toObjectReference } from "models/objectOnWellbore"; -import WbGeometryObject from "models/wbGeometry"; -import WbGeometrySection from "models/wbGeometrySection"; -import React, { - ChangeEvent, - Dispatch, - SetStateAction, - useEffect, - useState -} from "react"; -import JobService, { JobType } from "services/jobService"; -import styled from "styled-components"; - -export interface WbGeometrySectionPropertiesModalInterface { - wbGeometrySection: WbGeometrySection; - dispatchOperation: (action: HideModalAction) => void; - wbGeometry: WbGeometryObject; -} - -const WbGeometrySectionPropertiesModal = ( - props: WbGeometrySectionPropertiesModalInterface -): React.ReactElement => { - const { wbGeometrySection, dispatchOperation, wbGeometry } = props; - const [editableWbgs, setEditableWbGeometrySection] = - useState(null); - const [isLoading, setIsLoading] = useState(false); - - const onSubmit = async (updatedWbGeometrySection: WbGeometrySection) => { - setIsLoading(true); - const wbGeometryReference: ObjectReference = toObjectReference(wbGeometry); - const modifyWbGeometrySectionJob = { - wbGeometrySection: updatedWbGeometrySection, - wbGeometryReference - }; - await JobService.orderJob( - JobType.ModifyWbGeometrySection, - modifyWbGeometrySectionJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableWbGeometrySection(JSON.parse(JSON.stringify(wbGeometrySection))); //deep copy - }, [wbGeometrySection]); - - const invalidGrade = - errorOnDeletion(wbGeometrySection.grade, editableWbgs?.grade) || - editableWbgs?.grade?.length > 32; - const invalidMdTop = - wbGeometrySection.mdTop && editableWbgs?.mdTop.value == undefined; - const invalidMdBottom = - wbGeometrySection.mdBottom && editableWbgs?.mdBottom.value == undefined; - const invalidTvdTop = - wbGeometrySection.tvdTop && editableWbgs?.tvdTop.value == undefined; - const invalidTvdBottom = - wbGeometrySection.tvdBottom && editableWbgs?.tvdBottom.value == undefined; - const invalidIdSection = - wbGeometrySection.idSection && editableWbgs?.idSection.value == undefined; - const invalidOdSection = - wbGeometrySection.odSection && editableWbgs?.odSection.value == undefined; - const invalidWtPerLen = - wbGeometrySection.wtPerLen && editableWbgs?.wtPerLen.value == undefined; - const invalidDiaDrift = - wbGeometrySection.diaDrift && editableWbgs?.diaDrift.value == undefined; - const invalidFactFric = - wbGeometrySection.factFric != null && editableWbgs?.factFric == undefined; - return ( - <> - {editableWbgs && ( - - - { - setEditableWbGeometrySection({ - ...editableWbgs, - typeHoleCasing: selectedItems[0] - }); - }} - hideClearButton={!!wbGeometrySection.typeHoleCasing} - onFocus={(e) => e.preventDefault()} - /> - - - - - - - - { - setEditableWbGeometrySection({ - ...editableWbgs, - curveConductor: selectedItems[0] == "false" ? false : true - }); - }} - hideClearButton={wbGeometrySection.curveConductor != null} - /> - - ) => - setEditableWbGeometrySection({ - ...editableWbgs, - grade: e.target.value - }) - } - /> - ) => { - setEditableWbGeometrySection({ - ...editableWbgs, - factFric: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value) - }); - }} - /> - - } - confirmDisabled={ - invalidGrade || - invalidMdTop || - invalidMdBottom || - invalidTvdTop || - invalidTvdBottom || - invalidIdSection || - invalidOdSection || - invalidWtPerLen || - invalidDiaDrift || - invalidFactFric - } - onSubmit={() => onSubmit(editableWbgs)} - isLoading={isLoading} - /> - )} - - ); -}; - -interface MeasureFieldProps { - measure: Measure; - editableWbgs: WbGeometrySection; - invalid: boolean; - name: string; - setResult: Dispatch>; -} - -const MeasureField = (props: MeasureFieldProps): React.ReactElement => { - const { measure, editableWbgs, invalid, name, setResult } = props; - return ( - ) => { - measure.value = isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value); - setResult({ - ...editableWbgs - }); - }} - /> - ); -}; - -const errorOnDeletion = (original?: string, edited?: string): boolean => { - if (!original) { - return false; - } - return edited == null || edited.length == 0; -}; - -const Layout = styled.div` - display: grid; - grid-template-columns: repeat(2, auto); - gap: 1rem; -`; - -export default WbGeometrySectionPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WellPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WellPropertiesModal.tsx deleted file mode 100644 index d2eef21df..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/WellPropertiesModal.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import ModalDialog from "components/Modals/ModalDialog"; -import { - PropertiesModalMode, - validText, - validTimeZone -} from "components/Modals/ModalParts"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import Well from "models/well"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; - -export interface WellPropertiesModalProps { - mode: PropertiesModalMode; - well: Well; - dispatchOperation: (action: HideModalAction) => void; -} - -const WellPropertiesModal = ( - props: WellPropertiesModalProps -): React.ReactElement => { - const { mode, well, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableWell, setEditableWell] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const editMode = mode === PropertiesModalMode.Edit; - - const onSubmit = async (updatedWell: Well) => { - setIsLoading(true); - const wellJob = { - well: updatedWell - }; - await JobService.orderJob( - mode == PropertiesModalMode.New ? JobType.CreateWell : JobType.ModifyWell, - wellJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableWell(well); - }, [well]); - - const validWellUid = validText(editableWell?.uid, 1, 64); - const validWellName = validText(editableWell?.name, 1, 64); - const validWellField = validText(editableWell?.field, 0, 64); - const validWellCountry = validText(editableWell?.country, 0, 32); - const validWellOperator = validText(editableWell?.operator, 0, 64); - - return ( - <> - {editableWell && ( - - ) => - setEditableWell({ ...editableWell, uid: e.target.value }) - } - /> - ) => - setEditableWell({ ...editableWell, name: e.target.value }) - } - /> - ) => - setEditableWell({ ...editableWell, field: e.target.value }) - } - /> - ) => - setEditableWell({ ...editableWell, country: e.target.value }) - } - /> - ) => - setEditableWell({ ...editableWell, operator: e.target.value }) - } - /> - ) => - setEditableWell({ ...editableWell, timeZone: e.target.value }) - } - /> - - {mode !== PropertiesModalMode.New && ( - <> - - - - )} - - } - confirmDisabled={ - !validWellName || - !validWellUid || - !validWellField || - !validWellOperator || - !validWellCountry || - !validTimeZone(editableWell.timeZone) - } - onSubmit={() => onSubmit(editableWell)} - isLoading={isLoading} - /> - )} - - ); -}; - -export default WellPropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/WellborePropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/WellborePropertiesModal.tsx deleted file mode 100644 index 3dfa79a0c..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/WellborePropertiesModal.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import { Autocomplete, TextField } from "@equinor/eds-core-react"; -import formatDateString from "components/DateFormatter"; -import { DateTimeField } from "components/Modals/DateTimeField"; -import ModalDialog from "components/Modals/ModalDialog"; -import { PropertiesModalMode, validText } from "components/Modals/ModalParts"; -import { invalidStringInput } from "components/Modals/PropertiesModalUtils"; -import { HideModalAction } from "contexts/operationStateReducer"; -import OperationType from "contexts/operationType"; -import { useOperationState } from "hooks/useOperationState"; -import MaxLength from "models/maxLength"; -import Wellbore, { wellboreHasChanges } from "models/wellbore"; -import React, { ChangeEvent, useEffect, useState } from "react"; -import JobService, { JobType } from "services/jobService"; -import styled from "styled-components"; - -export interface WellborePropertiesModalProps { - mode: PropertiesModalMode; - wellbore: Wellbore; - dispatchOperation: (action: HideModalAction) => void; -} - -const purposeValues = [ - "appraisal", - "development", - "exploration", - "fluid storage", - "general srvc", - "mineral", - "unknown" -]; - -const WellborePropertiesModal = ( - props: WellborePropertiesModalProps -): React.ReactElement => { - const { mode, wellbore, dispatchOperation } = props; - const { - operationState: { timeZone, dateTimeFormat } - } = useOperationState(); - const [editableWellbore, setEditableWellbore] = useState(null); - const [pristineWellbore, setPristineWellbore] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [dTimeKickoffValid, setDTimeKickoffValid] = useState(true); - const editMode = mode === PropertiesModalMode.Edit; - - const onSubmit = async (updatedWellbore: Wellbore) => { - setIsLoading(true); - const wellboreJob = { - wellbore: updatedWellbore - }; - await JobService.orderJob( - mode == PropertiesModalMode.New - ? JobType.CreateWellbore - : JobType.ModifyWellbore, - wellboreJob - ); - setIsLoading(false); - dispatchOperation({ type: OperationType.HideModal }); - }; - - useEffect(() => { - setEditableWellbore({ - ...wellbore, - dTimeKickoff: formatDateString( - wellbore.dTimeKickoff, - timeZone, - dateTimeFormat - ) - }); - setPristineWellbore(wellbore); - }, [wellbore]); - - const validWellboreUid = validText(editableWellbore?.uid, 1, 64); - const validWellboreName = validText(editableWellbore?.name, 1, 64); - - return ( - <> - {editableWellbore && ( - - ) => - setEditableWellbore({ - ...editableWellbore, - uid: e.target.value - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - name: e.target.value - }) - } - /> - - - - { - setEditableWellbore({ - ...editableWellbore, - wellborePurpose: selectedItems[0] - }); - }} - /> - {mode == PropertiesModalMode.Edit && ( - <> - - ) => - setEditableWellbore({ - ...editableWellbore, - number: e.target.value - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - suffixAPI: e.target.value - }) - } - /> - - ) => - setEditableWellbore({ - ...editableWellbore, - numGovt: e.target.value - }) - } - /> - { - setEditableWellbore({ - ...editableWellbore, - dTimeKickoff: dateTime - }); - setDTimeKickoffValid(valid); - }} - timeZone={timeZone} - /> - - ) => - setEditableWellbore({ - ...editableWellbore, - md: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.md.uom - } - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - tvd: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.tvd.uom - } - }) - } - /> - - - ) => - setEditableWellbore({ - ...editableWellbore, - mdKickoff: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.mdKickoff.uom - } - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - tvdKickoff: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.tvdKickoff.uom - } - }) - } - /> - - - ) => - setEditableWellbore({ - ...editableWellbore, - mdPlanned: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.mdPlanned.uom - } - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - tvdPlanned: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.tvdPlanned.uom - } - }) - } - /> - - - ) => - setEditableWellbore({ - ...editableWellbore, - mdSubSeaPlanned: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.mdSubSeaPlanned.uom - } - }) - } - /> - ) => - setEditableWellbore({ - ...editableWellbore, - tvdSubSeaPlanned: { - value: isNaN(parseFloat(e.target.value)) - ? undefined - : parseFloat(e.target.value), - uom: editableWellbore.tvdSubSeaPlanned.uom - } - }) - } - /> - - ) => - setEditableWellbore({ - ...editableWellbore, - dayTarget: { - value: isNaN(parseInt(e.target.value)) - ? undefined - : parseInt(e.target.value), - uom: editableWellbore.dayTarget.uom - } - }) - } - /> - - - ) => { - setEditableWellbore({ - ...editableWellbore, - comments: e.target.value - }); - }} - /> - - )} - - } - confirmDisabled={ - !validWellboreUid || - !validWellboreName || - !dTimeKickoffValid || - !wellboreHasChanges(pristineWellbore, editableWellbore) - } - onSubmit={() => onSubmit(editableWellbore)} - isLoading={isLoading} - /> - )} - - ); -}; - -const Container = styled.div` - display: flex; -`; - -export default WellborePropertiesModal; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/__tests__/LogCurveInfoPropertiesModal.test.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/__tests__/LogCurveInfoPropertiesModal.test.tsx deleted file mode 100644 index 595b03ab5..000000000 --- a/Src/WitsmlExplorer.Frontend/components/Modals/__tests__/LogCurveInfoPropertiesModal.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { mockEdsCoreReact } from "__testUtils__/mocks/EDSMocks"; -import { - getAxisDefinition, - getLogCurveInfo, - getLogObject, - renderWithContexts -} from "__testUtils__/testUtils"; -import LogCurveInfoPropertiesModal, { - LogCurveInfoPropertiesModalProps -} from "components/Modals/LogCurveInfoPropertiesModal"; -import JobService from "services/jobService"; -import { vi } from "vitest"; - -vi.mock("services/jobService"); -vi.mock("@equinor/eds-core-react", () => mockEdsCoreReact()); - -const simpleProps: LogCurveInfoPropertiesModalProps = { - logCurveInfo: getLogCurveInfo(), - dispatchOperation: vi.fn(), - selectedLog: getLogObject() -}; - -const propsWithAxisDefinition: LogCurveInfoPropertiesModalProps = { - ...simpleProps, - logCurveInfo: getLogCurveInfo({ - axisDefinitions: [getAxisDefinition()] - }) -}; - -it("Properties of a LogCurve should be shown in the modal", async () => { - const expectedLogCurveInfo = simpleProps.logCurveInfo; - - renderWithContexts(); - - const uidInput = screen.getByRole("textbox", { name: /uid/i }); - const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); - - expect(uidInput).toHaveValue(expectedLogCurveInfo.uid); - expect(mnemonicInput).toHaveValue(expectedLogCurveInfo.mnemonic); - - expect(uidInput).toBeDisabled(); - expect(mnemonicInput).toBeEnabled(); -}); - -it("AxisDefinition should be shown readonly in the LogCurveInfo modal when included in the props", async () => { - const expectedLogCurveInfo = propsWithAxisDefinition.logCurveInfo; - const expectedAxisDefinition = expectedLogCurveInfo.axisDefinitions[0]; - - renderWithContexts( - - ); - - const uidInput = screen.getByRole("textbox", { name: /uid/i }); - const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); - const axisDefinitionLabel = screen.getByText(/axisdefinition/i); - const orderInput = screen.getByRole("textbox", { name: /order/i }); - const countInput = screen.getByRole("textbox", { name: /count/i }); - const doubleValuesInput = screen.getByRole("textbox", { - name: /doubleValues/i - }); - - expect(uidInput).toHaveValue(expectedLogCurveInfo.uid); - expect(mnemonicInput).toHaveValue(expectedLogCurveInfo.mnemonic); - expect(axisDefinitionLabel).toHaveTextContent(expectedAxisDefinition.uid); - expect(orderInput).toHaveValue(expectedAxisDefinition.order.toString()); - expect(countInput).toHaveValue(expectedAxisDefinition.count.toString()); - expect(doubleValuesInput).toHaveValue(expectedAxisDefinition.doubleValues); - - expect(uidInput).toBeDisabled(); - expect(mnemonicInput).toBeEnabled(); - expect(orderInput).toBeDisabled(); - expect(countInput).toBeDisabled(); - expect(doubleValuesInput).toBeDisabled(); -}); - -it("Saving edited properties of a LogCurve should result in the order of a job", async () => { - const mockedOrderJob = vi.fn(); - JobService.orderJob = mockedOrderJob; - - const user = userEvent.setup(); - - renderWithContexts(); - - const mnemonicInput = screen.getByRole("textbox", { name: /mnemonic/i }); - const saveButton = screen.getByRole("button", { name: /save/i }); - - await user.type(mnemonicInput, "editedMnemonic"); - - expect(saveButton).toBeEnabled(); - - await user.click(saveButton); - - expect(mockedOrderJob).toHaveBeenCalledTimes(1); -}); diff --git a/Src/WitsmlExplorer.Frontend/components/Sidebar/LogTypeItem.tsx b/Src/WitsmlExplorer.Frontend/components/Sidebar/LogTypeItem.tsx index 59051024e..79a0fe1c7 100644 --- a/Src/WitsmlExplorer.Frontend/components/Sidebar/LogTypeItem.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Sidebar/LogTypeItem.tsx @@ -1,4 +1,5 @@ import { + WITSML_INDEX_TYPE, WITSML_INDEX_TYPE_DATE_TIME, WITSML_INDEX_TYPE_MD } from "components/Constants"; @@ -10,7 +11,6 @@ import { import LogsContextMenu, { LogsContextMenuProps } from "components/ContextMenus/LogsContextMenu"; -import { IndexCurve } from "components/Modals/LogPropertiesModal"; import LogItem from "components/Sidebar/LogItem"; import TreeItem from "components/Sidebar/TreeItem"; import { useConnectedServer } from "contexts/connectedServerContext"; @@ -88,14 +88,14 @@ export default function LogTypeItem({ const onContextMenu = ( event: MouseEvent, wellbore: Wellbore, - indexCurve: IndexCurve + indexType: WITSML_INDEX_TYPE ) => { preventContextMenuPropagation(event); const contextMenuProps: LogsContextMenuProps = { dispatchOperation, wellbore, servers, - indexCurve + indexType }; const position = getContextMenuPosition(event); dispatchOperation({ @@ -255,7 +255,7 @@ export default function LogTypeItem({ nodeId={logTypeGroupDepth} to={getNavPath(logTypeGroupDepth)} onContextMenu={(event: MouseEvent) => - onContextMenu(event, wellbore, IndexCurve.Depth) + onContextMenu(event, wellbore, WITSML_INDEX_TYPE_MD) } isActive={depthLogs?.some((log) => log.objectGrowing)} selected={ @@ -279,7 +279,7 @@ export default function LogTypeItem({ labelText={"Time"} to={getNavPath(logTypeGroupTime)} onContextMenu={(event: MouseEvent) => - onContextMenu(event, wellbore, IndexCurve.Time) + onContextMenu(event, wellbore, WITSML_INDEX_TYPE_DATE_TIME) } isActive={timeLogs?.some((log) => log.objectGrowing)} selected={ diff --git a/Src/WitsmlExplorer.Frontend/models/bhaStatusTypes.ts b/Src/WitsmlExplorer.Frontend/models/bhaStatusTypes.ts new file mode 100644 index 000000000..f5c74a050 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/models/bhaStatusTypes.ts @@ -0,0 +1 @@ +export const bhaStatusTypes = ["final", "progress", "plan", "unknown"]; diff --git a/Src/WitsmlExplorer.Frontend/models/commonData.tsx b/Src/WitsmlExplorer.Frontend/models/commonData.tsx index 79da36fa8..625cb73f7 100644 --- a/Src/WitsmlExplorer.Frontend/models/commonData.tsx +++ b/Src/WitsmlExplorer.Frontend/models/commonData.tsx @@ -19,5 +19,3 @@ export function emptyCommonData(): CommonData { serviceCategory: "" }; } - -export const itemStateValues = ["actual", "model", "plan", "unknown"]; diff --git a/Src/WitsmlExplorer.Frontend/models/indexCurve.ts b/Src/WitsmlExplorer.Frontend/models/indexCurve.ts new file mode 100644 index 000000000..7df0be3f9 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/models/indexCurve.ts @@ -0,0 +1,4 @@ +export enum IndexCurve { + Depth = "Depth", + Time = "Time" +} diff --git a/Src/WitsmlExplorer.Frontend/models/levelIntegerCode.ts b/Src/WitsmlExplorer.Frontend/models/levelIntegerCode.ts new file mode 100644 index 000000000..ea7c29a8c --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/models/levelIntegerCode.ts @@ -0,0 +1 @@ +export const levelIntegerCode = ["1", "2", "3", "4", "5"]; diff --git a/Src/WitsmlExplorer.Frontend/models/typeTubularAssy.ts b/Src/WitsmlExplorer.Frontend/models/typeTubularAssy.ts new file mode 100644 index 000000000..913481927 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/models/typeTubularAssy.ts @@ -0,0 +1,16 @@ +export const typeTubularAssy = [ + "drilling", + "directional drilling", + "fishing", + "condition mud", + "tubing conveyed logging", + "cementing", + "casing", + "clean out", + "completion or testing", + "coring", + "hole opening or underreaming", + "milling or dressing or cutting", + "wiper or check or reaming", + "unknown" +]; diff --git a/Src/WitsmlExplorer.Frontend/models/wellbore.tsx b/Src/WitsmlExplorer.Frontend/models/wellbore.tsx index 2848ab7f4..8e6403993 100644 --- a/Src/WitsmlExplorer.Frontend/models/wellbore.tsx +++ b/Src/WitsmlExplorer.Frontend/models/wellbore.tsx @@ -26,8 +26,8 @@ export interface WellboreProperties { name: string; wellUid: string; wellName?: string; - wellStatus: string; - wellType: string; + wellboreStatus: string; + wellboreType: string; isActive: boolean; number?: string; suffixAPI?: string; @@ -78,8 +78,8 @@ export function emptyWellbore(): Wellbore { name: "", wellUid: "", wellName: "", - wellStatus: "", - wellType: "", + wellboreStatus: "", + wellboreType: "", isActive: false, wellboreParentUid: "", wellboreParentName: "", diff --git a/Src/WitsmlExplorer.Frontend/models/wellborePurposeValues.ts b/Src/WitsmlExplorer.Frontend/models/wellborePurposeValues.ts new file mode 100644 index 000000000..70391ee2d --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/models/wellborePurposeValues.ts @@ -0,0 +1,9 @@ +export const wellborePurposeValues = [ + "appraisal", + "development", + "exploration", + "fluid storage", + "general srvc", + "mineral", + "unknown" +]; diff --git a/Src/WitsmlExplorer.Frontend/services/jobService.tsx b/Src/WitsmlExplorer.Frontend/services/jobService.tsx index d2a569de7..2a76123b0 100644 --- a/Src/WitsmlExplorer.Frontend/services/jobService.tsx +++ b/Src/WitsmlExplorer.Frontend/services/jobService.tsx @@ -139,9 +139,7 @@ export enum JobType { CopyWithParent = "CopyWithParent", CopyObjectsWithParent = "CopyObjectsWithParent", CreateWellbore = "CreateWellbore", - CreateLogObject = "CreateLogObject", - CreateRig = "CreateRig", - CreateTrajectory = "CreateTrajectory", + CreateObjectOnWellbore = "CreateObjectOnWellbore", DeleteComponents = "DeleteComponents", DeleteCurveValues = "DeleteCurveValues", DeleteObjects = "DeleteObjects", diff --git a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateLogWorkerTests.cs b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateLogWorkerTests.cs index 1be31ff3b..5182ab463 100644 --- a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateLogWorkerTests.cs +++ b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateLogWorkerTests.cs @@ -30,7 +30,7 @@ public class CreateLogWorkerTests private const string WellboreUid = "B-5209671"; private const string WellboreName = "NO 34/10-A-25 C - Main Wellbore"; private readonly Mock _witsmlClient; - private readonly CreateLogWorker _worker; + private readonly CreateObjectOnWellboreWorker _worker; public CreateLogWorkerTests() { @@ -39,19 +39,18 @@ public CreateLogWorkerTests() witsmlClientProvider.Setup(provider => provider.GetClient()).Returns(_witsmlClient.Object); ILoggerFactory loggerFactory = new LoggerFactory(); loggerFactory.AddSerilog(Log.Logger); - ILogger logger = loggerFactory.CreateLogger(); - _worker = new CreateLogWorker(logger, witsmlClientProvider.Object); + ILogger logger = loggerFactory.CreateLogger(); + _worker = new CreateObjectOnWellboreWorker(logger, witsmlClientProvider.Object); } [Fact] public async Task CreateDepthIndexedLog_OK() { - CreateLogJob job = CreateJobTemplate(); - SetupGetWellbore(); + CreateObjectOnWellboreJob job = CreateJobTemplate(WitsmlLog.WITSML_INDEX_TYPE_MD); List createdLogs = new(); _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(createdLogs.Add) + client.AddToStoreAsync(It.IsAny())) + .Callback(logs => createdLogs.Add(logs as WitsmlLogs)) .ReturnsAsync(new QueryResult(true)); await _worker.Execute(job); @@ -73,12 +72,11 @@ public async Task CreateDepthIndexedLog_OK() [Fact] public async Task CreateTimeIndexedLog_OK() { - CreateLogJob job = CreateJobTemplate("Time"); - SetupGetWellbore(); + CreateObjectOnWellboreJob job = CreateJobTemplate(WitsmlLog.WITSML_INDEX_TYPE_DATE_TIME); List createdLogs = new(); _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(createdLogs.Add) + client.AddToStoreAsync(It.IsAny())) + .Callback(logs => createdLogs.Add(logs as WitsmlLogs)) .ReturnsAsync(new QueryResult(true)); await _worker.Execute(job); @@ -97,56 +95,23 @@ public async Task CreateTimeIndexedLog_OK() Assert.Equal(CommonConstants.Unit.Second, indexLogCurve.Unit); } - [Fact] - public async Task CreateTimeIndexedLog_UseTimeAsIndexIfUnknown() - { - CreateLogJob job = CreateJobTemplate("strange"); - SetupGetWellbore(); - List createdLogs = new(); - _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(createdLogs.Add) - .ReturnsAsync(new QueryResult(true)); - - await _worker.Execute(job); - WitsmlLog createdLog = createdLogs.First().Logs.First(); - WitsmlLogCurveInfo indexLogCurve = createdLog.LogCurveInfo.First(); - Assert.Equal("Time", indexLogCurve.Mnemonic); - Assert.Equal(CommonConstants.Unit.Second, indexLogCurve.Unit); - } - - private static CreateLogJob CreateJobTemplate(string indexCurve = "Depth") + private static CreateObjectOnWellboreJob CreateJobTemplate(string indexType) { - return new CreateLogJob + return new CreateObjectOnWellboreJob { - LogObject = new LogObject + Object = new LogObject { Uid = LogUid, Name = LogName, WellUid = WellUid, + WellName = WellName, WellboreUid = WellboreUid, - IndexCurve = indexCurve - } + WellboreName = WellboreName, + IndexCurve = indexType == WitsmlLog.WITSML_INDEX_TYPE_MD ? "Depth" : "Time", + IndexType = indexType + }, + ObjectType = EntityType.Log }; } - - private void SetupGetWellbore() - { - _witsmlClient.Setup(client => - client.GetFromStoreAsync(It.IsAny(), It.Is((ops) => ops.ReturnElements == ReturnElements.Requested))) - .ReturnsAsync(new WitsmlWellbores - { - Wellbores = new List - { - new WitsmlWellbore - { - UidWell = WellUid, - Uid = WellboreUid, - Name = WellboreName, - NameWell = WellName - } - } - }); - } } } diff --git a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRigWorkerTests.cs b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRigWorkerTests.cs index 7ebdff046..5f99079e1 100644 --- a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRigWorkerTests.cs +++ b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRigWorkerTests.cs @@ -10,6 +10,7 @@ using Serilog; using Witsml; +using Witsml.Data; using Witsml.Data.Rig; using WitsmlExplorer.Api.Jobs; @@ -33,7 +34,7 @@ public class CreateRigWorkerTests private const string WellboreUid = "wellboreUid"; private readonly Mock _witsmlClient; - private readonly CreateRigWorker _worker; + private readonly CreateObjectOnWellboreWorker _worker; public CreateRigWorkerTests() { @@ -42,31 +43,35 @@ public CreateRigWorkerTests() witsmlClientProvider.Setup(provider => provider.GetClient()).Returns(_witsmlClient.Object); ILoggerFactory loggerFactory = new LoggerFactory(); loggerFactory.AddSerilog(Log.Logger); - ILogger logger = loggerFactory.CreateLogger(); - _worker = new CreateRigWorker(logger, witsmlClientProvider.Object); + ILogger logger = loggerFactory.CreateLogger(); + _worker = new CreateObjectOnWellboreWorker(logger, witsmlClientProvider.Object); } [Fact] public async Task CreateRig_Execute_MissingUid_InvalidOperationException() { - CreateRigJob job = CreateJobTemplate(uid: null); - InvalidOperationException exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Uid cannot be empty", exception.Message); + CreateObjectOnWellboreJob job = CreateJobTemplate(uid: null); + var (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Uid cannot be null", workerResult.Message); job = CreateJobTemplate(uid: string.Empty); - exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Uid cannot be empty", exception.Message); + (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Uid cannot be empty", workerResult.Message); _witsmlClient.Verify(client => client.AddToStoreAsync(It.IsAny()), Times.Never); } [Fact] public async Task CreateRig_Execute_MissingName_InvalidOperationException() { - CreateRigJob job = CreateJobTemplate(name: null); - InvalidOperationException exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Name cannot be empty", exception.Message); + CreateObjectOnWellboreJob job = CreateJobTemplate(name: null); + var (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Name cannot be null", workerResult.Message); job = CreateJobTemplate(name: string.Empty); - exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Name cannot be empty", exception.Message); + (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Name cannot be empty", workerResult.Message); _witsmlClient.Verify(client => client.AddToStoreAsync(It.IsAny()), Times.Never); } @@ -74,12 +79,12 @@ public async Task CreateRig_Execute_MissingName_InvalidOperationException() [Fact] public async Task CreateRig_Execute_ValidResults() { - CreateRigJob job = CreateJobTemplate(); + CreateObjectOnWellboreJob job = CreateJobTemplate(); List createdRigs = new(); _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(rig => createdRigs.Add(rig)) + client.AddToStoreAsync(It.IsAny())) + .Callback(rig => createdRigs.Add(rig as WitsmlRigs)) .ReturnsAsync(new QueryResult(true)); await _worker.Execute(job); @@ -94,11 +99,11 @@ public async Task CreateRig_Execute_ValidResults() Assert.Equal(WellboreUid, createdRig.UidWellbore); } - private static CreateRigJob CreateJobTemplate(string uid = Uid, string name = Name, string wellUid = WellUid, string wellName = WellName, string wellboreUid = WellboreUid) + private static CreateObjectOnWellboreJob CreateJobTemplate(string uid = Uid, string name = Name, string wellUid = WellUid, string wellName = WellName, string wellboreUid = WellboreUid) { - return new CreateRigJob + return new CreateObjectOnWellboreJob { - Rig = new Rig() + Object = new Rig() { Uid = uid, Name = name, @@ -110,7 +115,8 @@ private static CreateRigJob CreateJobTemplate(string uid = Uid, string name = Na ItemState = "plan", SourceName = "sourceName" } - } + }, + ObjectType = EntityType.Rig }; } } diff --git a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRiskWorkerTests.cs b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRiskWorkerTests.cs index ec9abc87e..7f0077df1 100644 --- a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRiskWorkerTests.cs +++ b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateRiskWorkerTests.cs @@ -26,9 +26,11 @@ public class CreateRiskWorkerTests private const string WellUid = "wellUid"; private const string WellName = "wellName"; private const string WellboreUid = "wellboreUid"; + private const string WellboreName = "wellboreName"; + private const string Uid = "riskUid"; private const string Name = "riskname"; private readonly Mock _witsmlClient; - private readonly CreateRiskWorker _worker; + private readonly CreateObjectOnWellboreWorker _worker; public CreateRiskWorkerTests() { @@ -37,22 +39,20 @@ public CreateRiskWorkerTests() witsmlClientProvider.Setup(provider => provider.GetClient()).Returns(_witsmlClient.Object); ILoggerFactory loggerFactory = new LoggerFactory(); loggerFactory.AddSerilog(Log.Logger); - ILogger logger = loggerFactory.CreateLogger(); - _worker = new CreateRiskWorker(logger, witsmlClientProvider.Object); + ILogger logger = loggerFactory.CreateLogger(); + _worker = new CreateObjectOnWellboreWorker(logger, witsmlClientProvider.Object); } [Fact] - public async Task ValidCreateRiskJobExecute() + public async Task ValidCreateObjectOnWellboreJobExecute() { - CreateRiskJob job = CreateJobTemplate(); + CreateObjectOnWellboreJob job = CreateJobTemplate(); List createdRisks = new(); _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(risk => createdRisks.Add(risk)) + client.AddToStoreAsync(It.IsAny())) + .Callback(risk => createdRisks.Add(risk as WitsmlRisks)) .ReturnsAsync(new QueryResult(true)); - _witsmlClient.Setup(client => client.GetFromStoreAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new WitsmlRisks() { Risks = new List() { new WitsmlRisk() } }); await _worker.Execute(job); @@ -64,23 +64,25 @@ public async Task ValidCreateRiskJobExecute() Assert.Equal(WellName, createdRisk.NameWell); } - private static CreateRiskJob CreateJobTemplate(string uid = WellboreUid, string name = Name, - string wellUid = WellUid, string wellName = WellName) + private static CreateObjectOnWellboreJob CreateJobTemplate() { - return new CreateRiskJob + return new CreateObjectOnWellboreJob { - Risk = new Risk + Object = new Risk { - Uid = uid, - Name = name, - WellUid = wellUid, - WellName = wellName, + Uid = Uid, + Name = Name, + WellUid = WellUid, + WellName = WellName, + WellboreUid = WellboreUid, + WellboreName = WellboreName, CommonData = new CommonData { ItemState = "model", SourceName = "SourceName" } - } + }, + ObjectType = EntityType.Risk }; } } diff --git a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateTrajectoryWorkerTests.cs b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateTrajectoryWorkerTests.cs index e4d044de4..9401b97fa 100644 --- a/Tests/WitsmlExplorer.Api.Tests/Workers/CreateTrajectoryWorkerTests.cs +++ b/Tests/WitsmlExplorer.Api.Tests/Workers/CreateTrajectoryWorkerTests.cs @@ -33,7 +33,7 @@ public class CreateTrajectoryWorkerTests private const string WellboreUid = "wellboreUid"; private readonly Mock _witsmlClient; - private readonly CreateTrajectoryWorker _worker; + private readonly CreateObjectOnWellboreWorker _worker; public CreateTrajectoryWorkerTests() { @@ -42,31 +42,35 @@ public CreateTrajectoryWorkerTests() witsmlClientProvider.Setup(provider => provider.GetClient()).Returns(_witsmlClient.Object); ILoggerFactory loggerFactory = new LoggerFactory(); loggerFactory.AddSerilog(Log.Logger); - ILogger logger = loggerFactory.CreateLogger(); - _worker = new CreateTrajectoryWorker(logger, witsmlClientProvider.Object); + ILogger logger = loggerFactory.CreateLogger(); + _worker = new CreateObjectOnWellboreWorker(logger, witsmlClientProvider.Object); } [Fact] public async Task CreateTrajectory_Execute_MissingUid_InvalidOperationException() { - var job = CreateJobTemplate(uid: null); - InvalidOperationException exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Uid cannot be empty", exception.Message); - job = CreateJobTemplate(uid: ""); - exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Uid cannot be empty", exception.Message); + CreateObjectOnWellboreJob job = CreateJobTemplate(uid: null); + var (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Uid cannot be null", workerResult.Message); + job = CreateJobTemplate(uid: string.Empty); + (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Uid cannot be empty", workerResult.Message); _witsmlClient.Verify(client => client.AddToStoreAsync(It.IsAny()), Times.Never); } [Fact] public async Task CreateTrajectory_Execute_MissingName_InvalidOperationException() { - var job = CreateJobTemplate(name: null); - InvalidOperationException exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Name cannot be empty", exception.Message); + CreateObjectOnWellboreJob job = CreateJobTemplate(name: null); + var (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Name cannot be null", workerResult.Message); job = CreateJobTemplate(name: string.Empty); - exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); - Assert.Equal("Name cannot be empty", exception.Message); + (workerResult, _) = await _worker.Execute(job); + Assert.False(workerResult.IsSuccess); + Assert.Equal("Name cannot be empty", workerResult.Message); _witsmlClient.Verify(client => client.AddToStoreAsync(It.IsAny()), Times.Never); } @@ -74,12 +78,12 @@ public async Task CreateTrajectory_Execute_MissingName_InvalidOperationException [Fact] public async Task CreateTrajectory_Execute_ValidResults() { - CreateTrajectoryJob job = CreateJobTemplate(); + CreateObjectOnWellboreJob job = CreateJobTemplate(); List createdTrajectories = new(); _witsmlClient.Setup(client => - client.AddToStoreAsync(It.IsAny())) - .Callback(trajectory => createdTrajectories.Add(trajectory)) + client.AddToStoreAsync(It.IsAny())) + .Callback(trajectory => createdTrajectories.Add(trajectory as WitsmlTrajectories)) .ReturnsAsync(new QueryResult(true)); await _worker.Execute(job); @@ -94,18 +98,19 @@ public async Task CreateTrajectory_Execute_ValidResults() Assert.Equal(WellboreUid, createdObject.UidWellbore); } - private static CreateTrajectoryJob CreateJobTemplate(string uid = Uid, string name = Name, string wellUid = WellUid, string wellName = WellName, string wellboreUid = WellboreUid) + private static CreateObjectOnWellboreJob CreateJobTemplate(string uid = Uid, string name = Name, string wellUid = WellUid, string wellName = WellName, string wellboreUid = WellboreUid) { - return new CreateTrajectoryJob + return new CreateObjectOnWellboreJob { - Trajectory = new Trajectory() + Object = new Trajectory() { Uid = uid, Name = name, WellUid = wellUid, WellName = wellName, WellboreUid = wellboreUid - } + }, + ObjectType = EntityType.Trajectory }; } } diff --git a/Tests/WitsmlExplorer.IntegrationTests/Api/Workers/CreateLogWorkerTests.cs b/Tests/WitsmlExplorer.IntegrationTests/Api/Workers/CreateLogWorkerTests.cs index 96370bb31..d25b3397d 100644 --- a/Tests/WitsmlExplorer.IntegrationTests/Api/Workers/CreateLogWorkerTests.cs +++ b/Tests/WitsmlExplorer.IntegrationTests/Api/Workers/CreateLogWorkerTests.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -9,7 +8,6 @@ using WitsmlExplorer.Api.Jobs; using WitsmlExplorer.Api.Models; using WitsmlExplorer.Api.Services; -using WitsmlExplorer.Api.Workers; using WitsmlExplorer.Api.Workers.Create; using Xunit; @@ -18,7 +16,7 @@ namespace WitsmlExplorer.IntegrationTests.Api.Workers { public class CreateLogWorkerTests { - private readonly CreateLogWorker _worker; + private readonly CreateObjectOnWellboreWorker _worker; private static readonly string WELL_UID = "fa53698b-0a19-4f02-bca5-001f5c31c0ca"; private static readonly string WELLBORE_UID = "eea43bf8-e3b7-42b6-b328-21b34cb505eb"; @@ -28,23 +26,24 @@ public CreateLogWorkerTests() var witsmlClientProvider = new WitsmlClientProvider(configuration); var loggerFactory = (ILoggerFactory)new LoggerFactory(); loggerFactory.AddSerilog(Log.Logger); - var logger = loggerFactory.CreateLogger(); - _worker = new CreateLogWorker(logger, witsmlClientProvider); + var logger = loggerFactory.CreateLogger(); + _worker = new CreateObjectOnWellboreWorker(logger, witsmlClientProvider); } [Fact(Skip = "Should only be run manually")] public async Task CreateLog_DepthIndexed() { - var job = new CreateLogJob + var job = new CreateObjectOnWellboreJob { - LogObject = new LogObject + Object = new LogObject { Uid = Guid.NewGuid().ToString(), Name = "Test depth", WellUid = WELL_UID, WellboreUid = WELLBORE_UID, IndexCurve = "Depth" - } + }, + ObjectType = EntityType.Log }; await _worker.Execute(job); @@ -53,16 +52,17 @@ public async Task CreateLog_DepthIndexed() [Fact(Skip = "Should only be run manually")] public async Task CreateLog_TimeIndexed() { - var job = new CreateLogJob + var job = new CreateObjectOnWellboreJob { - LogObject = new LogObject + Object = new LogObject { Uid = Guid.NewGuid().ToString(), Name = "Test time", WellUid = WELL_UID, WellboreUid = WELLBORE_UID, IndexCurve = "Time" - } + }, + ObjectType = EntityType.Log }; await _worker.Execute(job);