React my teams update sample (#830)
* Create data service * log error message * update readme
This commit is contained in:
parent
f2a845d82c
commit
a56811ce62
|
@ -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.
|
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.
|
The web part can be configured to open the team on the web browser or client app.
|
||||||
|
|
||||||
|
|
||||||
![Demo](./assets/Preview.png)
|
![Demo](./assets/Preview.png)
|
||||||
|
|
||||||
## Used SharePoint Framework Version
|
## 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
|
## Applies to
|
||||||
|
|
||||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
- [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* Office 365 subscription with SharePoint Online licence
|
- 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.
|
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
Solution|Author(s)
|
| Solution | Author(s) |
|
||||||
--------|---------
|
| -------------- | -------------- |
|
||||||
react-my-teams|Joel Rodrigues
|
| react-my-teams | Joel Rodrigues |
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
Version|Date|Comments
|
| Version | Date | Comments |
|
||||||
-------|----|--------
|
| ------- | ----------------- | --------------- |
|
||||||
1.0|February 26, 2019|Initial release
|
| 1.0 | February 26, 2019 | Initial release |
|
||||||
|
|
||||||
## Disclaimer
|
## 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
|
## Minimal Path to Awesome
|
||||||
|
|
||||||
* Clone this repository
|
- Clone this repository
|
||||||
* in the command line run:
|
- in the command line run:
|
||||||
* `npm install`
|
- `npm install`
|
||||||
* `gulp serve`
|
- `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
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { ITenant, ITeam, IChannel } from "../interfaces";
|
||||||
|
|
||||||
|
export interface ITeamsService {
|
||||||
|
GetTenantInfo(): Promise<ITenant>;
|
||||||
|
GetTeams(): Promise<ITeam[]>;
|
||||||
|
GetTeamChannels(teamId): Promise<IChannel[]>;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './ITeamsService';
|
||||||
|
export * from './TeamsService';
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDom from 'react-dom';
|
import * as ReactDom from 'react-dom';
|
||||||
import { Version } from '@microsoft/sp-core-library';
|
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
BaseClientSideWebPart,
|
BaseClientSideWebPart,
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
|
@ -11,6 +11,7 @@ import * as strings from 'MyTeamsWebPartStrings';
|
||||||
import { MyTeams, IMyTeamsProps } from './components/myTeams';
|
import { MyTeams, IMyTeamsProps } from './components/myTeams';
|
||||||
import { ITenant } from '../../shared/interfaces';
|
import { ITenant } from '../../shared/interfaces';
|
||||||
import { MSGraphClient } from '@microsoft/sp-http';
|
import { MSGraphClient } from '@microsoft/sp-http';
|
||||||
|
import { TeamsService, ITeamsService } from '../../shared/services';
|
||||||
|
|
||||||
export interface IMyTeamsWebPartProps {
|
export interface IMyTeamsWebPartProps {
|
||||||
tenantInfo: ITenant;
|
tenantInfo: ITenant;
|
||||||
|
@ -20,25 +21,32 @@ export interface IMyTeamsWebPartProps {
|
||||||
export default class MyTeamsWebPart extends BaseClientSideWebPart<IMyTeamsWebPartProps> {
|
export default class MyTeamsWebPart extends BaseClientSideWebPart<IMyTeamsWebPartProps> {
|
||||||
|
|
||||||
private _graphClient: MSGraphClient;
|
private _graphClient: MSGraphClient;
|
||||||
|
private _teamsService: ITeamsService;
|
||||||
|
|
||||||
public async onInit(): Promise<void> {
|
public async onInit(): Promise<void> {
|
||||||
|
|
||||||
this._graphClient = await this.context.msGraphClientFactory.getClient();
|
|
||||||
// get tenant info if not available yet
|
|
||||||
if (!this.properties.tenantInfo && this.properties.openInClientApp) {
|
if (DEBUG && Environment.type === EnvironmentType.Local) {
|
||||||
this.properties.tenantInfo = await this._getTenantInfo();
|
console.log("Mock data service not implemented yet");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this._graphClient = await this.context.msGraphClientFactory.getClient();
|
||||||
|
this._teamsService = new TeamsService(this._graphClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onInit();
|
return super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async render(): Promise<void> {
|
public async render(): Promise<void> {
|
||||||
|
|
||||||
const element: React.ReactElement<IMyTeamsProps> = React.createElement(
|
const element: React.ReactElement<IMyTeamsProps> = React.createElement(
|
||||||
MyTeams,
|
MyTeams,
|
||||||
{
|
{
|
||||||
graphClient: this._graphClient,
|
teamsService: this._teamsService,
|
||||||
tenantId: this.properties.tenantInfo.id,
|
tenantInfo: this.properties.tenantInfo,
|
||||||
|
updateTenantInfo: (value: ITenant) => {
|
||||||
|
this.properties.tenantInfo = value;
|
||||||
|
},
|
||||||
openInClientApp: this.properties.openInClientApp
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
export interface IMyTeamsProps {
|
||||||
graphClient: MSGraphClient;
|
teamsService: ITeamsService;
|
||||||
tenantId: string;
|
tenantInfo: ITenant;
|
||||||
|
updateTenantInfo: (value: ITenant) => void;
|
||||||
openInClientApp: boolean;
|
openInClientApp: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { ITeam } from "../../../../shared/interfaces";
|
import { ITeam, ITenant } from "../../../../shared/interfaces";
|
||||||
|
|
||||||
export interface IMyTeamsState {
|
export interface IMyTeamsState {
|
||||||
items: ITeam[];
|
items: ITeam[];
|
||||||
|
tenantInfo: ITenant;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { List } from 'office-ui-fabric-react/lib/List';
|
||||||
import styles from '../myTeams/MyTeams.module.scss';
|
import styles from '../myTeams/MyTeams.module.scss';
|
||||||
import { IMyTeamsProps, IMyTeamsState } from '.';
|
import { IMyTeamsProps, IMyTeamsState } from '.';
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
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> {
|
export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
items: []
|
items: [],
|
||||||
|
tenantInfo: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,16 +31,27 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
|
||||||
|
|
||||||
private _load = async (): Promise<void> => {
|
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._myTeams = await this._getTeams();
|
||||||
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
items: this._myTeams
|
items: this._myTeams,
|
||||||
|
tenantInfo: tenantInfo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactElement<IMyTeamsProps> {
|
public render(): React.ReactElement<IMyTeamsProps> {
|
||||||
return (
|
return (
|
||||||
<FocusZone>
|
<FocusZone id="testId">
|
||||||
<List
|
<List
|
||||||
className={styles.myTeams}
|
className={styles.myTeams}
|
||||||
items={this._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 => {
|
private _onRenderCell = (team: ITeam, index: number | undefined): JSX.Element => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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>
|
<span>{team.displayName}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openChannel = async (teamId: string, tenantId: string): Promise<void> => {
|
private _openChannel = async (teamId: string): Promise<void> => {
|
||||||
let link = '#';
|
let link = '#';
|
||||||
|
|
||||||
const teamChannels: IChannel[] = await this._getTeamChannels(teamId);
|
const teamChannels: IChannel[] = await this._getTeamChannels(teamId);
|
||||||
const channel = teamChannels[0];
|
const channel = teamChannels[0];
|
||||||
|
|
||||||
if (this.props.openInClientApp) {
|
if (this.props.openInClientApp && this.state.tenantInfo) {
|
||||||
link = `https://teams.microsoft.com/l/channel/${channel.id}/${channel.displayName}?groupId=${teamId}&tenantId=${tenantId}`;
|
link = `https://teams.microsoft.com/l/channel/${channel.id}/${channel.displayName}?groupId=${teamId}&tenantId=${this.state.tenantInfo.id}`;
|
||||||
} else {
|
} else {
|
||||||
link = `https://teams.microsoft.com/_#/conversations/${channel.displayName}?threadId=${channel.id}&ctx=channel`;
|
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');
|
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[]> => {
|
private _getTeams = async (): Promise<ITeam[]> => {
|
||||||
let myTeams: ITeam[] = [];
|
let myTeams: ITeam[] = [];
|
||||||
try {
|
try {
|
||||||
const teamsResponse = await this.props.graphClient.api('me/joinedTeams').version('v1.0').get();
|
myTeams = await this.props.teamsService.GetTeams();
|
||||||
myTeams = teamsResponse.value as ITeam[];
|
console.log(myTeams);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error getting teams');
|
console.log('Error getting teams', error);
|
||||||
}
|
}
|
||||||
return myTeams;
|
return myTeams;
|
||||||
}
|
}
|
||||||
|
@ -89,10 +113,10 @@ export class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
|
||||||
private _getTeamChannels = async (teamId): Promise<IChannel[]> => {
|
private _getTeamChannels = async (teamId): Promise<IChannel[]> => {
|
||||||
let channels: IChannel[] = [];
|
let channels: IChannel[] = [];
|
||||||
try {
|
try {
|
||||||
const channelsResponse = await this.props.graphClient.api(`teams/${teamId}/channels`).version('v1.0').get();
|
channels = await this.props.teamsService.GetTeamChannels(teamId);
|
||||||
channels = channelsResponse.value as IChannel[];
|
console.log(channels);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error getting channels for team ' + teamId);
|
console.log('Error getting channels for team ' + teamId, error);
|
||||||
}
|
}
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue