Skip to content

Commit

Permalink
Merge pull request #2019 from janburak/FIX-2013
Browse files Browse the repository at this point in the history
FIX-2013 implement add, delete, and update functions for query view
  • Loading branch information
janburak authored Aug 24, 2023
2 parents 147301f + 71b0a2e commit c66933e
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 25 deletions.
84 changes: 80 additions & 4 deletions Src/Witsml/WitsmlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ public interface IWitsmlClient
Task<(T, short resultCode)> GetGrowingDataObjectFromStoreAsync<T>(T query, OptionsIn optionsIn) where T : IWitsmlGrowingDataQueryType, new();
Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn);
Task<QueryResult> AddToStoreAsync<T>(T query) where T : IWitsmlQueryType;
Task<string> AddToStoreAsync(string query, OptionsIn optionsIn = null);
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<string> DeleteFromStoreAsync(string query, OptionsIn optionsIn = null);
Task<QueryResult> TestConnectionAsync();
Task<WitsmlCapServers> GetCap();
Uri GetServerHostname();
Expand Down Expand Up @@ -179,24 +182,29 @@ private async Task<T> GetFromStoreInnerAsync<T>(T query, OptionsIn optionsIn) wh
}
}

public async Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn)
private string GetQueryType(string query)
{
XmlReaderSettings settings = new()
{
IgnoreComments = true,
IgnoreProcessingInstructions = true,
IgnoreWhitespace = true
IgnoreWhitespace = true,
XmlResolver = null
};
using XmlReader reader = XmlReader.Create(new StringReader(query), settings);
// attempt to read the query type from the first nested element, such as <logs><_log_>[...]</log></logs>
reader.Read();
reader.Read();
string type = reader.Name;
if (string.IsNullOrEmpty(type))
if (string.IsNullOrEmpty(reader.Name))
{
throw new Exception("Could not determine WITSML type based on query");
}
return reader.Name;
}

public async Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn)
{
string type = GetQueryType(query);
WMLS_GetFromStoreRequest request = new()
{
WMLtypeIn = type,
Expand All @@ -206,6 +214,8 @@ public async Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn)
};

WMLS_GetFromStoreResponse response = await _client.WMLS_GetFromStoreAsync(request);
LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_GetFromStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), response.XMLout, response.Result, response.SuppMsgOut);

if (response.IsSuccessful())
return response.XMLout;
Expand Down Expand Up @@ -247,6 +257,28 @@ public async Task<QueryResult> AddToStoreAsync<T>(T query) where T : IWitsmlQuer
}
}

public async Task<string> AddToStoreAsync(string query, OptionsIn optionsIn = null)
{
string type = GetQueryType(query);
WMLS_AddToStoreRequest request = new()
{
WMLtypeIn = type,
OptionsIn = optionsIn?.GetKeywords() ?? "",
XMLin = query,
CapabilitiesIn = _clientCapabilities
};

WMLS_AddToStoreResponse response = await _client.WMLS_AddToStoreAsync(request);
LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_AddToStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

if (response.IsSuccessful())
return "Function completed successfully";

WMLS_GetBaseMsgResponse errorResponse = await _client.WMLS_GetBaseMsgAsync(response.Result);
throw new Exception($"Error while adding to store: {response.Result} - {errorResponse.Result}. {response.SuppMsgOut}");
}

public async Task<QueryResult> UpdateInStoreAsync<T>(T query) where T : IWitsmlQueryType
{
try
Expand Down Expand Up @@ -279,6 +311,28 @@ public async Task<QueryResult> UpdateInStoreAsync<T>(T query) where T : IWitsmlQ
}
}

