* feat: enable re-run OCR (draft)

* add rerun OCR for single file functionality

* added tooltip

* add icon and changed position

* added - rerun OCR for all documents in project

* fix: remove idents

* feat: enable re-run OCR (draft)

* add rerun OCR for single file functionality

* added tooltip

* add icon and changed position

* added - rerun OCR for all documents in project

* refactor: delete redundant local state and rename

* fix: indent

* style: change submenu text

* docs: added manual test runbook

* style: idents

* refactor: rename dropdown class name

* renaming: re-run.. to run, files to documents..

* fix: style and grammar

* fix: run on not visited

* docs: new Test Runbook revision

* style: fix wording and spacing

* refactor: use anonymous wrapper function for onclick

Co-authored-by: kunzheng <58841788+kunzms@users.noreply.github.com>
Co-authored-by: Robert Stewart (eXcell <v-stewro@microsoft.com>
This commit is contained in:
alex-krasn 2020-06-11 11:14:07 -07:00 коммит произвёл GitHub
Родитель 7c57de9656
Коммит cbe9b0ed1c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 403 добавлений и 312 удалений

Просмотреть файл

@ -0,0 +1,24 @@
# Test Runbook
## **Enable rerun OCR for current or all documents**
> ### Feature description ###
Add the following buttons to the canvas command bar:
- "Run OCR on current document"
- "Run OCR on all documents"
> ### Use Case ###
**`As`** a user
**`I want`** to rerun OCR on documents
**`So`** I can update OCR results
> ### Acceptance criteria ###
#### Scenario One ####
**`Given`** I've opened a project containing documents and I'm on the Tag Editor page.
**`When`** I click "Run OCR on current document" in the canvas command bar.
**`Then`** I should see "Running OCR..." for the current docucment. When running OCR finishes, I should be able to view the document's updated OCR JSON file.
#### **Scenario Two** ####
**`Given`** I've opened a project containing documents and I'm on the Tag Editor page.
**`When`** I click "Run OCR on all documents" in the canvas command bar.
**`Then`** I should see "Running OCR..." for all documents. When running OCR finishes for each document, I should be ale to view each document's updated OCR JSON file.

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,190 +1,202 @@
{ {
"fontName": "fabric-icons", "fontName": "fabric-icons",
"fontFamilyName": "FabricMDL2Icons", "fontFamilyName": "FabricMDL2Icons",
"excludeGlyphs": false, "excludeGlyphs": false,
"excludeThirdPartyIcons": false, "excludeThirdPartyIcons": false,
"chunkSubsets": false, "chunkSubsets": false,
"hashFontFileName": true, "hashFontFileName": true,
"glyphs": [ "glyphs": [
{ {
"name": "Insights", "name": "Table",
"unicode": "E3AF" "unicode": "ED86"
}, },
{ {
"name": "MachineLearning", "name": "TextField",
"unicode": "E3B8" "unicode": "EDC3"
}, },
{ {
"name": "TagGroup", "name": "TextDocument",
"unicode": "E3F6" "unicode": "F029"
}, },
{ {
"name": "ChevronDown", "name": "DocumentManagement",
"unicode": "E70D" "unicode": "EFFC"
}, },
{ {
"name": "ChevronUp", "name": "OpenFolderHorizontal",
"unicode": "E70E" "unicode": "ED25"
}, },
{ {
"name": "Edit", "name": "Info",
"unicode": "E70F" "unicode": "E946"
}, },
{ {
"name": "Add", "name": "Label",
"unicode": "E710" "unicode": "E932"
}, },
{ {
"name": "Cancel", "name": "Documentation",
"unicode": "E711" "unicode": "EC17"
}, },
{ {
"name": "Settings", "name": "AddTo",
"unicode": "E713" "unicode": "ECC8"
}, },
{ {
"name": "Link", "name": "Hide3",
"unicode": "E71B" "unicode": "F6AC"
}, },
{ {
"name": "Filter", "name": "WarningSolid",
"unicode": "E71C" "unicode": "F736"
}, },
{ {
"name": "ZoomOut", "name": "BranchMerge",
"unicode": "E71F" "unicode": "F295"
}, },
{ {
"name": "Search", "name": "PlugConnected",
"unicode": "E721" "unicode": "F302"
}, },
{ {
"name": "CheckboxComposite", "name": "Plug",
"unicode": "E73A" "unicode": "F300"
}, },
{ {
"name": "CheckMark", "name": "AlertSolid",
"unicode": "E73E" "unicode": "F331"
}, },
{ {
"name": "Up", "name": "Refresh",
"unicode": "E74A" "unicode": "E72C"
}, },
{ {
"name": "Down", "name": "CheckboxComposite",
"unicode": "E74B" "unicode": "E73A"
}, },
{ {
"name": "Delete", "name": "Cancel",
"unicode": "E74D" "unicode": "E711"
}, },
{ {
"name": "Cloud", "name": "More",
"unicode": "E753" "unicode": "E712"
}, },
{ {
"name": "ChevronLeft", "name": "Settings",
"unicode": "E76B" "unicode": "E713"
}, },
{ {
"name": "ChevronRight", "name": "Link",
"unicode": "E76C" "unicode": "E71B"
}, },
{ {
"name": "Home", "name": "Filter",
"unicode": "E80F" "unicode": "E71C"
}, },
{ {
"name": "MapLayers", "name": "ZoomOut",
"unicode": "E81E" "unicode": "E71F"
}, },
{ {
"name": "View", "name": "Search",
"unicode": "E890" "unicode": "E721"
}, },
{ {
"name": "Download", "name": "Add",
"unicode": "E896" "unicode": "E710"
}, },
{ {
"name": "Help", "name": "CheckMark",
"unicode": "E897" "unicode": "E73E"
}, },
{ {
"name": "ZoomIn", "name": "ChevronLeft",
"unicode": "E8A3" "unicode": "E76B"
}, },
{ {
"name": "Rename", "name": "ChevronRight",
"unicode": "E8AC" "unicode": "E76C"
}, },
{ {
"name": "Copy", "name": "Up",
"unicode": "E8C8" "unicode": "E74A"
}, },
{ {
"name": "Tag", "name": "Down",
"unicode": "E8EC" "unicode": "E74B"
}, },
{ {
"name": "Label", "name": "Delete",
"unicode": "E932" "unicode": "E74D"
}, },
{ {
"name": "Info", "name": "Cloud",
"unicode": "E946" "unicode": "E753"
}, },
{ {
"name": "AddTo", "name": "Edit",
"unicode": "ECC8" "unicode": "E70F"
}, },
{ {
"name": "OpenFolderHorizontal", "name": "ChevronUp",
"unicode": "ED25" "unicode": "E70E"
}, },
{ {
"name": "Table", "name": "ChevronDown",
"unicode": "ED86" "unicode": "E70D"
}, },
{ {
"name": "TextField", "name": "Copy",
"unicode": "EDC3" "unicode": "E8C8"
}, },
{ {
"name": "DocumentManagement", "name": "ZoomIn",
"unicode": "EFFC" "unicode": "E8A3"
}, },
{ {
"name": "TextDocument", "name": "Rename",
"unicode": "F029" "unicode": "E8AC"
}, },
{ {
"name": "BranchMerge", "name": "Tag",
"unicode": "F295" "unicode": "E8EC"
}, },
{ {
"name": "Plug", "name": "Download",
"unicode": "F300" "unicode": "E896"
}, },
{ {
"name": "PlugConnected", "name": "View",
"unicode": "F302" "unicode": "E890"
}, },
{ {
"name": "AlertSolid", "name": "Help",
"unicode": "F331" "unicode": "E897"
}, },
{ {
"name": "Hide3", "name": "Home",
"unicode": "F6AC" "unicode": "E80F"
}, },
{ {
"name": "WarningSolid", "name": "MapLayers",
"unicode": "F736" "unicode": "E81E"
}, },
{ {
"name": "BookAnswers", "name": "Insights",
"unicode": "F8A4" "unicode": "E3AF"
} },
] {
} "name": "MachineLearning",
"unicode": "E3B8"
},
{
"name": "TagGroup",
"unicode": "E3F6"
},
{
"name": "BookAnswers",
"unicode": "F8A4"
}
]
}

Просмотреть файл

@ -86,7 +86,7 @@ export class AssetPreview extends React.Component<IAssetPreviewProps, IAssetPrev
public render() { public render() {
const { loaded, hasError } = this.state; const { loaded, hasError } = this.state;
const size = this.props.asset.size; const { size } = this.props.asset;
const classNames = ["asset-preview"]; const classNames = ["asset-preview"];
if (size) { if (size) {
if (size.width > size.height) { if (size.width > size.height) {

Просмотреть файл

@ -6,7 +6,7 @@
height: 100%; height: 100%;
padding-bottom: 0px; padding-bottom: 0px;
} }
.map { .map {
background-color: Gainsboro; background-color: Gainsboro;
width: 100%; width: 100%;
@ -57,7 +57,7 @@
color: white; color: white;
text-shadow: text-shadow:
1px 1px 0 #000, 1px 1px 0 #000,
-1px -1px 0 #000, -1px -1px 0 #000,
1px -1px 0 #000, 1px -1px 0 #000,
-1px 1px 0 #000, -1px 1px 0 #000,
1px 1px 0 #000; 1px 1px 0 #000;
@ -86,3 +86,8 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.additional-action-dropdown {
margin-left: 0.5rem;
margin-right: -1rem;
}

Просмотреть файл

@ -52,6 +52,7 @@ export interface ICanvasProps extends React.Props<Canvas> {
onCanvasRendered?: (canvas: HTMLCanvasElement) => void; onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
onRunningOCRStatusChanged?: (isRunning: boolean) => void; onRunningOCRStatusChanged?: (isRunning: boolean) => void;
onTagChanged?: (oldTag: ITag, newTag: ITag) => void; onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
runOcrForAllDocs?: (runForAllDocs:boolean) => void;
} }
export interface ICanvasState { export interface ICanvasState {
@ -212,6 +213,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
handleZoomIn={this.handleCanvasZoomIn} handleZoomIn={this.handleCanvasZoomIn}
handleZoomOut={this.handleCanvasZoomOut} handleZoomOut={this.handleCanvasZoomOut}
layers={this.state.layers} layers={this.state.layers}
handleRunOcr={this.runOcr}
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
/> />
<ImageMap <ImageMap
ref={(ref) => this.imageMap = ref} ref={(ref) => this.imageMap = ref}
@ -285,6 +288,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
); );
} }
private runOcrForAllDocuments = () => {
this.setState({ocrStatus: OcrStatus.runningOCR})
this.props.runOcrForAllDocs(true);
}
public updateSize() { public updateSize() {
this.imageMap.updateSize(); this.imageMap.updateSize();
} }
@ -938,14 +946,19 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
}); });
} }
private loadOcr = async () => { private runOcr = () => {
this.loadOcr(true);
}
private loadOcr = async (force?: boolean) => {
const asset = this.state.currentAsset.asset; const asset = this.state.currentAsset.asset;
if (asset.isRunningOCR) { if (asset.isRunningOCR) {
// Skip loading OCR this time since it's running. This will be triggered again once it's finished. // Skip loading OCR this time since it's running. This will be triggered again once it's finished.
return; return;
} }
try { try {
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus); const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus, force);
if (asset.id === this.state.currentAsset.asset.id) { if (asset.id === this.state.currentAsset.asset.id) {
// since get OCR is async, we only set currentAsset's OCR // since get OCR is async, we only set currentAsset's OCR
this.setState({ this.setState({

Просмотреть файл

@ -6,6 +6,8 @@ import { getDarkGreyTheme } from "../../../../common/themes";
interface ICanvasCommandBarProps { interface ICanvasCommandBarProps {
handleZoomIn: () => void; handleZoomIn: () => void;
handleZoomOut: () => void; handleZoomOut: () => void;
handleRunOcr: () => void;
handleRunOcrForAllDocuments: () => void;
handleLayerChange: (layer: string) => void; handleLayerChange: (layer: string) => void;
layers: any; layers: any;
} }
@ -81,6 +83,29 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
iconProps: { iconName: "ZoomIn" }, iconProps: { iconName: "ZoomIn" },
onClick: () => props.handleZoomIn(), onClick: () => props.handleZoomIn(),
}, },
{
key: "additionalActions",
title: "Additional actions",
ariaLabel: "Additional actions",
className: "additional-action-dropdown",
iconProps: { iconName: "More" },
subMenuProps: {
items: [
{
key: "runOcrForCurrentDocument",
text: "Run OCR on current document",
iconProps: { iconName: "TextDocument" },
onClick: () => props.handleRunOcr(),
},
{
key: "runOcrForAllDocuments",
text: "Run OCR for all documents",
iconProps: { iconName: "Documentation" },
onClick: () => props.handleRunOcrForAllDocuments(),
},
],
},
},
]; ];
return ( return (

Просмотреть файл

@ -37,7 +37,6 @@ import { constants } from "../../../../common/constants";
import PreventLeaving from "../../common/preventLeaving/preventLeaving"; import PreventLeaving from "../../common/preventLeaving/preventLeaving";
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner"; import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes"; import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
import { SkipButton } from "../../shell/skipButton";
/** /**
* Properties for Editor Page * Properties for Editor Page
@ -221,7 +220,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
theme={getPrimaryGreenTheme()} theme={getPrimaryGreenTheme()}
className="editor-page-sidebar-run-ocr" className="editor-page-sidebar-run-ocr"
type="button" type="button"
onClick={this.loadAllOCRs} onClick={() => this.loadOcrForNotVisited()}
disabled={this.state.isRunningOCRs}> disabled={this.state.isRunningOCRs}>
{this.state.isRunningOCRs ? {this.state.isRunningOCRs ?
<div> <div>
@ -231,7 +230,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
ariaLive="off" ariaLive="off"
labelPosition="right" labelPosition="right"
/> />
</div> : "Run OCR on all files" </div> : "Run OCR on unvisited documents"
} }
</PrimaryButton> </PrimaryButton>
</div>} </div>}
@ -269,7 +268,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
lockedTags={this.state.lockedTags} lockedTags={this.state.lockedTags}
hoveredLabel={this.state.hoveredLabel} hoveredLabel={this.state.hoveredLabel}
setTableToView={this.setTableToView} setTableToView={this.setTableToView}
closeTableView={this.closeTableView}> closeTableView={this.closeTableView}
runOcrForAllDocs={this.loadOcrForNotVisited}>
<AssetPreview <AssetPreview
controlsEnabled={this.state.isValid} controlsEnabled={this.state.isValid}
onBeforeAssetChanged={this.onBeforeAssetSelected} onBeforeAssetChanged={this.onBeforeAssetSelected}
@ -629,11 +629,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
}); });
} }
private loadAllOCRs = async () => { public loadOcrForNotVisited = async (runForAll?: boolean) => {
if (this.state.isRunningOCRs) { if (this.state.isRunningOCRs) {
return; return;
} }
const { project } = this.props; const { project } = this.props;
const ocrService = new OCRService(project); const ocrService = new OCRService(project);
if (this.state.assets) { if (this.state.assets) {
@ -641,14 +640,16 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
try { try {
await throttle( await throttle(
constants.maxConcurrentServiceRequests, constants.maxConcurrentServiceRequests,
this.state.assets.filter((asset) => asset.state === AssetState.NotVisited).map((asset) => asset.id), this.state.assets
.filter((asset) => runForAll ? asset : asset.state === AssetState.NotVisited)
.map((asset) => asset.id),
async (assetId) => { async (assetId) => {
// Get the latest version of asset. // Get the latest version of asset.
const asset = this.state.assets.find((asset) => asset.id === assetId); const asset = this.state.assets.find((asset) => asset.id === assetId);
if (asset && asset.state === AssetState.NotVisited) { if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
try { try {
this.updateAssetState(asset.id, true); this.updateAssetState(asset.id, true);
await ocrService.getRecognizedText(asset.path, asset.name); await ocrService.getRecognizedText(asset.path, asset.name, undefined, runForAll);
this.updateAssetState(asset.id, false, AssetState.Visited); this.updateAssetState(asset.id, false, AssetState.Visited);
} catch (err) { } catch (err) {
this.updateAssetState(asset.id, false); this.updateAssetState(asset.id, false);
@ -659,7 +660,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
}); });
} }
} }
}); }
);
} finally { } finally {
this.setState({ isRunningOCRs: false }); this.setState({ isRunningOCRs: false });
} }

Просмотреть файл

@ -54,7 +54,9 @@ export function registerIcons() {
MapLayers: "\uE81E", MapLayers: "\uE81E",
BookAnswers: "\uF8A4", BookAnswers: "\uF8A4",
Cancel: "\uE711", Cancel: "\uE711",
Refresh: "\uE72C",
Documentation: "\uEC17",
More: "\uE712",
}, },
}); });
} }

Просмотреть файл

@ -32,7 +32,9 @@ export class OCRService {
public async getRecognizedText( public async getRecognizedText(
filePath: string, filePath: string,
fileName: string, fileName: string,
onStatusChanged?: (ocrStatus: OcrStatus) => void): Promise<any> { onStatusChanged?: (ocrStatus: OcrStatus) => void,
rewrite?: boolean
): Promise<any> {
Guard.empty(filePath); Guard.empty(filePath);
Guard.empty(this.project.apiUriBase); Guard.empty(this.project.apiUriBase);
@ -43,7 +45,7 @@ export class OCRService {
try { try {
notifyStatusChanged(OcrStatus.loadingFromAzureBlob); notifyStatusChanged(OcrStatus.loadingFromAzureBlob);
ocrJson = await this.readOcrFile(ocrFileName); ocrJson = await this.readOcrFile(ocrFileName);
if (!this.isValidOcrFormat(ocrJson)) { if (!this.isValidOcrFormat(ocrJson) || rewrite) {
ocrJson = await this.fetchOcrUriResult(filePath, ocrFileName); ocrJson = await this.fetchOcrUriResult(filePath, ocrFileName);
} }
} catch (e) { } catch (e) {
@ -52,7 +54,6 @@ export class OCRService {
} finally { } finally {
notifyStatusChanged(OcrStatus.done); notifyStatusChanged(OcrStatus.done);
} }
return ocrJson; return ocrJson;
} }