Skip to content

Copy whole wellbore - re-running failed subjobs #2729 #2748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

171 changes: 139 additions & 32 deletions Src/WitsmlExplorer.Api/Workers/Copy/CopyWellboreWithObjectsWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ public interface ICopyWellboreWithObjectsWorker

public class CopyWellboreWithObjectsWorker : BaseWorker<CopyWellboreWithObjectsJob>, IWorker, ICopyWellboreWithObjectsWorker
{
private readonly ICopyWellboreWorker _copyWellboreWorker;
private readonly ICopyObjectsWorker _copyObjectsWorker;
private readonly IDocumentRepository<Server, Guid> _witsmlServerRepository;
private readonly IObjectService _objectService;
private const string Skipped = "Skipped";
private const string Fail = "Fail";
private string _sourceServerName;
private string _targetServerName;
public CopyWellboreWithObjectsWorker(ILogger<CopyWellboreWithObjectsJob> logger, ICopyWellboreWorker copyWellboreWorker, IWitsmlClientProvider witsmlClientProvider, ICopyObjectsWorker copyObjectsWorker, IDocumentRepository<Server, Guid> witsmlServerRepository = null) : base(witsmlClientProvider, logger)
public CopyWellboreWithObjectsWorker(ILogger<CopyWellboreWithObjectsJob> logger, IWitsmlClientProvider witsmlClientProvider, ICopyObjectsWorker copyObjectsWorker, IObjectService objectService, IDocumentRepository<Server, Guid> witsmlServerRepository = null) : base(witsmlClientProvider, logger)
{
_copyWellboreWorker = copyWellboreWorker;
_copyObjectsWorker = copyObjectsWorker;
_witsmlServerRepository = witsmlServerRepository;
_objectService = objectService;
}

public JobType JobType => JobType.CopyWellboreWithObjects;
Expand All @@ -57,34 +59,62 @@ public CopyWellboreWithObjectsWorker(ILogger<CopyWellboreWithObjectsJob> logger,
Target = job.Target,
Source = job.Source.WellboreReference
};
var existingWellbore = await WorkerTools.GetWellbore(targetClient, copyWellboreJob.Target);
WitsmlWellbore existingTargetWellbore = await WorkerTools.GetWellbore(targetClient, job.Target, ReturnElements.All);
WitsmlWellbore sourceWellbore = await WorkerTools.GetWellbore(sourceClient, job.Source.WellboreReference, ReturnElements.All);

if (existingWellbore != null)
if (_sourceServerName == _targetServerName && job.Source.WellboreReference.WellboreUid == job.Target.WellboreUid && job.Source.WellboreReference.WellUid == job.Target.WellUid)
{
string errorMessage = "Failed to copy wellbore with objects. Target wellbore already exists.";
Logger.LogError("{ErrorMessage} {Reason} - {JobDescription}", errorMessage, errorMessage, job.Description());
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessage, errorMessage, sourceServerUrl: sourceClient.GetServerHostname()), null);
string errorMessageSameWellbore = "Failed to copy wellbore with objects - tried to copy the same wellbore into the same well on the same server. If you intend to duplicate the wellbore you must change the wellbore uid.";
Logger.LogError("{ErrorMessage} {Reason} - {JobDescription}", errorMessageSameWellbore, errorMessageSameWellbore, job.Description());
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessageSameWellbore, errorMessageSameWellbore, sourceServerUrl: sourceClient.GetServerHostname()), null);
}

(WorkerResult result, RefreshAction refresh) wellboreResult =
await _copyWellboreWorker.Execute(copyWellboreJob,
cancellationToken);
if (!wellboreResult.result.IsSuccess)
string errorMessage = "Failed to copy wellbore.";

if (sourceWellbore == null)
{
Logger.LogError("{ErrorMessage} - {JobDescription}", errorMessage, job.Description());
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessage, sourceServerUrl: sourceClient.GetServerHostname()), null);
}

