Skip to content

Commit

Permalink
FIX-1951 implement query view
Browse files Browse the repository at this point in the history
  • Loading branch information
janburak committed Aug 18, 2023
1 parent 7e018b5 commit 1791965
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 41 deletions.
19 changes: 16 additions & 3 deletions Src/Witsml/WitsmlClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.ServiceModel.Security;
using System.Threading.Tasks;
Expand Down Expand Up @@ -179,9 +180,21 @@ private async Task<T> GetFromStoreInnerAsync<T>(T query, OptionsIn optionsIn) wh

public async Task<string> GetFromStoreAsync(string query, OptionsIn optionsIn)
{
XmlDocument xmlQuery = new();
xmlQuery.LoadXml(query);
string type = xmlQuery.FirstChild?.FirstChild?.Name;
string type = "";
try
{
XmlReaderSettings settings = new()
{
IgnoreComments = true,
IgnoreProcessingInstructions = true,
IgnoreWhitespace = true
};
using XmlReader reader = XmlReader.Create(new StringReader(query), settings);
reader.Read();
reader.Read();
type = reader.Name;
}
catch (Exception) { }
if (string.IsNullOrEmpty(type))
{
throw new Exception("Could not determine WITSML type based on query");
Expand Down
37 changes: 37 additions & 0 deletions Src/WitsmlExplorer.Api/HttpHandlers/WitsmlQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

using Witsml.ServiceReference;

using WitsmlExplorer.Api.Extensions;
using WitsmlExplorer.Api.Models;
using WitsmlExplorer.Api.Services;

namespace WitsmlExplorer.Api.HttpHandlers
{
public static class WitsmlQueryHandler
{

[Produces(typeof(string))]
public static async Task<IResult> PostQuery(IWitsmlClientProvider witsmlClientProvider, HttpRequest httpRequest)
{
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 witsmlClientProvider.GetClient().GetFromStoreAsync(query.Body, new OptionsIn(query.ReturnElements));
return TypedResults.Ok(result);
}
catch (Exception e)
{
return TypedResults.Ok(e.Message);
}
}

}
}
13 changes: 13 additions & 0 deletions Src/WitsmlExplorer.Api/Models/WitsmlQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

using Witsml.ServiceReference;

namespace WitsmlExplorer.Api.Models
{
public class WitsmlQuery
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public ReturnElements ReturnElements { get; init; }
public string Body { get; init; }
}
}
2 changes: 2 additions & 0 deletions Src/WitsmlExplorer.Api/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ 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("/jobs/{jobType}", JobHandler.CreateJob, useOAuth2);
app.MapGet("/jobs/userjobinfos", JobHandler.GetUserJobInfos, useOAuth2);
app.MapGet("/jobs/userjobinfo/{jobId}", JobHandler.GetUserJobInfo, useOAuth2);
Expand Down
9 changes: 6 additions & 3 deletions Src/WitsmlExplorer.Frontend/components/ContentView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { ReactElement, useContext, useEffect, useState } from "react";
import styled from "styled-components";
import NavigationContext, { selectedJobsFlag, selectedServerManagerFlag } from "../contexts/navigationContext";
import NavigationContext, { ViewFlags } from "../contexts/navigationContext";
import { ObjectType } from "../models/objectType";
import { BhaRunsListView } from "./ContentViews/BhaRunsListView";
import ChangeLogsListView from "./ContentViews/ChangeLogsListView";
Expand All @@ -15,6 +15,7 @@ import LogsListView from "./ContentViews/LogsListView";
import { MessagesListView } from "./ContentViews/MessagesListView";
import MudLogView from "./ContentViews/MudLogView";
import { MudLogsListView } from "./ContentViews/MudLogsListView";
import QueryView from "./ContentViews/QueryView";
import { RigsListView } from "./ContentViews/RigsListView";
import { RisksListView } from "./ContentViews/RisksListView";
import ServerManager from "./ContentViews/ServerManager";
Expand Down Expand Up @@ -85,9 +86,11 @@ const ContentView = (): React.ReactElement => {
setObjectView(false);
} else if (currentSelected === selectedLogCurveInfo) {
setView(<CurveValuesView />);
} else if (currentSelected === selectedJobsFlag) {
} else if (currentSelected === ViewFlags.Jobs) {
setView(<JobsView />);
} else if (currentSelected === selectedServerManagerFlag) {
} else if (currentSelected === ViewFlags.Query) {
setView(<QueryView />);
} else if (currentSelected === ViewFlags.ServerManager) {
setView(<ServerManager />);
} else {
throw new Error(`No view is implemented for item: ${JSON.stringify(currentSelected)}`);
Expand Down
116 changes: 116 additions & 0 deletions Src/WitsmlExplorer.Frontend/components/ContentViews/QueryView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Button, TextField } from "@equinor/eds-core-react";
import React, { useContext, useState } from "react";
import styled from "styled-components";
import OperationContext from "../../contexts/operationContext";
import QueryService from "../../services/queryService";
import { Colors } from "../../styles/Colors";
import { StyledNativeSelect } from "../Select";

