Add External User to a Office 365 Group Programmatically

The MSGraph Invitation Manager

Quick overview of Invitation Resource Type, for full information and details please go to Microsoft Graph API Reference.

The resource type has the following definition:

Methods

MethodReturn TypeDescription
Create invitationinvitationWrite properties and relationships of invitation object.

Properties

PropertyTypeDescription
invitedUserDisplayNameStringThe display name of the user being invited.
invitedUserEmailAddressStringThe email address of the user being invited. Required.
invitedUserMessageInfoinvitedUserMessageInfoAdditional configuration for the message being sent to the invited user, including customizing message text, language and cc recipient list.
sendInvitationMessageBooleanIndicates whether an email should be sent to the user being invited or not. The default is false.
inviteRedirectUrlStringThe URL user should be redirected to once the invitation is redeemed. Required.
inviteRedeemUrlStringThe URL user can use to redeem his invitation. Read-Only
invitedUserTypeStringThe userType of the user being invited. By default, this is Guest. You can invite as Member if you’re are company administrator.
statusStringThe status of the invitation. Possible values: PendingAcceptance, Completed, InProgress, and Error

Create an Invitation

To create a external user we have to do a POST to https://graph.microsoft.com/beta/invitations, I use here the beta version of the API but it is available in version 1 too, in the request body we have to specify the following parameters :

Required Parameters:

  • invitedUserEmailAddress
  • inviteRedirectUrl

Optional Parameters:

  • invitedUserDisplayName
  • sendInvitationMessage
  • invitedUserMessageInfo
  • invitedUserType

Response :

If the invitation is created the API return a Invitation object with the follow information:

{
  "id": "string",
  "invitedUserDisplayName": "string",
  "invitedUserEmailAddress": "string",
  "invitedUserMessageInfo": {"@odata.type": "microsoft.graph.invitedUserMessageInfo"},
  "sendInvitationMessage": false,
  "inviteRedirectUrl": "string",
  "inviteRedeemUrl": "string",
  "status": "string",
  "invitedUser": {"@odata.type": "microsoft.graph.user"},
  "invitedUserType": "string"
}

Sample Scenario

I create an Azure Function, in TypeScript that is part of a provisioning process of creating a collaborative Team Site that will be used by internal and external users and it is called by the provisioning process.

The Azure Function receive as parameter, user e-mail, user name of external user and the group id of group to add the user. The Azure Function add the user to AAD and Group and return the information of invitation created.

Here the extract of code of the Azure Function that create the Invite and add user to the Office365 group.

At this moment we have the user created on AAD and added to Office 365 group, but the process is not completed! The user must complete the redemption process and once completed, the invited user becomes an external user in the organization.

In this my Scenario the process of provisioning is responsible to send a email to the user with the redemption url returned by the Azure Function, property “inviteRedeemUrl”

Here exemple of execution of Azure Function:

Until the external user complete the Redemption Process the user is an “Invited User in AAD”.

The Redemption process check if the user has a Microsoft Account and if not the user must follow the steps to create an account and after he will be redirect to the url of the group defined.

The Code is available here .

Thanks for reading !

Simple way to deploy any asset with your SPFx Application

Sometimes we need to include some assets with our SPFx Application (web part or application customizer), like images, JSON files, or another stuff. By default this assets aren’t recognised by SPFx in build chain process.

For example we have some images that are in a folder that I called “assets” and I want that will be deployed with my SPFx Application because we use them in our SPFx application.

To include this assets in our SPFx package we need to create a file called “copy-static-assets.json” in the config folder and in the extensions property that is an array add an entry for each file extension that you want be deployed.

When we execute gulp bundle we can see in the log messages the indication that our assets was include, checking the messages :

After our assets will be include in our SPFx Aplication.

Thanks for reading!

Add SPFx Web Part to a Full Width Column

Introduction

In the communication site we can create a Full width Column that allows us to add a web part that will have full On the communication website, we can create a full width column that allows us to add a web part that will be full width. Once created, we will see a list of Web Parts that can be added to the section, not all Web Parts can be added to a Full Width Column Section.

Here the screenshot how it look like after added a Full with Column to a page.

How to allow an custom SPFX web part can be add to a Full width Column

We can allow our SPFx web part to be added to Full width Column, for that we must have to add a new property to our web part manifest file, called “supportsFullBleed“, this property is type boolean and have to set to true.

Here sample of manifest file with property enable.

