Skip to content

Commit

Permalink
Merge pull request equinor#366 from aszalos-roland/#173_Export_log_to…
Browse files Browse the repository at this point in the history
…_.csv

equinor#173 Export log to .csv
  • Loading branch information
marmid74 authored May 25, 2021
2 parents 11160b0 + e06f138 commit 47f090c
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React, { useContext, useEffect, useState } from "react";
import React, { useCallback, useContext, useEffect, useState } from "react";
import styled from "styled-components";
import orderBy from "lodash/orderBy";
import LogObjectService from "../../services/logObjectService";
import { truncateAbortHandler } from "../../services/apiClient";
import NavigationContext from "../../contexts/navigationContext";
import { CurveSpecification, LogData, LogDataRow } from "../../models/logData";
import { ContentTableColumn, ContentType, VirtualizedContentTable, ContentTableRow, getIndexRanges } from "./table";
import { ContentType, VirtualizedContentTable, ContentTableRow, ExportableContentTableColumn, Order, getIndexRanges } from "./table";
import { WITSML_INDEX_TYPE_DATE_TIME } from "../Constants";
import { LinearProgress } from "@material-ui/core";
import OperationContext from "../../contexts/operationContext";
import OperationType from "../../contexts/operationType";
import { getContextMenuPosition } from "../ContextMenus/ContextMenu";
import { Button, Grid, LinearProgress } from "@material-ui/core";
import useExport from "../../hooks/useExport";
import MnemonicsContextMenu from "../ContextMenus/MnemonicsContextMenu";
import { LogCurveInfoRow } from "./LogCurveInfoListView";
import LogObject from "../../models/logObject";
import { DeleteLogCurveValuesJob } from "../../models/jobs/deleteLogCurveValuesJob";
import OperationContext from "../../contexts/operationContext";
import LogObject from "../../models/logObject";
import { getContextMenuPosition } from "../ContextMenus/ContextMenu";
import OperationType from "../../contexts/operationType";

interface CurveValueRow extends LogDataRow, ContentTableRow {}

