This chapter continues our objective of putting multiple pieces of the Microsoft 365 environment together into cohesive solutions. It stemmed from a customer wanting to try the Microsoft Bookings application but that not being approved for use. I suggested using our familiar combination of SharePoint and Power Apps. For our sample here, I added the automatic scheduling of an associated Teams meeting via Power Automate. It has a few advantages over Bookings, such as being able to schedule on behalf of another user and the ability to offer multiple appointments at the same time.
Configuring the SharePoint List
Figure 22-1 shows the SharePoint list where we store our data.
Importantly, we include the time component in our AppointmentDate column since the user will be picking from available dates and times. We include Modified By in our view since we are going to allow either the person who created the appointment1 OR the person for whom it was scheduled edit the appointment. We include a Subject and Comments for the user to give some additional detail on what they want to discuss in the reservation. Status is a choice column with the default value of Available. Our Power App will change it to Booked when the appointment is scheduled. If the user cancels, it will put it back to Available and clear the Subject and Comments. We store the Participant (Person column) separately so a user can schedule for someone else as mentioned before2. We default AppointmentLength to 20 (minutes) but preserve the ability to change this value if desired. Finally, we have a ScheduledTeamsMeeting Yes/No column. We will use that later with our scheduled Power Automate flow. We will query for all the Booked reservations where ScheduledTeamsMeeting is No. The flow will then schedule the meeting and set this value to Yes.
Next on the docket is Power Apps.
Creating our Application in Power Apps
Our first task is to set a variable in during the application’s OnStart event, as shown in Listing 22-1.
Set(
varUserEmail,
User().Email
);
Set(
varParticipant,
Blank()
)
Listing 22-1
OnStart to set initial variables
We will use varUserEmail later in determining if there are existing reservations the current user can edit. varParticipant is useful in updating the participant as well as showing or hiding our error label. We initialize it to be Blank().
Figure 22-2 shows the Home screen of our application.
As we have seen in other examples, we use a Gallery control to view and select the available records. In this case, it shows the available appointments. We filter our Reservations list to only show available ones in the future by setting the Items property:
Note that we need to say Status.Value because Status is a Choice column. We use Now() rather than Today(), since only the former includes the current time as well as the current date. We display the appointment date and time in the “long” date format we want by using the DateTimeValue()method together with the Text() method, which has a second parameter to format the date and time in the Wednesday, Sep 07, 2022 09:00 AM format. Here is the label’s Text property:
We call the SearchUserV2() method from Office365Users, passing in the SearchText of the combo box. That method returns a record, but its value is the table that the box wants. We set the DisplayFields and SearchFields properties to both be ["DisplayName"] so the user’s name displays and searches correctly. We also set isSearchable to true and SelectMultiple to false. It will be useful to have the selected user stored in the varParticipant variable, which we set in both the combo box’s OnSelect and OnChange events3:
Set(varParticipant, ParticipantBox.Selected)
We have an ErrorLabel to remind the user what they need to do before confirming the appointment. Since we need a participant and a Subject as a minimum, we set its Visible property to:
IsBlank(varParticipant) Or SubjectBox.Text = ""
To write our reservation back to the SharePoint list, we use our old friend the Patch() method, as shown in Listing 22-2.
This is less complicated than some of our other examples as we don’t need to create any new records. Instead, we use the LookUp()method to find the correct SharePoint list item. We then set the Subject and Comments to the values entered in their respective controls. Setting the Status is non-trivial, though, since it is a Choice column in SharePoint. We normally just match it to a drop-down box, but in this case we always want to always set it to Booked. I originally tried to create a variable from a hidden drop-down box, but that was strangely unreliable. Luckily, a quick search found the proper {Value: “Booked”} syntax. We’ve covered how to update a SharePoint Person column, so we won’t cover it again here other than to note how we took advantage of our varParticipant variable. The ScheduledTeamsMeeting column should already be false, but we set it that way to be on the safe side. We don’t set AppointmentLength since that is part of the appointment and not set in our design by the person scheduling. We Reset() our controls so they are empty if the user selects another appointment or returns after editing an appointment. We also set our varParticipant to Blank() to be sure that it doesn’t persist now that we have cleared the combo box where we set it.
Our design is to only show the Existing Appointments button if the user has a future appointment. So we set its Visible property, as shown in Listing 22-3.
Visible property for the Existing Appointment button
This is a bit different than our normal syntax, but my original formulation4 was subject to delegation issues. Instead, we use the With() function to evaluate our filter and treat it as a single record. As we described in the last section, we want for either the participant or the person who made the reservation to be able to edit it. Since both Participant and Modified By are Person columns, we can get their Email property and compare it to the current user’s email. We also wanted our Status to be Booked and for the appointment to be in the future.
When the user clicks the Existing Appointments button, we want to go to the ModifyAppointment screen and thus have this in its OnSelect event:
Our gallery’s Items property uses the same Filter() as our Existing Appointments button to get future appointments for which the user is either the participant or the one to set up the appointment:
Since the user can edit the existing Subject or Comments, we link their Default properties to the selected appointment:
varSelectedModifiedApt.Subject
and
varSelectedModifiedApt.Comments
This will display the existing values that the user can then edit if desired. This is helpful as we will write the contents of these boxes back to the appointment (even if they haven’t changed).
The rest of the logic is in the two buttons. Listing 22-4 shows the OnSelect code for the Delete Appointmentbutton.
Patch(
Reservations,
LookUp(
Reservations,
ID = varSelectedModifiedApt.ID
),
{
Subject: "",
Comments: "",
Status: {Value: "Available"},
ScheduledTeamsMeeting: false
}
);
Navigate(Home)
Listing 22-4
OnSelect code for the Delete Appointment button
We want to Patch() the selected appointment by clearing its Subject and Comments, setting its Status back to Available, and setting ScheduledTeamsMeeting to false. We’d like to clear the Participant as well, but that is not currently possible. But since we will write a new Participant when the appointment is rescheduled, we should be fine.6 We then navigate back to the Home screen.
The logic for the Update Appointment button is similar, as shown in Listing 22-5.
Patch(
Reservations,
LookUp(
Reservations,
ID = varSelectedModifiedApt.ID
),
{
Subject: UpdatedSubjectBox.Text,
Comments: UpdatedCommentsBox.Text
}
);
Navigate(Home)
Listing 22-5
OnSelect code for the Update Appointment button
We update both the Subject and the Comments and then return to the Home screen.
We are now ready to go to Power Automate to schedule a Teams meeting for each booked appointment.
Adding a Scheduled Flow in Power Automate
While we could have launched Power Automate from within our application, in this case, it makes more sense to have a scheduled flow, as shown in Figure 22-4, perhaps every day at 5 AM.
That will minimize the rework if the user cancels or changes the appointment the same day. While we will schedule a Teams meeting for each future appointment, we could add additional logic to only schedule meetings one week or some other time frame before the appointment. Figure 22-5 shows how we set the frequency and the time for the flow to run.
After setting up this recurrence, we go to the SharePoint connector and configure the Get items action, as shown in Figure 22-6.
We configure the Site Address and List Name to match our connection in Power Apps. We then need to click Show advanced options in order to choose which items we want. We do that by entering an Open Data Protocol (OData) filter query, as shown in Figure 22-7.
This has a different syntax than what for many of us is a more familiar Standard Query Language (SQL) query, but the concepts are the same. In words, we want to find the list items that have been booked, are in the future, and have not already been scheduled as Teams meetings. Here is the text of the OData query:
(ScheduledTeamsMeeting ne '1') and (AppointmentDate ge '@{utcNow()}') and (Status eq 'Booked')
ne means “not equals,” ge means “greater than or equals,” and eq means “equals.” Notice also how a 1 represents true and a 0 represents false. We use the Expression window in Figure 22-7 to insert the utcNow() method to dynamically insert the current time in Universal Time Coordinated (UTC) format (previously known as Greenwich Mean Time). Since our AppointmentDate value from SharePoint includes a time zone component, Power Automate gets the math right on whether the time is in the future. We could include an OData Order By value, but we are going to process all the list items we get and thus the order does not matter.
We next need to loop through all the list items that are returned by the query. As shown in Figure 22-8, we do this with the Apply to each action and use the values attribute, which is the list of the SharePoint items.
The next part takes some trial and error. The AppointmentDate value from the list items at first looks to be right in that the format is of this form7:
2022-08-24T17:00:00Z
But using this value directly when we schedule the Teams meeting causes an error. Instead, we use the Compose action, as shown in Figure 22-9, and then use the formatDateTime()expression.
Getting the syntax for the AppointmentDate is not trivial:
The key part is items('Apply_to_each'). This will be the name of the loop and could by 'Apply_to_each2' or a similar name. The second parameter is the date format, which means four-digit year, two-digit month, two-digit date, time zone, two-digit hour in a 24-hour format, and two-digit minute. Looking at our Run history, the output of our Compose action is:
2022-08-24T17:00
So what we needed to get rid of was the seconds and the Z (which means the time zone is UTC).
We similarly need the ending date and time of our meeting. We again use the Compose action, as shown in Figure 22-10.
We again use the formatDateTime()expression but also add in the addMinutes() expression to incorporate our AppointmentLength column:
Notice how we have to also use the int() method to format the value as an integer. That gives us a value that looks like this:
2022-08-24T17:20
We are now finally ready to schedule our meeting. We select the Create a Teams meeting action and choose our primary Calendar from the list of available calendars. Figure 22-11 shows how we use the Participant Display Name value from our SharePoint list item in the Subject of the meeting.
We use a similar technique (Figure 22-12) to add the Subject, Comments, Participant, and Scheduled By to the Message of the meeting.
Notice how we use the HTML Line Break (<br />) to get hard returns in the message. We use the Outputs from our Compose action (Figure 22-9) for the Start time and do the same for the End time (see Figure 22-13).
Our last scheduling task is to add our Participant Email as a Required Attendee, as shown in Figure 22-13.
Now that we have scheduled the meeting, we need to make sure the next time the flow runs that it doesn’t try to schedule another one. To do that, we need to use the SharePoint Update item action to set the ScheduledTeamsMeeting column to Yes, as shown in Figure 22-14.
Notice how use the ID from our Get items action (within our loop) in order to make sure we are editing the right item. We set the Status to Booked just to be on the safe side and then set ScheduledTeamsMeeting to Yes.
We did it! Figure 22-15 shows an example of our scheduled Teams meeting.
This solution doesn’t cancel the meeting if the user cancels the reservation in our Power App, but that would be a good enhancement. We could also easily connect Power BI to our SharePoint list to make a dashboard showing appointments, what percentage of available appointments were booked, and much more.
Summary
In this chapter, we connected Power Apps to SharePoint to keep track of appointment reservations. We incorporated some specialized logic to allow either the submitter or the person with the registration to edit or cancel their reservations. We then used the ability of Power Automate to do scheduled flows to automatically create Teams Meetings for the appointments. To pull this off, we learned how to do OData filter queries so that we could limit to just those appointments without a Teams Meeting as well as some gyrations around date and time formats. We then updated our SharePoint list to reflect that the meetings had been scheduled so we could avoid duplicates. In the next chapter, we will dig into Power Apps Forms control for more sophisticated adding and editing of data.