- 1. Logic App A: Process uploaded documents from Blob Storage with Form Recognizer and store results to Blob Storage
- 2. Logic App B: Upon POST request, retrieve content of parsed results from Blob Storage and respond to request with content in body
- 3. PowerApp: Display folders and files from Blob Storage, display PDFs and call Logic App B using custom connector to display parsed results
1. Logic App A: Process uploaded documents from Blob Storage with Form Recognizer and store results to Blob Storage
Logic App A is responsible for processing uploaded forms in the background. Upon a PDF being uploaded to Blob Storage, a Logic App will be triggered and will call a Form Recognizer to analyze the contents. The output of the Form Recognizer will be stored in Blob Storage by the Logic App.
Depending on your use case, it may be better to store the output of the Form Recognizer in a structured database such as Cosmos DB. For the sake of this Demo, we wanted to display only the transcript as received by the Form Recognizer so we decided to store it in Blob Storage.
- In the Azure Portal, create new resource group for project. Mine will be called
document-parsing-backend
- In the Azure Portal, create a storage account in your resource group for this project. Mine will be called
documentstoragebackend
. Performance standard and redundancy LRS are sufficient for this proof-of-concept. - In the Azure Portal, create a logic app in your resource group for this project. Mine will be called
documentprocessingbackend
. The type isconsumption
for this proof-of-concept. - In the Azure Portal, create a Form Recognizer resource in your resource group for this project. Mine will be called
documentparsingformrecog
. The pricing tier will befree
for this proof-of-concept. - Create 2 folders (containers) in the storage account:
documents
anddocuments-parsed
.documents
will store the PDFs anddocuments-parsed
will store the Form Recognizer results.
-
In the Logic App, open the Logic App Designer (logic app from blank).
-
Search for Blob Storage and select the
When a blob is added or modified (properties only) (V2)
trigger-
Connection name:
documentstoragebackend
(your choice of a connection name) -
Authentication type:
Access Key
-
Azure Storage Account name: the name of your storage account, in my case
documentstoragebackend
-
Azure Storage Account Access Key: in another tab fetch your storage account access key & click create
-
Select your storage account name as previously configured (
Use connection settings
) -
Select container
/documents
-
Set Number of blobs to return to
10
-
How often do you want to check for items: once per minute
-
-
Add a new step, search for
Parse JSON
-
Set the Content as List of File (from the dynamic content modal)
-
Set the Schema as follows (as documented):
Schema
{ "properties": { "DisplayName": { "type": "string" }, "ETag": { "type": "string" }, "FileLocator": { "type": "string" }, "Id": { "type": "string" }, "IsFolder": { "type": "boolean" }, "LastModified": { "type": "string" }, "LastModifiedBy": {}, "MediaType": { "type": "string" }, "Name": { "type": "string" }, "Path": { "type": "string" }, "Size": { "type": "integer" } }, "type": "object" }
-
-
Add a new step,
Get blob Content (V2)
-
For the Storage Account name, select
Use connection settings
-
To specify the blob, select Id from the Dynamic content modal
-
Set Infer content type to
yes
-
-
Add a new step, searching for Analyze Layout from the Form Recognizer service.
-
Enter a connection name for your form recognizer, in my case
documentparsingformrecog
-
In another tab, retrieve your Form Recognizer service Endpoint URL and Account key in the Azure Portal.
-
Enter your Endpoint URL, in my case
https://documentparsingformrecog.cognitiveservices.azure.com/
-
Enter your Account Key & click create
-
Set Document/Image file content as
File Content
from the dynamic content modal
-
-
Add a new step, searching for
Create blob V2
-
Set Storage account name as
Use connection settings
-
Set Folder path as
/documents-parsed
-
Set display name as
DisplayName.json
-
Set Blob content as
analyzeResult
from the dynamic content modal
-
-
Save the logic app.
-
Test that the logic app is working properly
-
From your resource group (in my case,
document-parsing-backend
), go to your storage account (in my case,document-parsing-backend
) -
Navigate to the containers. In the `/documents/ container, upload a sample PDF from your computer.
-
Return to the Logic App, in my case
documentprocessingbackend
-
In the Logic App Overview, you should see a run has started in the Runs History tab (this may take up to 1 minute to be triggered). Click on it to see the details. From here, you can see each step in the Logic App, their inputs and their outputs.
-
Lastly, return to your storage account and verify that a blob with the parsed data has been stored in the
/documents-parsed
container as show in the screenshot. You can also download the file to ensure the contents are as expected as well. You should see a json object with the transcript as parsed by Form Recognizer.{"version":"2.1.0","readResults":[{"page":1,"angle":0,"width":8.2639,"height":11.6944,"unit":"inch","lines":[{"boundingBox":[2.7009,0.7449,5.5057,0.7449,5.5057,1.0947,2.7009,1.0947],"text":"James Anderson", ... }
-
2. Logic App B: Upon POST request, retrieve content of parsed results from Blob Storage and respond to request with content in body
Logic App B will provide an API to retrieve the contents of the Blob Storage file as an HTTP Response. This will allow our PowerApp to display the JSON as content. This is necessary because PowerApps does not have a native connector to display Txt/JSON data within an app.
*This step could be reconsidered for your use case. If you decided to store your data within a database such as Cosmos DB, you could connect to it directly from within your PowerApp and this extra API would not be necessary.
- Deploy a new Logic App resource to your existing resource group (in my case
document-parsing-backend
)-
Set Resource-group as the one you created for this project
-
Set the Type to
Consumption
-
Set the Logic App name to anything, in my case
documents-parsed-fetching
-
Select any Region
-
Set Enable log analytics to
No
-
Click Review + Create
-
-
Navigate to the Logic App you just created and select
Blank Logic App
-
Search for
When a HTTP request is received
as your trigger- Set the Request Body JSON Schema as follows (when calling this endpoint, we will pass an object with a key filePath and a value representing the filePath of the document-parsed we want):
{ "properties": { "filePath": { "type": "string" } }, "type": "object" }
- Set the Request Body JSON Schema as follows (when calling this endpoint, we will pass an object with a key filePath and a value representing the filePath of the document-parsed we want):
-
Add a new step, searching for
Get blob content using path (V2)
-
Set the connection name to represent a connection to your Storage account, in my case
documentparsedfetching
-
Set the Authentication type as
Access Key
-
Set the Azure Storage Account name to the name of your storage account, in my case
documentstoragebackend
-
Set the Azure Storage Account Access Key to the Access Key of your Storage Account (retrieve it as done in the previous step)
-
Click create
-
Set Storage account name to
Use connection strings
-
Set Blob path to filePath from the Dynamic Content modal
-
Set Infer content type to No (we just want to extract the raw content)
-
-
Add a new step, searching for
Response
-
Set Status Code as
200
-
Set Headers as
Content-Type
application/json
-
Set Body as follows (as screenshot) (note that quotes ("") must surround the
File Content
so that it is returned as a string):{ "fileContent": "[INSERT FILE CONTENT FROM DYNAMIC CONTENT MODAL]" }
-
-
Save the Logic App.
-
From the Logic App designer, click
Run Trigger
>Run with payload
to test your logic app.-
Leave URL as default
-
Set Method as
POST
-
Set body as follows, replacing the path of the filePath for the filePath of your parsed document from the documents-parsed container in your storage account. In my case, the body looks as follows:
{ "filePath": "/documents-parsed/Resume 24.pdf.json" }
-
Press Run. You should see a Response Body with as an object, with a key fileContent and a value consisting of the contents of the JSON file (see screenshot)
-
3. PowerApp: Display folders and files from Blob Storage, display PDFs and call Logic App B using custom connector to display parsed results
-
Go to make.powerapps.com and make a new Blank Canvas App, making sure to select the
Tablet
format -
Implement the folder view of documents from the Blob Storage following this tutorial
Steps
- Connect your PowerApp to the Azure Blob Storage account (as documented)
-
On the left, select the Data tab.
-
Click the Add data button, searching for Azure Blob Storage (see screenshot)
-
To configure the Blob Storage connection, select
Authentication type
asAccess Key
,Azure Storage Account name
as the name of your storage account in the Azure Portal,Azure Storage Account Access Key
as the access key of your storage account (as retrieved previously). -
Click connect
-
- Implement the view to see Blob Storage containers and folders as documented
-
From the Insert tab, insert a Vertical Gallery
-
Set the data source for the Vertical Gallery to be Azure Blob Storage
-
Set the Items of the Vertical Gallery
Gallery1
toAzureBlobStorage.ListRootFolderV2().value
-
Set the layout of the Vertical Gallery to be
Title
-
From the Insert tab, insert another Vertical Gallery
-
Set the Items of the Vertical Gallery
Gallery 2
toAzureBlobStorage.ListFolderV2(Gallery1.Selected.Id).value
-
Set the Layout as
Title
-
For both Vertical Galleries
Gallery 1
andGallery 2
, set the Template Fill attribute to:If(ThisItem.IsSelected, RGBA(0,0,0,0.3), RGBA(0,0,0,0) )
-
Reduce the size of the Font to 12 and the size of the arrow
-
- Connect your PowerApp to the Azure Blob Storage account (as documented)
-
Implement the file upload as documented
Steps
-
From the Insert tab, search for Add Picture and add it to your PowerApp
-
Set the
Text
toSelect PDF
-
Resize the button to fit below the folder view
-
From the Insert tab, search for Button and add it to your PowerApp
-
Set the
Text
toUpload
-
From the Insert tab, search for
Text input
and add it to your PowerApp -
Set the
Text
toFilename
-
Set the
OnSelect
property of the Button toAzureBlobStorage.CreateFile(Gallery1.Selected.Name, TextInput1.Text, UploadedImage1.Image)
-
Test the application to see if you can upload a new file. Note that when uploading, select any type of file to have the option to upload PDFs when selecting your file. You will have to click out of your parent folder and back to see the contents of the folder refresh.
-
(Optimization, skip if you are content with the workaround noted above to refresh the folder contents) Set the
Items
ofGallery 2
toSecondLevelList
.SecondLevelList
is a global variable which will be set later. -
Add the following code to the
OnSelect
of the upload ButtonClearCollect(SecondLevelList, AzureBlobStorage.ListFolderV2(Gallery1.Selected.Id).value);
-
Set the
OnSelect
property of theNextArrow
ofGallery 1
toSelect(Parent); ClearCollect(SecondLevelList, AzureBlobStorage.ListFolderV2(Gallery1.Selected.Id).value);
-
Test your application, uploading
-
-
Display the selected PDF
-
From the Insert tab, insert the
PDF viewer (experimental)
-
Set the
Document
attribute toAzureBlobStorage.GetFileContent(Gallery2.Selected.Id)
-
Test that the PDF viewer is working. It may take some time to load the PDF or you may have to click out of the currently selected PDF to another one to see the PDF viewer in effect.
-
-
Create a custom connector to fetch the content of the file from our Logic App B
-
Save the PowerApp & exit the Power App to return to the main menu
-
Under the Data tab, click Custom Connectors
-
Create a new Custom Connector from Blank
-
Configure the general configs of the Custom Connector
-
Open a new tab and return to your Logic App B in the Azure Studio. From the Logic App designer, click Run Trigger (with payload). From here, retrieve the URL.
-
Returning back to our PowerApps studio, we can finish configuring our custom connector. The
host
will be the host from the URL, and the base URL will be from workflows to triggers as such/workflows/345fge...../triggers
(see screenshot). When finished, click Security to move to the next page.
-
-
Set
Authentication type
toNo authentication
. Click Next to move onto the definition. -
In the Definition page, click
New action
to add an action.- Set the
Summary
toCall API
- Set the
Operation ID
tocallAPI
- Moving onto the request, click
Import from sample
- Fill out the sample request as indicated in the Logic App B. Set the
Verb
asPOST, set the
URLas noted in your Logic App B (
https://prod-06.northcentralus.logic.azure.com:443/workflows/82d5da629f124...`). Set theHeaders
asContent-Type application/json
. Set the Body as{ "fileContent": "string of file content" }
If your
Import
button is disabled, switch the input method and back. 5. For each of the Request Query parameters, edit them and ensure that the data type is string (in my case, the sv parameter defaults to integer but it should be string)-
In the Response, click the default response.
-
Click Import from sample. Set the
Headers
toContent-Type application/json
and theBody
to the following{ "filePath": "string path" }
Refer back to the Response in your Logic App B designer to see the details of the response returned.
- Set the
-
Click Create Connector to create the custom connector
-
Test the Custom Connector by going to the test tab
-
Create new connection (this may redirect you to another page, return back to the custom connector Test tab and you will notice that a connection has been created)
-
For the parameters of the callAPI call, you will have to parse your URL from your Logic App B. For example, my Logic App B URL is the following (abridged for security) https://prod-06.northcentralus.logic.azure.com:443/workflows/82d5[ABRIDGED]a5/triggers/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=1B-[ABRIDGED]-3dz8.
In this case,
api-version
will be2016-10-01
,sp
will be/triggers/manual/run
,sv
will be1.0
,sig
will be1B-[ABRIDGED]-3dz8
.The Content-Type will be
application/json
For further guidance, refer to the screenshots below.
-
Click test. You should receive a 200 response with a body (see screenshot)
-
-
-
Call the Custom Connector from the Power App
-
Return to the PowerApp
-
From the Data tab, add the Custom Connector (in my case
fetch-documents
) -
Replace the OnSelect with the following:
Select(Parent); Set(PDFPath, Concatenate(ThisItem.DisplayName, ".json")); Set(ParsedPDF, 'fetch-documents'.callAPI({'api-version':"2016-10-01", sp: "/triggers/manual/run", sv: "1.0", sig: "1B-[ABRIDGED]-3dz8", 'Content-Type': "application/json", filePath: Concatenate("/documents-parsed/", PDFPath)}));
Here
Set(PDFPath, Concatenate(ThisItem.DisplayName, ".json"));
, we set PDFPath (a global variable), to the name of the selected file, with the added .json file extension as we are retrieving the parsed files from our Blob Storage.Here
Set(ParsedPDF, 'fetch-documents'.callAPI({'api-version':"2016-10-01", sp: "/triggers/manual/run", sv: "1.0", sig: "1B-[ABRIDGED]-3dz8", 'Content-Type': "application/json", filePath: Concatenate("/documents-parsed/", PDFPath)}))
, we also set ParsedPDF, a global variable containing the response of the Custom Connector API Call. We call the custom connector (in my casefetch-documents
), calling the callAPI method and passing all the required parameters as we did earlier when we were testing our custom connector. -
From the Insert tab, insert a Text > HTML Text
-
Set the text of the HTML Text as ParsedPDF.fileContent.
-
Test your PowerApp to see if your PowerApp is working.
-
NOTE: In my experience, calling the Custom Connector from the PowerApp can be a bit tricky. For instance, while trying to implement, I have gotten this issue:
fetch-documents.callAPI failed: {"error":{"code":"AuthorizationFailed","message":"The authentication credentials are not valid."}}
In order to resolve this, I had to debug the Custom Connector, going back and setting default values in the Custom Connector definition. Using the Swagger editor for the Custom Connector can also be useful.