if (cancellationToken is { IsCancellationRequested: true })
{
string errorMessage = "Failed to copy wellbore with objects - creation of wellbore failed";
Logger.LogError("{ErrorMessage} {Reason} - {JobDescription}", errorMessage, wellboreResult.result.Reason, job.Description());
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessage, wellboreResult.result.Reason, sourceServerUrl: sourceClient.GetServerHostname()), null);
return (new WorkerResult(targetClient.GetServerHostname(), false, CancellationMessage(), CancellationReason(), sourceServerUrl: sourceClient.GetServerHostname()), null);
}

var existingObjectsOnWellbore =
await GetWellboreObjects(job, sourceClient);
sourceWellbore.Uid = job.Target.WellboreUid;
sourceWellbore.Name = job.Target.WellboreName;
sourceWellbore.UidWell = job.Target.WellUid;
sourceWellbore.NameWell = job.Target.WellName;

WitsmlWellbores wellbores = new() { Wellbores = { sourceWellbore } };

QueryResult result = existingTargetWellbore == null
? await targetClient.AddToStoreAsync(wellbores)
: await targetClient.UpdateInStoreAsync(wellbores);

if (!result.IsSuccessful)
{
Logger.LogError("{ErrorMessage} {Reason} - {JobDescription}", errorMessage, result.Reason, job.Description());
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessage, result.Reason, sourceServerUrl: sourceClient.GetServerHostname()), null);
}

var existingObjectsOnSourceWellbore =
await GetWellboreObjects(job, sourceClient);
var existingObjectsOnTargetWellbore = existingTargetWellbore != null
? await _objectService.GetAllObjectsOnWellbore(existingTargetWellbore.UidWell, existingTargetWellbore.Uid)
: new List<SelectableObjectOnWellbore>();

var (objectsToCopy, objectsToSkip) = GetObjectsToCopyAndSkip(existingObjectsOnSourceWellbore, existingObjectsOnTargetWellbore);

var totalEstimatedDuration = CalculateTotalEstimatedDuration(existingObjectsOnWellbore);
var totalEstimatedDuration = CalculateTotalEstimatedDuration(objectsToCopy);
long elapsedDuration = 0;