export enum ReturnElements {
All = "all",
IdOnly = "idOnly",
HeaderOnly = "headerOnly",
DataOnly = "dataOnly",
StationLocationOnly = "stationLocationOnly",
LatestChangeOnly = "latestChangeOnly",
Requested = "requested"
}

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

const onChangeReturnElements = (event: any) => {
setReturnElements(event.target.value);
};

const sendQuery = () => {
const getResult = async () => {
setIsLoading(true);
let response = await QueryService.postQuery(query, returnElements);
if (response.startsWith("<")) {
//https://stackoverflow.com/questions/376373/pretty-printing-xml-with-javascript
const xmlDoc = new DOMParser().parseFromString(response, "application/xml");
const xsltDoc = new DOMParser().parseFromString(
[
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">',
' <xsl:strip-space elements="*"/>',
' <xsl:template match="para[content-style][not(text())]">',
' <xsl:value-of select="normalize-space(.)"/>',
" </xsl:template>",
' <xsl:template match="node()|@*">',
' <xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>',
" </xsl:template>",
' <xsl:output indent="yes"/>',
"</xsl:stylesheet>"
].join("\n"),
"application/xml"
);

const xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsltDoc);
const resultDoc = xsltProcessor.transformToDocument(xmlDoc);
response = new XMLSerializer().serializeToString(resultDoc);
}

setResult(response);
setIsLoading(false);
};
getResult();
};

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)} />
<div style={{ display: "flex", alignItems: "flex-end", gap: "1rem" }}>
<StyledNativeSelect label="Return elements" id="return-elements" onChange={onChangeReturnElements} defaultValue={ReturnElements.All} colors={colors}>
{Object.values(ReturnElements).map((value) => {
return (
<option key={value} value={value}>
{value}
</option>
);
})}
</StyledNativeSelect>
<Button onClick={sendQuery} disabled={isLoading}>
Execute
</Button>
</div>
</div>
<div>
<StyledTextField id="output" multiline colors={colors} readOnly value={result} />
</div>
</div>
</>
);
};
const StyledTextField = styled(TextField)<{ colors: Colors }>`
${(props) => `border: 1px solid ${props.colors.interactive.tableBorder};`}
height: 100%;
&&& > div {
${(props) => `background-color: ${props.colors.ui.backgroundLight};`}
height: 100% !important;
border: 0;
box-shadow: none;
}
div > textarea {
height: 100%;
overflow: scroll;
text-wrap: nowrap;
line-height: 15px;
font-size: 13px;
font-family: monospace;
}
div > div {
display: none; /* disable input adornment */
}
`;

export default QueryView;
19 changes: 3 additions & 16 deletions Src/WitsmlExplorer.Frontend/components/Modals/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, NativeSelect } from "@equinor/eds-core-react";
import { Button } from "@equinor/eds-core-react";
import { Typography } from "@material-ui/core";
import React, { useContext } from "react";
import styled from "styled-components";
Expand All @@ -7,10 +7,11 @@ import { TimeZone, UserTheme } from "../../contexts/operationStateReducer";
import OperationType from "../../contexts/operationType";
import { getAccountInfo, msalEnabled, signOut } from "../../msal/MsalAuthProvider";
import AuthorizationService from "../../services/authorizationService";
import { Colors, dark, light } from "../../styles/Colors";
import { dark, light } from "../../styles/Colors";
import Icon from "../../styles/Icons";
import { STORAGE_MODE_KEY, STORAGE_THEME_KEY, STORAGE_TIMEZONE_KEY } from "../Constants";
import { getOffsetFromTimeZone } from "../DateFormatter";
import { StyledNativeSelect } from "../Select";
import ModalDialog from "./ModalDialog";

