Getting Onboard with Power Platform as a Business Central Developer

Intro

One of the things that I wanted to come away with from Directions EMEA last week was a better overview of the Power Platform. What is it? What can we do with it? When should we use it? How does it fit into the overall solutions that we are designing for our Business Central customers?

If you’re anything like me then you’ve been aware that you need to get on the Power Platform train at some point – but you haven’t been quite sure how or when. You’ve been watching suspiciously from the platform wondering where this train even goes, how it gets there and how much a ticket costs. On closer inspection, it looks like the train is already full of consultants and citizen developers digitally transforming each other…*shudder*

Still, we can’t put it off forever. If Microsoft are to be believed then the vast majority of customisations in the future are going to be developed in low-code platforms.

Good news for me was that there was more Power Platform content at Directions than you could shake two sticks at. I still have some questions about how to manage Power Platform development as part of our Business Central solutions, but I’ve come away more convinced that it is going to be an important part in the future and an inclination to get on board.

Series

I haven’t been blogging for a while because, life, but I thought it might be interesting to blog my way through my Power Platform learning curve. Hopefully you’ll find something useful but at the very least you’ll be able to laugh at my fumbling attempts to make sense of it all.

Flow from Business Central

I’m going to start with something that was demoed by Microsoft in one of the Directions keynotes that struck me as very significant. We can trigger a Power Automate flow from an action on a page in Business Central.

But couldn’t we do that before? Yes, we’ve been able to trigger a flow with an HTTP trigger and call the URL from an action on a page, for example. What’s different now is that you can add a new action to specific tables and/or pages in Business Central, with some UI, to trigger a flow for a given record without any AL development.

Maybe you’re thrilled by the possibilities that this opens up. Finally you are not so dependent on developers to get stuff done. Or maybe you are horrified that anyone can add an action to a page without building an extension, adding the code to source control, running a pipeline or any tests.

I’ll show some examples and explore the possibilities next time…

Tip: Share a Git Hooks Directory Across Your Repositories

TL;DR

git config --global core.hookspath '<path to hooks directory>'

Sharing Hooks Across Repos

