mirror of
https://github.com/pnp/sp-dev-fx-webparts.git
synced 2025-02-07 13:38:39 +00:00
Example of MS Graph Teams beta API usage (#652)
This commit is contained in:
parent
f071f531bc
commit
d3c7739383
25
samples/react-team-creator/.editorconfig
Normal file
25
samples/react-team-creator/.editorconfig
Normal file
@ -0,0 +1,25 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
32
samples/react-team-creator/.gitignore
vendored
Normal file
32
samples/react-team-creator/.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
10
samples/react-team-creator/.yo-rc.json
Normal file
10
samples/react-team-creator/.yo-rc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.6.0",
|
||||
"libraryName": "teams-creator",
|
||||
"libraryId": "3284bbaa-0441-4153-9738-69a27a4b5956",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"isCreatingSolution": true
|
||||
}
|
||||
}
|
65
samples/react-team-creator/README.md
Normal file
65
samples/react-team-creator/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# React Teams Creator Web Part
|
||||
|
||||
## Summary
|
||||
|
||||
The web part illustrates usage of MS Graph beta APIs to work with Teams:
|
||||
* O365 group creation
|
||||
* Team creation
|
||||
* Channel creation
|
||||
* Installation of an app
|
||||
* Adding tab
|
||||
* Getting apps from App Catalog
|
||||
|
||||
![React Side Panel Client-Side Web Part](./assets/teams-creator.png)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.6.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [MS Graph](https://developer.microsoft.com/en-us/graph)
|
||||
* [MS Teams](https://docs.microsoft.com/en-us/microsoftteams/microsoft-teams)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
teams-creator-client-side-solution | Alex Terentiev ([Sharepointalist Inc.](http://www.sharepointalist.com), [AJIXuMuK](https://github.com/AJIXuMuK))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 17, 2018|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.**
|
||||
|
||||
## Features
|
||||
Sample features:
|
||||
- O365 Group creation
|
||||
- MS Team creation
|
||||
- Channel creation
|
||||
- Teams App installation
|
||||
- Adding Teams tab
|
||||
- Usage of PnP React Controls
|
||||
|
||||
## Caveats
|
||||
- There is no way to filter Teams Apps requested from App Catalog by App Type. Because of that dropdown displays not only apps that are available as Tabs but all of them.
|
||||
- Although the app can be added as a Tab there is no API to configure the app completely. At least, there is no way to figure out what settings are there for this or that specific app. So, after tab creation in the Teams app a user will see "Set up tab" button in the fresh tab.
|
||||
|
||||
## Building the code
|
||||
|
||||
```bash
|
||||
git clone the repo
|
||||
npm i
|
||||
npm i -g gulp
|
||||
gulp
|
||||
```
|
||||
|
||||
This package produces the following:
|
||||
|
||||
* lib/* - intermediate-stage commonjs build artifacts
|
||||
* dist/* - the bundled script, along with other resources
|
||||
* deploy/* - all resources which should be uploaded to a CDN.
|
BIN
samples/react-team-creator/assets/teams-creator.png
Normal file
BIN
samples/react-team-creator/assets/teams-creator.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
19
samples/react-team-creator/config/config.json
Normal file
19
samples/react-team-creator/config/config.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"teams-creator-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/teamsCreator/TeamsCreatorWebPart.js",
|
||||
"manifest": "./src/webparts/teamsCreator/TeamsCreatorWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"TeamsCreatorWebPartStrings": "lib/webparts/teamsCreator/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
4
samples/react-team-creator/config/copy-assets.json
Normal file
4
samples/react-team-creator/config/copy-assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "teams-creator",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
21
samples/react-team-creator/config/package-solution.json
Normal file
21
samples/react-team-creator/config/package-solution.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "teams-creator-client-side-solution",
|
||||
"id": "3284bbaa-0441-4153-9738-69a27a4b5956",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Group.ReadWrite.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "AppCatalog.ReadWrite.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/teams-creator.sppkg"
|
||||
}
|
||||
}
|
10
samples/react-team-creator/config/serve.json
Normal file
10
samples/react-team-creator/config/serve.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
4
samples/react-team-creator/config/write-manifests.json
Normal file
4
samples/react-team-creator/config/write-manifests.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
7
samples/react-team-creator/gulpfile.js
vendored
Normal file
7
samples/react-team-creator/gulpfile.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
18201
samples/react-team-creator/package-lock.json
generated
Normal file
18201
samples/react-team-creator/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
samples/react-team-creator/package.json
Normal file
36
samples/react-team-creator/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "teams-creator",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.6.0",
|
||||
"@microsoft/sp-lodash-subset": "1.6.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.6.0",
|
||||
"@microsoft/sp-webpart-base": "1.6.0",
|
||||
"@pnp/spfx-controls-react": "1.9.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.6.0",
|
||||
"@microsoft/sp-module-interfaces": "1.6.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.6.0",
|
||||
"tslint-microsoft-contrib": "~5.0.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
1
samples/react-team-creator/src/index.ts
Normal file
1
samples/react-team-creator/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "ebead8b1-1dcf-4426-a06a-a11137cc9b6e",
|
||||
"alias": "TeamsCreatorWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Teams Creator" },
|
||||
"description": { "default": "Teams Creator description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Teams Creator"
|
||||
}
|
||||
}]
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'TeamsCreatorWebPartStrings';
|
||||
import TeamsCreator from './components/TeamsCreator';
|
||||
import { ITeamsCreatorProps } from './components/ITeamsCreatorProps';
|
||||
|
||||
export interface ITeamsCreatorWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class TeamsCreatorWebPart extends BaseClientSideWebPart<ITeamsCreatorWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<ITeamsCreatorProps > = React.createElement(
|
||||
TeamsCreator,
|
||||
{
|
||||
context: this.context
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface ITeamsCreatorProps {
|
||||
/**
|
||||
* Web Part context
|
||||
*/
|
||||
context: WebPartContext;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.teamsCreator {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.teamSection, .channelSection {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
text-align: right;
|
||||
margin-top: 20px;
|
||||
|
||||
.button {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $ms-color-errorText;
|
||||
}
|
||||
}
|
@ -0,0 +1,583 @@
|
||||
import * as React from 'react';
|
||||
import styles from './TeamsCreator.module.scss';
|
||||
import { ITeamsCreatorProps } from './ITeamsCreatorProps';
|
||||
import { TextField, ITextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
|
||||
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { PrimaryButton, DefaultButton, ActionButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Spinner } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import { PeoplePicker, PrincipalType, IPeoplePickerUserItem } from "@pnp/spfx-controls-react/lib/PeoplePicker";
|
||||
import * as strings from 'TeamsCreatorWebPartStrings';
|
||||
import { MSGraphClient } from '@microsoft/sp-http';
|
||||
|
||||
/**
|
||||
* State of the component
|
||||
*/
|
||||
export enum CreationState {
|
||||
/**
|
||||
* Initial state - user input
|
||||
*/
|
||||
notStarted,
|
||||
/**
|
||||
* creating all selected elements (group, team, channel, tab)
|
||||
*/
|
||||
creating,
|
||||
/**
|
||||
* everything has been created
|
||||
*/
|
||||
created,
|
||||
/**
|
||||
* error during creation
|
||||
*/
|
||||
error
|
||||
}
|
||||
|
||||
/**
|
||||
* App definition returned from App Catalog
|
||||
*/
|
||||
export interface ITeamsApp {
|
||||
id: string;
|
||||
externalId?: string;
|
||||
name: string;
|
||||
version: string;
|
||||
distributionMethod: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* State
|
||||
*/
|
||||
export interface ITeamsCreatorState {
|
||||
/**
|
||||
* Selected team name. Also used as group name
|
||||
*/
|
||||
teamName?: string;
|
||||
/**
|
||||
* team description
|
||||
*/
|
||||
teamDescription?: string;
|
||||
/**
|
||||
* Group owners
|
||||
*/
|
||||
owners?: string[];
|
||||
/**
|
||||
* group members
|
||||
*/
|
||||
members?: string[];
|
||||
/**
|
||||
* Flag if channel should be created
|
||||
*/
|
||||
createChannel?: boolean;
|
||||
/**
|
||||
* channel name
|
||||
*/
|
||||
channelName?: string;
|
||||
/**
|
||||
* channel description
|
||||
*/
|
||||
channelDescription?: string;
|
||||
/**
|
||||
* flag if we need to add a tab
|
||||
*/
|
||||
addTab?: boolean;
|
||||
/**
|
||||
* tab name
|
||||
*/
|
||||
tabName?: string;
|
||||
/**
|
||||
* teams apps from app catalog
|
||||
*/
|
||||
apps?: ITeamsApp[];
|
||||
/**
|
||||
* current state of the component
|
||||
*/
|
||||
creationState?: CreationState;
|
||||
/**
|
||||
* creation spinner text
|
||||
*/
|
||||
spinnerText?: string;
|
||||
/**
|
||||
* id of the selected app to be added as tab
|
||||
*/
|
||||
selectedAppId?: string;
|
||||
}
|
||||
|
||||
export default class TeamsCreator extends React.Component<ITeamsCreatorProps, ITeamsCreatorState> {
|
||||
constructor(props: ITeamsCreatorProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
creationState: CreationState.notStarted
|
||||
};
|
||||
|
||||
this._onClearClick = this._onClearClick.bind(this);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<ITeamsCreatorProps> {
|
||||
|
||||
const {
|
||||
teamName,
|
||||
teamDescription,
|
||||
createChannel,
|
||||
channelName,
|
||||
channelDescription,
|
||||
addTab,
|
||||
tabName,
|
||||
apps,
|
||||
creationState,
|
||||
spinnerText,
|
||||
selectedAppId
|
||||
} = this.state;
|
||||
|
||||
const appsDropdownOptions: IDropdownOption[] = apps ? apps.map(app => { return { key: app.id, text: app.name }; }) : [];
|
||||
|
||||
return (
|
||||
<div className={styles.teamsCreator}>
|
||||
<h2>{strings.Welcome}</h2>
|
||||
<div className={styles.container}>
|
||||
{{
|
||||
0: <div>
|
||||
<div className={styles.teamSection}>
|
||||
<TextField required={true} label={strings.TeamNameLabel} value={teamName} onChanged={this._onTeamNameChange.bind(this)}></TextField>
|
||||
<TextField label={strings.TeamDescriptionLabel} value={teamDescription} onChanged={this._onTeamDescriptionChange.bind(this)}></TextField>
|
||||
<PeoplePicker
|
||||
context={this.props.context}
|
||||
titleText={strings.Owners}
|
||||
personSelectionLimit={3}
|
||||
showHiddenInUI={false}
|
||||
principleTypes={[PrincipalType.User]}
|
||||
selectedItems={this._onOwnersSelected.bind(this)}
|
||||
isRequired={true} />
|
||||
<PeoplePicker
|
||||
context={this.props.context}
|
||||
titleText={strings.Members}
|
||||
personSelectionLimit={3}
|
||||
showHiddenInUI={false}
|
||||
principleTypes={[PrincipalType.User]}
|
||||
selectedItems={this._onMembersSelected.bind(this)} />
|
||||
</div>
|
||||
<Checkbox label={strings.CreateChannel} checked={createChannel} onChange={this._onCreateChannelChange.bind(this)} />
|
||||
{createChannel && <div>
|
||||
<div className={styles.channelSection}>
|
||||
<TextField required={createChannel} label={strings.ChannelName} value={channelName} onChanged={this._onChannelNameChange.bind(this)}></TextField>
|
||||
<TextField label={strings.ChannelDescription} value={channelDescription} onChanged={this._onChannelDescriptionChange.bind(this)}></TextField>
|
||||
</div>
|
||||
<Checkbox label={strings.AddTab} checked={addTab} onChange={this._onAddTabChange.bind(this)} />
|
||||
{addTab && <div>
|
||||
<TextField required={addTab} label={strings.TabName} value={tabName} onChanged={this._onTabNameChange.bind(this)}></TextField>
|
||||
<Dropdown
|
||||
required={addTab}
|
||||
label={strings.App}
|
||||
disabled={!this.state.apps}
|
||||
options={appsDropdownOptions}
|
||||
selectedKey={selectedAppId}
|
||||
onChanged={this._onAppSelected.bind(this)}></Dropdown>
|
||||
</div>}
|
||||
</div>}
|
||||
<div className={styles.buttons}>
|
||||
<PrimaryButton text={strings.Create} className={styles.button} onClick={this._onCreateClick.bind(this)} />
|
||||
<DefaultButton text={strings.Clear} className={styles.button} onClick={this._onClearClick} />
|
||||
</div>
|
||||
</div>,
|
||||
1: <div>
|
||||
<Spinner label={spinnerText} />
|
||||
</div>,
|
||||
2: <div>
|
||||
<div>{strings.Success}</div>
|
||||
<PrimaryButton iconProps={{ iconName: 'TeamsLogo' }} href='https://aka.ms/mstfw' target='_blank'>{strings.OpenTeams}</PrimaryButton>
|
||||
<DefaultButton onClick={this._onClearClick}>{strings.StartOver}</DefaultButton>
|
||||
</div>,
|
||||
4: <div>
|
||||
<div className={styles.error}>{strings.Error}</div>
|
||||
<DefaultButton onClick={this._onClearClick}>{strings.StartOver}</DefaultButton>
|
||||
</div>
|
||||
}[creationState]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _onTeamNameChange(value: string) {
|
||||
this.setState({
|
||||
teamName: value
|
||||
});
|
||||
}
|
||||
|
||||
private _onTeamDescriptionChange(value: string) {
|
||||
this.setState({
|
||||
teamDescription: value
|
||||
});
|
||||
}
|
||||
|
||||
private _onChannelNameChange(value: string) {
|
||||
this.setState({
|
||||
channelName: value
|
||||
});
|
||||
}
|
||||
|
||||
private _onChannelDescriptionChange(value: string) {
|
||||
this.setState({
|
||||
channelDescription: value
|
||||
});
|
||||
}
|
||||
|
||||
private _onTabNameChange(value: string) {
|
||||
this.setState({
|
||||
tabName: value
|
||||
});
|
||||
}
|
||||
|
||||
private _onAppSelected(item: IDropdownOption) {
|
||||
this.setState({
|
||||
selectedAppId: item.key as string
|
||||
});
|
||||
}
|
||||
|
||||
private _onCreateChannelChange(e: React.FormEvent<HTMLElement | HTMLInputElement>, checked: boolean) {
|
||||
this.setState({
|
||||
createChannel: checked
|
||||
});
|
||||
}
|
||||
|
||||
private _onAddTabChange(e: React.FormEvent<HTMLElement | HTMLInputElement>, checked: boolean) {
|
||||
this.setState({
|
||||
addTab: checked
|
||||
});
|
||||
|
||||
this._getAvailableApps();
|
||||
}
|
||||
|
||||
private _onMembersSelected(members: IPeoplePickerUserItem[]) {
|
||||
this.setState({
|
||||
members: members.map(m => m.id)
|
||||
});
|
||||
}
|
||||
|
||||
private _onOwnersSelected(owners: IPeoplePickerUserItem[]) {
|
||||
this.setState({
|
||||
owners: owners.map(o => o.id)
|
||||
});
|
||||
}
|
||||
|
||||
private async _onCreateClick() {
|
||||
this._processCreationRequest();
|
||||
}
|
||||
private _onClearClick() {
|
||||
this._clearState();
|
||||
}
|
||||
|
||||
private _clearState() {
|
||||
this.setState({
|
||||
teamName: '',
|
||||
teamDescription: '',
|
||||
members: [],
|
||||
owners: [],
|
||||
createChannel: false,
|
||||
channelName: '',
|
||||
channelDescription: '',
|
||||
addTab: false,
|
||||
tabName: '',
|
||||
selectedAppId: '',
|
||||
creationState: CreationState.notStarted,
|
||||
spinnerText: ''
|
||||
});
|
||||
}
|
||||
|
||||
private async _getAvailableApps(): Promise<void> {
|
||||
if (this.state.apps) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = this.props.context;
|
||||
const graphClient = await context.msGraphClientFactory.getClient();
|
||||
|
||||
const appsResponse = await graphClient.api('appCatalogs/teamsApps').version('beta').get();
|
||||
const apps = appsResponse.value as ITeamsApp[];
|
||||
apps.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
apps: apps
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main flow
|
||||
*/
|
||||
private async _processCreationRequest() {
|
||||
const context = this.props.context;
|
||||
// initializing graph client to be used in all requests
|
||||
const graphClient = await context.msGraphClientFactory.getClient();
|
||||
|
||||
this.setState({
|
||||
creationState: CreationState.creating,
|
||||
spinnerText: strings.CreatingGroup
|
||||
});
|
||||
|
||||
//
|
||||
// Create a group first
|
||||
//
|
||||
const groupId = await this._createGroup(graphClient);
|
||||
if (!groupId) {
|
||||
this._onError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
spinnerText: strings.CreatingTeam
|
||||
});
|
||||
|
||||
//
|
||||
// Create team
|
||||
//
|
||||
const teamId = await this._createTeamWithAttempts(groupId, graphClient);
|
||||
if (!teamId) {
|
||||
this._onError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.createChannel) {
|
||||
this.setState({
|
||||
creationState: CreationState.created
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
spinnerText: strings.CreatingChannel
|
||||
});
|
||||
|
||||
//
|
||||
// Create channel
|
||||
//
|
||||
const channelId = await this._createChannel(teamId, graphClient);
|
||||
if (!channelId) {
|
||||
this._onError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.addTab) {
|
||||
this.setState({
|
||||
creationState: CreationState.created
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
spinnerText: strings.InstallingApp
|
||||
});
|
||||
|
||||
//
|
||||
// install app
|
||||
//
|
||||
const isInstalled = await this._installApp(teamId, graphClient);
|
||||
if (!isInstalled) {
|
||||
this._onError();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
spinnerText: strings.CreatingTab
|
||||
});
|
||||
|
||||
//
|
||||
// add tab
|
||||
//
|
||||
const isTabCreated = await this._addTab(teamId, channelId, graphClient);
|
||||
if (!isTabCreated) {
|
||||
this._onError();
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
creationState: CreationState.created
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _onError(message?: string): void {
|
||||
this.setState({
|
||||
creationState: CreationState.error
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the app to the team
|
||||
* @param teamId team Id
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _installApp(teamId: string, graphClient: MSGraphClient): Promise<boolean> {
|
||||
try {
|
||||
await graphClient.api(`teams/${teamId}/apps`).version('beta').post({
|
||||
id: this.state.selectedAppId
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tab to the specified channel of the team
|
||||
* @param teamId team id
|
||||
* @param channelId channel id
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _addTab(teamId: string, channelId: string, graphClient: MSGraphClient): Promise<boolean> {
|
||||
try {
|
||||
await graphClient.api(`teams/${teamId}/channels/${channelId}/tabs`).version('beta').post({
|
||||
name: this.state.tabName,
|
||||
teamsAppId: this.state.selectedAppId
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates channel in the team
|
||||
* @param teamId team id
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _createChannel(teamId: string, graphClient: MSGraphClient): Promise<string> {
|
||||
const {
|
||||
channelName,
|
||||
channelDescription
|
||||
} = this.state;
|
||||
|
||||
try {
|
||||
const response = await graphClient.api(`teams/${teamId}/channels`).version('beta').post({
|
||||
displayName: channelName,
|
||||
description: channelDescription
|
||||
});
|
||||
|
||||
return response.id;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates O365 group
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _createGroup(graphClient: MSGraphClient): Promise<string> {
|
||||
const displayName = this.state.teamName;
|
||||
const mailNickname = this._generateMailNickname(displayName);
|
||||
|
||||
let {
|
||||
owners,
|
||||
members
|
||||
} = this.state;
|
||||
|
||||
const groupRequest = {
|
||||
displayName: displayName,
|
||||
description: this.state.teamDescription,
|
||||
groupTypes: [
|
||||
'Unified'
|
||||
],
|
||||
mailEnabled: true,
|
||||
mailNickname: mailNickname,
|
||||
securityEnabled: false
|
||||
};
|
||||
if (owners && owners.length) {
|
||||
groupRequest['owners@data.bind'] = owners.map(owner => {
|
||||
return `https://graph.microsoft.com/v1.0/users/${owner}`;
|
||||
});
|
||||
}
|
||||
if (members && members.length) {
|
||||
groupRequest['members@data.bind'] = members.map(member => {
|
||||
return `https://graph.microsoft.com/v1.0/users/${member}`;
|
||||
});
|
||||
}
|
||||
try {
|
||||
const response = await graphClient.api('groups').version('beta').post(groupRequest);
|
||||
return response.id;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team. as mentioned in the documentation - we need to make multiple attempts if team creation request errored
|
||||
* @param groupId group id
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _createTeamWithAttempts(groupId: string, graphClient: MSGraphClient): Promise<string> {
|
||||
|
||||
let attemptsCount = 0;
|
||||
let teamId: string = '';
|
||||
|
||||
//
|
||||
// From the documentation: If the group was created less than 15 minutes ago, it's possible for the Create team call to fail with a 404 error code due to replication delays.
|
||||
// The recommended pattern is to retry the Create team call three times, with a 10 second delay between calls.
|
||||
//
|
||||
do {
|
||||
teamId = await this._createTeam(groupId, graphClient);
|
||||
if (teamId) {
|
||||
attemptsCount = 3;
|
||||
}
|
||||
else {
|
||||
attemptsCount++;
|
||||
}
|
||||
} while (attemptsCount < 3);
|
||||
|
||||
return teamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits 10 seconds and tries to create a team
|
||||
* @param groupId group id
|
||||
* @param graphClient graph client
|
||||
*/
|
||||
private async _createTeam(groupId: string, graphClient: MSGraphClient): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
setTimeout(() => {
|
||||
graphClient.api(`groups/${groupId}/team`).version('beta').put({
|
||||
memberSettings: {
|
||||
allowCreateUpdateChannels: true
|
||||
},
|
||||
messagingSettings: {
|
||||
allowUserEditMessages: true,
|
||||
allowUserDeleteMessages: true
|
||||
},
|
||||
funSettings: {
|
||||
allowGiphy: true,
|
||||
giphyContentRating: "strict"
|
||||
}
|
||||
}).then(response => {
|
||||
resolve(response.id);
|
||||
}, () => {
|
||||
resolve('');
|
||||
});
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates mail nick name by display name of the group
|
||||
* @param displayName group display name
|
||||
*/
|
||||
private _generateMailNickname(displayName: string): string {
|
||||
return displayName.toLowerCase().replace(/\s/gmi, '-');
|
||||
}
|
||||
}
|
29
samples/react-team-creator/src/webparts/teamsCreator/loc/en-us.js
vendored
Normal file
29
samples/react-team-creator/src/webparts/teamsCreator/loc/en-us.js
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"TeamNameLabel": "Team name",
|
||||
"TeamDescriptionLabel": "Team description",
|
||||
"Owners": "Owners",
|
||||
"Members": "Members",
|
||||
"CreateChannel": "Create new channel in the Team",
|
||||
"ChannelName": "Channel name",
|
||||
"ChannelDescription": "Channel description",
|
||||
"AddTab": "Add a new tab to the channel",
|
||||
"TabName": "Tab name",
|
||||
"App": "Select an app to be added as a tab",
|
||||
"Welcome": "Welcom to Teams Creator Web Part!",
|
||||
"Create": "Create",
|
||||
"Clear": "Clear",
|
||||
"CreatingGroup": "Creating O365 Group...",
|
||||
"CreatingTeam": "Creating team...",
|
||||
"CreatingChannel": "Creating channel...",
|
||||
"InstallingApp": "Installing app...",
|
||||
"CreatingTab": "Creating tab to host the app...",
|
||||
"Error": "Something went wrong during the process. Please, refer browser console for more details.",
|
||||
"Success": "All selected components have been successfully created.",
|
||||
"StartOver": "Start over",
|
||||
"OpenTeams": "Open Teams"
|
||||
}
|
||||
});
|
32
samples/react-team-creator/src/webparts/teamsCreator/loc/mystrings.d.ts
vendored
Normal file
32
samples/react-team-creator/src/webparts/teamsCreator/loc/mystrings.d.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
declare interface ITeamsCreatorWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
TeamNameLabel: string;
|
||||
TeamDescriptionLabel: string;
|
||||
Owners: string;
|
||||
Members: string;
|
||||
CreateChannel: string;
|
||||
ChannelName: string;
|
||||
ChannelDescription: string;
|
||||
AddTab: string;
|
||||
TabName: string;
|
||||
App: string;
|
||||
Welcome: string;
|
||||
Create: string;
|
||||
Clear: string;
|
||||
CreatingGroup: string;
|
||||
CreatingTeam: string;
|
||||
CreatingChannel: string;
|
||||
InstallingApp: string;
|
||||
CreatingTab: string;
|
||||
Error: string;
|
||||
Success: string;
|
||||
StartOver: string;
|
||||
OpenTeams: string;
|
||||
}
|
||||
|
||||
declare module 'TeamsCreatorWebPartStrings' {
|
||||
const strings: ITeamsCreatorWebPartStrings;
|
||||
export = strings;
|
||||
}
|
34
samples/react-team-creator/tsconfig.json
Normal file
34
samples/react-team-creator/tsconfig.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
32
samples/react-team-creator/tslint.json
Normal file
32
samples/react-team-creator/tslint.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"rulesDirectory": [
|
||||
"tslint-microsoft-contrib"
|
||||
],
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user