You should now have Completed the Following things:
- Importing implemented Artefacts
- Implement Wizard Step 1 (Part1)
Next you will complete the implementation of the first step of the wizard.
We will now replace the notification with code to persist the changes in dataverse. We will use different approaches for new and edit:
- Edit: We will use the standard
SubmitForm
command - New: We will implement an embedded flow.
The reason for the embedded flow is a limitation of the SubmitForm
command when creating a new record. So far no simple built-in way exists to retrieve the values of the newly created record. This is a problem because in our case the primary key of the record is autogenerated. That means we cannot infer the record from the user input. The only known workaround would be to use all other columns as combined key to guarantee uniqueness. The embedded workflow gives us a chance to write the record AND to retrieve all values including the autogenerated primary key for the subsequent upload. Moreover it is the perfect opportunity to play with PowerAutomate hands-on.
To create an embedded flow click on the power automate icon >>
and then click Create new flow
as shown below:
Clicking on + Add flow
takes you to the flow designer:
In the designer you normally just see the initial step. The second step in the screesnhot just serves as illustration for the scenarios that can hit you.
The following general rules apply:
- To add an action at the end click on the button
New step
- To expand a step do a left mouse single click on the step
- To insert a step between two existing ones click on the plus symbol between the steps
- Clicking the
Flow Checker
informs you about errors - To delete a step click on the three dots right to the header. There you find an entry for removal.
- Saving the flow requires a name. Click on the text
Untitled
and enter your desired name. Afterwards press the save button.
In the PowerApps (V2)
initial action input parameters must be declared explicitly. We need the following parameters that are then later populated by the form:
Parameter | Type | Name of parameter |
---|---|---|
Importing user name | Text | Username |
Description | Text | Description |
Year | Number | Year |
To enter the parameter klick at the header of the step so that the action is expanded. You should see a link to add a parameter named + Add an input
. Run the following steps for each parameter:
- Klick at
Add
link - Specify the type by clicking at the icon according to the table. You should now get a new entry with a box for the name and the value.
- Enter the name according to the table IN THE LEFT BOX and leave the right textfield for the description empty.
At the end your initial step should like in the picture below:
In the next step we have to get the internal ID of the record holding the importing user. We will use the List Rows
action within dataverse for that. Click New Step
and enter dataverse
in the search field as shown below:
Pick the action List rows
. The screenshot below shows the added action:
Set the fields as shown in the table:
Field | Value |
---|---|
Table name | IMP_USERS |
Row count | 1 |
The expression for Filter rows
we use for filtering the rows by the importing username that was specified in the form. The required expression is hackpp_sceapp_cst_username eq '<value from form>'
.
hackpp_sceapp_cst_username
is one of the various internal column names of the CST_USERNAME
in the targeted table.
To populate <value from form>
we have to generate a new parameter. The screenshot below shows how that is done. Click into the field with the above expression. If you don't see an extra window click the small link under the field named Add dynamic content
. Select in the tab Dynamic Content
and enter the name of the parameter Username
. The screenshot shows the reaction of the designer after Username
was picked.
NOTE: The generated expression must be inside the single quotes.
In the next step we will add a new row that represents our import header. We will use the Add a new row
action within dataverse. Click New Step
and enter dataverse
in the search field. Pick the action Add a new row
. The screenshot below shows the action. Select IMP_CO2_CONS_RAW_HDR
as table name. As a result the table specific columns will be shown as illustrated in the screenshot below. Mandatory fields are marked with an asteriks. The generated UI is not correct reagrding CST_IMP_CODE
. Yes as logical primary key it is mandatory. However due to the auto generated definition no value is required. We will provide a special dummy to satisfy the constraints:
As a first value we will set the value for CST_IMP_USERNAMES. Power Platform expects an expression <EntitySetName>(<GUID of record>)>
. The entity set name in our case is hackpp_sceapp_imp_users
and <GUID of record>
is the result of the previous action. Enter hackpp_sceapp_imp_users()
and position the mouse cursor into the parentheses. Power Platform will assist you in completing the dynamic content needed here. Pick IMP_USER
from the displayed options in the tab Dynamic content
as shown in the screenshot.
NOTE: The generated expression must be inside the parenthesis.
When you selected the value you will notice a change: Dataverse embeds the new Add new row
task in a loop since the previous command might return multiple rows. The screenshot below shows this new situation:
You could initialize a variable with first(outputs('List_rows')?['body'])
or outputs('List_rows')?['body']?[0]
to ignore the looping, but let's try to understand Apply to each
better. The major input is the output from the previous step displayed as value x
. To understand it you have to be aware of the JSON structure that is returned by List rows
. Below you find a sample:
{
"@odata.context": "https://orgc9bd3046.crm6.dynamics.com/api/data/v9.1/$metadata#cr953_workflowmaxes(cr953_jobid,cr953_...)",
"@Microsoft.Dynamics.CRM.totalrecordcount": -1,
"@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded": false,
"value": [{
"@odata.type": "#Microsoft.Dynamics.CRM.cst_users",
"@odata.id": "https://domain.crm6.dynamics.com/api/data/v9.1/cst_users(cf5033a4-c9c8-eb11-bacc-00224817f...)",
"hackpp_sceapp_imp_userid": "cf5033a4-c9c8-eb11-bacc-00154817f386"
...
}]
}
The used path body/value
is another word for navigating to the value
property. As you can see in the JSON the property value
is an array. The loop now cycles over each entry. Expand the Add a new row
by clicking in the header. We need the id which is represented by IMP_USER x
. Hoover with the mouse over the expression. The expression items('Apply_to_each')?['hackpp_sceapp_imp_userid']
picks the technical id from the current array item.
For setting CST_IMP_TS
we use the built-in function utcNow
. As you can see in the screenshot we are in the tab Expression
. Enter it as formula in the bar as shown below:
Follow the instructions in the table for the remaining fields. Enter the parameters for year and description exactly in that order. Otherwise the parameter order in the snippet for calling the flow won't be correct:
Field | Value |
---|---|
CST_IMP_CODE | Set the field to the special expression null as you did it for utcNow . Otherwise you get an error. |
CST_IMP_YEAR | Use the way for adding a parameter as before. If you don't see the expression Year under PowerApps (V2) within the tab Dynamic content you have to click on See more . |
CST_IMP_DESC | Use the way for adding a parameter as before. If you don't find Description under PowerApps (V2) within the tab Dynamic content you have to click on See more . |
CST_IMP_STATE | Select Pending from the dropdown |
When you have filled out everything your action should look like the following:
As a last step we now have to return the primary key of the newly created record. Just adding a return step by clicking on Add an action
INSIDE THE LOOP unfortunately does not work. Trying that results in an error. Therefore we have to store the primary key in a variable. This requires two steps:
- Defining & Initializing a variable
- Assigning the value
Insert a step before List rows
by clicking on the plus icon. Enter as category Variable
and pick the action Initialize variable
. The screenshot below shows the resulting situation (Ignore that the screenshot has not been updated to PowerApps (V2) action
):
Enter a name of your choice in the field Name
and chose as Type String
.
Click the button Add an action
inside the loop that is highlighted below. Add a new action to Set variable
. The screenshot below shows the result:
Select the name you specified in Initialize variable
from the dropdown in the field Name
. Select the CST_IMP_CODE
as value as shown below:
Now we are ready to return the value. Click on the button + New step
to add a new action AT TH END OUTSIDE THE LOOP. Enter PowerApp
as category and add a new action Respond to a PowerApp or flow
. In the beginning the task is empty as shown below:
Create a return parameter by clicking on Add an output
. Chose Text
as type. Afterwards you will see now a new entry that we have to configure as shown below:
Enter returnedval
as name for the parameter. Click into the value field and let Power Platform assist you as shown in the screenshot below. You have to pick the variable name from the dynamic content tab you defined (In the screenshot below the variable was named TempVar
). With that we have returned the primary key of the newly created import header row:
Save your flow under the new name you want. It will be automatically added to your app when you started the flow designer with + Add flow
.
We replace now the OnSelect
property from the previous step. We plan for different ways to persist the changes. In the edit case we can use the standard SubmitForm
to persist the changes. We just add a confirmation message for the user. For new we have run our flow and store returned value for later processing in a local variable. The user is also informed with an additional message. Enter the following formula in the property OnSelect
that is doing all that (You have insert your flow name):
If(locImpMode = "New",
UpdateContext(
{locParaUserName: WizardStepImpHdrMainViewImportUserNameDropDown.Selected.CST_USERNAME,
locParaYear: WizardStepImpHdrMainViewImportYearTextBox.Text,
locParaDesc: WizardStepImpHdrMainViewImportDescTextBox.Value});
UpdateContext(
{locNewImpCode: <Name of your flow>.Run(locParaUserName, locParaDesc, locParaYear).returnedval });
Notify("Import " & locNewImpCode & " has been created."),
SubmitForm(WizardStepImpHdrMainView);
Notify("Import " & WizardStepImpHdrMainView.LastSubmit.CST_IMP_CODE & " has been updated."))
The important new takeaways from that code are:
-
Multiple expressions
No special block identifiers as curly brackets in C are required to run multiple commands. Commands are just separated by semikolon.
-
If expression
The generic form of the if is
if(<test cond>, <expressions to run if true>, <expressions to run if false>)
. In our case the expressions for the if and the true case are just a bit longer. -
UpdateContext
Sets the local variables (scoped per screen) to the values specified in curly brackets.
-
Calling the flow
Running a flow with parameters requires the run command as follows:
<flowname>.Run(<parameters separated by comma>)
. Referencing the return value is done by.<name of return parameter>
. -
LastSubmit: Refers to the stored data. Unfortunately it works for edit only.
Start from the import overview page WizardStepImpHeader
to ensure a correct screen context. Press the play button after selecting the overview screen to start the tests. Thanks to your changes the following scenarios should now work:
Test | Expected Result , |
---|---|
Creation of new record | Click on new import button in the import overview screen. You should see a new record in the dataverse table after clicking submit. The displayed message should be accordingly. |
Editing existing record | Filter down to a single record and click on the edit import button in the import overview screen. Fields on the form are prefilled with the record you selected. You should see the updated record in the dataverse table. The displayed message should be accordingly. |