foreach (var ((entityType, logIndexType), objectList) in existingObjectsOnWellbore)
foreach (var ((entityType, logIndexType), objectList) in objectsToCopy)
{
long estimatedObjectDuration = objectList.Objects.Count() * GetEstimatedDuration(entityType, logIndexType);
var objectCount = objectList.Objects.Count();
if (objectCount == 0) continue;
long estimatedObjectDuration = objectCount * GetEstimatedDuration(entityType, logIndexType);
long currentElapsedDuration = elapsedDuration; // Capture the value to avoid closure issues when we increment the duration later as the progress reporter is async.
IProgress<double> subJobProgressReporter = new Progress<double>(subJobProgress =>
{
Expand All @@ -100,24 +130,101 @@ await _copyWellboreWorker.Execute(copyWellboreJob,
elapsedDuration += estimatedObjectDuration;
}

var fails = reportItems.Count(x => x.Status == "Fail");
string summary = fails > 0
? $"Partially copied wellbore with some child objects. Failed to copy {fails} out of {reportItems.Count} objects."
: "Successfully copied wellbore with all supported child objects.";
var skippedReportItems = GetSkippedReportItems(objectsToSkip);
reportItems.AddRange(skippedReportItems);

string summary = CreateSummaryMessage(reportItems);
BaseReport report = CreateCopyWellboreWithObjectsReport(reportItems, summary, job);
job.JobInfo.Report = report;
if (fails > 0)

Logger.LogInformation("{JobType} - Job successful. {Description}", GetType().Name, job.Description());
WorkerResult workerResult = new(targetClient.GetServerHostname(), true, summary, sourceServerUrl: sourceClient.GetServerHostname());
RefreshAction refreshAction = new RefreshWell(targetClient.GetServerHostname(), job.Target.WellUid, RefreshType.Update);
return (workerResult, refreshAction);
}

private static List<CopyWellboreWithObjectsReportItem> GetSkippedReportItems(Dictionary<(EntityType, string), IWitsmlObjectList> objectsToSkip)
{
var reportItems = new List<CopyWellboreWithObjectsReportItem>();
foreach (var ((entityType, logType), objectList) in objectsToSkip)
{
WorkerResult workerResult = new(targetClient.GetServerHostname(), true, summary, sourceServerUrl: sourceClient.GetServerHostname());
RefreshAction refreshAction = new RefreshWell(targetClient.GetServerHostname(), job.Target.WellUid, RefreshType.Update);
return (workerResult, refreshAction);
foreach (var obj in objectList.Objects)
{
var reportItem = new CopyWellboreWithObjectsReportItem()
{
Phase = "Copy " + entityType,
Name = obj.Name,
Uid = obj.Uid,
Message = "Object already exists",
Status = Skipped
};
reportItems.Add(reportItem);
}
}
else
return reportItems;
}

private static (Dictionary<(EntityType, string), IWitsmlObjectList> objectsToCopy, Dictionary<(EntityType, string), IWitsmlObjectList> objectsToSkip) GetObjectsToCopyAndSkip
(
Dictionary<(EntityType, string), IWitsmlObjectList> existingObjectsOnSourceWellbore,
ICollection<SelectableObjectOnWellbore> existingObjectsOnTargetWellbore
)
{
var objectsToCopy = new Dictionary<(EntityType, string), IWitsmlObjectList>();
var objectsToSkip = new Dictionary<(EntityType, string), IWitsmlObjectList>();

foreach (var (key, sourceObjectList) in existingObjectsOnSourceWellbore)
{
var sourceEntityType = key.Item1.ToString();

var toCopy = sourceObjectList.Objects
.Where(obj => !existingObjectsOnTargetWellbore.Any(
target => target.Uid == obj.Uid && target.ObjectType == sourceEntityType
))
.ToList();

var toSkip = sourceObjectList.Objects
.Where(obj => existingObjectsOnTargetWellbore.Any(
target => target.Uid == obj.Uid && target.ObjectType == sourceEntityType
))
.ToList();

if (Activator.CreateInstance(sourceObjectList.GetType()) is IWitsmlObjectList copyList &&
Activator.CreateInstance(sourceObjectList.GetType()) is IWitsmlObjectList skipList)
{
copyList.Objects = toCopy;
skipList.Objects = toSkip;

objectsToCopy[key] = copyList;
objectsToSkip[key] = skipList;
}
}

return (objectsToCopy, objectsToSkip);
}

private static string CreateSummaryMessage(List<CopyWellboreWithObjectsReportItem> reportItems)
{
var fails = reportItems.Count(x => x.Status == Fail);
var skipped = reportItems.Count(x => x.Status == Skipped);
var reportItemsCount = reportItems.Count;

if (fails == 0 && skipped == 0)
{
WorkerResult workerResult = new(targetClient.GetServerHostname(), true, summary, sourceServerUrl: sourceClient.GetServerHostname());
RefreshAction refreshAction = new RefreshWell(targetClient.GetServerHostname(), job.Target.WellUid, RefreshType.Update);
return (workerResult, refreshAction);
return
"Successfully copied wellbore with all supported child objects.";
}

string summary =
"Partially copied wellbore with some child objects.";
summary += (fails > 0
? $" Failed to copy {fails} out of {reportItemsCount} objects."
: string.Empty);

summary += (skipped > 0
? $" Skipped to copy {skipped} out of {reportItemsCount} objects."
: string.Empty);
return summary;
}

private static long GetEstimatedDuration(EntityType objectType, string logIndexType = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@ import {
import { Server } from "../../models/server";
import WellboreReference from "models/jobs/wellboreReference";
import WellReference from "models/jobs/wellReference";
import ModalDialog from "./ModalDialog";
import { ChangeEvent, useState } from "react";
import ModalDialog, { ModalWidth } from "./ModalDialog";
import { ChangeEvent, MouseEvent, useEffect, useState } from "react";
import { MixedObjectsReferences } from "models/selectableObjectOnWellbore";
import { useGetAllObjectsOnWellbore } from "hooks/query/useGetAllObjectsOnWellbore";
import ProgressSpinner from "components/ProgressSpinner";
import {
ContentTable,
ContentTableColumn,
ContentType
} from "components/ContentViews/table";
import { SubObjectsSelectionModalContentLayout } from "./SubObjectsSelectionModal";
import WarningBar from "components/WarningBar";
import { useGetWellbore } from "hooks/query/useGetWellbore";

export interface ChangeWellboreUidModalProps {
servers: Server[];
Expand All @@ -36,14 +46,54 @@ const ChangeWellboreUidModal = (
props.sourceWellboreWithMixedObjectsReferences.wellboreReference.wellboreUid
);

const [debouncedWellboreId, setDebouncedWellboreId] = useState<string>(
props.sourceWellboreWithMixedObjectsReferences.wellboreReference.wellboreUid
);

const { objectsOnWellbore: objectsOnWellbore, isFetching } =
useGetAllObjectsOnWellbore(
AuthorizationService.selectedServer,
props.targetWell.wellUid,
debouncedWellboreId
);

const { wellbore } = useGetWellbore(
AuthorizationService.selectedServer,
props.targetWell.wellUid,
debouncedWellboreId
);

const sameObjectsOnWellbore =
objectsOnWellbore !== undefined
? objectsOnWellbore.filter((x) =>
props.sourceWellboreWithMixedObjectsReferences.selectedObjects.find(
(y) => y.uid === x.uid && y.objectType === x.objectType
)
)
: null;

const columns: ContentTableColumn[] = [
{ property: "objectType", label: "Object type", type: ContentType.String },
{ property: "logType", label: "Log type", type: ContentType.String },
{ property: "uid", label: "Uid", type: ContentType.String },
{ property: "name", label: "Name", type: ContentType.String }
];

useEffect(() => {
const delay = setTimeout(() => {
setDebouncedWellboreId(wellboreUid);
}, 1000);
return () => clearTimeout(delay);
}, [wellboreUid]);

const onConfirm = async () => {
dispatchOperation({ type: OperationType.HideModal });

const target: WellboreReference = {
wellUid: props.targetWell.wellUid,
wellName: props.targetWell.wellName,
wellboreName: wellboreName,
wellboreUid: wellboreUid
wellboreUid: debouncedWellboreId
};
const orderCopyJob = () => {
const copyJob = createCopyWellboreWithObjectsJob(
Expand All @@ -66,11 +116,14 @@ const ChangeWellboreUidModal = (
confirmText={`Paste`}
cancelText={`Cancel`}
confirmDisabled={
!validText(wellboreName, 1, 64) || !validText(wellboreUid, 1, 64)
!validText(wellboreName, 1, 64) ||
!validText(wellboreUid, 1, 64) ||
isFetching
}
onSubmit={onConfirm}
switchButtonPlaces={true}
isLoading={false}
width={ModalWidth.LARGE}
content={
<ModalContentLayout>
<TextField
Expand All @@ -92,9 +145,10 @@ const ChangeWellboreUidModal = (
? "The UID must be 1-64 characters"
: ""
}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setWellboreUid(e.target.value)
}
onClick={(e: MouseEvent<HTMLInputElement>) => e.stopPropagation()}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setWellboreUid(e.target.value);
}}
/>
<TextField
id={"wellboreName"}
Expand All @@ -111,6 +165,28 @@ const ChangeWellboreUidModal = (
setWellboreName(e.target.value)
}
/>
{isFetching && <ProgressSpinner message="Fetching data" />}
{wellbore !== undefined &&
objectsOnWellbore === undefined &&
!isFetching && (
<SubObjectsSelectionModalContentLayout>
<WarningBar message="The wellbore exists on the target server, but no objects will be overwritten as all uids differs" />
</SubObjectsSelectionModalContentLayout>
)}
{wellbore !== undefined &&
objectsOnWellbore !== undefined &&
objectsOnWellbore.length > 0 &&
!isFetching && (
<SubObjectsSelectionModalContentLayout>
<WarningBar message="Some objects already exist on the target server. Only objects that do not already exist will be copied." />
<ContentTable
viewId="subObjectsListView"
columns={columns}
data={sameObjectsOnWellbore}
disableSearchParamsFilter={true}
/>
</SubObjectsSelectionModalContentLayout>
)}
</ModalContentLayout>
}
/>
Expand Down
Loading