Skip to content

Commit

Permalink
Merge pull request #2062 from eliasbruvik/FIX-2011
Browse files Browse the repository at this point in the history
FIX-2011 Implement search list view
  • Loading branch information
eliasbruvik authored Oct 3, 2023
2 parents 38463fd + 2061a5a commit f20d5c2
Show file tree
Hide file tree
Showing 24 changed files with 438 additions and 223 deletions.
1 change: 1 addition & 0 deletions Src/WitsmlExplorer.Api/Models/ObjectSearchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ namespace WitsmlExplorer.Api.Models
public class ObjectSearchResult : ObjectOnWellbore
{
public string SearchProperty { get; set; }
public EntityType ObjectType { get; set; }
}
}
18 changes: 5 additions & 13 deletions Src/WitsmlExplorer.Api/Query/ObjectQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,7 @@ public static IEnumerable<T> CopyObjectsQuery<T>(IEnumerable<T> objects, WitsmlW

public static IWitsmlObjectList GetWitsmlObjectsByType(EntityType type)
{
WitsmlObjectOnWellbore o = EntityTypeHelper.ToObjectOnWellbore(type);
o.UidWell = "";
o.UidWellbore = "";
o.Uid = "";
o.Name = "";
return (IWitsmlObjectList)o.AsSingletonWitsmlList();
return GetWitsmlObjectsWithParamByType(type, null, null);
}

public static IWitsmlObjectList GetWitsmlObjectsWithParamByType(EntityType type, string objectProperty, string objectPropertyValue)
Expand All @@ -61,16 +56,13 @@ public static IWitsmlObjectList GetWitsmlObjectsWithParamByType(EntityType type,
o.UidWell = "";
o.UidWellbore = "";
o.Uid = "";
o.NameWell = "";
o.NameWellbore = "";
o.Name = "";
if (objectProperty != null)
{
PropertyInfo property = o.GetType().GetProperty(objectProperty.CapitalizeFirstLetter());
if (property == null || !property.CanWrite)
{
throw new ArgumentException($"{objectProperty} must be a supported property of a {type}.");
}
property.SetValue(o, objectPropertyValue);
}
o = QueryHelper.AddPropertyToObject(o, objectProperty, objectPropertyValue);
};
return (IWitsmlObjectList)o.AsSingletonWitsmlList();
}

Expand Down
19 changes: 11 additions & 8 deletions Src/WitsmlExplorer.Api/Query/QueryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ public static class QueryHelper
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="obj">The object to which properties will be added.</param>
/// <param name="properties">A collection of property names to add, optionally supporting nested properties (e.g., "commonData.sourceName").</param>
/// <param name="properties">A list of property names to add, optionally supporting nested properties (e.g., "commonData.sourceName").</param>
/// <param name="propertyValues">A list of property values to add, corresponding to the element in properties.</param>
/// <returns>The modified object with added properties.</returns>
public static T AddPropertiesToObject<T>(T obj, IEnumerable<string> properties)
public static T AddPropertiesToObject<T>(T obj, IList<string> properties, IList<object> propertyValues = null)
{
foreach (string property in properties)
for (var i = 0; i < properties.Count; i++)
{
obj = AddPropertyToObject(obj, property);
obj = AddPropertyToObject(obj, properties[i], propertyValues?[i]);
}
return obj;
}
Expand All @@ -31,8 +32,9 @@ public static T AddPropertiesToObject<T>(T obj, IEnumerable<string> properties)
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="obj">The object to which the property will be added.</param>
/// <param name="property">The name of the property to add, optionally supporting nested properties (e.g., "commonData.sourceName").</param>
/// /// <param name="propertyValue">The value property should be set to.</param>
/// <returns>The modified object with the added property.</returns>
public static T AddPropertyToObject<T>(T obj, string property)
public static T AddPropertyToObject<T>(T obj, string property, object propertyValue = null)
{
string childProperty = null;
if (property.Contains('.'))
Expand All @@ -41,6 +43,7 @@ public static T AddPropertyToObject<T>(T obj, string property)
property = propertyParts[0];
childProperty = propertyParts[1];
}
var isNested = !string.IsNullOrEmpty(childProperty);

PropertyInfo propertyInfo = obj.GetType().GetProperty(property.CapitalizeFirstLetter());

Expand All @@ -49,11 +52,11 @@ public static T AddPropertyToObject<T>(T obj, string property)
throw new ArgumentException($"{property} must be a supported property of a {obj.GetType()}.");
}

object instance = GetOrCreateInstanceOfProperty(obj, propertyInfo);
object instance = (!isNested && propertyValue != null) ? propertyValue : GetOrCreateInstanceOfProperty(obj, propertyInfo);

if (!string.IsNullOrEmpty(childProperty))
if (isNested)
{
instance = AddPropertyToObject(instance, childProperty);
instance = AddPropertyToObject(instance, childProperty, propertyValue);
}

propertyInfo.SetValue(obj, instance);
Expand Down
1 change: 1 addition & 0 deletions Src/WitsmlExplorer.Api/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static void ConfigureApi(this WebApplication app, IConfiguration configur
app.MapGet("/wells/{wellUid}", WellHandler.GetWell, useOAuth2);

app.MapGet("/objects/{objectType}", ObjectHandler.GetObjectsByType, useOAuth2);
app.MapGet("/objects/{objectType}/{objectProperty}", ObjectHandler.GetObjectsWithParamByType, useOAuth2);
app.MapGet("/objects/{objectType}/{objectProperty}/{objectPropertyValue}", ObjectHandler.GetObjectsWithParamByType, useOAuth2);

app.MapGet("/wells/{wellUid}/wellbores/{wellboreUid}", WellboreHandler.GetWellbore, useOAuth2);
Expand Down
22 changes: 14 additions & 8 deletions Src/WitsmlExplorer.Api/Services/ObjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;

using Witsml.Data;
using Witsml.ServiceReference;
Expand Down Expand Up @@ -55,6 +56,8 @@ public async Task<IEnumerable<ObjectSearchResult>> GetObjectsByType(EntityType o
WellboreUid = obj.UidWellbore,
WellUid = obj.UidWell,
Name = obj.Name,
WellboreName = obj.NameWellbore,
WellName = obj.NameWell
}
);
}
Expand All @@ -65,8 +68,12 @@ public async Task<IEnumerable<ObjectSearchResult>> GetObjectsWithParamByType(Ent
{
throw new ArgumentException($"{nameof(objectType)} must be a valid type of an object on wellbore");
}
else if (objectProperty == null)
{
throw new ArgumentException("objectProperty cannot be null!");
}

if (objectProperty != null)
if (!objectPropertyValue.IsNullOrEmpty())
{
// send a request to see if the server is capable of searching by the property.
IWitsmlObjectList capabilityQuery = (IWitsmlObjectList)EntityTypeHelper.ToObjectOnWellbore(objectType).AsSingletonWitsmlList();
Expand Down Expand Up @@ -95,17 +102,16 @@ public async Task<IEnumerable<ObjectSearchResult>> GetObjectsWithParamByType(Ent
Uid = obj.Uid,
WellboreUid = obj.UidWellbore,
WellUid = obj.UidWell,
Name = obj.Name
Name = obj.Name,
WellboreName = obj.NameWellbore,
WellName = obj.NameWell,
ObjectType = objectType
};
if (objectProperty != null)
{
PropertyInfo witsmlProperty = obj.GetType().GetProperty(objectProperty.CapitalizeFirstLetter());
if (witsmlProperty != null)
{
string propertyValue = witsmlProperty.GetValue(obj)?.ToString();
searchResult.SearchProperty = propertyValue;
}
string propertyValue = (string)QueryHelper.GetPropertyFromObject(obj, objectProperty);
searchResult.SearchProperty = propertyValue;
}
return searchResult;
Expand Down
10 changes: 5 additions & 5 deletions Src/WitsmlExplorer.Api/Workers/MissingDataWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private async Task<IEnumerable<MissingDataReportItem>> CheckWell(MissingDataChec
Uid = wellReference.WellUid,
Name = "",
};
QueryHelper.AddPropertiesToObject(well, check.Properties);
QueryHelper.AddPropertiesToObject(well, check.Properties.ToList());
return well;
}).ToList()
};
Expand All @@ -129,7 +129,7 @@ private async Task<List<MissingDataReportItem>> CheckWellbore(MissingDataCheck c
Name = "",
NameWell = "",
};
QueryHelper.AddPropertiesToObject(wellbore, check.Properties);
QueryHelper.AddPropertiesToObject(wellbore, check.Properties.ToList());
return wellbore;
}).ToList()
: wellReferences.Select(wellReference =>
Expand All @@ -141,7 +141,7 @@ private async Task<List<MissingDataReportItem>> CheckWellbore(MissingDataCheck c
Name = "",
NameWell = "",
};
QueryHelper.AddPropertiesToObject(wellbore, check.Properties);
QueryHelper.AddPropertiesToObject(wellbore, check.Properties.ToList());
return wellbore;
}).ToList()
};
Expand All @@ -166,7 +166,7 @@ private async Task<List<MissingDataReportItem>> CheckObject(MissingDataCheck che
o.NameWellbore = "";
o.UidWell = wellboreReference.WellUid;
o.NameWell = "";
QueryHelper.AddPropertiesToObject(o, check.Properties);
QueryHelper.AddPropertiesToObject(o, check.Properties.ToList());
return o;
}).ToList()
: wellReferences.Select(wellReference =>
Expand All @@ -178,7 +178,7 @@ private async Task<List<MissingDataReportItem>> CheckObject(MissingDataCheck che
o.NameWellbore = "";
o.UidWell = wellReference.WellUid;
o.NameWell = "";
QueryHelper.AddPropertiesToObject(o, check.Properties);
QueryHelper.AddPropertiesToObject(o, check.Properties.ToList());
return o;
}).ToList();

Expand Down
1 change: 1 addition & 0 deletions Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export function getObjectSearchResult(overrides?: Partial<ObjectSearchResult>):
return {
...getObjectOnWellbore(),
searchProperty: "searchProperty",
objectType: ObjectType.Log,
...overrides
};
}
Expand Down
3 changes: 3 additions & 0 deletions Src/WitsmlExplorer.Frontend/components/ContentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ObjectSearchListView from "./ContentViews/ObjectSearchListView";
import QueryView from "./ContentViews/QueryView";
import { RigsListView } from "./ContentViews/RigsListView";
import { RisksListView } from "./ContentViews/RisksListView";
Expand Down Expand Up @@ -92,6 +93,8 @@ const ContentView = (): React.ReactElement => {
setView(<QueryView />);
} else if (currentSelected === ViewFlags.ServerManager) {
setView(<ServerManager />);
} else if (currentSelected === ViewFlags.ObjectSearchView) {
setView(<ObjectSearchListView />);
} else {
throw new Error(`No view is implemented for item: ${JSON.stringify(currentSelected)}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Typography } from "@equinor/eds-core-react";
import React, { useContext, useEffect, useState } from "react";
import { FilterContext, filterTypeToProperty, getFilterTypeInformation, getSearchRegex, isObjectFilterType, isSitecomSyntax } from "../../contexts/filter";
import NavigationContext from "../../contexts/navigationContext";
import NavigationType from "../../contexts/navigationType";
import OperationContext from "../../contexts/operationContext";
import OperationType from "../../contexts/operationType";
import LogObject from "../../models/logObject";
import ObjectOnWellbore from "../../models/objectOnWellbore";
import { ObjectType } from "../../models/objectType";
import Well from "../../models/well";
import Wellbore, { calculateLogTypeId } from "../../models/wellbore";
import ObjectService from "../../services/objectService";
import { getContextMenuPosition } from "../ContextMenus/ContextMenu";
import { ObjectTypeToContextMenu } from "../ContextMenus/ContextMenuMapping";
import LoadingContextMenu from "../ContextMenus/LoadingContextMenu";
import { ObjectContextMenuProps } from "../ContextMenus/ObjectMenuItems";
import { ContentTable, ContentTableColumn, ContentTableRow, ContentType } from "./table";

export interface ObjectSearchRow extends ContentTableRow, ObjectOnWellbore {
object: ObjectOnWellbore;
well: Well;
wellbore: Wellbore;
objectType: ObjectType;
}

export const ObjectSearchListView = (): React.ReactElement => {
const {
navigationState: { wells },
dispatchNavigation
} = useContext(NavigationContext);
const { dispatchOperation } = useContext(OperationContext);
const { selectedFilter } = useContext(FilterContext);
const [rows, setRows] = useState<ObjectSearchRow[]>([]);

useEffect(() => {
if (isObjectFilterType(selectedFilter.filterType)) {
const regex = getSearchRegex(selectedFilter.name);
setRows(
selectedFilter.searchResults
.filter((searchResult) => isSitecomSyntax(selectedFilter.name) || regex.test(searchResult.searchProperty)) // If we later want to filter away empty results, use regex.test(searchResult.searchProperty ?? ""))
.map((searchResult, index) => {
const well = wells?.find((w) => w.uid == searchResult.wellUid);
const wellbore = well?.wellbores?.find((wb) => wb.uid == searchResult.wellboreUid);
return {
id: index,
...searchResult,
[filterTypeToProperty[selectedFilter.filterType]]: searchResult.searchProperty,
object: searchResult,
well,
wellbore
};
})
);
}
}, [selectedFilter]);

const fetchSelectedObject = async (checkedObjectRow: ObjectSearchRow) => {
return await ObjectService.getObject(checkedObjectRow.wellUid, checkedObjectRow.wellboreUid, checkedObjectRow.uid, checkedObjectRow.objectType);
};

const onContextMenu = async (event: React.MouseEvent<HTMLLIElement>, {}, checkedObjectRows: ObjectSearchRow[]) => {
const position = getContextMenuPosition(event);
dispatchOperation({ type: OperationType.DisplayContextMenu, payload: { component: <LoadingContextMenu />, position } });
const wellbore = checkedObjectRows[0].wellbore;
const objectType = checkedObjectRows[0].objectType;
const fetchedObject = await fetchSelectedObject(checkedObjectRows[0]);
const contextProps: ObjectContextMenuProps = { checkedObjects: [fetchedObject], wellbore };
const component = ObjectTypeToContextMenu[objectType];
if (component) {
dispatchOperation({ type: OperationType.DisplayContextMenu, payload: { component: React.createElement(component, { ...contextProps }), position } });
}
};

const getColumns = () => {
const columns: ContentTableColumn[] = [
{ property: "objectType", label: "objectType", type: ContentType.String },
{ property: "name", label: "name", type: ContentType.String },
{ property: "wellboreName", label: "wellboreName", type: ContentType.String },
{ property: "wellName", label: "wellName", type: ContentType.String },
{ property: "uid", label: "uid", type: ContentType.String },
{ property: "wellboreUid", label: "wellboreUid", type: ContentType.String },
{ property: "wellUid", label: "wellUid", type: ContentType.String }
];

if (filterTypeToProperty[selectedFilter?.filterType] != "name") {
columns.unshift({ property: filterTypeToProperty[selectedFilter?.filterType], label: filterTypeToProperty[selectedFilter?.filterType], type: ContentType.String });
}

return columns;
};

const onSelect = async (row: ObjectSearchRow) => {
const objects = await ObjectService.getObjects(row.wellUid, row.wellboreUid, row.objectType);
if (row.objectType == ObjectType.Log) {
const logTypeGroup = calculateLogTypeId(row.wellbore, (objects.find((o) => o.uid == row.uid) as LogObject)?.indexType);
row.wellbore.logs = objects;
dispatchNavigation({
type: NavigationType.SelectLogType,
payload: { well: row.well, wellbore: row.wellbore, logTypeGroup: logTypeGroup }
});
} else {
dispatchNavigation({
type: NavigationType.SelectObjectGroup,
payload: { objectType: row.objectType, wellUid: row.wellUid, wellboreUid: row.wellboreUid, objects }
});
}
};

return rows.length == 0 ? (
<Typography style={{ padding: "1rem", whiteSpace: "pre-line" }}>{getFilterTypeInformation(selectedFilter.filterType)}</Typography>
) : (
<ContentTable
viewId="objectOnWellboreListView"
columns={getColumns()}
onSelect={onSelect}
data={rows}
onContextMenu={onContextMenu}
downloadToCsvFileName={`${selectedFilter.filterType}_search`}
/>
);
};

export default ObjectSearchListView;
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ export const ContentTable = React.memo((contentTableProps: ContentTableProps): R
onColumnVisibilityChange: setColumnVisibility,
onColumnSizingChange: setColumnSizing,
onRowSelectionChange: (updaterOrValue) => {
const newRowSelection = updaterOrValue instanceof Function ? updaterOrValue(rowSelection) : updaterOrValue;
const prevSelection = checkableRows ? rowSelection : {};
let newRowSelection = updaterOrValue instanceof Function ? updaterOrValue(prevSelection) : updaterOrValue;
if (!checkableRows && Object.keys(newRowSelection).length == 0) newRowSelection = rowSelection;
setRowSelection(newRowSelection);
onRowSelectionChange?.(data.filter((_, index) => newRowSelection[index]));
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ObjectType } from "../../models/objectType";
import BhaRunContextMenu from "./BhaRunContextMenu";
import FluidsReportContextMenu from "./FluidsReportContextMenu";
import FormationMarkerContextMenu from "./FormationMarkerContextMenu";
import LogObjectContextMenu from "./LogObjectContextMenu";
import MessageObjectContextMenu from "./MessageObjectContextMenu";
import MudLogContextMenu from "./MudLogContextMenu";
import { ObjectContextMenuProps } from "./ObjectMenuItems";
import RigContextMenu from "./RigContextMenu";
import RiskObjectContextMenu from "./RiskContextMenu";
import TrajectoryContextMenu from "./TrajectoryContextMenu";
import TubularContextMenu from "./TubularContextMenu";
import WbGeometryObjectContextMenu from "./WbGeometryContextMenu";

export const ObjectTypeToContextMenu: Record<ObjectType, React.ComponentType<ObjectContextMenuProps> | null> = {
[ObjectType.BhaRun]: BhaRunContextMenu,
[ObjectType.ChangeLog]: null,
[ObjectType.FluidsReport]: FluidsReportContextMenu,
[ObjectType.FormationMarker]: FormationMarkerContextMenu,
[ObjectType.Log]: LogObjectContextMenu,
[ObjectType.Message]: MessageObjectContextMenu,
[ObjectType.MudLog]: MudLogContextMenu,
[ObjectType.Rig]: RigContextMenu,
[ObjectType.Risk]: RiskObjectContextMenu,
[ObjectType.Trajectory]: TrajectoryContextMenu,
[ObjectType.Tubular]: TubularContextMenu,
[ObjectType.WbGeometry]: WbGeometryObjectContextMenu
};
Loading

0 comments on commit f20d5c2

Please sign in to comment.