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

104 changes: 68 additions & 36 deletions Src/WitsmlExplorer.Api/Workers/Copy/CopyWellboreWithObjectsWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ public class CopyWellboreWithObjectsWorker : BaseWorker<CopyWellboreWithObjectsJ
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, ICopyWellboreWorker copyWellboreWorker, 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 @@ -59,12 +63,12 @@ public CopyWellboreWithObjectsWorker(ILogger<CopyWellboreWithObjectsJob> logger,
};
var existingWellbore = await WorkerTools.GetWellbore(targetClient, copyWellboreJob.Target);

if (existingWellbore != null)
{
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);
}
var existingObjectsOnWellbore =
await GetWellboreObjects(job, sourceClient);
var existingObjectsOnTargetWellbore = existingWellbore != null ?
await _objectService.GetAllObjectsOnWellbore(
existingWellbore.UidWell, existingWellbore.Uid) : new List<SelectableObjectOnWellbore>();
var uidsOfNonExistingObjectsOnWellbore = existingObjectsOnTargetWellbore.Select(x => x.Uid).ToList();

(WorkerResult result, RefreshAction refresh) wellboreResult =
await _copyWellboreWorker.Execute(copyWellboreJob,
Expand All @@ -76,15 +80,13 @@ await _copyWellboreWorker.Execute(copyWellboreJob,
return (new WorkerResult(targetClient.GetServerHostname(), false, errorMessage, wellboreResult.result.Reason, sourceServerUrl: sourceClient.GetServerHostname()), null);
}

var existingObjectsOnWellbore =
await GetWellboreObjects(job, sourceClient);

var totalEstimatedDuration = CalculateTotalEstimatedDuration(existingObjectsOnWellbore);
var totalEstimatedDuration = CalculateTotalEstimatedDuration(existingObjectsOnWellbore, uidsOfNonExistingObjectsOnWellbore);
long elapsedDuration = 0;

foreach (var ((entityType, logIndexType), objectList) in existingObjectsOnWellbore)
{
long estimatedObjectDuration = objectList.Objects.Count() * GetEstimatedDuration(entityType, logIndexType);
var nonExistingObjectCount = objectList.Objects.Count(x => uidsOfNonExistingObjectsOnWellbore.IndexOf(x.Uid) > -1);
long estimatedObjectDuration = nonExistingObjectCount * 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 @@ -94,30 +96,43 @@ await _copyWellboreWorker.Execute(copyWellboreJob,
});

List<CopyWellboreWithObjectsReportItem> objectTypeReportItems = await CopyWellboreObjectsByType(
job, entityType, objectList.Objects, subJobProgressReporter, cancellationToken
job, entityType, objectList.Objects, subJobProgressReporter, uidsOfNonExistingObjectsOnWellbore, cancellationToken
);
reportItems.AddRange(objectTypeReportItems);
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 fails = reportItems.Count(x => x.Status == Fail);
var skipped = reportItems.Count(x => x.Status == Skipped);
string summary =
CreateSummaryMessage(fails, skipped, reportItems.Count);
BaseReport report = CreateCopyWellboreWithObjectsReport(reportItems, summary, job);
job.JobInfo.Report = report;
if (fails > 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);
}
else

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 string CreateSummaryMessage(int fails, int skipped,
int reportItemsCount)
{
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 All @@ -130,12 +145,13 @@ private static long GetEstimatedDuration(EntityType objectType, string logIndexT
return 10;
}

private static long CalculateTotalEstimatedDuration(Dictionary<(EntityType, string), IWitsmlObjectList> objectsOnWellbore)
private static long CalculateTotalEstimatedDuration(Dictionary<(EntityType, string), IWitsmlObjectList> objectsOnWellbore, List<string> uids)
{
long estimatedDuration = 0;
foreach (var ((entityType, logIndexType), objectList) in objectsOnWellbore)
{
estimatedDuration += objectList.Objects.Count() * GetEstimatedDuration(entityType, logIndexType);
var nonExistingObjectCount = objectList.Objects.Count(x => uids.IndexOf(x.Uid) > -1);
estimatedDuration += nonExistingObjectCount * GetEstimatedDuration(entityType, logIndexType);
}
return estimatedDuration;
}
Expand Down Expand Up @@ -204,7 +220,7 @@ private async Task<CopyWellboreWithObjectsReportItem> CopyOneObject(WitsmlObject
}
}

