Commit the webpart sample

This commit is contained in:
David Ramalho 2021-02-28 16:12:24 +00:00
parent be1b788c4b
commit 880d67c93a
26 changed files with 18310 additions and 0 deletions

View 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

View 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

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.11.0",
"libraryName": "teams-messages",
"libraryId": "ca0032ad-5f6e-483f-85b5-6b82a9096356",
"packageManager": "npm",
"isDomainIsolated": true,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,95 @@
# Display List
## Summary
Sample that shows how to send a message to Micosoft Teams using a SharePoint framework solution using Microsoft Graph.
![Message Teams Webpart preview](./assets/webPart-preview.png).
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
## Prerequisites
* SharePoint Online tenant
* You have provided permission in SharePoint admin for accessing Graph API on behalf of your solution. We can do it before deployment as proactive steps, or after deployment. You can refer to [steps about how to do this post-deployment](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aad-tutorial#deploy-the-solution-and-grant-permissions). Basically you have to use API Access Page of SharePoint admin and add below permission for our use case.
```
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "ChatMessage.Send"
},
{
"resource": "Microsoft Graph",
"scope": "Chat.Create"
},
{
"resource": "Microsoft Graph",
"scope": "Chat.ReadWrite"
},
{
"resource": "Microsoft Graph",
"scope": "User.Read"
},
{
"resource": "Microsoft Graph",
"scope": "User.ReadWrite.All"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.ReadWrite.All"
}
]
```
## Concepts
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Using react framework in SPFx webpart
* Calling Microsoft Graph API in SPFx webpart
## Applies to
* [SharePoint Framework Developer](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
## Solution
Solution|Author(s)
--------|---------
teams-messages| David Ramalho([@davRamalho](https://twitter.com/davRamalho))
## Version history
Version|Date|Comments
-------|----|--------
1.0|February 28, 2021|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.**
---
## Minimal Path to Awesome
- Clone this repository
- in the command line run:
- `npm install`
- `gulp serve`
![Message Teams Webpart](./assets/MessageTeams.gif).

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"teams-messages-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/teamsMessages/TeamsMessagesWebPart.js",
"manifest": "./src/webparts/teamsMessages/TeamsMessagesWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"TeamsMessagesWebPartStrings": "lib/webparts/teamsMessages/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -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-messages",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,50 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "teams-messages-client-side-solution",
"id": "ca0032ad-5f6e-483f-85b5-6b82a9096356",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"webApiPermissionRequests": [{
"resource": "Microsoft Graph",
"scope": "ChatMessage.Send"
},
{
"resource": "Microsoft Graph",
"scope": "Chat.Create"
},
{
"resource": "Microsoft Graph",
"scope": "Chat.ReadWrite"
},
{
"resource": "Microsoft Graph",
"scope": "User.Read"
},
{
"resource": "Microsoft Graph",
"scope": "User.ReadWrite.All"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.ReadWrite.All"
}
],
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/teams-messages.sppkg"
}
}

View 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/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,7 @@
'use strict';
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(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
{
"name": "teams-messages",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/spfx-controls-react": "2.4.0",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"devDependencies": {
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"@types/webpack-env": "1.13.1",
"@types/es6-promise": "0.0.33"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,89 @@
import { MSGraphClientFactory } from '@microsoft/sp-http';
export interface IMSGraphInterface {
getCurrentUserId(): Promise<any>;
getUserId(userEmail: string): Promise<any>;
createUsersChat(requesterId: string, birthdayPersonId: string): Promise<any>;
sendMessage(chatId: string, chatMessage: string): Promise<any>;
}
export default async function msGraphProvider(msGraphClientFactory: MSGraphClientFactory): Promise<IMSGraphInterface> {
const msGraphClient = await msGraphClientFactory.getClient();
//GET https://graph.microsoft.com/beta/users/{id}
const getUserId = async (userEmail: string) => {
let resultGraph = await msGraphClient.api(`/users/${userEmail}`).get();
return resultGraph.id;
};
const getCurrentUserId = async () => {
let resultGraph = await msGraphClient.api(`me`).get();
return resultGraph.id;
};
// POST https://graph.microsoft.com/beta/chats
// Content-Type: application/json
// {
// "chatType": "oneOnOne",
// "members": [
// {
// "@odata.type": "#microsoft.graph.aadUserConversationMember",
// "roles": ["owner"],
// "user@odata.bind": "https://graph.microsoft.com/beta/users('8b081ef6-4792-4def-b2c9-c363a1bf41d5')"
// },
// {
// "@odata.type": "#microsoft.graph.aadUserConversationMember",
// "roles": ["owner"],
// "user@odata.bind": "https://graph.microsoft.com/beta/users('82af01c5-f7cc-4a2e-a728-3a5df21afd9d')"
// }
// ]
// }
const createUsersChat = async (requesterId: string, birthdayPersonId: string) => {
let body: any = {
"chatType": "oneOnOne",
"members": [
{
"@odata.type": "#microsoft.graph.aadUserConversationMember",
"roles": ["owner"],
"user@odata.bind": `https://graph.microsoft.com/beta/users('${requesterId}')`
},
{
"@odata.type": "#microsoft.graph.aadUserConversationMember",
"roles": ["owner"],
"user@odata.bind": `https://graph.microsoft.com/beta/users('${birthdayPersonId}')`
}
]
};
let resultGraph = await msGraphClient.api(`chats`).version("beta").post(body);
return resultGraph.id;
};
// POST https://graph.microsoft.com/beta/chats/{id}/messages
// Content-type: application/json
// {
// "body": {
// "content": "Hello World"
// }
// }
const sendMessage = async (chatId: string, chatMessage: string) => {
let body = {
"body": {
"content": chatMessage
}
};
let resultGraph = await msGraphClient.api(`chats/${chatId}/messages`).version("beta").post(body);
return resultGraph;
};
return {
getUserId,
sendMessage,
createUsersChat,
getCurrentUserId
};
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "af807f68-01ba-4af3-b842-87cf02ae7fb9",
"alias": "TeamsMessagesWebPart",
"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,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "teams-messages" },
"description": { "default": "teams-messages description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "teams-messages"
}
}]
}

View File

@ -0,0 +1,60 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'TeamsMessagesWebPartStrings';
import {TeamsMessages} from './components/TeamsMessages';
import { ITeamsMessagesProps } from './components/ITeamsMessagesProps';
export interface ITeamsMessagesWebPartProps {
description: string;
}
export default class TeamsMessagesWebPart extends BaseClientSideWebPart<ITeamsMessagesWebPartProps> {
public render(): void {
const element: React.ReactElement<ITeamsMessagesProps> = React.createElement(
TeamsMessages,
{
webPartContext: 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
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,5 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface ITeamsMessagesProps {
webPartContext: WebPartContext;
}

View File

@ -0,0 +1 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';

View File

@ -0,0 +1,77 @@
import * as React from 'react';
import styles from './TeamsMessages.module.scss';
import { ITeamsMessagesProps } from './ITeamsMessagesProps';
import { escape } from '@microsoft/sp-lodash-subset';
import useMsGraphProvider, { IMSGraphInterface } from '../../services/msGraphProvider';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
import { BaseButton, Button, IPersonaProps, MessageBar, MessageBarType, PrimaryButton, TextField } from 'office-ui-fabric-react';
export const TeamsMessages: React.FunctionComponent<ITeamsMessagesProps> = (
props: ITeamsMessagesProps
) => {
const [user, setUser] = React.useState<IPersonaProps[]>();
const [text, setText] = React.useState<string>("");
const [success, setSuccess] = React.useState<boolean>(false);
const [msGraphProvider, setMSGraphProvider] = React.useState<IMSGraphInterface>();
const fetchMsGraphProvider = async () => {
setMSGraphProvider(await useMsGraphProvider(props.webPartContext.msGraphClientFactory));
};
const _getPeoplePickerItems = (item: IPersonaProps[]) => {
setUser(item);
}
const _onChangeFirstTextFieldValue = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setText(newValue);
};
const onClickSubmit = async (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button, MouseEvent>) => {
event.preventDefault();
let currentUserId = await msGraphProvider.getCurrentUserId();
let userIdToSendMessage = await msGraphProvider.getUserId(user[0].secondaryText);
let chatOfUser = await msGraphProvider.createUsersChat(userIdToSendMessage, currentUserId);
let result = await msGraphProvider.sendMessage(chatOfUser, text);
if (result) {
setSuccess(true);
}
};
React.useEffect(() => {
fetchMsGraphProvider();
}, []);
return (
<>
{success &&
<MessageBar
messageBarType={MessageBarType.success}
isMultiline={false}
>
Message send with success.
</MessageBar>
}
<PeoplePicker
context={props.webPartContext}
titleText="People to send message"
personSelectionLimit={1}
onChange={_getPeoplePickerItems}
principalTypes={[PrincipalType.User]}
resolveDelay={1000} />
<TextField
label="Message to send"
value={text}
onChange={_onChangeFirstTextFieldValue}
/>
<PrimaryButton
style={{ marginTop: 10 }}
onClick={onClickSubmit}>
Submit
</PrimaryButton>
</>
);
};

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITeamsMessagesWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'TeamsMessagesWebPartStrings' {
const strings: ITeamsMessagesWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,39 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"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
}
}