{
 
 "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
  "id": "bd3165a7-de0f-46bf-b961-7c07b0a5b7ad",
  "alias": "NewsWebPart",
  "componentType": "WebPart",
  "supportsFullBleed": true, 
  "version": "*",
  "manifestVersion": 2,
  ...
 
}

After deploy the web part , and fi we go to our page that have a Full Width Column Section, you will see our web part in a list of web parts allowed to add.

Here a sample:

Thanks for reading!

Microsoft Graph Open Extensions

Microsoft Graph Open Extension allows to add untyped custom data to resources, like User and Messages, and there single API endpoint that gives you the possibility to extend Microsoft Graph with your own application data. You can add custom properties to Microsoft Graph resources without requiring an external data store.

Here a list of resources that are supported

ResourceVersion
Administrative unitPreview only
Calendar eventGA
Group calendar eventGA
Group conversation thread postGA
deviceGA
groupGA
messageGA
organizationGA
Personal contactGA
userGA

List of Microsoft Graph Rest API’s Available

Use Case

Here I will show you how to use open extension to add new custom data to User Resource.

Scenario

I want to give the user the possibility to manage their owns application links, for that I create SPFx web part where the user can define the name, description, Url and Icon. This applications will be rendering as grid of tiles on SharePoint Site, Teams Tab or Teams Personal App.

Here a screenshots of My Personal Apps.

User add new Application links

The links are saved in User resource instance in Extensions property.

We can see how the extension was created in User resource instance executing the followed MSGraph Rest API.

https://graph.microsoft.com/v1.0/me?$select=id,displayName,mail,mobilePhone&$expand=extensions

In the response you can see in the Extensions property the data that was saved.




{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(id,displayName,mail,mobilePhone,extensions())/$entity",
    "id": "33be46e1-8651-4ba3-8b43-2acbf95e71e8",
    "displayName": "João José Mendes",
    "mail": "joao.mendes@devjjm.onmicrosoft.com",
    "mobilePhone": null,
    "extensions@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('33be46e1-8651-4ba3-8b43-2acbf95e71e8')/extensions",
    "extensions": [
        {
            "@odata.type": "#microsoft.graph.openTypeExtension",
            "Apps": [
                {
                    "name": "My SAP",
                    "description": "SAP Access",
                    "Url": "https://www.sap.pt",
                    "iconName": "AppIconDefault",
                    "tableData": {
                        "id": 0
                    }
                },
                {
                    "name": "Q&A",
                    "description": "Quick Access",
                    "Url": "https://www.qa.pt",
                    "iconName": "SurveyQuestions",
                    "tableData": {
                        "id": 1
                    }
                },
                {
                    "name": "My Reports",
                    "description": "FI Reports 2",
                    "Url": "https://www.sap.pt",
                    "iconName": "CRMReport",
                    "tableData": {
                        "id": 2
                    }
                },
                {
                    "name": "My CRM",
                    "description": "CRM - Support",
                    "Url": "https://www.crm.pt",
                    "iconName": "AnalyticsView",
                    "tableData": {
                        "id": 3
                    }
                },
                {
                    "name": "Travels",
                    "description": "Travel Agencies",
                    "Url": "https://www.tap.pt",
                    "iconName": "AirplaneSolid",
                    "tableData": {
                        "id": 4
                    }
                },
                {
                    "name": "My Redmind",
                    "description": "Redmind",
                    "Url": "https://www.red.pt",
                    "iconName": "Help",
                    "tableData": {
                        "id": 5
                    }
                },
                {
                    "name": "Office 365",
                    "description": "My Office 365",
                    "Url": "https://office.com",
                    "iconName": "OfficeLogo",
                    "tableData": {
                        "id": 6
                    }
                },
                {
                    "name": "TN3270",
                    "description": "Mainframe Access",
                    "Url": "https://tn3270web/",
                    "iconName": "Database",
                    "tableData": {
                        "id": 7
                    }
                },
                {
                    "name": "My App",
                    "description": "Access to My App",
                    "Url": "https://www.google.pt",
                    "iconName": "PowerApps2Logo",
                    "tableData": {
                        "id": 8
                    }
                }
            ],
            "id": "MyApps"
        }
    ]
}

The Extensions property is an array with all of Extensions created for the resource and is derived from the extension abstract type. Each extension has an extensionName (id in the response) property which is the only pre-defined, writable property for all extensions, along with your custom data.

My Personal Applications will create an extension called “MyApps” for the User and has an attribute called “Applications”, which is a set of objects with information about the applications defined by the user..

The Extension is automatically created if not exists when the User save their list of Applications .

Create an Extension

We can create extensions when creating a new resource or for existing resources, we just have to include the value of the Extensions property in the body of the request.

In this case the Extension is created by the Application for the current user. we can access to extension of a resource using the

Sample of the request with body that is called.

POST https://graph.microsoft.com/v1.0/me/extensions
{
  "@odata.type" : "microsoft.graph.openTypeExtension",
  "extensionName" : "MyApps",
  "Apps": [
                {
                    "name": "My SAP",
                    "description": "SAP Access",
                    "Url": "https://www.sap.pt",
                    "iconName": "AppIconDefault",
                    "tableData": {
                        "id": 0
                    }
                },
                {
                    "name": "Q&A",
                    "description": "Quick Access",
                    "Url": "https://www.qa.pt",
                    "iconName": "SurveyQuestions",
                    "tableData": {
                        "id": 1
                    }
                },
                {
                    "name": "My Reports",
                    "description": "FI Reports 2",
                    "Url": "https://www.sap.pt",
                    "iconName": "CRMReport",
                    "tableData": {
                        "id": 2
                    }
                },
                {
                    "name": "My CRM",
                    "description": "CRM - Support",
                    "Url": "https://www.crm.pt",
                    "iconName": "AnalyticsView",
                    "tableData": {
                        "id": 3
                    }
                },
                {
                    "name": "Travels",
                    "description": "Travel Agencies",
                    "Url": "https://www.tap.pt",
                    "iconName": "AirplaneSolid",
                    "tableData": {
                        "id": 4
                    }
                },
                {
                    "name": "My Redmind",
                    "description": "Redmind",
                    "Url": "https://www.red.pt",
                    "iconName": "Help",
                    "tableData": {
                        "id": 5
                    }
                },
                {
                    "name": "Office 365",
                    "description": "My Office 365",
                    "Url": "https://office.com",
                    "iconName": "OfficeLogo",
                    "tableData": {
                        "id": 6
                    }
                },
                {
                    "name": "TN3270",
                    "description": "Mainframe Access",
                    "Url": "https://tn3270web/",
                    "iconName": "Database",
                    "tableData": {
                        "id": 7
                    }
                },
                {
                    "name": "My App",
                    "description": "Access to My App",
                    "Url": "https://www.google.pt",
                    "iconName": "PowerApps2Logo",
                    "tableData": {
                        "id": 8
                    }
                }
            ]
}

Update an Extension

To update an extension, we need to PATCH the extension created on the specified instance of a resource and on the property we want to update.

Sample of update a list of Applications of User.

PATCH https://graph.microsoft.com/v1.0/me/extensions/myapps
{
    "Apps": [
                {
                    "name": "My SAP",
                    "description": "SAP Access",
                    "Url": "https://www.sap.pt",
                    "iconName": "AppIconDefault",
                    "tableData": {
                        "id": 0
                    }
                },
                {
                    "name": "Q&A",
                    "description": "Quick Access",
                    "Url": "https://www.qa.pt",
                    "iconName": "SurveyQuestions",
                    "tableData": {
                        "id": 1
                    }
                },
                {
                    "name": "My Reports",
                    "description": "FI Reports 2",
                    "Url": "https://www.sap.pt",
                    "iconName": "CRMReport",
                    "tableData": {
                        "id": 2
                    }
                },
                {
                    "name": "My CRM",
                    "description": "CRM - Support",
                    "Url": "https://www.crm.pt",
                    "iconName": "AnalyticsView",
                    "tableData": {
                        "id": 3
                    }
                },
                {
                    "name": "Travels",
                    "description": "Travel Agencies",
                    "Url": "https://www.tap.pt",
                    "iconName": "AirplaneSolid",
                    "tableData": {
                        "id": 4
                    }
                },
                {
                    "name": "My Redmind",
                    "description": "Redmind",
                    "Url": "https://www.red.pt",
                    "iconName": "Help",
                    "tableData": {
                        "id": 5
                    }
                },
                {
                    "name": "Office 365",
                    "description": "My Office 365",
                    "Url": "https://office.com",
                    "iconName": "OfficeLogo",
                    "tableData": {
                        "id": 6
                    }
                },
                {
                    "name": "TN3270",
                    "description": "Mainframe Access",
                    "Url": "https://tn3270web/",
                    "iconName": "Database",
                    "tableData": {
                        "id": 7
                    }
                },
                {
                    "name": "My App 2",
                    "description": "Access to My App",
                    "Url": "https://www.google.pt",
                    "iconName": "PowerApps2Logo",
                    "tableData": {
                        "id": 8
                    }
                }
            ]
} 

