React my teams update sample (#830)

* Create data service

* log error message

* update readme
This commit is contained in:
Joel Rodrigues 2019-04-08 21:51:31 +01:00 committed by Vesa Juvonen
parent f2a845d82c
commit a56811ce62
8 changed files with 158 additions and 53 deletions

View File

@ -5,7 +5,6 @@
This sample uses Microsoft Graph to list the Teams the current user is a member of. When the user clicks on one of the teams, the web part retrieves information about the default channel (General) and opens it.
The web part can be configured to open the team on the web browser or client app.
![Demo](./assets/Preview.png)
## Used SharePoint Framework Version
@ -14,37 +13,46 @@ The web part can be configured to open the team on the web browser or client app
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
- [SharePoint Framework](https:/dev.office.com/sharepoint)
## Prerequisites
* Office 365 subscription with SharePoint Online licence
* SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
- Office 365 subscription with SharePoint Online licence
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
## Solution
Solution|Author(s)
--------|---------
react-my-teams|Joel Rodrigues
| Solution | Author(s) |
| -------------- | -------------- |
| react-my-teams | Joel Rodrigues |
## Version history
Version|Date|Comments
-------|----|--------
1.0|February 26, 2019|Initial release
| Version | Date | Comments |
| ------- | ----------------- | --------------- |
| 1.0 | February 26, 2019 | Initial release |
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
**THIS CODE IS PROVIDED _AS IS_ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp serve`
- Clone this repository
- in the command line run:
- `npm install`
- `gulp serve --nobrowser`
- navigate to the hosted version of SharePoint workbench, eg. https://contoso.sharepoint.com/_layouts/15/workbench.aspx
### Grant the service principal permission to the MicroSoft Graph API
o365 spo login https://contoso-admin.sharepoint.com
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'User.Read.All'
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'User.ReadWrite.All'
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Group.Read.All'
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Group.ReadWrite.All'
## Features

View File

@ -0,0 +1,7 @@
import { ITenant, ITeam, IChannel } from "../interfaces";
export interface ITeamsService {
GetTenantInfo(): Promise<ITenant>;
GetTeams(): Promise<ITeam[]>;
GetTeamChannels(teamId): Promise<IChannel[]>;
}

View File

@ -0,0 +1,64 @@
import { MSGraphClient } from "@microsoft/sp-http";
import { ITeam, IChannel, ITenant } from "../interfaces";
import { ITeamsService } from "./ITeamsService";
export class TeamsService implements ITeamsService {
private _graphClient: MSGraphClient;
/**
* class constructor
* @param _graphClient the graph client to be used on the request
*/
constructor(graphClient: MSGraphClient) {
// set web part context
this._graphClient = graphClient;
}
public GetTenantInfo = async (): Promise<ITenant> => {
return await this._getTenantInfo();
}
private _getTenantInfo = async (): Promise<ITenant> => {
let tenant: ITenant = null;
try {
const tenantResponse = await this._graphClient.api('organization').select('id').version('v1.0').get();
tenant = tenantResponse.value as ITenant;
} catch (error) {
console.log('Error getting tenant information', error);
}
return tenant;
}
public GetTeams = async (): Promise<ITeam[]> => {
return await this._getTeams();
}
private _getTeams = async (): Promise<ITeam[]> => {
let myTeams: ITeam[] = [];
try {
const teamsResponse = await this._graphClient.api('me/joinedTeams').version('v1.0').get();
myTeams = teamsResponse.value as ITeam[];
} catch (error) {
console.log('Error getting teams', error);
}
return myTeams;
}
public GetTeamChannels = async (teamId): Promise<IChannel[]> => {
return await this._getTeamChannels(teamId);
}
private _getTeamChannels = async (teamId): Promise<IChannel[]> => {
let channels: IChannel[] = [];
try {
const channelsResponse = await this._graphClient.api(`teams/${teamId}/channels`).version('v1.0').get();
channels = channelsResponse.value as IChannel[];
} catch (error) {
console.log('Error getting channels for team ' + teamId, error);
}
return channels;
}
}

View File

@ -0,0 +1,2 @@
export * from './ITeamsService';
export * from './TeamsService';

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
@ -11,6 +11,7 @@ import * as strings from 'MyTeamsWebPartStrings';
import { MyTeams, IMyTeamsProps } from './components/myTeams';
import { ITenant } from '../../shared/interfaces';
import { MSGraphClient } from '@microsoft/sp-http';
import { TeamsService, ITeamsService } from '../../shared/services';
export interface IMyTeamsWebPartProps {
tenantInfo: ITenant;
@ -20,25 +21,32 @@ export interface IMyTeamsWebPartProps {
export default class MyTeamsWebPart extends BaseClientSideWebPart<IMyTeamsWebPartProps> {
private _graphClient: MSGraphClient;
private _teamsService: ITeamsService;
public async onInit(): Promise<void> {
if (DEBUG && Environment.type === EnvironmentType.Local) {
console.log("Mock data service not implemented yet");
} else {
this._graphClient = await this.context.msGraphClientFactory.getClient();
// get tenant info if not available yet
if (!this.properties.tenantInfo && this.properties.openInClientApp) {
this.properties.tenantInfo = await this._getTenantInfo();
this._teamsService = new TeamsService(this._graphClient);
}
return super.onInit();
}
public async render(): Promise<void> {
const element: React.ReactElement<IMyTeamsProps> = React.createElement(
MyTeams,
{
graphClient: this._graphClient,
tenantId: this.properties.tenantInfo.id,
teamsService: this._teamsService,
tenantInfo: this.properties.tenantInfo,
updateTenantInfo: (value: ITenant) => {
this.properties.tenantInfo = value;
},
openInClientApp: this.properties.openInClientApp
}
);
@ -75,16 +83,4 @@ export default class MyTeamsWebPart extends BaseClientSideWebPart<IMyTeamsWebPar
]
};
}
private _getTenantInfo = async (): Promise<ITenant> => {
let tenant: ITenant = null;
try {
const tenantResponse = await this._graphClient.api('organization').select('id').version('v1.0').get();
tenant = tenantResponse.value as ITenant;
console.log(tenant);
} catch (error) {
console.log('Error getting tenant information');
}
return tenant;
}
}

View File

@ -1,7 +1,10 @@
import { MSGraphClient } from "@microsoft/sp-http";
import { ITeamsService } from "../../../../shared/services";
import { ITenant } from "../../../../shared/interfaces";
import { IWebPartContext } from "@microsoft/sp-webpart-base";
export interface IMyTeamsProps {
graphClient: MSGraphClient;
tenantId: string;
teamsService: ITeamsService;
tenantInfo: ITenant;
updateTenantInfo: (value: ITenant) => void;
openInClientApp: boolean;
}

View File

@ -1,5 +1,6 @@
import { ITeam } from "../../../../shared/interfaces";
import { ITeam, ITenant } from "../../../../shared/interfaces";
export interface IMyTeamsState {
items: ITeam[];
tenantInfo: ITenant;
}

View File

@ -4,7 +4,7 @@ import { List } from 'office-ui-fabric-react/lib/List';
import styles from '../myTeams/MyTeams.module.scss';
import { IMyTeamsProps, IMyTeamsState } from '.';
import { escape } from '@microsoft/sp-lodash-subset';
import { ITeam, IChannel } from '../../../../shared/interfaces';
import { ITeam, IChannel, ITenant } from '../../../../shared/interfaces';
export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
@ -14,7 +14,8 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
super(props);
this.state = {
items: []
items: [],
tenantInfo: null
};
}
@ -30,16 +31,27 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
private _load = async (): Promise<void> => {
// get tenant info if required and not available yet
// then update the web part properties to persist the value
let tenantInfo: ITenant = this.props.tenantInfo;
if ((!this.props.tenantInfo || this.props.tenantInfo === undefined) && this.props.openInClientApp) {
tenantInfo = await this._getTenantInfo();
this.props.updateTenantInfo(tenantInfo);
}
// get teams
this._myTeams = await this._getTeams();
this.setState({
items: this._myTeams
items: this._myTeams,
tenantInfo: tenantInfo
});
}
public render(): React.ReactElement<IMyTeamsProps> {
return (
<FocusZone>
<FocusZone id="testId">
<List
className={styles.myTeams}
items={this._myTeams}
@ -51,23 +63,24 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
}
private _onRenderCell = (team: ITeam, index: number | undefined): JSX.Element => {
return (
<div>
<a href="#" title='Click to open channel' onClick={this._openChannel.bind(this, team.id, this.props.tenantId)}>
<a href="#" title='Click to open channel' onClick={this._openChannel.bind(this, team.id)}>
<span>{team.displayName}</span>
</a>
</div>
);
}
private _openChannel = async (teamId: string, tenantId: string): Promise<void> => {
private _openChannel = async (teamId: string): Promise<void> => {
let link = '#';
const teamChannels: IChannel[] = await this._getTeamChannels(teamId);
const channel = teamChannels[0];
if (this.props.openInClientApp) {
link = `https://teams.microsoft.com/l/channel/${channel.id}/${channel.displayName}?groupId=${teamId}&tenantId=${tenantId}`;
if (this.props.openInClientApp && this.state.tenantInfo) {
link = `https://teams.microsoft.com/l/channel/${channel.id}/${channel.displayName}?groupId=${teamId}&tenantId=${this.state.tenantInfo.id}`;
} else {
link = `https://teams.microsoft.com/_#/conversations/${channel.displayName}?threadId=${channel.id}&ctx=channel`;
}
@ -75,13 +88,24 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
window.open(link, '_blank');
}
private _getTenantInfo = async (): Promise<ITenant> => {
let tenant: ITenant = null;
try {
tenant = await this.props.teamsService.GetTenantInfo();
console.log(tenant);
} catch (error) {
console.log('Error getting tenant information', error);
}
return tenant;
}
private _getTeams = async (): Promise<ITeam[]> => {
let myTeams: ITeam[] = [];
try {
const teamsResponse = await this.props.graphClient.api('me/joinedTeams').version('v1.0').get();
myTeams = teamsResponse.value as ITeam[];
myTeams = await this.props.teamsService.GetTeams();
console.log(myTeams);
} catch (error) {
console.log('Error getting teams');
console.log('Error getting teams', error);
}
return myTeams;
}
@ -89,10 +113,10 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
private _getTeamChannels = async (teamId): Promise<IChannel[]> => {
let channels: IChannel[] = [];
try {
const channelsResponse = await this.props.graphClient.api(`teams/${teamId}/channels`).version('v1.0').get();
channels = channelsResponse.value as IChannel[];
channels = await this.props.teamsService.GetTeamChannels(teamId);
console.log(channels);
} catch (error) {
console.log('Error getting channels for team ' + teamId);
console.log('Error getting channels for team ' + teamId, error);
}
return channels;
}