Skip to content

Commit

Permalink
download csv file (microsoft#812)
Browse files Browse the repository at this point in the history
* download csv file

* combine table to csv

* split keyvalue and table, add additional fields

* combine csv and table result.Include layout tables

* format code
  • Loading branch information
starain-pactera authored Dec 17, 2020
1 parent 7f70e20 commit 79c0b22
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"chart.js": "^2.9.3",
"exif-js": "^2.3.0",
"file-type": "^14.6.2",
"jszip": "^3.5.0",
"lodash": "^4.17.20",
"ol": "^5.3.3",
"promise.allsettled": "^1.0.2",
Expand Down
28 changes: 23 additions & 5 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { encryptObject, decryptObject, encrypt, decrypt } from "./crypto";
import UTIF from "utif";
import {constants} from "./constants";
import _ from "lodash";
import JsZip from 'jszip';

// tslint:disable-next-line:no-var-requires
const tagColors = require("../react/components/common/tagColors.json");
Expand Down Expand Up @@ -404,15 +405,32 @@ export function poll(func, timeout, interval): Promise<any> {
* @param fileName
* @param prefix
*/
export function downloadAsJsonFile(data: any, fileName: string, prefix?: string): void {
const predictionData = JSON.stringify(data);
const fileURL = window.URL.createObjectURL(new Blob([predictionData]));
export function downloadFile(data: any, fileName: string, prefix?: string): void {
const fileURL = window.URL.createObjectURL(new Blob([data]));
const fileLink = document.createElement("a");
const fileBaseName = fileName.split(".")[0];
const downloadFileName = prefix + "Result-" + fileBaseName + ".json";
const downloadFileName = prefix + "Result-" + fileName ;

fileLink.href = fileURL;
fileLink.setAttribute("download", downloadFileName);
document.body.appendChild(fileLink);
fileLink.click();
}

export type zipData = {
fileName: string;
data: any;
}

export function downloadZipFile(data: zipData[], fileName: string): void {
const zip = new JsZip();
data.forEach(item => {
zip.file(item.fileName, item.data);
})
zip.generateAsync({type: "blob"}).then(content => {
const fileLink = document.createElement("a");
fileLink.href = window.URL.createObjectURL(content);
fileLink.setAttribute("download", fileName+".zip");
document.body.appendChild(fileLink);
fileLink.click();
});
}
4 changes: 4 additions & 0 deletions src/react/components/common/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
.keep-button-80px {
max-width: 80px;
}
.keep-button-120px {
min-width: 120px;
width: 120px;
}

.container-items-end {
display: flex;
Expand Down
75 changes: 68 additions & 7 deletions src/react/components/pages/prebuiltPredict/layoutPredictPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
Separator,
Spinner,
SpinnerSize,
TooltipHost
TooltipHost,
ContextualMenu,
IContextualMenuProps
} from "@fluentui/react";
import Fill from "ol/style/Fill";
import Stroke from "ol/style/Stroke";
Expand All @@ -22,7 +24,7 @@ import url from "url";
import {constants} from "../../../../common/constants";
import {interpolate, strings} from "../../../../common/strings";
import {getPrimaryGreenTheme, getPrimaryWhiteTheme} from "../../../../common/themes";
import {downloadAsJsonFile, poll} from "../../../../common/utils";
import {downloadFile, poll, zipData, downloadZipFile} from "../../../../common/utils";
import {
ErrorCode,
IApplicationState,
Expand All @@ -42,6 +44,7 @@ import {TableView} from "../editorPage/tableView";
import {ILayoutHelper, LayoutHelper} from "./layoutHelper";
import {ILoadFileHelper, LoadFileHelper} from "./LoadFileHelper";
import {ITableHelper, ITableState, TableHelper} from "./tableHelper";
import _ from "lodash";

interface ILayoutPredictPageProps extends RouteComponentProps {
prebuiltSettings: IPrebuiltSettings;
Expand Down Expand Up @@ -174,7 +177,21 @@ export class LayoutPredictPage extends React.Component<Partial<ILayoutPredictPag

render() {
const analyzeDisabled: boolean = this.getAnalyzeDisabled();

const menuProps: IContextualMenuProps = {
className: "keep-button-120px",
items: [
{
key: 'JSON',
text: 'JSON',
onClick: () => this.onJsonDownloadClick()
},
{
key: 'Table',
text: 'Table',
onClick: () => this.onCSVDownloadClick()
}
]
}
return (
<>
<div
Expand Down Expand Up @@ -249,12 +266,14 @@ export class LayoutPredictPage extends React.Component<Partial<ILayoutPredictPag
<div className="container-items-center container-space-between results-container">
<h5 className="results-header">{strings.layoutPredict.layoutResults}</h5>
<PrimaryButton
className="align-self-end keep-button-80px"
className="align-self-end keep-button-120px"
theme={getPrimaryGreenTheme()}
text={strings.layoutPredict.download}
allowDisabledFocus
autoFocus={true}
onClick={this.onDownloadClick}
onClick={this.onJsonDownloadClick}
menuProps={menuProps}
menuAs={this.getMenu}
/>
</div>
}
Expand Down Expand Up @@ -285,10 +304,48 @@ export class LayoutPredictPage extends React.Component<Partial<ILayoutPredictPag
this.setState({withPageRange, pageRange, pageRangeIsValid});
}

onDownloadClick = () => {
onJsonDownloadClick = () => {
const {layoutData} = this.state;
if (layoutData) {
downloadAsJsonFile(layoutData, this.state.fileLabel, "Layout-");
downloadFile(JSON.stringify(layoutData), this.state.fileLabel+".json", "Layout-");
}
}
onCSVDownloadClick = () => {
const {layoutData} = this.state;
if (layoutData) {
const analyzeResult = layoutData.analyzeResult;
const ocrPageResults = analyzeResult["pageResults"];
const data: zipData[] = [];
for (let i = 0; i < ocrPageResults.length; i++) {
const currentPageResult = ocrPageResults[i];
if (currentPageResult?.tables) {
currentPageResult.tables.forEach((table, index) => {
if (table.cells && table.columns && table.rows) {
let tableContent = "";
let rowIndex = 0;
table.cells.forEach(cell => {
if (cell.rowIndex === rowIndex) {
tableContent += `"${cell.text}"${cell.columnSpan ? _.repeat(',', cell.columnSpan) : ','}`;
}
else {
tableContent += "\n";
tableContent += `"${cell.text}"${cell.columnSpan ? _.repeat(',', cell.columnSpan) : ','}`;
rowIndex = cell.rowIndex;
}
});
if (tableContent.length > 0) {
data.push({
fileName: `Layout-page-${i + 1}-table-${index + 1}.csv`,
data: tableContent
});
}
}
});
}
}
if (data.length > 0) {
downloadZipFile(data, this.state.fileLabel + "tables");
}
}
}

Expand Down Expand Up @@ -585,4 +642,8 @@ export class LayoutPredictPage extends React.Component<Partial<ILayoutPredictPag
ServiceHelper.handleServiceError({...err, endpoint: endpointURL});
}
}

private getMenu(props: IContextualMenuProps): JSX.Element {
return <ContextualMenu {...props} />;
}
}
1 change: 1 addition & 0 deletions src/react/components/pages/predict/predictPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
}
{Object.keys(predictions).length > 0 && this.props.project &&
<PredictResult
downloadPrefix="Analyze"
predictions={predictions}
analyzeResult={this.analyzeResults}
page={this.state.currentPage}
Expand Down
101 changes: 87 additions & 14 deletions src/react/components/pages/predict/predictResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import React from "react";
import {ITag} from "../../../../models/applicationState";
import "./predictResult.scss";
import {getPrimaryGreenTheme} from "../../../../common/themes";
import {PrimaryButton} from "@fluentui/react";
import {PrimaryButton, ContextualMenu, IContextualMenuProps, IIconProps} from "@fluentui/react";
import {strings} from "../../../../common/strings";
import {tagIndexKeys} from "../../common/tagInput/tagIndexKeys";
import {downloadFile, downloadZipFile, zipData} from "../../../../common/utils";

export interface IAnalyzeModelInfo {
docType: string,
Expand Down Expand Up @@ -42,7 +44,21 @@ export default class PredictResult extends React.Component<IPredictResultProps,
}
// not sure if we decide to filter item by the page
const items = Object.values(predictions).filter(Boolean).sort((p1, p2) => p1.displayOrder - p2.displayOrder);

const menuProps: IContextualMenuProps = {
className: "keep-button-120px",
items: [
{
key: 'JSON',
text: 'JSON',
onClick: () => this.triggerJSONDownload()
},
{
key: 'CSV',
text: 'CSV',
onClick: () => this.triggerCSVDownload()
}
]
}
return (
<div>
<div className="container-items-center container-space-between results-container">
Expand All @@ -58,12 +74,13 @@ export default class PredictResult extends React.Component<IPredictResultProps,
:<span></span>
}
<PrimaryButton
className="align-self-end keep-button-80px"
className="align-self-end keep-button-120px"
theme={getPrimaryGreenTheme()}
text="Download"
allowDisabledFocus
autoFocus={true}
onClick={this.triggerDownload}
menuProps={menuProps}
menuAs={this.getMenu}
/>
</div>
{this.props.children}
Expand All @@ -78,6 +95,10 @@ export default class PredictResult extends React.Component<IPredictResultProps,
);
}

private getMenu(props: IContextualMenuProps): JSX.Element {
return <ContextualMenu {...props} />;
}

private renderItem = (item: any, key: any) => {
const postProcessedValue = this.getPostProcessedValue(item);
const style: any = {
Expand Down Expand Up @@ -151,18 +172,70 @@ export default class PredictResult extends React.Component<IPredictResultProps,
this.props.onAddAssetToProject();
}
}
private triggerDownload = (): void => {

private triggerJSONDownload = (): void => {
const {analyzeResult} = this.props;
const predictionData = JSON.stringify(analyzeResult);
const fileURL = window.URL.createObjectURL(new Blob([predictionData]));
const fileLink = document.createElement("a");
const fileBaseName = this.props.downloadResultLabel.split(".")[0];
const downloadFileName = this.props.downloadPrefix + "Result-" + fileBaseName + ".json";

fileLink.href = fileURL;
fileLink.setAttribute("download", downloadFileName);
document.body.appendChild(fileLink);
fileLink.click();
downloadFile(predictionData, this.props.downloadResultLabel + ".json", this.props.downloadPrefix);
}

private triggerCSVDownload = (): void => {
const data: zipData[] = [];
const items = this.getItems();
let csvContent: string = `Key,Value,Confidence,Page,Bounding Box`;
items.forEach(item => {
csvContent += `\n"${item.fieldName}","${item.text ?? ""}",${isNaN(item.confidence)? "NaN":(item.confidence * 100).toFixed(2) + "%"},${item.page},"[${item.boundingBox}]"`;
});
data.push({
fileName: `${this.props.downloadPrefix}${this.props.downloadResultLabel}-keyvalues.csv`,
data: csvContent
});

let tableContent: string = "";
const itemNames=["fieldName","text","confidence","page","boundingBox"];
const getValue=(item:any, fieldName:string)=>{
switch(fieldName){
case "fieldName":
return `"${item[fieldName]}"`;
case "text":
return `"${item[fieldName]}"`;
case "confidence":
return isNaN(item.confidence)? "NaN":(item.confidence * 100).toFixed(2) + "%";
case "page":
return item[fieldName];
case "boundingBox":
return `"[${item.boundingBox}]"`;
default:
return "";
}
}
itemNames.forEach(name=>{
tableContent+=(name+",");
items.forEach(item=>{
tableContent+=(getValue(item,name)+",");
})
tableContent+="\n";
})
data.push({
fileName: `${this.props.downloadPrefix}${this.props.downloadResultLabel}-table.csv`,
data: tableContent
});
downloadZipFile(data, this.props.downloadResultLabel);
}

private getItems() {
const {tags, predictions} = this.props;
const tagsDisplayOrder = tags.map((tag) => tag.name);
for (const name of Object.keys(predictions)) {
const prediction = predictions[name];
if (prediction != null) {
prediction.fieldName = name;
prediction.displayOrder = tagsDisplayOrder.indexOf(name);
}
}
// not sure if we decide to filter item by the page
const items = Object.values(predictions).filter(Boolean).sort((p1, p2) => p1.displayOrder - p2.displayOrder);
return items;
}

private toPercentage = (x: number): string => {
Expand Down
Loading

0 comments on commit 79c0b22

Please sign in to comment.