private async Task<List<CopyWellboreWithObjectsReportItem>> CopyWellboreObjectsByType(CopyWellboreWithObjectsJob job, EntityType entityType, IEnumerable<WitsmlObjectOnWellbore> objectsToCopy, IProgress<double> progressReporter, CancellationToken? cancellationToken)
private async Task<List<CopyWellboreWithObjectsReportItem>> CopyWellboreObjectsByType(CopyWellboreWithObjectsJob job, EntityType entityType, IEnumerable<WitsmlObjectOnWellbore> objectsToCopy, IProgress<double> progressReporter, List<string> uidsOfNonExistingObjectsOnWellbore, CancellationToken? cancellationToken)
{
var reportItems = new List<CopyWellboreWithObjectsReportItem>();
var totalObjects = objectsToCopy?.Count() ?? 0;
Expand All @@ -214,13 +230,29 @@ private async Task<List<CopyWellboreWithObjectsReportItem>> CopyWellboreObjectsB
{
var currentIndex = i; // Capture the value to avoid closure issues when i is increased as the progress reporter is async.
var objectOnWellbore = objectsToCopy.ElementAt(i);
IProgress<double> subJobProgressReporter = new Progress<double>(subJobProgress =>
if (uidsOfNonExistingObjectsOnWellbore.IndexOf(objectOnWellbore.Uid) == -1)
{
var progress = ((double)currentIndex / totalObjects) + ((double)subJobProgress / totalObjects);
progressReporter.Report(progress);
});
var reportItem = await CopyOneObject(objectOnWellbore, job, entityType, subJobProgressReporter, cancellationToken);
reportItems.Add(reportItem);
IProgress<double> subJobProgressReporter = new Progress<double>(subJobProgress =>
{
var progress = ((double)currentIndex / totalObjects) + ((double)subJobProgress / totalObjects);
progressReporter.Report(progress);
});

var reportItem = await CopyOneObject(objectOnWellbore, job, entityType, subJobProgressReporter, cancellationToken);
reportItems.Add(reportItem);
}
else
{
var reportItem = new CopyWellboreWithObjectsReportItem()
{
Phase = "Copy " + entityType,
Name = objectOnWellbore.Name,
Uid = objectOnWellbore.Uid,
Message = "Object allready exists",
Status = Skipped
};
reportItems.Add(reportItem);
}
}
}
return reportItems;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ 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";

export interface ChangeWellboreUidModalProps {
servers: Server[];
Expand All @@ -36,14 +45,48 @@ 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 sameObjectsOnWellbore =
objectsOnWellbore !== undefined
? objectsOnWellbore.filter((x) =>
props.sourceWellboreWithMixedObjectsReferences.selectedObjects.find(
(y) => y.uid === x.uid
)
)
: 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);
}, 500);
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 +109,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 +138,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 +158,23 @@ const ChangeWellboreUidModal = (
setWellboreName(e.target.value)
}
/>
{isFetching && <ProgressSpinner message="Fetching data" />}
{objectsOnWellbore !== undefined &&
objectsOnWellbore.length > 0 &&
!isFetching && (
<SubObjectsSelectionModalContentLayout>
<WarningBar
message=" These objects already exist on target. Only on target not existing
objects will be copied."
/>
<ContentTable
viewId="subObjectsListView"
columns={columns}
data={sameObjectsOnWellbore}
disableSearchParamsFilter={true}
/>
</SubObjectsSelectionModalContentLayout>
)}
</ModalContentLayout>
}
/>
Expand Down
Loading