cancel
Showing results for 
Search instead for 
Did you mean: 
Reply
Franx
Frequent Visitor

Track user progress throughout your app

Hello,

 

Has anyone created or does anyone could be so kind to share with me what could be the most efficient way to develop my app logic to track the individual user progress throughout the app?

 

As of now, I've been thinking on creating a SharePoint list that will contain many fields (all single line text):
LastName

FirstName

CourseID

CourseDescription

FirstGrade

SecondGrade

ThirdGrade

FourthGrade

TotalGrade

ParticipationGrade

TeamworkGrade

TrainingGrade

FinalGrade

userEmail

ActivityDateTime

 

My logic on AppStart is to:

 

Set(varUserEmail, Lower(User().Email));
Set(varUserLastName, Office365Users.MyProfileV2().surname);
Set(varUserFirstName, Office365Users.MyProfileV2().givenName);

 

 

And then on the click of a button in the first main/welcome screen, the OnSelect logic is:

 

Patch(SPTrainee, Defaults(SPTrainee), {LastName:varUserLastName,FirstName:varUserFirstName,FirstGrade:"0",userEmail:varUserEmail,ActivityDateTime:Now(),CourseID:"1",CourseDescription:btnMainScreen.Text});Navigate(NextScreen,Cover)

 


This is the first time I am trying to code a user progress tracking session, and I am not certain this is the best way to approach this. Could anyone be so kind to share any guidance on the best way to approach this? basically the intention is to track all the changes that the user will be performing while using (this changes or actions should have an associated score which I am not certain how to implement without thinking on doing this for each element which would be tremendously time consuming if I write the formula for every single checkbox element) to later visualize a final score, associated with to each user. Also, the intention should be to track the user usedTime to complete an specific action, for this I would require to save the time every time the user checks that something has been completed (in a checkbox list), finally I am not sure if this logic should update a single record line for each user, instead of having multiple rows of data associated to a single user. (to me, the second feels like the SharePoint datasource will be overloaded very soon and make the app perform slowly).

 

Appreciate your assitance.

Thank you.

2 REPLIES 2
GarethPrisk
Resident Rockstar
Resident Rockstar

You are correct that SharePoint has the potential to become overloaded, but this depends on the volume of your data. How many users do you expect to be using the app (i.e. rows in the SharePoint list, at least 1 per user)?

 

A few design notes:

  • You should consider a more reliable, less complicated mechanism for identifying the current user
  • Then, for your SharePoint list, you should have a row per user
    • The columns you have above are fine
    • I would add a column specifically for storing the non-formatted result of User().Email as your identifier (maybe replacing userEmail's value/purpose)
    • This will allow you to Lookup the user's row, to drive app behavior, which is delegable when written correctly
    • https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-filter-lookup
    • Example: Set(gblUserRecord, LookUp(SPTrainee, userEmail = gblUserEmail))
  • You now have an object to read to drive your app's behavior
    • You can determine which screen to Navigate to (if they've already done 1,2, then navigate to 3, for example)
    • You can continuously write back to this record (next point)
  • If you don't have a record
    • Then Patch the List and use that Patched row to set your variable
    • Example: If(IsBlank(gblUserRecord), Set(gblUserRecord, Patch(SPTrainee, Defaults(SPTrainee), {["add fields"]})));
  • If you have a record, be sure to update that row
    • Still use the Patch function, but pass along the record object with it
    • The tricky part here is updating the record in the context of the app, since you can't update/patch a global variable record object
      • You can put the record into a Collection, and then transact it/update the variable from it
      • Example, per app session: ClearCollect(colUserRecord, gblUserRecord)
      • Then you can update that local collection as needed
      • Example: UpdateIf(colUserRecord,true,{column: value})
      • As needed, you can update the variable from this row (for app use, and before patching back) - this only applies after you've gotten the record (LookUp) from SharePoint and are now using the record locally
      • Example: Set(gblUserRecord, First(colUserRecord) )
    • Then update SharePoint (when needed)
    • Example: Patch(SPTrainee, LookUp(SPTrainee, userEmail = gblUserEmail)), gblUserRecord)

 

This give you some flexibility to bind your controls to the gblUserRecord variable for visualizing things as you go. Alternatively, it'll be the colUserRecord's single record value. Either way, this will give you the persistence you need to show the user things in the app and then a way to update any changes back to your centralized list.

 

It's not discussed here, but you may want to consider multiple lists (refactor the data model), instead of columns. Crude example below. You may be able to get away with a single list, but obviously it becomes more cumbersome and rigid the more you use columns instead of a data model.

  1. List of Users
    • John
    • Jane
  2. List of Classes
    • Science
    • Math
  3. List of User Grades
    • John Science (A)
    • John Math (C)
    • Jane Science (B)
    • Jane Math (A)

Hello @GarethPrisk 

 

Thanks for your response, I've been implementing this today and it is helping me a lot, but I got an error, if you happen to know what this error is all about and how to fix it, that'd be a great help.

What I did so far:

On App OnStart:

 

Set(varUserEmail, Lower(User().Email));
Set(globalUserRecord, LookUp(SPTrainee, userEmail=varUserEmail));
If(IsBlank(globalUserRecord), Set(globalUserRecord, Patch(SPTrainee, Defaults(SPTrainee), {LastName:varUserLastName, FirstName:varUserFirstName,FirstGrade:"0",userEmail:varUserEmail,ActivityDateTime:Now()})));
ClearCollect(colUserRecord, globalUserRecord);

 

Above is working great, it is creating a single user row, per user.

Then, in the first screen the user sees, I will start collecting some data based on the user interaction with the app, for instance, I will first collect the button text to insert that piece as the Course Description when the button is pressed.

 

OnSelect of a button:

UpdateIf(colUserRecord,true,{CourseDescription:btnMain.Text}));
Set(globalUserRecord,First(colUserRecord));
Patch(SPTrainee, LookUp(SPTrainee, userEmail=varUserEmail),globalUserRecord);
Navigate(Onboarding, ScreenTransition.Cover);

 

Up to this point, everything is working OK. Then in the next screen (Onboarding), I would like to start collecting the user score, based on some checkboxes selection. I have the following in the OnSelect property:

ClearCollect(colUserRecord,globalUserRecord);
If(chkCheckMark.Value=true,Set(Score,Score+1),Set(Score,Score-1));
UpdateIf(colUserRecord,true,{FirstGrade:Score});
Set(globalUserRecord,First(colUserRecord));
Patch(SPTrainee, LookUp(SPTrainee, userEmail=varUserEmail), globalUserRecord); //This one has the red line below it, giving the error "{VersionNumber}: The specified column is read-only and can't be modified."

I am getting an error which says {VersionNumber}: The specified column is read-only and can't be modified and I can't figure out what I am doing incorrectly.

 

Appreciate any assistance.

 

Thank you!

Helpful resources

Announcements
Super User 2 - 2022 Congratulations 768x460.png

Welcome Super Users

The Super User program for 2022 - Season 2 has kicked off!

Power Platform Conf 2022 768x460.jpg

Join us for Microsoft Power Platform Conference

The first Microsoft-sponsored Power Platform Conference is coming in September. 100+ speakers, 150+ sessions, and what's new and next for Power Platform.

<
Users online (4,139)