Cloning Teams and Configuring Tabs via Microsoft Graph: Configuring the SharePoint and Files tabs

Cloning Teams and Configuring Tabs via Microsoft Graph: Configuring the SharePoint and Files tabs

Our blog post series is closing to the end. In the Prelude, I covered what were the reasons for starting this blog post series in the first place, and why cloning teams and after that configuring the tabs automatically is valuable. Part 1 was all about how you can successfully clone teams via Microsoft Graph and what kind of struggles you might encounter while at it. After that, we proceeded to the blog posts about individual tab configurations.

At first, I showed you how you can automatically configure the OneNote tab to either show the default OneNote file that gets created when you clone a team, or how you can create new OneNote files for the group and display those.

After OneNote, I shared with you the configuration instructions for Planner tabs, and perhaps also got you intrigued about cloning Planner plans. And in this final blog post, I will show you how you can configure two SharePoint related tabs: the SharePoint tab itself and Files.

Published articles in this blog post series

Table of Contents

  1. SharePoint App ID
  2. Forming the body for the update request
  3. Supportive methods
  4. Provision subfolder structures for the Files tab
  5. Afterword

SharePoint App ID

If you’ve been following this blog post series until now, you already have this kind of a TeamsAppId class in your project. Now it is time to add the SharePoint ID to it, to accompany the previously added IDs. And if you are new to the series: with the ID we can figure out what kind of a tab we are dealing with, because each different type of tab requires its own individual configurations.

public class TeamsAppId
{
    public static readonly string OneNote = "0d820ecd-def2-4297-adad-78056cde7c78";
    public static readonly string Planner = "com.microsoft.teamspace.tab.planner";
    public static readonly string SharePoint = "2a527703-1f6f-4559-a332-d8a7d288cd88";
}

Forming the body for the update request

Unlike with the previous two tabs we’ve configured (OneNote and Planner), we are actually not creating any new resources here. I’m a big fan of provisioning resources to new SharePoint sites via SharePoint site templates and PnP templates, so I rather use those this time as well.

The nice thing about configuring the SharePoint tab is that you do not need to have the resources available yet when you are configuring the tab. You only need to know what URL the resource will be located at after it has been provisioned. You can be certain that the tab will display your content, whether you provision the resources before or after configuring the tab.

Here, we deduce that the tab in question is indeed SharePoint, and immediately after proceed to construct the body for our update request.

if (teamsAppId == TeamsAppId.SharePoint)
{
    body = await ConfigureSharePointTab(newTeamId, templateTab.SelectToken("configuration.contentUrl").ToString());
}

The SharePoint tab can be used for displaying both lists and pages. Luckily, the URLs are very similar for the most part, and the only things we need to figure out are the new team site URL and the relative URL of the resource that we want to display.

Example content URL for a page:

https://laurakokkarinen.sharepoint.com/sites/example/_layouts/15/teamslogon.aspx?spfx=true&dest=https%3A%2F%2Flaurakokkarinen.sharepoint.com%2Fsites%2Fexample%2F_layouts%2F15%2FpageName.aspx

Example content URL for a list:

https://laurakokkarinen.sharepoint.com/sites/example/_layouts/15/teamslogon.aspx?spfx=true&dest=https%3A%2F%2Flaurakokkarinen.sharepoint.com%2Fsites%2Fexample%2FLists%2FlistName%2FAllItems.aspx%3Fp%3D11

Constructing those URLs is perhaps not as straight forward as you might first expect because it requires us to make a extra call to Microsoft Graph. But in the end, it doesn’t result in that many lines of code.

When calling this method, we’ll pass the new team ID and the template tab content URL to it as parameters. We extract the relative URL from the template tab content URL, and combine it with the URL of the new SharePoint team site that automatically got created when we cloned the team. To be on the safe side, we fetch the team site URL from Microsoft Graph using the ID of the new team, because it is not guaranteed that the team name is always an exact match with the site name.

