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.
|
||||
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
|
||||
|
||||
|
|
|
@ -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 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> {
|
||||
|
||||
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();
|
||||
|
||||
|
||||
if (DEBUG && Environment.type === EnvironmentType.Local) {
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ITeam } from "../../../../shared/interfaces";
|
||||
import { ITeam, ITenant } from "../../../../shared/interfaces";
|
||||
|
||||
export interface IMyTeamsState {
|
||||
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 { 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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue