Skip to content

Commit

Permalink
Merge pull request #2222 from jaroslavbliznak/feature/184_Batch_updat…
Browse files Browse the repository at this point in the history
…e_mnemonics

FIX-184 Batch update of mnemonics
  • Loading branch information
jaroslavbliznak authored Feb 19, 2024
2 parents 7069122 + 91cccdc commit b9c191e
Show file tree
Hide file tree
Showing 19 changed files with 783 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Src/Witsml/CommonConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public static class CommonConstants
public const string NewLine = "\n";
public const int DefaultClientRequestTimeOutSeconds = 90;
public const int DefaultReloadIntervalMinutes = 15;
public const string Yes = "Yes";
public const string No = "No";

public static class DepthIndex
{
Expand Down
42 changes: 42 additions & 0 deletions Src/Witsml/Helpers/EnumHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Witsml.Helpers;

/// <summary>
/// Helper class for working with Enum.
/// </summary>
public static class EnumHelper
{
/// <summary>
/// Gets a list of descriptions for all values in the specified Enum.
/// </summary>
/// <typeparam name="TEnum">The Enum type.</typeparam>
/// <returns>A list of string descriptions for all Enum values.</returns>
public static List<string> GetEnumDescriptions<TEnum>() where TEnum : Enum
{
return Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.Select(value => GetEnumDescription(value))
.ToList();
}

/// <summary>
/// Gets the description of a specific Enum value. If the description is not found, then return the enum value.
/// </summary>
/// <param name="value">The Enum value.</param>
/// <typeparam name="TEnum">The Enum type.</typeparam>
/// <returns>The description of the Enum value.</returns>
public static string GetEnumDescription<TEnum>(TEnum value)
where TEnum : Enum
{
var field = typeof(TEnum).GetField(value.ToString());
return field != null &&
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
is DescriptionAttribute descriptionAttribute
? descriptionAttribute.Description
: value.ToString();
}
}
69 changes: 69 additions & 0 deletions Src/WitsmlExplorer.Api/Jobs/BatchModifyLogCurveInfoJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using System.Linq;

using WitsmlExplorer.Api.Jobs.Common;
using WitsmlExplorer.Api.Models;

namespace WitsmlExplorer.Api.Jobs
{
/// <summary>
/// Job for batch modification of logCurveInfo.
/// </summary>
public record BatchModifyLogCurveInfoJob : Job
{
/// <summary>
/// WellboreReference API model.
/// </summary>
public WellboreReference WellboreReference { get; init; }

/// <summary>
/// Edited logCurveInfo API model.
/// </summary>
public LogCurveInfo EditedLogCurveInfo { get; init; }

/// <summary>
/// Collection of logCurveInfos and log Uids API models.
/// </summary>
public ICollection<LogCurveInfoBatchItem> LogCurveInfoBatchItems
{
get;
init;
}

/// <summary>
/// Getting a description of batch-modified LogCurveInfos.
/// </summary>
/// <returns></returns>
public override string Description()
{
return $"To Batch Modify - Uids: {string.Join(", ", LogCurveInfoBatchItems.Select(batchItem => batchItem.LogCurveInfoUid))}";
}

/// <summary>
/// Getting name of logCurveInfo.
/// </summary>
/// <returns>null</returns>
public override string GetObjectName()
{
return null;
}

/// <summary>
/// Getting name of wellbore.
/// </summary>
/// <returns>String of wellbore name.</returns>
public override string GetWellboreName()
{
return WellboreReference.WellboreName;
}

/// <summary>
/// Getting name of well.
/// </summary>
/// <returns>String of well name.</returns>
public override string GetWellName()
{
return WellboreReference.WellName;
}
}
}
3 changes: 2 additions & 1 deletion Src/WitsmlExplorer.Api/Models/JobType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public enum JobType
AnalyzeGaps,
SpliceLogs,
CompareLogData,
CountLogDataRows
CountLogDataRows,
BatchModifyLogCurveInfo
}
}
8 changes: 8 additions & 0 deletions Src/WitsmlExplorer.Api/Models/LogCurveInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@ public class LogCurveInfo
public List<AxisDefinition> AxisDefinitions { get; init; }
public string CurveDescription { get; init; }
public string TypeLogData { get; init; }
public string TraceState { get; init; }
public string NullValue { get; init; }
}

public class LogCurveInfoBatchItem
{
public string LogUid { get; init; }
public string LogCurveInfoUid { get; init; }
}
}
24 changes: 24 additions & 0 deletions Src/WitsmlExplorer.Api/Models/LogTraceState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.ComponentModel;

namespace WitsmlExplorer.Api.Models;

