This is more of an sharing of "what goes on under the hood" kind of a configuration of what most of my applications look like by default.
This is by no means the ONLY WAY to do applications, it is just a jumpstart for anyone who is just really getting into how to deploy applications in 2024/2025 and avoid most of Microsoft's dropped balls and extra licensing costs.
2027 me is going to laugh so hard at this. Assuming some recently elected moron doesn't blow the world up before then.
I Start Every Project With a Microsoft Team
Before I even touch the Power Platform I build out a Microsoft Team. I assume that the business or group that needs an app probably has a Team already and I immediately ignore that because the needs of the app are almost always slightly different than the team as a whole.
For example:
- Having non-department members reading/writing data
- Having a subset of the team using the app without sharing w/ others
- Having this data being secured slightly differently than the broader team
- Having limited access to rows and even columns within the dataset
- Not wanting some IT person seeing all the nonsense going on inside their team
So I spin up a Team, invite the person who asked me to do X and then start ideating within that space and throwing some sample data tables, screens, and eventually an app that we can rapidly build together without fear of others chiming in and giving their $.02 (or whatever a piddling amount of your own local currency might be).
This also sets us up for success on the most basic applications where departments want to self-manage security. They can invite people to be Owners (which see everything) or Members (which see some things), or even ultimately set up custom guest access as needed (if it comes to that).
Keeping everything in Teams reinforces communications via that method as well and stops all the back/forth emails that we all lose track of. Putting it into a single repository gives us the opportunity to reinforce good behavior and keep all of our documentation in one place. Even when that documentation is "Hey, can you make this Blue today?".
How the Application Starts: Splash Screen
Because I still don't believe that Microsoft has settled into what the future-state of how your apps start will ultimately be, I've avoided all of their roundabout methodology for a brute-force loading screen. Yes, it's hacky, but it works 100% of the time.
Everything always starts w/ a similar configuration to what you see above. One or more "warning" groups based upon values assessed during startup, a button where all the starting logic goes (except for two very basic settings and an automation flag in the OnVisible for the screen). This lets us tell the end-user what we're doing, show blocking warnings before we go too deep (old version, status of user, etc.), and have an easily testable startup process that is also pretty much all in one place.
The Splash Screen OnVisible code
The OnVisible for my splash screen is always some variation of:
Why? Because Microsoft has done enough games w/ the platform that I don't trust them not to break my sh1t. The fact that you cannot access the Version of what's currently published vs. what's running and do a hard block or soft block (or even just access the current version easily) shows how much they care. They don't. So stop relying on them to solve real-world problems and put in your own versioning and update it if/when you need people to upgrade and/or you need to be 100% certain that an end-user who is testing is using the same version as you.
I assume some day they'll update it all. Probably 5 years after that I'll consider trusting them not to change their minds.
The Button
Whoooo boy do I hate having to do things this way, but again, it works.
All of my actual code that does anything is fired by clicking a button. That button is only visible when we're testing and the pauseAutomation variable is TRUE (see the above code). Otherwise, if the automation is paused, it's easy to edit the code and/or click it manually to test. Simple.
This code is for all the things I don't trust Microsoft to do for me before my app really starts going. Verifying user status (have they been here before, are they supposed to be here now, are they part of a special handling group, etc.). I also do any pre-loads or configuration of things that I probably will use later in the app or want to cache to keep things speedy for my end-users.
However, the first thing you need is a way to turn the automatic OnVisible event On/Off. So on another reference screen I have a button that just has the following logic:
pauseAutomation,
!pauseAutomation
);
As well, it has logic on the Button.Text property of:
pauseAutomation,
"Un-Pause Automation",
"Pause Automation"
So the button changes the text based upon the value of pauseAutomation. Sometimes I am even user-friendly to myself.
Back to the main button on the Splash screen. The OnSelect code it always starts like:
This means that the code where the ellipses are (...) is only executed if I get a "TRUE" (and yes, I send raw text and not a yes/no back from Power Automate). If not, then I set my flag for the warning group to now be Visible (if you haven't guessed the OnVisible for that group is showVersionWarning). You might also notice that I'm setting a Context Variable named loadingStatus, which I use to selectively display to the end-user if things might take a moment and/or if I want to know what is causing a delay for them.
Why do I do this using Power Automate vs. SharePoint? Because I find it easier to compartmentalize applications on the Power Platform vs. creating global lookups. This is a pure style choice, but I reserve the right to make fun of you if you choose to have some master lookup table of versions of all applications. I regularly encounter situations where SharePoint doesn't refresh when I want it to and cached data is used. However, when Power Automate runs after a save, it always has the latest data in it. When I am updating a version and forcing it, I want it to stop people NOW.
Yes, I still use the old designer for easier screenshots. Fight me. |
Beyond the basic version control code, you can nest a few other checks inside this for say Admin vs. regular users, check to see if somebody passed in some Parameters (like a test user's email address), pre-load some of your data to build out controls so you have very responsive (in the way that 99% of the world means that word) controls.
Speaking of...
Pre-building Dropdowns
I almost always include code here to pre-build my dropdowns for various screens and only do something a little different if I expect them to change. I also include the word "All" at the top of my collections so the code looks like:
regionsCol,
"All"
);
Collect(
regionsCol,
Sort(
Distinct(
myDatasourceName,
region
),
Value,
SortOrder.Ascending
)
);
The italicized items are the things you would definitely change. This code puts "All" at the top, then adds in everything else alphabetically. I use this for easy sort/filtering later in my app. If someone is going to do a hard filter via a dropdown for the region value, then this gives a pre-built list that makes things very fast and isn't re-building it constantly each time someone clicks it.
Sometimes I do use hardcoded dropdowns instead of building them dynamically (via the Distinct() approach), and if I do then that code is also here.
NOTE: Some of this *might* wind up in named Functions soon. I haven't yet decided that I trust Microsoft on these, but I believe they'll wind up being something that will survive the ongoing build/purge cycle within all platforms.
Building Out Test Code
Because I like to run through a number of test scenarios early in development and have the ability both to test while I'm editing and also call the app from a URL w/ some test parameters inline, I include logic similar to this to catch if I'm testing in the editor:
If(Len(Param("someParameter")) > 0,
Set( //True path
testing,
true
);
Set(
endUserEmail,
Lower(Param("someParameter"))
),
Set( // False path
testing,
false
);
Set(
endUserEmail,
Lower(User().Email)
);
)
);
You can use this as-is and just replace the italicized code with whatever parameter you want to pass in. What this means is I can set a variable when editing using Set(testing, true) and force into test mode, or detect that I'm testing when sending inline test scenarios.
I use code at different part of the app sometimes to redirect emails or not post to production lists, etc. to see if we're in test-mode or not and can leave that in place for production testing.
Some of you might read the above and wonder if I'm using Test Studio. The answer is no. Maybe I could, but we don' have a large enough team and stratified work streams to currently justify it. Of course, I believe I mentioned that I don't trust Microsoft to not break things, so I'll just do my own testing thank you very much.
Other Startup Stuff
Again, these are just the things I do pretty much every app. Depending upon what's going on, I will also nest in lookups to see things like:
- New User or Returning?
- Status of User's submission(s)
- Warning of app migration or downtime
- Cache data that won't or isn't likely to change
All the things that Microsoft hates us doing "at the start" because they don't want their own metrics to look bad.
However, the reason this works (and likely always will) is because this isn't on Microsoft's metrics, it's on ours. They're counting how many milliseconds until the app loads and displays that first screen. After that...they don't care.
How I Access Data
This is definitely one of those situations where I'm flipping a coin of where I think the app is headed. Simple department app w/ no outside users? Probably straight to SharePoint list(s). Any possibility that someone outside of that department is using the app and there are "tiered" access levels? Probably through Power Automate using JSON to transmit data.
Setting up direct to SharePoint is easier. Power Automate w/ JSON is more flexible security-wise and also if I imagine a near-future state where "something else" is holding the data. It really comes down to where I feel the risk is taking me and the time I have available.
However, my designs usually look very similar with the data sources being something similar to:
Static data hardcoded in and Dropdown values pre-built at start |
And the Power Automate sources looking like:
nameOfApp_(optional minorNameOfApp)_function |
The Datasources might include SharePoint calls, but more often than not (and in the case above) the data is retrieved/written by Power Automate. Now anyone reading my blog regularly knows I despise Power Automate. However, this is a common scenario where I group a number of microservices-style Power Automate scripts in to do certain tasks that we'd use SharePoint direct connections for.
Why do I do this more often than not? Because security complexity for any datasource (that might change) is drastically reduced through this method. If somebody suddenly says person X can see Y on Mondays but not Sundays, then who cares what SharePoint (or Dataverse -- <barf>) thinks about security. Just set that up in your app (or if you hate yourself, your Automate script) and move on.
I will admit that sometimes I will allow an app to do a real-time lookup from a SharePoint list for certain things. However, I find that risky enough that I rarely want to allow the business teams to do it on their own. Some exceptions are large lists that have regular updates that I don't want anything to do with (7000 row crosswalk of business units and cost centers? No thank you). While things that don't change often or might really impact me if they do change (brand colors for example) I just import in from Excel so they are hardcoded.
However, for small single-department applications, I would have no pushback for anyone using a direct connection to SharePoint. It's definitely easier and requires less planning than going around through Power Automate and having to move the data in/out of JSON.
How I build Screens
There is no single way I build out screens, but I will admit that I have settled into a very standard method for displaying information for many users. It isn't sexy and I'm perfectly willing to let better UX people come up w/ better ideas, but this is for getting the basics in place extremely fast. That means, I almost always start w/ a similar look/feel to this:
Free text search, couple of filters, and a gallary |
This gives users something that they can work from that feels familiar across applications. It combines:
- free text search
- Static dropdowns
- refresh ability
- sort
You can go into the detail with my article here. However, this is something I essentially copy/paste to each application (when Microsoft is allowing me to do so) and then put in all the details specific to the UX needed (what columns, datasources, hard filters, etc.).
Do I name things properly? Yes. Sometimes. Eventually. But yes:
I don't start like this. Initially, everything's just Label1, Label1_1, etc. until I start grouping things up and have decided on how the UI will look/feel. In fact, I'd say wait and only really rename things when you're getting close to the end. With the definite exception of controls you're going to key off of (dropdowns, Listboxes, Text Inputs, etc.) that you want to reference across controls. I'd do those first thing. If you never name the label next to a control anything over than Label1, nobody's going to judge you too harshly.
Definitely not me.
Test and Reference Screens
Meaning that they're just the controls or things I'm testing out currently. Or sometimes they are more permanent reference screens that I use again so that I can see things I'm selecting more clearly:
These wind up at the bottom of my app screens where again, the reference items (like the colors above) are named something that is somewhat meaningful, while the test screens are just whatever they were to begin with:Anyone looking at my app should know that the normal screens are at the top, the reference screens come next (any controls or standard things I might reference including documents I want to edit in-app), and followed finally by whatever garbage I'm testing.
Speaking of garbage and testing...
I Write Standalone Test Apps For Large Code Tests
While some of my testing does wind up on screens, if I'm going to create some elaborate If() statements, ForAll() Collections, or spin up a bunch of test data scenarios, I don't want that causing issues in my main app. Collections are global. So me doing something stupid on Page 5 can break everything on pages 1-4. So throwaway apps that access the same data where you can test methods are useful:
Complete lack of naming standards for controls too! |
I also chop up the code in my throwaways into discrete Buttons. In the image above those are 3 discrete steps to get/manipulate the data into something I think I want to work with followed by a final button that actually does the thing I think I want this to do.
It lets me chop up testing and gather data into discrete controls w/o breaking things. For example, if you're editing code that fills a Collection and it is all in a single control, then you can't just click on that Collection in the broken control to view it. However you can view it in other controls.
So populate your basic data first, then start messing with it in another control.
Just for fun, this last button on my screen above is a TBD blog post about an alternative way to grab values from JSON vs. having to map it. See if you can stand to look at the following (which also puts the data into a Tab-delimited list that can be copy/pasted into Excel:
ForAll(
spCacheFilteredCol As nextItem,
Collect(
exportRows,
nextItem.personNumber & Char(9) & LookUp(impactedCol,personNumber=nextItem.personNumber).finalNotificationScenario & Char(9) & Mid(
Mid(
nextItem.rd140,
Find(
TextInput2_1.Text,
nextItem.rd140
) + Len(TextInput2_1.Text),
50
),
1,
Find(
Char(34),
Mid(
nextItem.rd140,
Find(
TextInput2_1.Text,
nextItem.rd140
) + Len(TextInput2_1.Text),
50
)
) - 1
) & Char(9) & Mid(
Mid(
nextItem.rd140,
Find(
TextInput2.Text,
nextItem.rd140
) + Len(TextInput2.Text),
50
),
1,
Find(
Char(34),
Mid(
nextItem.rd140,
Find(
TextInput2.Text,
nextItem.rd140
) + Len(TextInput2.Text),
50
)
) - 1
) & Char(10)
)
);
I'd try to make it more understandable for you, but as I said, test code. It's pure garbage that worked great. So now that means time to copy/pasta it to the main application and clean it all up.
HINT: It is grabbing data from a SP list that has every detail about a person exported from an ERP system and put into a JSON field named rd140, then pulling two discrete values from the raw text without having to use the ParseJSON() step that I'd normally lean on.
I Use Microservice Style Power Automate Flows
Just a few steps and very simple to troubleshoot. I also edit the columns displayed in Power Automate's run history to include key items I'm passing in:
That makes it very easy to see which user or test scenario had an issue and jump right to it.
Honestly, the less time you spend troubleshooting anything in Power Automate the better. So writing small code and displaying key variables on your run history will help you from murdering anyone on the Microsoft Power Automate product team.
I Use a Lot of JSON
Once you go to the trouble of understanding how to push/pull JSON from PowerApps to...well...anything...the world gets a lot more open and connected. It also shows us a future that reaches outside of Microsoft's grasp to solve a number of alternative database structures that you might not really feel like hosting in Azure and might feel like hosting saaaaaay on premise.
We aren't specifically doing that today, but all the pieces are lining up for that future state. Just for clarity, the most recent use-case I had (still completely within Microsoft's borders) was taking A LOT of employee data (all benefits, all information) from certain staff and hosting it short-term in a lookup table. When you have over 40,000 people in your organization (not counting contractors), that is a lot of data. However, I just put it into a 3 column table that I eventually expanded out to a whole whopping 5 (only to make lookups easier).
That table consisted of my primary lookup column (person number key from the ERP system - <barf>), two keys that later came up as handy (staff and manager email addresses - easy keys for the Power Platform to use) and two mutiline columns just chock full of everything you ever wanted to know about that employee all in JSON.
It sped up queries remarkably and avoided some limitations on SharePoint (column width) that suddenly meant nothing when it all was just jammed into one field.
Limit on X # of rows in a single query from SharePoint? How about I stuff 1000 rows into a single row's single field? Check. Mate.
I Don't Use Solutions
Why? Lazy.
Plus, it is one of many things Microsoft is trying wrap around the core product that try to convince you to use more Dataverse.
Dataverse is stupid and you're stupid for using it. And I don't care who you are. So says 2024 John.
Final Thoughts
None of this means I am 100% correct.
I don't work in your org and I'm not wrestling w/ your problems. Different approaches can help you solve different problems.
We also don't use Dev/Test/Prod environments and we also just use our own accounts for much of what happens. We could use service accounts but service accounts were nearly verboten for the Power Platform 5 years ago and everything since is all wrapped around Dataverse to make it all (sorta) work. We just deal w/ the occasional problem of people leaving the org or changing roles when it happens instead of causing problems every f'ing day by using service accounts. Everything we do is live and our users certainly seem to like it that way. Even if sometimes we booger something up for a few minutes.
I tend to prioritize responsiveness and agility over the plodding pace of how Agile(tm) is marketed today. Therefore, we operate more like a pre-Agile(tm) version of Agile known as Extreme Programming (XP). Our developers are gods on this platform and they are responsible for cleaning up their own messes. It gives them great power and I'm sure Uncle Ben has something to say about that.
The ongoing push from Microsoft to make the Co-Pilot buttons larger and cover more of the screen real estate really bothers me. I worry sometimes that I'm not using it "enough", but then I try it and want to punch whoever suggested the code it writes. I just want it to go away. Bring back Clippy and I'd be less angry than every time I click on a control I can't see parts of it because the Co-Pilot prompt is trying to trick me into clicking it.
This platform is slowly being consumed by Microsoft's push for AI revenues. So I do worry that they will eventually ruin my ability to accomplish our goals without inserting it into everything. However, for now, it remains a good platform to use and solve business issues.
So long as you don't click on all the obvious buttons/prompts and dig deep to find "start with page design" and "blank canvas" (that they'll probably start reducing the font on after moving it to the end of the choices under a further obfuscated "See More" label).
If 2027 me is still coding between searching for food in the post-capitalist hellscape, then I'm sure he'll leave a comment to tell me how wrong I am and that I just need to give Co-Pilot a chance. Co-Pilot Uber Alles!
No comments:
Post a Comment
Because some d-bag is throwing 'bot posts at my blog I've turned on full Moderation. If your comment doesn't show up immediately then that's why.
DIAF Visualpath team