Skip to content
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

Cascade deletes well #2556

Merged
merged 13 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Src/Witsml/ServiceReference/OptionsIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public record OptionsIn(
int? MaxReturnNodes = null,
int? RequestLatestValues = null,
bool? RequestObjectSelectionCapability = null,
bool? CascadedDelete = null,
string OptionsInString = null)
{
public string OptionsInString { get; init; } = ValidateOptionsInString(OptionsInString);
Expand All @@ -34,6 +35,10 @@ public string GetKeywords()
{
keywords.Add($"requestObjectSelectionCapability=true");
}
if (CascadedDelete == true)
{
keywords.Add($"cascadedDelete=true");
}
if (!string.IsNullOrEmpty(OptionsInString))
{
keywords.Add(OptionsInString);
Expand Down
15 changes: 13 additions & 2 deletions Src/Witsml/WitsmlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public interface IWitsmlClient
Task<QueryResult> UpdateInStoreAsync<T>(T query) where T : IWitsmlQueryType;
Task<string> UpdateInStoreAsync(string query, OptionsIn optionsIn = null);
Task<QueryResult> DeleteFromStoreAsync<T>(T query) where T : IWitsmlQueryType;
Task<QueryResult> DeleteFromStoreAsync<T>(T query, OptionsIn optionsIn) where T : IWitsmlQueryType;
Task<string> DeleteFromStoreAsync(string query, OptionsIn optionsIn = null);
Task<QueryResult> TestConnectionAsync();
Task<WitsmlCapServers> GetCap();
Expand Down Expand Up @@ -367,15 +368,25 @@ public async Task<string> UpdateInStoreAsync(string query, OptionsIn optionsIn =
throw new Exception($"Error while adding to store: {response.Result} - {errorResponse.Result}. {response.SuppMsgOut}");
}

public async Task<QueryResult> DeleteFromStoreAsync<T>(T query) where T : IWitsmlQueryType
public Task<QueryResult> DeleteFromStoreAsync<T>(T query) where T : IWitsmlQueryType
{
return DeleteFromStoreAsyncImplementation(query);
}

public Task<QueryResult> DeleteFromStoreAsync<T>(T query, OptionsIn optionsIn) where T : IWitsmlQueryType
{
return DeleteFromStoreAsyncImplementation(query, optionsIn);
}

private async Task<QueryResult> DeleteFromStoreAsyncImplementation<T>(T query, OptionsIn optionsIn = null) where T : IWitsmlQueryType
{
try
{
WMLS_DeleteFromStoreRequest request = new()
{
WMLtypeIn = query.TypeName,
QueryIn = XmlHelper.Serialize(query),
OptionsIn = string.Empty,
OptionsIn = optionsIn == null ? string.Empty : optionsIn.GetKeywords(),
CapabilitiesIn = _clientCapabilities
};

Expand Down
10 changes: 8 additions & 2 deletions Src/WitsmlExplorer.Api/Jobs/DeleteJobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ namespace WitsmlExplorer.Api.Jobs
{
public record DeleteComponentsJob : IDeleteJob<ComponentReferences> { }
public record DeleteObjectsJob : IDeleteJob<ObjectReferences> { }
public record DeleteWellboreJob : IDeleteJob<WellboreReference> { }
public record DeleteWellJob : IDeleteJob<WellReference> { }
public record DeleteWellboreJob : IDeleteJob<WellboreReference>
{
public bool CascadedDelete { get; init; }
}
public record DeleteWellJob : IDeleteJob<WellReference>
{
public bool CascadedDelete { get; init; }
}
}
3 changes: 2 additions & 1 deletion Src/WitsmlExplorer.Api/Workers/Delete/DeleteWellWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ public DeleteWellWorker(ILogger<DeleteWellJob> logger, IWitsmlClientProvider wit

public override async Task<(WorkerResult, RefreshAction)> Execute(DeleteWellJob job, CancellationToken? cancellationToken = null)
{
bool cascadedDelete = job.CascadedDelete;
string wellUid = job.ToDelete.WellUid;

WitsmlWells witsmlWell = WellQueries.DeleteWitsmlWell(wellUid);
QueryResult result = await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWell);
QueryResult result = cascadedDelete ? await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWell, new OptionsIn(CascadedDelete: true)) : await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWell);
if (result.IsSuccessful)
{
Logger.LogInformation("Deleted well. WellUid: {WellUid}", wellUid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ public DeleteWellboreWorker(ILogger<DeleteWellboreJob> logger, IWitsmlClientProv

public override async Task<(WorkerResult, RefreshAction)> Execute(DeleteWellboreJob job, CancellationToken? cancellationToken = null)
{
bool cascadedDelete = job.CascadedDelete;
string wellUid = job.ToDelete.WellUid;
string wellboreUid = job.ToDelete.WellboreUid;

WitsmlWellbores witsmlWellbore = WellboreQueries.DeleteWitsmlWellbore(wellUid, wellboreUid);
QueryResult result = await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWellbore);
QueryResult result = cascadedDelete ? await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWellbore, new OptionsIn(CascadedDelete: true)) : await GetTargetWitsmlClientOrThrow().DeleteFromStoreAsync(witsmlWellbore);
if (result.IsSuccessful)
{
Logger.LogInformation("Deleted wellbore. WellUid: {WellUid}, WellboreUid: {WellboreUid}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { WellRow } from "components/ContentViews/WellsListView";
import ContextMenu from "components/ContextMenus/ContextMenu";
import { StyledIcon } from "components/ContextMenus/ContextMenuUtils";
import NestedMenuItem from "components/ContextMenus/NestedMenuItem";
import ConfirmModal from "components/Modals/ConfirmModal";
import ConfirmDeletionModal, {
ConfirmDeletionModalProps
} from "components/Modals/ConfirmDeletionModal";
import DeleteEmptyMnemonicsModal, {
DeleteEmptyMnemonicsModalProps
} from "components/Modals/DeleteEmptyMnemonicsModal";
Expand Down Expand Up @@ -104,37 +106,31 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => {
);
};

const deleteWell = async () => {
const deleteWell = async (cascadedDelete: boolean) => {
dispatchOperation({ type: OperationType.HideContextMenu });
dispatchOperation({ type: OperationType.HideModal });
const job: DeleteWellJob = {
toDelete: {
wellUid: well.uid,
wellName: well.name
}
},
cascadedDelete
};
await JobService.orderJob(JobType.DeleteWell, job);
};

const onClickDelete = async () => {
const confirmation = (
<ConfirmModal
heading={"Delete well?"}
content={
<span>
This will permanently delete <strong>{well.name}</strong> with uid:{" "}
<strong>{well.uid}</strong>
</span>
}
onConfirm={deleteWell}
confirmColor={"danger"}
confirmText={"Delete well"}
switchButtonPlaces={true}
/>
);
const userCredentialsModalProps: ConfirmDeletionModalProps = {
componentType: "well",
objectName: well.name,
objectUid: well.uid,
onSubmit(cascadedDelete) {
deleteWell(cascadedDelete);
}
};
dispatchOperation({
type: OperationType.DisplayModal,
payload: confirmation
payload: <ConfirmDeletionModal {...userCredentialsModalProps} />
});
};

Expand Down Expand Up @@ -219,7 +215,11 @@ const WellContextMenu = (props: WellContextMenuProps): React.ReactElement => {
<StyledIcon name="add" color={colors.interactive.primaryResting} />
<Typography color={"primary"}>New Wellbore</Typography>
</MenuItem>,
<MenuItem key={"deleteWell"} onClick={onClickDelete}>
<MenuItem
key={"deleteWell"}
onClick={onClickDelete}
disabled={!!checkedWellRows && checkedWellRows.length !== 1}
>
<StyledIcon
name="deleteToTrash"
color={colors.interactive.primaryResting}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import {
import { pasteObjectOnWellbore } from "components/ContextMenus/CopyUtils";
import NestedMenuItem from "components/ContextMenus/NestedMenuItem";
import { useClipboardReferences } from "components/ContextMenus/UseClipboardReferences";
import ConfirmModal from "components/Modals/ConfirmModal";
import ConfirmDeletionModal, {
ConfirmDeletionModalProps
} from "components/Modals/ConfirmDeletionModal";
import DeleteEmptyMnemonicsModal, {
DeleteEmptyMnemonicsModalProps
} from "components/Modals/DeleteEmptyMnemonicsModal";
Expand Down Expand Up @@ -107,7 +109,7 @@ const WellboreContextMenu = (
);
};

const deleteWellbore = async () => {
const deleteWellbore = async (cascadedDelete: boolean) => {
dispatchOperation({ type: OperationType.HideContextMenu });
dispatchOperation({ type: OperationType.HideModal });
const job: DeleteWellboreJob = {
Expand All @@ -116,30 +118,24 @@ const WellboreContextMenu = (
wellboreUid: wellbore.uid,
wellName: wellbore.wellName,
wellboreName: wellbore.name
}
},
cascadedDelete
};
await JobService.orderJob(JobType.DeleteWellbore, job);
};

const onClickDelete = async () => {
const confirmation = (
<ConfirmModal
heading={"Delete wellbore?"}
content={
<span>
This will permanently delete <strong>{wellbore.name}</strong> with
uid: <strong>{wellbore.uid}</strong>
</span>
}
onConfirm={deleteWellbore}
confirmColor={"danger"}
confirmText={"Delete wellbore"}
switchButtonPlaces={true}
/>
);
const userCredentialsModalProps: ConfirmDeletionModalProps = {
componentType: "wellbore",
objectName: wellbore.name,
objectUid: wellbore.uid,
onSubmit(cascadedDelete) {
deleteWellbore(cascadedDelete);
}
};
dispatchOperation({
type: OperationType.DisplayModal,
payload: confirmation
payload: <ConfirmDeletionModal {...userCredentialsModalProps} />
});
};

Expand Down Expand Up @@ -237,7 +233,11 @@ const WellboreContextMenu = (
)}
</Typography>
</MenuItem>,
<MenuItem key={"deleteWellbore"} onClick={onClickDelete}>
<MenuItem
key={"deleteWellbore"}
onClick={onClickDelete}
disabled={!!checkedWellboreRows && checkedWellboreRows.length !== 1}
>
<StyledIcon
name="deleteToTrash"
color={colors.interactive.primaryResting}
Expand Down
eliasbruvik marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ModalContentLayout } from "components/Modals/ModalDialog";
import { Checkbox } from "components/StyledComponents/Checkbox";
import OperationType from "contexts/operationType";
import { useOperationState } from "hooks/useOperationState";
import React, { ChangeEvent, useState } from "react";
import ConfirmModal from "./ConfirmModal";
import WarningBar from "components/WarningBar";

export interface ConfirmDeletionModalProps {
componentType: string;
objectName: string;
objectUid: string;
onSubmit: (cascadedDelete: boolean) => void;
}

const ConfirmDeletionModal = (
props: ConfirmDeletionModalProps
): React.ReactElement => {
const {
operationState: { colors },
dispatchOperation
} = useOperationState();

const [cascadedDelete, setCascadedDelete] = useState<boolean>(false);

const onConfirmClick = async () => {
props.onSubmit(cascadedDelete);
dispatchOperation({ type: OperationType.HideModal });
};

return (
<ConfirmModal
heading={"Delete " + props.componentType + "?"}
content={
<>
<ModalContentLayout>
<span>
This will permanently delete {props.componentType}{" "}
<strong>{props.objectName}</strong> with uid:{" "}
<strong>{props.objectUid}</strong>
</span>

<Checkbox
label={`Delete all objects under ` + props.componentType + `?`}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setCascadedDelete(e.target.checked);
}}
colors={colors}
/>

{cascadedDelete && (
<WarningBar
message={
`This action will permanently delete the ${props.componentType} ` +
`'${props.objectName}' and all associated objects. ` +
`This cannot be undone. Please ensure you want to delete the entire structure.`
}
/>
)}
</ModalContentLayout>
</>
}
onConfirm={onConfirmClick}
confirmColor={"danger"}
confirmText={"Delete " + props.componentType}
switchButtonPlaces={true}
/>
);
};

export default ConfirmDeletionModal;
2 changes: 2 additions & 0 deletions Src/WitsmlExplorer.Frontend/models/jobs/deleteJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export interface DeleteComponentsJob {

export interface DeleteWellboreJob {
toDelete: WellboreReference;
cascadedDelete: boolean;
}

export interface DeleteWellJob {
toDelete: {
wellUid: string;
wellName: string;
};
cascadedDelete: boolean;
}
2 changes: 1 addition & 1 deletion Tests/Witsml.Tests/ServiceReference/OptionsInTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void GetKeywords_OptionsInString_MultipleKeywords_ReturnsCorrectValue()
[Fact]
public void GetKeywords_OptionsInStringAndOtherOptions_ReturnsCorrectValue()
{
OptionsIn optionsIn = new(ReturnElements.DataOnly, 50, 100, true, "foo=bar;baz=qux");
OptionsIn optionsIn = new(ReturnElements.DataOnly, 50, 100, true, false, "foo=bar;baz=qux");
Assert.Equal("returnElements=data-only;maxReturnNodes=50;requestLatestValues=100;requestObjectSelectionCapability=true;foo=bar;baz=qux", optionsIn.GetKeywords());
}

Expand Down
Loading