public async Task<string> UpdateInStoreAsync(string query, OptionsIn optionsIn = null)
{
string type = GetQueryType(query);
WMLS_UpdateInStoreRequest request = new()
{
WMLtypeIn = type,
OptionsIn = optionsIn?.GetKeywords() ?? "",
XMLin = query,
CapabilitiesIn = _clientCapabilities
};

WMLS_UpdateInStoreResponse response = await _client.WMLS_UpdateInStoreAsync(request);
LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_UpdateInStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

if (response.IsSuccessful())
return "Function completed successfully";

WMLS_GetBaseMsgResponse errorResponse = await _client.WMLS_GetBaseMsgAsync(response.Result);
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
{
try
Expand Down Expand Up @@ -311,6 +365,28 @@ public async Task<QueryResult> DeleteFromStoreAsync<T>(T query) where T : IWitsm
}
}

public async Task<string> DeleteFromStoreAsync(string query, OptionsIn optionsIn = null)
{
string type = GetQueryType(query);
WMLS_DeleteFromStoreRequest request = new()
{
WMLtypeIn = type,
OptionsIn = optionsIn?.GetKeywords() ?? "",
QueryIn = query,
CapabilitiesIn = _clientCapabilities
};

WMLS_DeleteFromStoreResponse response = await _client.WMLS_DeleteFromStoreAsync(request);
LogQueriesSentAndReceived<IWitsmlQueryType>(nameof(_client.WMLS_DeleteFromStoreAsync), _serverUrl, null, optionsIn,
query, response.IsSuccessful(), null, response.Result, response.SuppMsgOut);

if (response.IsSuccessful())
return "Function completed successfully";

WMLS_GetBaseMsgResponse errorResponse = await _client.WMLS_GetBaseMsgAsync(response.Result);
throw new Exception($"Error while adding to store: {response.Result} - {errorResponse.Result}. {response.SuppMsgOut}");
}