private static async Task<string> ConfigureSharePointTab(string teamId, string templateTabContentUrl)
{
    var contentRelativeUrl = GetSharePointTabContentRelativeUrl(templateTabContentUrl);
    if (contentRelativeUrl == null) return null;

    var webUrl = (await MicrosoftGraph.GetGroupSite(teamId)).SelectToken("webUrl").ToString();

    return $"{{ 'configuration': {{ 'contentUrl': '{webUrl}/_layouts/15/teamslogon.aspx?spfx=true&dest={WebUtility.UrlEncode($"{webUrl}/{contentRelativeUrl}")}' }} }}";
}

The contentUrl of the template tab has an encoded version of the resource URL in one of the query string parameters: dest. We first extract that parameter value from the full content URL, URL decode it, and then only take the site relative URL of the resource. E.g., if the decoded dest parameter value is https://laurakokkarinen.sharepoint.com/sites/example/Lists/listname/AllItems.aspx?p=11, the bit we end up returning is just Lists/listname/AllItems.aspx?p=11.

private static string GetSharePointTabContentRelativeUrl(string contentUrl)
{
    const string param = "&dest=";
    var index = contentUrl.IndexOf(param, StringComparison.OrdinalIgnoreCase) + param.Length;
    var dest = WebUtility.UrlDecode(contentUrl.Substring(index));

    var frags = dest.Split('/');

    return string.Join("/", frags, 5, frags.Length - 5);
}

Supportive methods

In addition to the code above, you’ll also need the following method to fetch the Office 365 group connected team site URL from MicrosoftGraph. If you’ve been following this blog post series until now and have already included the classes from the Configuring Tabs — The Fundamentals blog post, you only need to add the following method to the MicrosoftGraph helper class.

public static async Task<JToken> GetGroupSite(string groupId)
{
    return await HttpRequest.GetResponseBodyAsJson($"https://graph.microsoft.com/v1.0/groups/{groupId}/sites/root", HttpRequest.Method.Get);
}

Provision subfolder structures for the Files tab

Now that we are talking about SharePoint, there is another thing I’d like to touch upon. You remember when I mentioned using PnP templates to provision the resources we want to display in the SharePoint tab? There’s also another nifty thing we can do with them for Teams tabs while we are at it.

You’ve no doubt taken notice of the Files tab that displays all the files you’ve added to the Teams channel. We can actually provision subfolder structures for those Files tabs with PnP templates.

Because the files (and folders) are in fact stored in SharePoint, there’s nothing stopping us from provisioning folder structures with PnP templates that get displayed on Teams’ side as well. In addition to PnP templates, you can also provision the folders using CSOM or the SharePoint REST API.

We don’t need to deal with any folder or channel IDs or anything like that. The teams channels link to the correct folders simply based on their names. If we just make sure that we are provisioning the root level folders to the Documents library with the exact same names as what the channels are called, those folders will automatically get mapped to the correct channels on Teams when users go to browse the Files tabs. And under those root level folders, we can provision additional folders that get displayed in the Files tab.

Here is an example of what the Folders section of the PnP template could look like to achieve a similar view as in the image above. If you are not yet familiar with PnP templates, here are a couple of links to the provisioning console application sample and the latest schema documentation.

<pnp:Folders>	 	 
    <pnp:Folder Name="General">	 	 
        <pnp:Folder Name="Schematics"></pnp:Folder>	 	 
        <pnp:Folder Name="Material options"></pnp:Folder>	 	 
        <pnp:Folder Name="Images for the instruction manual"></pnp:Folder> 	 
    </pnp:Folder>	 	 
    <pnp:Folder Name="Announcements">
        <pnp:Folder Name="Published materials"></pnp:Folder>
    </pnp:Folder>
    <pnp:Folder Name="Backlog">	 	 
        <pnp:Folder Name="Attachments"></pnp:Folder>	 	 
    </pnp:Folder>	 	 
    <pnp:Folder Name="Meetings">	 	 
        <pnp:Folder Name="Shared with stakeholders"></pnp:Folder>	 	 
        <pnp:Folder Name="Internal"></pnp:Folder>	 	 
    </pnp:Folder>	 	 
    <pnp:Folder Name="Schedule and Budget">	 	 
        <pnp:Folder Name="Original estimates"></pnp:Folder>	 	 
    </pnp:Folder>	 	 