const timeZoneLabels: Record<TimeZone, string> = {
Expand Down Expand Up @@ -114,18 +115,4 @@ const HorizontalLayout = styled.div`
}
`;

const StyledNativeSelect = styled(NativeSelect)<{ colors: Colors }>`
select {
background: ${(props) => props.colors.text.staticTextFeildDefault};
color: ${(props) => props.colors.text.staticIconsDefault};
option {
background: ${(props) => props.colors.ui.backgroundLight};
color: ${(props) => props.colors.text.staticIconsDefault};
}
}
label {
color: ${(props) => props.colors.text.staticIconsDefault};
}
`;

export { SettingsModal };
4 changes: 2 additions & 2 deletions Src/WitsmlExplorer.Frontend/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useContext, useEffect, useState } from "react";
import styled from "styled-components";
import { NavigationAction } from "../contexts/navigationAction";
import { SelectLogTypeAction, SelectObjectGroupAction, SelectServerAction, SelectWellAction, SelectWellboreAction } from "../contexts/navigationActions";
import NavigationContext, { NavigationState, Selectable, selectedJobsFlag } from "../contexts/navigationContext";
import NavigationContext, { NavigationState, Selectable, ViewFlags } from "../contexts/navigationContext";
import NavigationType from "../contexts/navigationType";
import OperationContext from "../contexts/operationContext";
import { ObjectType, pluralizeObjectType } from "../models/objectType";
Expand Down Expand Up @@ -148,7 +148,7 @@ const getObjectCrumb = (navigationState: NavigationState, dispatch: (action: Nav
};

const getJobsCrumb = (currentSelected: Selectable) => {
return currentSelected == selectedJobsFlag
return currentSelected == ViewFlags.Jobs
? {
name: "Jobs"
}
Expand Down
17 changes: 17 additions & 0 deletions Src/WitsmlExplorer.Frontend/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NativeSelect } from "@equinor/eds-core-react";
import styled from "styled-components";
import { Colors } from "../styles/Colors";

export const StyledNativeSelect = styled(NativeSelect)<{ colors: Colors }>`
select {
background: ${(props) => props.colors.text.staticTextFeildDefault};
color: ${(props) => props.colors.text.staticIconsDefault};
option {
background: ${(props) => props.colors.ui.backgroundLight};
color: ${(props) => props.colors.text.staticIconsDefault};
}
}
label {
color: ${(props) => props.colors.text.staticIconsDefault};
}
`;
9 changes: 9 additions & 0 deletions Src/WitsmlExplorer.Frontend/components/TopRightCornerMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Button } from "@equinor/eds-core-react";
import React, { useContext } from "react";
import styled from "styled-components";
import NavigationContext from "../contexts/navigationContext";
import NavigationType from "../contexts/navigationType";
import OperationContext from "../contexts/operationContext";
import OperationType from "../contexts/operationType";
import useDocumentDimensions from "../hooks/useDocumentDimensions";
Expand Down Expand Up @@ -40,6 +41,10 @@ const TopRightCornerMenu = (): React.ReactElement => {
dispatchOperation({ type: OperationType.DisplayModal, payload: <UserCredentialsModal {...userCredentialsModalProps} /> });
};

const openQueryView = () => {
dispatchNavigation({ type: NavigationType.SelectQueryView, payload: {} });
};

return (
<Layout>
{selectedServer?.currentUsername && (
Expand All @@ -50,6 +55,10 @@ const TopRightCornerMenu = (): React.ReactElement => {
)}
<ServerManagerButton showLabels={showLabels} />
<JobsButton showLabels={showLabels} />
<StyledButton colors={colors} variant={showLabels ? "ghost" : "ghost_icon"} onClick={openQueryView} disabled={!selectedServer}>
<Icon name="code" />
{showLabels && "Query"}
</StyledButton>
<StyledButton colors={colors} variant={showLabels ? "ghost" : "ghost_icon"} onClick={openSettingsMenu}>
<Icon name="settings" />
{showLabels && "Settings"}
Expand Down
2 changes: 2 additions & 0 deletions Src/WitsmlExplorer.Frontend/contexts/navigationAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SelectLogTypeAction,
SelectObjectAction,
SelectObjectGroupAction,
SelectQueryViewAction,
SelectServerAction,
SelectServerManagerAction,
SelectWellAction,
Expand Down Expand Up @@ -55,6 +56,7 @@ export type NavigationAction =
| CollapseTreeNodeChildrenAction
| ExpandTreeNodesAction
| SelectJobsAction
| SelectQueryViewAction
| SelectLogTypeAction
| SelectLogCurveInfoAction
| SelectWellAction
Expand Down
4 changes: 4 additions & 0 deletions Src/WitsmlExplorer.Frontend/contexts/navigationActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export interface SelectJobsAction extends Action {
type: NavigationType.SelectJobs;
}

export interface SelectQueryViewAction extends Action {
type: NavigationType.SelectQueryView;
}

export interface SelectObjectGroupAction extends Action {
type: NavigationType.SelectObjectGroup;
payload: { wellUid: string; wellboreUid: string; objectType: ObjectType; objects: ObjectOnWellbore[] | null };
Expand Down
7 changes: 5 additions & 2 deletions Src/WitsmlExplorer.Frontend/contexts/navigationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ const NavigationContext = createContext<NavigationContextProps>({} as Navigation

export type Selectable = Server | Well | Wellbore | string | BhaRun | LogObject | LogCurveInfoRow[] | Trajectory | MessageObject | RiskObject | Rig | WbGeometryObject;

export const selectedJobsFlag = "jobs";
export const selectedServerManagerFlag = "serverManager";
export enum ViewFlags {
Jobs = "jobs",
Query = "query",
ServerManager = "serverManager"
}

export interface NavigationState {
selectedServer: Server;
Expand Down
Loading

0 comments on commit 1791965

Please sign in to comment.