public async Task<QueryResult> TestConnectionAsync()
{
WMLS_GetVersionResponse response = await _client.WMLS_GetVersionAsync();
Expand Down
52 changes: 50 additions & 2 deletions Src/WitsmlExplorer.Api/HttpHandlers/WitsmlQueryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,47 @@ namespace WitsmlExplorer.Api.HttpHandlers
{
public static class WitsmlQueryHandler
{
// file deepcode ignore XmlInjection: the incoming xml documents are only used to retrieve the query type that is sent further to the WITSML server

[Produces(typeof(string))]
public static async Task<IResult> PostQuery(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
public static async Task<IResult> AddToStore(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
{
IWitsmlClient witsmlClient = witsmlClientProvider.GetClient() ?? throw new WitsmlClientProviderException($"No WITSML access", (int)HttpStatusCode.Unauthorized, ServerType.Target);
try
{
WitsmlQuery query = await httpRequest.Body.Deserialize<WitsmlQuery>();
string result = await witsmlClient.AddToStoreAsync(query.Body);
return TypedResults.Ok(result);
}
catch (Exception e)
{
return TypedResults.Ok(e.Message);
}
}

[Produces(typeof(string))]
public static async Task<IResult> DeleteFromStore(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
{
IWitsmlClient witsmlClient = witsmlClientProvider.GetClient() ?? throw new WitsmlClientProviderException($"No WITSML access", (int)HttpStatusCode.Unauthorized, ServerType.Target);
try
{
WitsmlQuery query = await httpRequest.Body.Deserialize<WitsmlQuery>();
string result = await witsmlClient.DeleteFromStoreAsync(query.Body);
return TypedResults.Ok(result);
}
catch (Exception e)
{
return TypedResults.Ok(e.Message);
}
}

[Produces(typeof(string))]
public static async Task<IResult> GetFromStore(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
{
IWitsmlClient witsmlClient = witsmlClientProvider.GetClient() ?? throw new WitsmlClientProviderException($"No WITSML access", (int)HttpStatusCode.Unauthorized, ServerType.Target);
try
{
WitsmlQuery query = await httpRequest.Body.Deserialize<WitsmlQuery>();
// file deepcode ignore XmlInjection: the body is only used to retrieve the query type that is sent further to the WITSML server
string result = await witsmlClient.GetFromStoreAsync(query.Body, new OptionsIn(query.ReturnElements));
return TypedResults.Ok(result);
}
Expand All @@ -35,5 +67,21 @@ public static async Task<IResult> PostQuery(IWitsmlClientProvider witsmlClientPr
}
}

[Produces(typeof(string))]
public static async Task<IResult> UpdateInStore(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
{
IWitsmlClient witsmlClient = witsmlClientProvider.GetClient() ?? throw new WitsmlClientProviderException($"No WITSML access", (int)HttpStatusCode.Unauthorized, ServerType.Target);
try
{
WitsmlQuery query = await httpRequest.Body.Deserialize<WitsmlQuery>();
string result = await witsmlClient.UpdateInStoreAsync(query.Body);
return TypedResults.Ok(result);
}
catch (Exception e)
{
return TypedResults.Ok(e.Message);
}
}

}
}
5 changes: 4 additions & 1 deletion Src/WitsmlExplorer.Api/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ public static void ConfigureApi(this WebApplication app, IConfiguration configur
app.MapGet(routes[EntityType.WbGeometry] + "/{wbGeometryUid}", WbGeometryHandler.GetWbGeometry, useOAuth2);
app.MapGet(routes[EntityType.WbGeometry] + "/{wbGeometryUid}/" + ComponentType.WbGeometrySection.ToPluralLowercase(), WbGeometryHandler.GetWbGeometrySections, useOAuth2);

app.MapPost("/query", WitsmlQueryHandler.PostQuery, useOAuth2);
app.MapPost("/query/addtostore", WitsmlQueryHandler.AddToStore, useOAuth2);
app.MapPost("/query/deletefromstore", WitsmlQueryHandler.DeleteFromStore, useOAuth2);
app.MapPost("/query/getfromstore", WitsmlQueryHandler.GetFromStore, useOAuth2);
app.MapPost("/query/updateinstore", WitsmlQueryHandler.UpdateInStore, useOAuth2);

app.MapPost("/jobs/{jobType}", JobHandler.CreateJob, useOAuth2);
app.MapGet("/jobs/userjobinfos", JobHandler.GetUserJobInfos, useOAuth2);
Expand Down
100 changes: 85 additions & 15 deletions Src/WitsmlExplorer.Frontend/components/ContentViews/QueryView.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Button, TextField } from "@equinor/eds-core-react";
import React, { useContext, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import styled from "styled-components";
import OperationContext from "../../contexts/operationContext";
import { DispatchOperation } from "../../contexts/operationStateReducer";
import OperationType from "../../contexts/operationType";
import QueryService from "../../services/queryService";
import { Colors } from "../../styles/Colors";
import ConfirmModal from "../Modals/ConfirmModal";
import { StyledNativeSelect } from "../Select";

export enum ReturnElements {
Expand All @@ -16,39 +19,84 @@ export enum ReturnElements {
Requested = "requested"
}

export enum StoreFunction {
GetFromStore = "GetFromStore",
AddToStore = "AddToStore",
DeleteFromStore = "DeleteFromStore",
UpdateInStore = "UpdateInStore"
}

const QueryView = (): React.ReactElement => {
const {
operationState: { colors }
operationState: { colors },
dispatchOperation
} = useContext(OperationContext);
const [query, setQuery] = useState("");
const [query, setQuery] = useState(retrieveStoredQuery());
const [result, setResult] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isXmlResponse, setIsXmlResponse] = useState(false);
const [returnElements, setReturnElements] = useState(ReturnElements.All);

const onChangeReturnElements = (event: any) => {
setReturnElements(event.target.value);
};
const [storeFunction, setStoreFunction] = useState(StoreFunction.GetFromStore);

const sendQuery = () => {
const getResult = async () => {
const getResult = async (dispatchOperation?: DispatchOperation | null) => {
dispatchOperation?.({ type: OperationType.HideModal });
setIsLoading(true);
let response = await QueryService.postQuery(query, returnElements);
const requestReturnElements = storeFunction == StoreFunction.GetFromStore ? returnElements : undefined;
let response = await QueryService.postQuery(query, storeFunction, requestReturnElements);
if (response.startsWith("<")) {
response = formatXml(response);
}
setIsXmlResponse(response.startsWith("<"));
setResult(response);
setIsLoading(false);
};
getResult();
if (storeFunction == StoreFunction.DeleteFromStore) {
displayConfirmation(() => getResult(dispatchOperation), dispatchOperation);
} else {
getResult();
}
};

useEffect(() => {
const dispatch = setTimeout(() => {
try {
localStorage.setItem("queryViewInput", query);
} catch {
/* disregard unavailable local storage */
}
}, 200);
return () => clearTimeout(dispatch);
}, [query]);

return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1rem", height: "100%", padding: "1rem" }}>
<div style={{ display: "grid", gridTemplateRows: "1fr auto", gap: "1rem", height: "100%" }}>
<StyledTextField id="input" multiline colors={colors} onChange={(e: any) => setQuery(e.target.value)} />
<StyledTextField id="input" multiline colors={colors} onChange={(e: any) => setQuery(e.target.value)} defaultValue={query} />
<div style={{ display: "flex", alignItems: "flex-end", gap: "1rem" }}>
<StyledNativeSelect label="Return elements" id="return-elements" onChange={onChangeReturnElements} defaultValue={ReturnElements.All} colors={colors}>
<StyledNativeSelect
label="Function"
id="function"
onChange={(event: any) => setStoreFunction(event.target.value)}
defaultValue={StoreFunction.GetFromStore}
colors={colors}
>
{Object.values(StoreFunction).map((value) => {
return (
<option key={value} value={value}>
{value}
</option>
);
})}
</StyledNativeSelect>
<StyledNativeSelect
label="Return elements"
id="return-elements"
onChange={(event: any) => setReturnElements(event.target.value)}
defaultValue={ReturnElements.All}
colors={colors}
>
{Object.values(ReturnElements).map((value) => {
return (
<option key={value} value={value}>
Expand All @@ -63,13 +111,35 @@ const QueryView = (): React.ReactElement => {
</div>
</div>
<div>
<StyledTextField id="output" multiline colors={colors} readOnly value={result} />
<StyledTextField id="output" multiline colors={colors} readOnly value={result} textWrap={!isXmlResponse} />
</div>
</div>
</>
);
};

const displayConfirmation = (onConfirm: () => void, dispatchOperation: DispatchOperation) => {
const confirmation = (
<ConfirmModal
heading={"Delete object?"}
content={<span>Are you sure you want to delete this object?</span>}
onConfirm={onConfirm}
confirmColor={"danger"}
confirmText={"Delete"}
switchButtonPlaces={true}
/>
);
dispatchOperation({ type: OperationType.DisplayModal, payload: confirmation });
};

const retrieveStoredQuery = () => {
try {
return localStorage.getItem("queryViewInput") ?? "";
} catch {
return "";
}
};

const formatXml = (xml: string) => {
//https://stackoverflow.com/questions/376373/pretty-printing-xml-with-javascript
const xmlDoc = new DOMParser().parseFromString(xml, "application/xml");
Expand All @@ -95,7 +165,7 @@ const formatXml = (xml: string) => {
return new XMLSerializer().serializeToString(resultDoc);
};

const StyledTextField = styled(TextField)<{ colors: Colors }>`
const StyledTextField = styled(TextField)<{ colors: Colors; textWrap?: boolean }>`
border: 1px solid ${(props) => props.colors.interactive.tableBorder};
height: 100%;
&&& > div {
Expand All @@ -107,7 +177,7 @@ const StyledTextField = styled(TextField)<{ colors: Colors }>`
div > textarea {
height: 100%;
overflow: scroll;
text-wrap: nowrap;
text-wrap: ${(props) => (props.textWrap ? "wrap" : "nowrap")};
line-height: 15px;
font-size: 13px;
font-family: monospace;
Expand Down
Loading

0 comments on commit c66933e

Please sign in to comment.