public enum LogTraceState
{
[Description("depth adjusted")]
DepthAdjusted,

[Description("edited")]
Edited,

[Description("joined")]
Joined,

[Description("processed")]
Processed,

[Description("raw")]
Raw,

[Description("unknown")]
Unknown
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace WitsmlExplorer.Api.Models.Reports;

/// <summary>
/// Batch modification report for logCurveInfos.
/// </summary>
public class BatchModifyLogCurveInfoReport : BaseReport
{
}

/// <summary>
/// The item information about a batch modification is extended with LogUid.
/// </summary>
public class BatchModifyLogCurveInfoReportItem : BatchModifyReportItem
{
/// <summary>
/// Log unique identifier.
/// </summary>
public string LogUid { get; init; }
}
2 changes: 2 additions & 0 deletions Src/WitsmlExplorer.Api/Services/LogObjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ public async Task<ICollection<LogCurveInfo>> GetLogCurveInfo(string wellUid, str
Unit = logCurveInfo.Unit,
CurveDescription = logCurveInfo.CurveDescription,
TypeLogData = logCurveInfo.TypeLogData,
TraceState = logCurveInfo.TraceState,
NullValue = logCurveInfo.NullValue,
AxisDefinitions = logCurveInfo.AxisDefinitions?.Select(a => new AxisDefinition()
{
Uid = a.Uid,
Expand Down
153 changes: 153 additions & 0 deletions Src/WitsmlExplorer.Api/Workers/Modify/BatchModifyLogCurveInfoWorker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using Witsml;
using Witsml.Data;
using Witsml.Data.Measures;
using Witsml.Helpers;
using Witsml.ServiceReference;

using WitsmlExplorer.Api.Jobs;
using WitsmlExplorer.Api.Models;
using WitsmlExplorer.Api.Models.Reports;
using WitsmlExplorer.Api.Services;

namespace WitsmlExplorer.Api.Workers.Modify;

/// <summary>
/// Worker for batch modification of LogCurveInfo.
/// </summary>
public class BatchModifyLogCurveInfoWorker : BaseWorker<BatchModifyLogCurveInfoJob>, IWorker
{
public JobType JobType => JobType.BatchModifyLogCurveInfo;

public BatchModifyLogCurveInfoWorker(ILogger<BatchModifyLogCurveInfoJob> logger, IWitsmlClientProvider witsmlClientProvider) : base(witsmlClientProvider, logger) { }

/// <summary>
/// Executes a batch modification job for LogCurveInfoItem properties.
/// </summary>
/// <param name="job">The job model contains batch modification parameters for logCurveInfo.</param>
/// <returns>Task of the worker Result in a report with a result of batch modification.</returns>
public override async Task<(WorkerResult, RefreshAction)> Execute(BatchModifyLogCurveInfoJob job)
{
Verify(job);

Logger.LogInformation("Started {JobType}. {jobDescription}", JobType, job.Description());

WitsmlLogs logHeaders = await GetLogHeaders(job.WellboreReference.WellUid, job.WellboreReference.WellboreUid, job.LogCurveInfoBatchItems.Select(x => x.LogUid).Distinct().ToArray());

IList<(string logUid, WitsmlLogCurveInfo logCurveInfo)> originalLogCurveInfoData = job.LogCurveInfoBatchItems
.SelectMany(batchItem =>
{
WitsmlLog logHeader = logHeaders.Logs.Find(l => l.Uid == batchItem.LogUid);
var curveInfo = logHeader?.LogCurveInfo.FirstOrDefault(c => c.Uid == batchItem.LogCurveInfoUid);
return curveInfo != null ? new[] { (logHeader.Uid, curveInfo) } : Array.Empty<(string, WitsmlLogCurveInfo)>();
})
.ToList();

IList<WitsmlLogs> logCurveInfosToUpdateQueries = originalLogCurveInfoData
.Select(obj => GetModifyLogCurveInfoQuery(job, obj)).ToList();
List<QueryResult> modifyResults = logCurveInfosToUpdateQueries
.Select(async query => await GetTargetWitsmlClientOrThrow().UpdateInStoreAsync(query))
.Select(updateTask => updateTask.Result).ToList();

var report = CreateReport(job, originalLogCurveInfoData, modifyResults);
job.JobInfo.Report = report;

if (modifyResults.Any(result => !result.IsSuccessful))
{
string errorMessage = $"Failed to modify some LogCurveInfos";
var reason = "Inspect the report for details";
Logger.LogError("{ErrorMessage}. {jobDescription}", errorMessage, job.Description());
return (new WorkerResult(GetTargetWitsmlClientOrThrow().GetServerHostname(), false, errorMessage, reason, null, job.JobInfo.Id), null);
}

Logger.LogInformation("{JobType} - Job successful", GetType().Name);
WorkerResult workerResult = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), true, $"The LogCurveInfo properties have been updated in the batch.", jobId: job.JobInfo.Id);
RefreshObjects refreshAction = new(GetTargetWitsmlClientOrThrow().GetServerHostname(), job.WellboreReference.WellUid, job.WellboreReference.WellboreUid, EntityType.Log);
return (workerResult, refreshAction);
}

private BatchModifyLogCurveInfoReport CreateReport(BatchModifyLogCurveInfoJob job, IList<(string logUid, WitsmlLogCurveInfo logCurveInfo)> logCurveInfoData, IList<QueryResult> results)
{
var reportItems = logCurveInfoData.Select((obj, index) => new BatchModifyLogCurveInfoReportItem
{
WellUid = job.WellboreReference.WellUid,
WellboreUid = job.WellboreReference.WellboreUid,
LogUid = obj.logUid,
Uid = obj.logCurveInfo.Uid,
IsSuccessful = results[index].IsSuccessful ? CommonConstants.Yes : CommonConstants.No,
FailureReason = results[index].IsSuccessful ? string.Empty : results[index].Reason
}).ToList();

return new BatchModifyLogCurveInfoReport()
{
Title = "Batch Update LogCurveInfo Report",
Summary = $"Updated {logCurveInfoData.Count} objects",
WarningMessage = results.Any(result => !result.IsSuccessful) ? "Some logCurveInfos were not modified. Inspect the reasons below." : null,
ReportItems = reportItems
};
}

private WitsmlLogs GetModifyLogCurveInfoQuery(BatchModifyLogCurveInfoJob job, (string logUid, WitsmlLogCurveInfo logCurveInfo) originalLogCurveInfoData)
{
if (!string.IsNullOrEmpty(job.EditedLogCurveInfo.TraceState))
{
originalLogCurveInfoData.logCurveInfo.TraceState = job.EditedLogCurveInfo.TraceState;
}

originalLogCurveInfoData.logCurveInfo.SensorOffset = job.EditedLogCurveInfo.SensorOffset?.ToWitsml<WitsmlLengthMeasure>();

if (!string.IsNullOrEmpty(job.EditedLogCurveInfo.NullValue))
{
originalLogCurveInfoData.logCurveInfo.NullValue = job.EditedLogCurveInfo.NullValue;
}

return new()
{
Logs = new List<WitsmlLog>
{
new()
{
UidWell = job.WellboreReference.WellUid,
UidWellbore = job.WellboreReference.WellboreUid,
Uid = originalLogCurveInfoData.logUid,
LogCurveInfo = new List<WitsmlLogCurveInfo>()
{
originalLogCurveInfoData.logCurveInfo
}
}
}
};
}

private async Task<WitsmlLogs> GetLogHeaders(string wellUid, string wellboreUid, string[] logUids)
{
return await LogWorkerTools.GetLogsByIds(GetTargetWitsmlClientOrThrow(), wellUid, wellboreUid, logUids, ReturnElements.HeaderOnly);
}

private void Verify(BatchModifyLogCurveInfoJob job)
{
if (!job.LogCurveInfoBatchItems.Any())
{
throw new InvalidOperationException("LogCurveInfoBatchItems must be specified");
}

if (string.IsNullOrEmpty(job.WellboreReference.WellUid))
{
throw new InvalidOperationException("WellUid cannot be empty");
}

if (string.IsNullOrEmpty(job.WellboreReference.WellboreUid))
{
throw new InvalidOperationException("WellboreUid cannot be empty");
}

ModifyUtils.VerifyMeasure(job.EditedLogCurveInfo.SensorOffset, nameof(job.EditedLogCurveInfo.SensorOffset));
ModifyUtils.VerifyAllowedValues(job.EditedLogCurveInfo.TraceState, EnumHelper.GetEnumDescriptions<LogTraceState>(), nameof(job.EditedLogCurveInfo.TraceState));
}
}
2 changes: 2 additions & 0 deletions Src/WitsmlExplorer.Frontend/__testUtils__/testUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ export function getLogCurveInfo(
curveDescription: "curveDescription",
typeLogData: "typeLogData",
sensorOffset: getMeasure(),
nullValue: "123",
traceState: "raw",
...overrides
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export interface LogCurveInfoRow extends ContentTableRow {
isActive: boolean;
logCurveInfo: LogCurveInfo;
}

export const LogCurveInfoListView = (): React.ReactElement => {
const { navigationState, dispatchNavigation } = useContext(NavigationContext);
const {
Expand Down Expand Up @@ -179,6 +178,8 @@ export const LogCurveInfoListView = (): React.ReactElement => {
unit: logCurveInfo.unit,
sensorOffset: measureToString(logCurveInfo.sensorOffset),
mnemAlias: logCurveInfo.mnemAlias,
traceState: logCurveInfo.traceState,
nullValue: logCurveInfo.nullValue,
logUid: selectedLog.uid,
wellUid: selectedWell.uid,
wellboreUid: selectedWellbore.uid,
Expand Down Expand Up @@ -250,6 +251,8 @@ export const LogCurveInfoListView = (): React.ReactElement => {
type: ContentType.Measure
},
{ property: "mnemAlias", label: "mnemAlias", type: ContentType.String },
{ property: "traceState", label: "traceState", type: ContentType.String },
{ property: "nullValue", label: "nullValue", type: ContentType.String },
{ property: "uid", label: "uid", type: ContentType.String }
];

Expand Down
Loading

0 comments on commit b9c191e

Please sign in to comment.