</pnp:Folders>
Note that the main channel folder is always called General, even if the channel name is different in Teams due to regional settings.

Here’s also an image of what the same subfolder structure looks like on SharePoint.

Afterword

Have you already been using PnP templates for provisioning resources on SharePoint sites, or are you just getting started? Do you feel like they are easy to get into it, or would you perhaps like me to write my own tutorial about them? Let me know in the comments!

This blog post is the last one in this series — at least for now. There’s still loads of different types of tabs we could configure programmatically, so who knows, maybe I’ll get back to this topic soon enough. 😉

What did you think of this blog series? Was the topic useful? Did you like reading shorter articles more often? Or do you prefer my usual style of having everything in one huge chunk of text? I write this blog for you, so it’d be good to hear your thoughts. 🙂

Thank you for sticking with me ’til the end, and until next time!

Laura



13 thoughts on “Cloning Teams and Configuring Tabs via Microsoft Graph: Configuring the SharePoint and Files tabs”

  • Laura
    Hi, our school is making the move to Teams, and I’ve been interested in looking at ways to help automate the process. We are using SDS to provision Teams with Class data via a OneRoster API and that is working well. I am looking into MANDATORY TABS in each Class Team that link to a manually created private / public Sharepoint Location that contains legacy files in the “Documents” folder. I have been doing this manually in each 1 of 650 Class Teams pointing at the relevant subject Sharepoint.
    Now I have found the Graph API and it interests me / scares me in equal measure. Simply because I could screw it up. I’ve managed to query and find the ID’s of the Teams, and Tabs, and Apps, and defaults, and now I would like to write a script to execute the ADD of a Sharepoint Docs folder to each of the Subjects, but wondered if you had any pearls of wisdom you could / would share? I’ve been reading your blog with interest. Great stuff. Many thanks Craig.

  • Hi Laura,
    Thanks for sharing your findings.
    I am trying to create a Tab that has custom SPFx WebPart solution which is already deployed in Team scope.
    I can create a tab by providing the body
    {
    “displayName”: “Opportunity Summary”,
    “canUpdateConfiguration”:true,
    “teamsApp@odata.bind” : “https://graph.microsoft.com/v1.0/appCatalogs/teamsApps(‘8f4b04a6-a79e-4677-b81c-dff4e34d9989’)”,
    “configuration”: {
    }
    }

    The AppId “8f4b04a6-a79e-4677-b81c-dff4e34d9989” is my custom SPFx Teams app.
    I am trying to find configuration parameters to set up a tab so that we don’t need to go manually on tab to set up a tab.

    Thanks,
    Uday

  • Hi Laura, found your article when searching for an explanation of an issue we experience at the moment. We also make use of the ‘magical’ automatic mapping of Channels to Folders as long as they have the same name. This way we can provision subfolder structure directly after creating the channel through a Graph call.

    This just magically stopped working on Dutch teams (created from a Dutch O365 Group) on an English tenant (with English root site). When we now in Teams go to the ‘files’ tab it cannot find the channel folder and results in an error in Teams. On English teams this still works fine.

    Are you aware of this bug? Do you have an idea of the root cause and possible workaround?

  • Love your blog posts! And I do like the “big chunk of text” style – meatier the better! And I think a tutorial on PNP Templates is a great idea. I know I would look forward to it! Thanks for your work here. You are a bookmarked resource for sure!

  • I’m just curious about how to deal with any of the 3rd party cloud storage systems, like Box, or Google. Is there some magic to clone any of those additional storage configurations?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.