Expand All @@ -37,10 +39,35 @@ export const CurveValuesView = (): React.ReactElement => {
const { navigationState } = useContext(NavigationContext);
const { dispatchOperation } = useContext(OperationContext);
const { selectedWell, selectedWellbore, selectedLog, selectedLogCurveInfo } = navigationState;
const [columns, setColumns] = useState<ContentTableColumn[]>([]);
const [tableData, setTableData] = useState<CurveValueRow[]>();
const [columns, setColumns] = useState<ExportableContentTableColumn<CurveSpecification>[]>([]);
const [tableData, setTableData] = useState<CurveValueRow[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [progress, setProgress] = useState<number>(0);
const [selectedRows, setSelectedRows] = useState<CurveValueRow[]>([]);
const { exportData, properties: exportOptions } = useExport({
fileExtension: ".csv",
newLineCharacter: "\n",
outputMimeType: "text/csv",
separator: ",",
omitSpecialCharactersFromFilename: true,
appendDateTime: true
});

const rowSelectionCallback = useCallback((rows: ContentTableRow[], sortOrder: Order, sortedColumn: string) => {
setSelectedRows(orderBy([...rows.map((row) => row as CurveValueRow)], sortedColumn, sortOrder));
}, []);

const exportSelectedIndexRange = useCallback(() => {
const exportColumns = columns.map((column) => `${column.columnOf.mnemonic}[${column.columnOf.unit}]`).join(exportOptions.separator);
const data = tableData.map((row) => columns.map((col) => row[col.columnOf.mnemonic] as string).join(exportOptions.separator)).join(exportOptions.newLineCharacter);
exportData(`${selectedWellbore.name}-${selectedLog.name}`, exportColumns, data);
}, [columns, tableData]);

const exportSelectedDataPoints = useCallback(() => {
const exportColumns = columns.map((column) => `${column.columnOf.mnemonic}[${column.columnOf.unit}]`).join(exportOptions.separator);
const data = selectedRows.map((row) => columns.map((col) => row[col.columnOf.mnemonic] as string).join(exportOptions.separator)).join(exportOptions.newLineCharacter);
exportData(`${selectedWellbore.name}-${selectedLog.name}`, exportColumns, data);
}, [columns, selectedRows]);

const onContextMenu = (event: React.MouseEvent<HTMLDivElement>, _: CurveValueRow, checkedContentItems: CurveValueRow[]) => {
const deleteLogCurveValuesJob = getDeleteLogCurveValuesJob(selectedLogCurveInfo, checkedContentItems, selectedLog);
Expand Down Expand Up @@ -79,6 +106,7 @@ export const CurveValuesView = (): React.ReactElement => {
.filter((curveSpecification) => isNewMnemonic(curveSpecification.mnemonic))
.map((curveSpecification) => {
return {
columnOf: curveSpecification,
property: curveSpecification.mnemonic,
label: `${curveSpecification.mnemonic} (${curveSpecification.unit})`,
type: getColumnType(curveSpecification)
Expand Down Expand Up @@ -160,18 +188,45 @@ export const CurveValuesView = (): React.ReactElement => {

return (
<Container>
{Boolean(tableData.length) && (
<ExportButtonGrid container spacing={1}>
<Grid item>
{
<Button disabled={isLoading} onClick={() => exportSelectedIndexRange()}>
Download all as .csv
</Button>
}
</Grid>
{Boolean(selectedRows.length) && (
<Grid item>
{
<Button disabled={isLoading} onClick={() => exportSelectedDataPoints()}>
Download selected as .csv
</Button>
}
</Grid>
)}
</ExportButtonGrid>
)}
{isLoading && <LinearProgress variant={"determinate"} value={progress} />}
{!isLoading && !tableData && <Message>No data</Message>}
{columns && tableData?.length > 0 && <VirtualizedContentTable columns={columns} onContextMenu={onContextMenu} data={tableData} checkableRows={true} />}
{!isLoading && !tableData.length && <Message>No data</Message>}
{Boolean(columns.length) && Boolean(tableData.length) && (
<VirtualizedContentTable columns={columns} onRowSelectionChange={rowSelectionCallback} onContextMenu={onContextMenu} data={tableData} checkableRows={true} />
)}
</Container>
);
};

const Container = styled.div`
height: calc(100% - 5px);
height: calc(100% - 55px);
width: 100%;
`;

const ExportButtonGrid = styled(Grid)`
padding-bottom: 10px;
padding-left: 10px;
`;

const Message = styled.div`
margin: 10px;
padding: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const innerGridElementType = forwardRef<HTMLDivElement, any>(({ children, ...res
innerGridElementType.displayName = "innerGridElementType";

export const VirtualizedContentTable = (props: ContentTableProps): React.ReactElement => {
const { columns, onSelect, onContextMenu, checkableRows } = props;
const { columns, onSelect, onContextMenu, checkableRows, onRowSelectionChange } = props;
const [data, setData] = useState<any[]>(props.data ?? []);
const [checkedContentItems, setCheckedContentItems] = useState<ContentTableRow[]>([]);
const [sortOrder, setSortOrder] = useState<Order>(Order.Ascending);
Expand All @@ -143,6 +143,10 @@ export const VirtualizedContentTable = (props: ContentTableProps): React.ReactEl
setData(() => orderBy(props.data, sortedColumn, sortOrder));
}, [props.data, sortOrder, sortedColumn]);

useEffect(() => {
onRowSelectionChange([...checkedContentItems], sortOrder, sortedColumn);
}, [checkedContentItems, sortOrder, sortedColumn]);

const sortByColumn = useCallback(
(columnToSort: string) => {
const isSameColumn = columnToSort === sortedColumn;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React from "react";

export interface ExportableContentTableColumn<T> extends ContentTableColumn {
columnOf: T;
}

export interface ContentTableColumn {
property: string;
label: string;
Expand All @@ -17,6 +21,7 @@ export interface ContentTableProps {
onSelect?: (row: ContentTableRow) => void;
onContextMenu?: (event: React.MouseEvent<HTMLElement, MouseEvent>, selectedItem: Record<string, any>, checkedItems: Record<string, any>[]) => void;
checkableRows?: boolean;
onRowSelectionChange?: (rows: ContentTableRow[], sortOrder: Order, sortedColumn: string) => void;
}

export enum Order {
Expand Down
42 changes: 42 additions & 0 deletions Src/WitsmlExplorer.Frontend/hooks/useExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export interface ExportProperties {
outputMimeType: string;
fileExtension: string;
separator: string;
newLineCharacter: string;
omitSpecialCharactersFromFilename?: boolean;
appendDateTime?: boolean;
}
interface ExportObject {
exportData: (fileName: string, header: string, data: string) => void;
properties: ExportProperties;
}
function omitSpecialCharacters(text: string): string {
return text.replace(/[&/\\#,+()$~%.'":*?<>{}]/g, "_");
}
function appendDateTime(append: boolean): string {
const now = new Date();
return append ? `-${now.getFullYear()}-${now.getMonth()}-${now.getDay()}T${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}` : "";
}
function useExport(props: ExportProperties): ExportObject {
const exportData = (fileName: string, header: string, data: string) => {
const link = document.createElement("a");
link.href = window.URL.createObjectURL(
new Blob([header, props.newLineCharacter, data], {
type: props.outputMimeType
})
);
link.download = props.omitSpecialCharactersFromFilename
? `${omitSpecialCharacters(fileName)}${appendDateTime(props.appendDateTime)}${props.fileExtension}`
: `${fileName}${appendDateTime(props.appendDateTime)}${props.fileExtension}`;
document.body.appendChild(link);
link.click();
//we might not need a timeout for clean up
setTimeout(function () {
window.URL.revokeObjectURL(link.href);
link.remove();
}, 200);
};
return { exportData: exportData, properties: props };
}

export default useExport;
5 changes: 4 additions & 1 deletion Src/WitsmlExplorer.Frontend/styles/material-eds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ const edsTheme = createMuiTheme({
"color": colors.ui.backgroundDefault,
"fontFamily": "EquinorMedium,Arial,sans-serif",
"textTransform": "none",
"fontSize": "1rem"
"fontSize": "1rem",
"&:disabled": {
backgroundColor: colors.interactive.disabledBorder
}
},
containedPrimary: {
"&:hover": {
Expand Down

0 comments on commit 47f090c

Please sign in to comment.