Delete an Extension

To delete an extension, we need to do an DELETE on the extension created on the specified instance of a resource. This will delete the extension and its properties..

Sample:

DELETE https://graph.microsoft.com/v1.0/me/extensions/myapps

Open extension limits

The following limits apply to directory resources (such as usergroupdevice):

  • Each open extension can have up to 2 KB of data (including the extension definition itself).
  • An application can add up to two open extensions per resource instance.

The following limits apply to Outlook resources (such as messageevent, and contact):

References:

Add custom data to resources using extensions

SPFx – My Personal Apps – Web Part

Thank you for reading!

Set Tiles View as Default View in a List

By default a list don’t have the tile view available like the document library has, but we can use List View Formating JSON to create Tiles and then the option will appear and we can switch from List to Tiles View.

Define Tiles View as Default View

Sometime we need to define the Tiles View as a default view on a list and here the steps to do it:

Create a New View

On the switch view options, selected “Save View as ” on the view that has the information you want.

Apply List Formating JSON file on the new view

Set Tiles View as Default View

On the switch view option select “Tiles” that appears after applied JSON, and then in switch view select “Set Current View as Default”.

After this steps the list has Tiles View as Default View.

Thanks for reading.

Styling Office UI Fabric Components

Introduction

 

Office UI Fabric is a responsive, mobile-first, front-end framework for developers, designed to make it simple to quickly create web experiences.

With Office UI Fabric we can apply simple CSS styles to make web applications look and feel like the rest of Office 365 Applications.

To Styling an Office UI Fabric component we can use the follow approaches:

  • Styles defined in SCSS file and applied with ClassName prop
  • Styles applied with style prop
  • Styles applied with styles prop using CSS-in-JS

Fabric’s recommended styling approach uses CSS-in-JS and revolves around the styles prop, which is provided by most Fabric components and allows strongly-typed customisations to individual areas of a component. 

 

Styling a Component

 

Unlike a style prop that only applies styles to the root component, the styles prop (provided by most Fabric components) allows you to control the styling of every part of a component: the root, the children, and even sub-components.

A component consists of DOM elements, or “areas.” Each of the areas should be targetable for styling.

To find the available areas for a component, use intellisense or look at the IComponentStyles interface of Control. (Substituting the actual component name for “Component”).

Here the TextField styles Interface as sample.

The styles can be defined in two ways – Object-based or Function-Based.

 

Object-based Styling

 

In Object-Based, we create a jscript object and define styles for each area of the component. like define above:

// Define styling, split out styles for each area.
const styles: IComponentStyles {
  root: { /* styles */ },
  child1: ['className', { /* styles */ }],
  child2: { /* styles */ }
  subComponentStyles: {
    subComponent: {
      root: { /* styles */ },
      child1: { /* styles */ },
    }
  }
}

// In render()
return <Component styles={styles} ... />;

 

Sample:

 

const theme = getTheme();
const styles = {
  root: [
    {
      background: theme.palette.themePrimary,
      display: 'none',
      selectors: {
        ':hover': {
          background: theme.palette.themeSecondary,
        },
        '&.isExpanded': {
            display: 'block'
        },
        '&:hover .childElement': {
            color: 'white' 
        }
      }
    }
  ]
};

// In render()
return <Component styles={styles} ... />;

 

Function-based Styling

 

With Function-based, the styling applied to each area may depend on the state of the component. So you should also be able to define styles as a function of these inputs:

// Take in styling input, split out styles for each area.
const styles = (props: IComponentStyleProps): IComponentStyles => {
  return {
    root: { /* styles */ },
    child1: ['className', { /* styles */ }],
    child2: ['className', props.someBoolean && { /* styles */ }],
    subComponentStyles: {
      subComponent: (subProps:ISubComponentStyleProps) => {
        const { theme, disabled, hasBoolean } = props; // parent props are available in subComponents
        const { required, hasOtherBoolean } = subProps;
        return ({
          root: { /* styles */ },
          child1: { /* styles */ },
        });
      }
    }
  };
}

// In render()
return <Component styles={styles} ... />;

 

Sample:

 

const styles = props => ({
  root: [
    {
      background: props.theme.palette.themePrimary,
      selectors: {
        ':hover': {
          background: props.theme.palette.themeSecondary,
        }
      }
    },
    props.isExpanded
      ? { display: 'block' }
      : { display: 'none' }
  ]
});

// In render()
return <Component styles={styles} ... />;

 

 

Conclusion