I posted before about using a pre-commit hook to check that I’m not committing anything that I really shouldn’t be (anything I’ve tagged with //DONOTCOMMIT).

Hooks are specified in the .git/hooks directory. That’s great, a git repository is completely contained within its parent folder, you can copy it somewhere else and all of the code, history and config come with it.

It’s not so convenient if you want to create some hooks that apply across multiple repositories though. You can just copy your hook files between all of your repos, or it turns out that there is a smarter way. Git config has a core.hookspath key. You can create a folder somewhere with the hooks that you want to apply to all repos and set this key.

Use git config --global to set the value of a key in the global config file and git config --global --list to list the config keys and their current values.

git config --global core.hookspath '<path to hooks directory>'

Spare Your Blushes with Pre-Commit Hooks

It’s Summer (at least in the northern hemisphere), hooray. You’ve booked some time off, wrapped up what you were working on as best you can, committed and pushed all your code, set your out-of-office and switched off Teams. Beautiful.

When you come back you flick through your messages to catch back up. What’s this? Some muppet commented out some vital code and pushed their changes? Who? Why?

It happens happened. That muppet was me.

There are good reasons why you might remove or add some code in your local environment but it is really important that those changes don’t end up in anyone else’s copy.

You can either:

  • Plan A: back yourself never to accidentally commit and push those changes
  • Plan B: add a pre-commit Git hook as an extra line of defense

I’ve played around with Git hooks before but still haven’t actually used them for anything serious. I think I’m going to start now.

Pre-Commit Hook

Open the (hidden) .git folder inside your repository and rename pre-commit.sample to pre-commit.

As the comments at the top of the file say, if you want to stop the commit then this script should echo some explanatory comment and return non-zero. This is mine:

if git diff --staged | grep 'DONOTCOMMIT' -qE; then
    echo "Your staged changes include DONOTCOMMIT"
    exit 1
fi

Before committing, Git looks for a pre-commit file in the hooks folder and executes it if it finds it.

git diff --staged gets a string of the changes which are staged i.e. going to be included in this commit. This string is piped to grep to match a regular expression – I’m keeping it simple and searching for the string ‘DONOTCOMMIT’ but you could get fancier if you wanted.

If DONOTCOMMIT is found in the staged changes then a message to that effect is shown and the scripts exit with 1 (which tells Git not to continue with the commit).

VS Code error dialog thrown by pre-commit hook

Next time I add or remove some code that is for my eyes only I’ll add a //DONOTCOMMIT comment alongside to remind me to undo it again when I push the code.

Execute JavaScript with WebPageViewer for Business Central

TL;DR

The WebPageViewer add-on has an overload to accept some JavaScript. You can use that to execute arbitrary script locally. WebPageViewer.SetContent(HTML: Text; JavaScript: Text);

JSON Formatting

This post starts with me wanting to format some JSON with line breaks for the user to read. It’s the response from an Azure Function which integrates with a local SQL server (interesting subject, maybe for another time). The result from SQL server is serialized into a (potentially very long) JSON string and this is the string that I want to present in a more human-readable format.

Sometimes I converge on a solution through a series of ideas, each of which slightly less bad than the previous. This was one of those times. If you don’t care about the train of thought then the solution I settled on was to use the JavaScript parameter of the WebPageViewer’s SetContent method.

If you’re still here then here are the stations that the train of thought pulled into, starting with the worst.

Requirement

Have some control on my page for the user to view the JSON returned from the Azure Function, formatted with line breaks.

1. Format at Source

Why not just add the line breaks when I am serializing the results in the C# of my Azure Functions? That way I don’t need to change anything in AL.

No, that’s dumb. That would make every response from the function larger than it needs to be just for the rare occasions when a human might want to read it. Don’t do that.

2. Call an Azure Function to Format the Result

I could have a second Azure Function to accept the unformatted result and return the formatted version. I could have a Function App which runs node.js and return the result in a couple of lines of code.

Wait, that’s absurd. Call another Azure Function just to execute two lines of JavaScript? And store the Uri for that function somewhere? In a setup table? Hard-coded? In a key vault? Seems somewhat over-engineered.

3. Create a User Control

Hang on. I’m being thick. We can execute whatever JavaScript we want in a user control. I can create a control with a textarea, or just a div, create a function to accept the unformatted JSON, format it and set the content of the div. No need to send the JSON outside of BC.

Closer, and if you want more control over how the JSON looks on screen probably the best bet. But, is it really necessary to create a user control just to execute some JavaScript? Still seems like too much work for what is only a very simple problem.

4. Use WebPageViewer

The WebPageViewer has a SetContent method (which I’ve written about before) which can accept HTML and JavaScript.

If you pass some script it will be executed when the page control is loaded. Perfect for what I need. I can just use the JSON.parse and JSON.stringify functions to read and then re-format my JSON text. I’m also wrapping it in pre tags and removing any single quotes in the text to format (because they will screw the JavaScript and I can’t be bothered to handle them properly).

The AL code ends up looks like this:

local procedure SetResult(NewResult: Text)
var
    JS: Text;
begin
    NewResult := NewResult.Replace('''', '');
    JS := StrSubstNo('document.write(''<pre>'' + JSON.stringify(JSON.parse(''%1''), '''', 2) + ''</pre>'');', NewResult);
    CurrPage.ResultsCtrl.SetContent('', JS);
end;

If you’re not using 26 single quotes in three lines of code then you’re not doing it right 😉

Tip: Editing RapidStart Configuration Package Files

TL;DR

  1. Extract the package with 7-Zip
  2. Open the extracted file in a VS Code / Notepad++ / text-editor-of-choice
  3. Edit the xml as required
  4. Use 7-Zip to compress in gzip format

Editing Config Packages

Sometimes you might want to edit a config package file without having to import and export a modified copy from BC. In my case I wanted to remove the Social Listening Setup table from the package. Microsoft have made this table obsolete and BC throws an error if I try to import the package with this table present. (Probably not a bad idea – stopping listening to socials).

Fortunately, a rapidstart file is just a compressed xml file. Extract the rapidstart file with 7-Zip and then open the extracted file in a text editor. The format of the file is pretty straight forward. Each table is represented with an XYZList node where XYZ is the name of the table which the table-level settings followed by one or more XYZ nodes with the data.

Here are two records for the Payment Terms table.

<PaymentTermsList>
  <TableID>3</TableID>
  <PageID>4</PageID>
  <SkipTableTriggers>1</SkipTableTriggers>
  <PaymentTerms>
    <Code PrimaryKey="1" ProcessingOrder="1">10 DAYS</Code>
    <DueDateCalculation ProcessingOrder="2">&lt;10D&gt;</DueDateCalculation>
    <DiscountDateCalculation ProcessingOrder="3">
    </DiscountDateCalculation>
    <Discount ProcessingOrder="4">0</Discount>
    <Description ProcessingOrder="5">Net 10 days</Description>
    <CalcPmtDisconCrMemos ProcessingOrder="6">0</CalcPmtDisconCrMemos>
    <LastModifiedDateTime ProcessingOrder="7">
    </LastModifiedDateTime>
    <Id ProcessingOrder="8">{6BD87497-B233-EB11-8E89-E8FD151D8C93}</Id>
  </PaymentTerms>
  <PaymentTerms>
    <Code>14 DAYS</Code>
    <DueDateCalculation>&lt;14D&gt;</DueDateCalculation>
    <DiscountDateCalculation>
    </DiscountDateCalculation>
    <Discount>0</Discount>
    <Description>Net 14 days</Description>
    <CalcPmtDisconCrMemos>0</CalcPmtDisconCrMemos>
    <LastModifiedDateTime>
    </LastModifiedDateTime>
    <Id>{6DD87497-B233-EB11-8E89-E8FD151D8C93}</Id>
  </PaymentTerms>
</PaymentTermsList>

All I need to do is find the offending Social Listening Setup node in my file and remove it. Here it is:

SocialListeningSetupList node

Once you are finished editing you can use 7-Zip to compress the file again with the gzip method and import.

Add to Archive with 7-Zip