From dfc11d2b96fc6790aef86d33eb4e63dcf3665c84 Mon Sep 17 00:00:00 2001 From: jaroslavbliznak Date: Tue, 27 Jun 2023 09:03:02 +0200 Subject: [PATCH] #1938 Added Create rig functionality --- Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs | 50 ++++++++ Src/WitsmlExplorer.Api/Models/JobType.cs | 1 + Src/WitsmlExplorer.Api/Query/RigQueries.cs | 12 +- .../Workers/Create/CreateRigWorker.cs | 63 ++++++++++ .../ContextMenus/RigsContextMenu.tsx | 66 ++++++++++ .../components/Modals/RigPropertiesModal.tsx | 23 +++- .../components/Sidebar/WellboreItem.tsx | 10 +- .../services/jobService.tsx | 1 + .../Workers/CreateRigWorkerTests.cs | 116 ++++++++++++++++++ 9 files changed, 329 insertions(+), 13 deletions(-) create mode 100644 Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs create mode 100644 Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs create mode 100644 Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx create mode 100644 Tests/WitsmlExplorer.Api.Tests/Workers/CreateRigWorkerTests.cs diff --git a/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs b/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs new file mode 100644 index 000000000..b2b29320a --- /dev/null +++ b/Src/WitsmlExplorer.Api/Jobs/CreateRigJob.cs @@ -0,0 +1,50 @@ +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/Models/JobType.cs b/Src/WitsmlExplorer.Api/Models/JobType.cs index 2a828b289..d8c65a4ed 100644 --- a/Src/WitsmlExplorer.Api/Models/JobType.cs +++ b/Src/WitsmlExplorer.Api/Models/JobType.cs @@ -36,6 +36,7 @@ public enum JobType CreateWellbore, CreateRisk, CreateMudLog, + CreateRig, CreateWbGeometry, BatchModifyWell, ImportLogData, diff --git a/Src/WitsmlExplorer.Api/Query/RigQueries.cs b/Src/WitsmlExplorer.Api/Query/RigQueries.cs index d34237c7a..306d9f999 100644 --- a/Src/WitsmlExplorer.Api/Query/RigQueries.cs +++ b/Src/WitsmlExplorer.Api/Query/RigQueries.cs @@ -76,23 +76,23 @@ public static WitsmlRigs CreateRig(Rig rig) AirGap = rig.AirGap != null ? new WitsmlLengthMeasure { Uom = rig.AirGap.Uom, Value = rig.AirGap.Value.ToString(CultureInfo.InvariantCulture) } : null, Name = rig.Name, TypeRig = rig.TypeRig, - Owner = rig.Owner, + Owner = rig.Owner.NullIfEmpty(), UidWellbore = rig.WellboreUid, - Approvals = rig.Approvals, - ClassRig = rig.ClassRig, + Approvals = rig.Approvals.NullIfEmpty(), + ClassRig = rig.ClassRig.NullIfEmpty(), DTimStartOp = StringHelpers.ToUniversalDateTimeString(rig.DTimStartOp), DTimEndOp = StringHelpers.ToUniversalDateTimeString(rig.DTimEndOp), EmailAddress = rig.EmailAddress, FaxNumber = rig.FaxNumber, IsOffshore = StringHelpers.OptionalBooleanToString(rig.IsOffshore), - Manufacturer = rig.Manufacturer, + Manufacturer = rig.Manufacturer.NullIfEmpty(), NameContact = rig.NameContact, RatingDrillDepth = rig.RatingDrillDepth != null ? new WitsmlLengthMeasure { Uom = rig.RatingDrillDepth.Uom, Value = rig.RatingDrillDepth.Value.ToString(CultureInfo.InvariantCulture) } : null, RatingWaterDepth = rig.RatingWaterDepth != null ? new WitsmlLengthMeasure { Uom = rig.RatingWaterDepth.Uom, Value = rig.RatingWaterDepth.Value.ToString(CultureInfo.InvariantCulture) } : null, - Registration = rig.Registration, + Registration = rig.Registration.NullIfEmpty(), TelNumber = rig.TelNumber, YearEntService = rig.YearEntService, - CommonData = new WitsmlCommonData() + CommonData = rig.CommonData == null ? null : new WitsmlCommonData() { SourceName = rig.CommonData.SourceName, DTimCreation = null, diff --git a/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs b/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs new file mode 100644 index 000000000..4e68e69e8 --- /dev/null +++ b/Src/WitsmlExplorer.Api/Workers/Create/CreateRigWorker.cs @@ -0,0 +1,63 @@ +using System; +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.Query; +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) + { + Verify(job.Rig); + + WitsmlRigs rigToCreate = RigQueries.CreateRig(job.Rig); + + 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.Frontend/components/ContextMenus/RigsContextMenu.tsx b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx new file mode 100644 index 000000000..466f64f38 --- /dev/null +++ b/Src/WitsmlExplorer.Frontend/components/ContextMenus/RigsContextMenu.tsx @@ -0,0 +1,66 @@ +import { Typography } from "@equinor/eds-core-react"; +import { MenuItem } from "@material-ui/core"; +import React from "react"; +import { v4 as uuid } from "uuid"; +import { DisplayModalAction, HideContextMenuAction, HideModalAction } from "../../contexts/operationStateReducer"; +import OperationType from "../../contexts/operationType"; +import Wellbore from "../../models/wellbore"; +import { colors } from "../../styles/Colors"; +import { PropertiesModalMode } from "../Modals/ModalParts"; +import ContextMenu from "./ContextMenu"; +import { StyledIcon } from "./ContextMenuUtils"; +import Rig from "../../models/rig"; +import RigPropertiesModal, { RigPropertiesModalProps } from "../Modals/RigPropertiesModal"; + +export interface RigsContextMenuProps { + dispatchOperation: (action: DisplayModalAction | HideModalAction | HideContextMenuAction) => void; + wellbore: Wellbore; +} + +const RigsContextMenu = (props: RigsContextMenuProps): React.ReactElement => { + const { dispatchOperation, wellbore } = props; + + const onClickNewRig = () => { + const newRig: Rig = { + uid: uuid(), + name: "", + wellUid: wellbore.wellUid, + wellName: wellbore.wellName, + wellboreUid: wellbore.uid, + wellboreName: wellbore.name, + airGap: null, + approvals: "", + commonData: null, + classRig: "", + dTimEndOp: "", + dTimStartOp: "", + emailAddress: "", + faxNumber: "", + manufacturer: "", + nameContact: "", + owner: "", + ratingDrillDepth: null, + ratingWaterDepth: null, + registration: "", + telNumber: "", + typeRig: "unknown", + yearEntService: null + }; + const rigPropertiesModalProps: RigPropertiesModalProps = { mode: PropertiesModalMode.New, rig: newRig, dispatchOperation }; + const action: DisplayModalAction = { type: OperationType.DisplayModal, payload: }; + dispatchOperation(action); + }; + + return ( + + + New Rig + + ]} + /> + ); +}; + +export default RigsContextMenu; diff --git a/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx b/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx index 51315d261..2c6d87c69 100644 --- a/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Modals/RigPropertiesModal.tsx @@ -40,7 +40,7 @@ const RigPropertiesModal = (props: RigPropertiesModalProps): React.ReactElement const wellboreRigJob = { rig: updatedRig }; - await JobService.orderJob(JobType.ModifyRig, wellboreRigJob); + await JobService.orderJob(editMode ? JobType.ModifyRig : JobType.CreateRig, wellboreRigJob); setIsLoading(false); dispatchOperation({ type: OperationType.HideModal }); }; @@ -51,14 +51,25 @@ const RigPropertiesModal = (props: RigPropertiesModalProps): React.ReactElement <> {editableRig && ( + setEditableRig({ ...editableRig, uid: e.target.value })} + /> - setEditableRig({ ...editableRig, nameContact: e.target.value })} /> } - confirmDisabled={!validText(editableRig.name) || !dTimStartOpValid || !dTimEndOpValid || !yearEntServiceValid} + confirmDisabled={!validText(editableRig.name) || !dTimStartOpValid || !dTimEndOpValid} onSubmit={() => onSubmit(editableRig)} isLoading={isLoading} /> diff --git a/Src/WitsmlExplorer.Frontend/components/Sidebar/WellboreItem.tsx b/Src/WitsmlExplorer.Frontend/components/Sidebar/WellboreItem.tsx index 6d30b841a..655b8a8e8 100644 --- a/Src/WitsmlExplorer.Frontend/components/Sidebar/WellboreItem.tsx +++ b/Src/WitsmlExplorer.Frontend/components/Sidebar/WellboreItem.tsx @@ -14,6 +14,7 @@ import ObjectService from "../../services/objectService"; import { getContextMenuPosition, preventContextMenuPropagation } from "../ContextMenus/ContextMenu"; import FluidsReportContextMenu from "../ContextMenus/FluidsReportContextMenu"; import LogsContextMenu, { LogsContextMenuProps } from "../ContextMenus/LogsContextMenu"; +import RigsContextMenu, { RigsContextMenuProps } from "../ContextMenus/RigsContextMenu"; import MudLogContextMenu from "../ContextMenus/MudLogContextMenu"; import TrajectoryContextMenu from "../ContextMenus/TrajectoryContextMenu"; import TubularContextMenu from "../ContextMenus/TubularContextMenu"; @@ -63,6 +64,13 @@ const WellboreItem = (props: WellboreItemProps): React.ReactElement => { dispatchOperation({ type: OperationType.DisplayContextMenu, payload: { component: , position } }); }; + const onRigsContextMenu = (event: React.MouseEvent, wellbore: Wellbore) => { + preventContextMenuPropagation(event); + const contextMenuProps: RigsContextMenuProps = { dispatchOperation, wellbore }; + const position = getContextMenuPosition(event); + dispatchOperation({ type: OperationType.DisplayContextMenu, payload: { component: , position } }); + }; + const onTubularsContextMenu = (event: React.MouseEvent, wellbore: Wellbore) => { preventContextMenuPropagation(event); const contextMenuProps: TubularsContextMenuProps = { dispatchNavigation, dispatchOperation, wellbore, servers }; @@ -117,7 +125,7 @@ const WellboreItem = (props: WellboreItemProps): React.ReactElement => { - + onRigsContextMenu(event, wellbore)} /> +/// Create new rig tests. +/// +public class CreateRigWorkerTests +{ + private const string Uid = "newRigUid"; + private const string Name = "newRigName"; + private const string WellUid = "wellUid"; + private const string WellName = "wellName"; + private const string WellboreUid = "wellboreUid"; + + private readonly Mock _witsmlClient; + private readonly CreateRigWorker _worker; + + public CreateRigWorkerTests() + { + Mock witsmlClientProvider = new(); + _witsmlClient = new Mock(); + 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); + } + + [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); + job = CreateJobTemplate(uid: ""); + exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); + Assert.Equal("Uid cannot be empty", exception.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); + job = CreateJobTemplate(name: ""); + exception = await Assert.ThrowsAsync(() => _worker.Execute(job)); + Assert.Equal("Name cannot be empty", exception.Message); + _witsmlClient.Verify(client => client.AddToStoreAsync(It.IsAny()), Times.Never); + } + + + [Fact] + public async Task CreateRig_Execute_ValidResults() + { + CreateRigJob job = CreateJobTemplate(); + List createdRigs = new(); + + _witsmlClient.Setup(client => + client.AddToStoreAsync(It.IsAny())) + .Callback(rig => createdRigs.Add(rig)) + .ReturnsAsync(new QueryResult(true)); + + await _worker.Execute(job); + + Assert.Single(createdRigs); + Assert.Single(createdRigs.First().Rigs); + WitsmlRig createdRig = createdRigs.First().Rigs.First(); + Assert.Equal(Uid, createdRig.Uid); + Assert.Equal(Name, createdRig.Name); + Assert.Equal(WellUid, createdRig.UidWell); + Assert.Equal(WellName, createdRig.NameWell); + Assert.Equal(WellboreUid, createdRig.UidWellbore); + } + + private static CreateRigJob CreateJobTemplate(string uid = Uid, string name = Name, string wellUid = WellUid, string wellName = WellName, string wellboreUid = WellboreUid) + { + return new CreateRigJob + { + Rig = new Rig() + { + Uid = uid, + Name = name, + WellUid = wellUid, + WellName = wellName, + WellboreUid = wellboreUid, + CommonData = new CommonData + { + ItemState = "plan", + SourceName = "sourceName" + } + } + }; + } +}