Skip to content

Commit

Permalink
format datetimes
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasbruvik committed Sep 2, 2024
1 parent 0d13ccd commit 1970754
Showing 1 changed file with 133 additions and 31 deletions.
164 changes: 133 additions & 31 deletions Src/WitsmlExplorer.Frontend/components/Modals/LogDataImportModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Accordion, Icon, List } from "@equinor/eds-core-react";
import { Accordion, Icon, List, TextField } from "@equinor/eds-core-react";
import { Button, Tooltip, Typography } from "@mui/material";
import { WITSML_INDEX_TYPE_MD } from "components/Constants";
import {
WITSML_INDEX_TYPE_DATE_TIME,
WITSML_INDEX_TYPE_MD
} from "components/Constants";
import {
ContentTable,
ContentTableColumn,
Expand All @@ -12,6 +15,8 @@ import ModalDialog, { ModalWidth } from "components/Modals/ModalDialog";
import WarningBar from "components/WarningBar";
import { useConnectedServer } from "contexts/connectedServerContext";
import OperationType from "contexts/operationType";
import { parse } from "date-fns";
import { zonedTimeToUtc } from "date-fns-tz";
import { useGetComponents } from "hooks/query/useGetComponents";
import { useOperationState } from "hooks/useOperationState";
import { ComponentType } from "models/componentType";
Expand All @@ -21,7 +26,7 @@ import ObjectReference from "models/jobs/objectReference";
import LogCurveInfo from "models/logCurveInfo";
import LogObject from "models/logObject";
import { toObjectReference } from "models/objectOnWellbore";
import React, { useCallback, useMemo, useState } from "react";
import React, { ChangeEvent, useCallback, useMemo, useState } from "react";
import JobService, { JobType } from "services/jobService";
import styled from "styled-components";
import {
Expand Down Expand Up @@ -73,16 +78,12 @@ const LogDataImportModal = (
>([]);
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [dateTimeFormat, setDateTimeFormat] = useState<string>(null);
const separator = ",";
const hasOverlap = checkOverlap(
targetLog,
uploadedFileColumns,
uploadedFileData,
logCurveInfoList
);

const validate = (fileColumns: ImportColumn[]) => {
const validate = (fileColumns: ImportColumn[], parseError?: string) => {
setError("");
if (parseError) setError(parseError);
if (fileColumns.length) {
if (fileColumns.map((col) => col.name).some((value) => value === ""))
setError(IMPORT_FORMAT_INVALID);
Expand All @@ -91,6 +92,43 @@ const LogDataImportModal = (
}
};

const getParsedData = () => {
if (
uploadedFileData &&
uploadedFileColumns &&
targetLog?.indexType === WITSML_INDEX_TYPE_DATE_TIME
) {
const indexCurveColumn = uploadedFileColumns?.find(
(x) => x.name === targetLog.indexCurve
)?.index;
try {
return parseDateTimeColumn(
uploadedFileData,
indexCurveColumn,
dateTimeFormat
);
} catch (error) {
validate(
uploadedFileColumns,
dateTimeFormat ? `Unable to parse data. ${error}` : null
);
return null;
}
}
return uploadedFileData;
};
const parsedData = useMemo(
() => getParsedData(),
[uploadedFileData, uploadedFileColumns, targetLog, dateTimeFormat]
);

const hasOverlap = checkOverlap(
targetLog,
uploadedFileColumns,
parsedData,
logCurveInfoList
);

const onSubmit = async () => {
setIsLoading(true);

Expand All @@ -99,7 +137,7 @@ const LogDataImportModal = (
targetLog: logReference,
mnemonics: uploadedFileColumns.map((col) => col.name),
units: uploadedFileColumns.map((col) => col.unit),
dataRows: uploadedFileData.map((line) => line.split(separator))
dataRows: parsedData.map((line) => line.split(separator))
};

await JobService.orderJob(JobType.ImportLogData, job);
Expand All @@ -126,6 +164,16 @@ const LogDataImportModal = (
const dataSection = extractLASSection(text, "ASCII", "A");
header = parseLASHeader(curveSection);
data = parseLASData(dataSection);
const indexCurveColumn = header.find(
(x) => x.name === targetLog.indexCurve
)?.index;
if (
targetLog.indexType === WITSML_INDEX_TYPE_DATE_TIME &&
indexCurveColumn !== null
) {
const dateTimeFormat = findDateTimeFormat(data, indexCurveColumn);
setDateTimeFormat(dateTimeFormat);
}
} else {
const headerLine = text.split("\n", 1)[0];
header = parseCSVHeader(headerLine);
Expand Down Expand Up @@ -242,7 +290,7 @@ const LogDataImportModal = (
</Accordion.Panel>
</Accordion.Item>
{uploadedFileColumns?.length &&
uploadedFileData?.length &&
parsedData?.length &&
targetLog?.indexCurve &&
!error && (
<Accordion.Item>
Expand All @@ -259,7 +307,7 @@ const LogDataImportModal = (
showPanel={false}
columns={contentTableColumns}
data={getTableData(
uploadedFileData,
parsedData,
uploadedFileColumns,
targetLog.indexCurve
)}
Expand All @@ -268,6 +316,17 @@ const LogDataImportModal = (
</Accordion.Item>
)}
</Accordion>
{targetLog?.indexType === WITSML_INDEX_TYPE_DATE_TIME &&
!!uploadedFileData?.length && (
<TextField
id="indexCurveFormat"
label="Index Curve Format"
value={dateTimeFormat ?? ""}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setDateTimeFormat(e.target.value);
}}
/>
)}
{hasOverlap && (
<WarningBar message="The import data overlaps existing data. Any overlap will be overwritten!" />
)}
Expand Down Expand Up @@ -303,20 +362,15 @@ const Container = styled.div`

const checkOverlap = (
targetLog: LogObject,
uploadedFileColumns: ImportColumn[],
uploadedFileData: string[],
columns: ImportColumn[],
data: string[],
logCurveInfoList: LogCurveInfo[]
) => {
if (!uploadedFileColumns || !uploadedFileData || !logCurveInfoList)
return false;
const importDataRanges = getDataRanges(
targetLog,
uploadedFileColumns,
uploadedFileData
);
if (!columns || !data || !logCurveInfoList) return false;
const importDataRanges = getDataRanges(targetLog, columns, data);

for (let index = 1; index < uploadedFileColumns.length; index++) {
const mnemonic = uploadedFileColumns[index].name;
for (let index = 1; index < columns.length; index++) {
const mnemonic = columns[index].name;
const logCurveInfo = logCurveInfoList.find(
(lci) => lci.mnemonic === mnemonic
);
Expand Down Expand Up @@ -356,24 +410,28 @@ const checkOverlap = (

const getDataRanges = (
targetLog: LogObject,
uploadedFileColumns: ImportColumn[],
uploadedFileData: string[]
columns: ImportColumn[],
data: string[]
): IndexRange[] => {
const dataRanges: IndexRange[] = [];
const indexCurveColumn = columns.find(
(col) => col.name === targetLog.indexCurve
)?.index;

for (let index = 0; index < uploadedFileColumns.length; index++) {
const firstRowWithData = uploadedFileData.find((dataRow) => {
for (let index = 0; index < columns.length; index++) {
const firstRowWithData = data.find((dataRow) => {
const data = dataRow.split(",")[index];
if (data) return true;
});

const lastRowWithData = uploadedFileData.findLast((dataRow) => {
const lastRowWithData = data.findLast((dataRow) => {
const data = dataRow.split(",")[index];
if (data) return true;
});

const firstRowWithDataIndex = firstRowWithData?.split(",")[0];
const lastRowWithDataIndex = lastRowWithData?.split(",")[0];
const firstRowWithDataIndex =
firstRowWithData?.split(",")[indexCurveColumn];
const lastRowWithDataIndex = lastRowWithData?.split(",")[indexCurveColumn];

if (
targetLog.indexType === WITSML_INDEX_TYPE_MD &&
Expand Down Expand Up @@ -413,4 +471,48 @@ const getTableData = (
});
};

const inputDateFormats: string[] = [
"YYYY-MM-DDTHH:mm:ss.sssZ", // ISO 8601 format
"HH:mm:ss/dd-MMM-yyyy"
];

const findDateTimeFormat = (
data: string[],
selectedColumn: number
): string | null => {
const dateString = data[0].split(",")[selectedColumn];
for (const format of inputDateFormats) {
try {
parseDateFromFormat(dateString, format);
return format;
} catch (error) {
// Ignore error, try next format.
}
}
return null;
};

const parseDateTimeColumn = (
data: string[],
selectedColumn: number,
inputFormat: string
) => {
const dataWithISOTimeColumn = data.map((dataRow) => {
const rowValues = dataRow.split(",");
rowValues[selectedColumn] = parseDateFromFormat(
rowValues[selectedColumn],
inputFormat
);
return rowValues.join(",");
});
return dataWithISOTimeColumn;
};

const parseDateFromFormat = (dateString: string, format: string) => {
const parsed = parse(dateString, format, new Date());
if (parsed.toString() === "Invalid Date")
throw new Error(`Unable to parse date ${dateString} with format ${format}`);
return zonedTimeToUtc(parsed, "UTC").toISOString();
};

export default LogDataImportModal;

0 comments on commit 1970754

Please sign in to comment.