Using the CSS-in-JS approach to styling components is flexible and powerful if we need to styling different “areas” or sub-components of component on easy way.

 

SPFx Tree Organization Chart WebPart

Summary

The Web Part Tree Organization Chart, shows the Organization Chart of the Company or the team, the webpart reads information from current user to build the Organization Chart.


We can configure in the webpart properties if it show all Organization Chart or the only user team, (same manager and peers).

Tree Organization Chart WebPart
Tree Organization Chart WebPart
My Team Organization Chart

Code is here, soon I will create a PR to sp-dev-fx-webparts.

Sharing is Caring…

Read and Update SPFx WebPart Properties from code

How many times you need to save some data in SPFx WebPart properties but don’t want this property appear in Properties Panel ?

How we you can do it ? I will explain how to read and save WebPart Properties from code using the REST API /_api/sitepages/pages

I start to define all WebPart Properties as usual, in manifest WebPart file

In this sample I have 3 properties , Title, lists and pivotData, the pivotData is a property that I want read and save in code, and don’t be available in Properties Panel of WebPart.


Property Panel Configuration



How to Read a Property

Where are WebPart properties saved? The WebPart properties are saved in the page column named “CanvasContent1”, this column is an array that has an entry for each WebPart on the page.

Each array entry is a JSON string that has all the information about WebPart, the WebPart properties are in the object called “webpartdata” in the “properties” object.

CanvasContent1” has all the information about all WebParts that exist on the page, each entry has an “id” field this “Id” identifies the WebPart instance installed on the page, because we can have multiple WebPart instances on the page and when you gets information from “CanvasContent1“, we have to read the correct information for the WebPart.

To get information from the page you have to do an http get to /_api/sitepages/pages({i}), example of “CanvasContent1” returned.

CanvasContent1

Here the sample of read properties for List PivotTable WebPart (List PivotTable WebPart Post)



How to Save Property

To save a property, you need to update the “CanvasContent1” column with new values ​​for the properties object.

To save new values you have to do a http post to /_api/sitepages/pages/page({i})/checkout , I can’t save page if it is not checkout before and then /_api/sitepages/pages/page({i})/savepage with new values for “CanvasContent1” Column.

Here the sample for update “CanvasContent1” with new values for List PivotTable WebPart. (List PivotTable WebPart Post)

Happy SharePointing!

Sharing is Caring!

List Pivot Table – WebPart

This WebPart allows you to do various types of data analysis, including charts. The WebPart uses a component based on pivottable.js

The configuration data of the Pivot Table is saved with the WebPart properties, the entire process of updating this data is done in real time when a change occurs in the Pivot Table.

The Source code is available here

Happy SharePointing…

Sharing is Caring!


Birthday SPFx, Web Part, Azure Function, MSGraph, @PnP/pnpjs

Birthdays SPFx Web Part,

The solution consists of SPFx Reusable Birthday Control (Tiles), an SPFx Web Part that loads data from a SharePoint List, which is updated every day by an Azure function using the MS Graph API

The SPFx reusable Birthday control has

Birthday Card component that is use to create a Birthday Tiles control.

Samples:

Screenshot 2018-11-25 at 16.09.58

Screenshot 2018-11-30 at 20.14.08

The Birthday tiles control receive the array of users that is passed by the web part for rendering the information about users birthdays.

Birthday Tiles reusable control , render method 

Screenshot 2018-11-25 at 16.33.13

the web part gets data from the SharePoint list and renders information about birthdays. 

Screenshot 2018-11-30 at 15.45.30

 

Screenshot 2018-11-25 at 16.58.22

 

The process to get birthdays consist in  an Azure Function that use @PnP/pnpjs library and MSGraph API to synchronise a SharePoint List located on tenant root site. The function create a list if it not exists.

The Azure function runs every day to get AAD users and the birthday date, on first run get all users but the next runs only get the changed users.

I use the https://graph.microsoft.com/v1.0/users/delta, query to get users from AAD.

How it works ?

Delta query has two objects that are nextLink URL and deltaLink URL, the nextLink if filled has the skipToken to read the next page of users, the deltaLink if present has the deltaToken to be used in the next call to check if there are users that have changed since the last time the function ran.

When Function Start :

Screenshot 2018-11-29 at 22.35.23

Read Users and add, Update or delete user from SharePoint List.

Screenshot 2018-11-29 at 22.25.36

On SharePoint Tenant Root Site I have my list with the user birthdays that I use on my Web Part.

Screenshot 2018-11-25 at 18.37.43

 

Thank you for reading. 🙂