Added Yammer Praise sample

This commit is contained in:
Ramin Ahmadi 2020-03-23 19:44:05 +00:00
parent 0868acab74
commit 3d2187e485
51 changed files with 807 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

32
samples/react-yammer-praise/.gitignore vendored Normal file
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.10.0",
"libraryName": "react-yammer-aadtoken",
"libraryId": "26a3fd56-6c52-4dc8-97fc-7d985adda244",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ramin Ahmadi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,71 @@
# Spfx Webpart Group members list with Presence information
## Summary
This sample shows how to post a praise to Yammer using aadTokenProvider (without Yammer JavaScript SDK).
![Post Praise to Yammer](./assets/screenshot.gif)
It also can be added to Microsoft Teams as Personal or Team Tabs.
![Post Praise to Yammer from Microsoft Teams](./assets/screenshot2.gif)
## Used SharePoint Framework Version
![SPFx v1.10.0](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
## Applies to
* [SharePoint Framework Developer](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Solution
Solution|Author(s)
--------|---------
react-yammer-praise|Ramin Ahmadi
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Mar 23, 2020|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
This sample illustrates the following concepts on top of the SharePoint Framework:
* Using AadTokenProvide to consume Yammer API.
* How to get User/Group information from Yammer.
* How to post a praise to Yammer.
* React Hooks
* Using async / await for the async calls
* Office UI fabric components
* Can be installed on Microsoft Teams as Personal app or a team tab
## Configuration
To get access to Yammer API, we need to add the required permission to “SharePoint Online Client Extensibility Web Application Principal” application:
* Navigate to Azure portal.
* Search for App Registration at top search box.
* Select “SharePoint Online Client Extensibility Web Application Principal“
* Select “API permissions” from left navigation.
* Click “Add a permission“.
* Select Yammer.
* Select “User_Impersonation” from delegated permissions.
* Click “Add permissions“.
* Click “Grant admin consent” button.
* Select “Yes, add other granted permissions to configured permissions“
* Click “Save and continue“.
* Click “Grant admin consent“.
* Select “Yes“.
Bundle and package the solution, deploy it to app catalog, then add the web part to any pages in SharePoint or add to your Teams.
Read my blog post for more information from [here](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-yammer-api).

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"react-yammer-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/reactYammer/ReactYammerWebPart.js",
"manifest": "./src/webparts/reactYammer/ReactYammerWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ReactYammerWebPartStrings": "lib/webparts/reactYammer/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": "react-yammer-aadtoken",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-yammer-aadtoken-client-side-solution",
"id": "26a3fd56-6c52-4dc8-97fc-7d985adda244",
"version": "1.0.0.8",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"webApiPermissionRequests": [
{
"resource": "Windows Azure Active Directory",
"scope": "User.Read"
}
],
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-yammer-praise.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'));

View File

@ -0,0 +1,44 @@
{
"name": "react-yammer-aadtoken",
"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.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@pnp/spfx-controls-react": "^1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"axios": "^0.19.2",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.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"
}
}

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,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "4642d66e-8f58-470f-b61f-61d4a959f4d1",
"alias": "ReactYammerWebPart",
"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","TeamsTab","TeamsPersonalApp"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Yammer Praise" },
"description": { "default": "Post Praise to Yammer" },
"officeFabricIconFontName": "YammerLogo",
"properties": {
"description": "Post Praise to Yammer"
}
}]
}

View File

@ -0,0 +1,74 @@
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 'ReactYammerWebPartStrings';
import ReactYammer from './components/ReactYammer';
import { IReactYammerProps } from './components/IReactYammerProps';
import { AadTokenProvider,AadHttpClient } from '@microsoft/sp-http';
import YammerProvider from './yammer/YammerProvider';
import { IYammerProvider } from './yammer/IYammerProvider';
export interface IReactYammerWebPartProps {
description: string;
}
export default class ReactYammerWebPart extends BaseClientSideWebPart<IReactYammerWebPartProps> {
private aadToken: string = "";
public render(): void {
let yammerProvider: IYammerProvider = new YammerProvider(this.aadToken, this.context.pageContext.user.email);
const element: React.ReactElement<IReactYammerProps> = React.createElement(
ReactYammer,
{
context: this.context,
yammerProvider
}
);
ReactDom.render(element, this.domElement);
}
public async onInit(): Promise<void> {
const tokenProvider: AadTokenProvider = await this.context.aadTokenProviderFactory.getTokenProvider();
await tokenProvider.getToken("https://api.yammer.com").then(token => {
this.aadToken = token;
}).catch(err => console.log(err));
}
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,7 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { IYammerProvider } from './../yammer/IYammerProvider';
export interface IReactYammerProps {
context: WebPartContext;
yammerProvider:IYammerProvider;
}

View File

@ -0,0 +1,45 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.reactYammer {
.previewBox{
overflow: auto;
position: relative;
border: 1px solid #dde0e6;
padding: 18px 24px 20px 59px;
margin-top: 10px;
font-size: 1.5rem;
line-height: 20px;
}
.previewContainer{
margin-top:10px;
}
.previewTitle{
font-weight: 700;
display: block;
margin: 0 0 1.5rem;
font-size: 15px;
}
.previewContent{
white-space: pre-wrap;
word-break: break-word;
}
.previewIcon{
position: absolute;
left: 15px;
top: 15px;
background: 0 0;
}
.previewComment{
word-wrap: break-word;
white-space: pre-line;
float: none;
width: auto;
padding: 0;
margin: 0;
font-size: 15px;
color: #343a41;
}
.btnSubmit{
margin-top:10px;
}
}

View File

@ -0,0 +1,172 @@
import * as React from 'react';
import styles from './ReactYammer.module.scss';
import { IReactYammerProps } from './IReactYammerProps';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import LoadingImage from './loading';
import IPraise from '../interface/IPraise';
import badges from './badges';
const ReactYammer: React.SFC<IReactYammerProps> = (props) => {
const [loading, setLoading] = React.useState(false);
const [nominee, setNominee] = React.useState("");
const [nomineeName, setNomineeName] = React.useState("");
const [icon, setIcon] = React.useState("");
const [groups, setGroups] = React.useState<IDropdownOption[]>([]);
const [comment, setComment] = React.useState("");
const [groupId, setGroupId] = React.useState("");
const [threadId,setThreadId] = React.useState("");
const [messageBarStatus, setMessageBarStatus] = React.useState({
type: MessageBarType.info,
message:<span></span>,
show: false
});
React.useEffect(() => {
props.yammerProvider.getGroups().then(grps => {
const options: IDropdownOption[] = grps.data.map(g => ({ key: g.id, text: g.full_name }));
setGroups(options);
}).catch(err => {
console.log(err);
});
}, []);
const _getPeoplePickerItems = (items: any[]) => {
setNomineeName(items[0].text);
setNominee(items[0].secondaryText);
};
const _onIconChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setIcon(item.text);
};
const _onGroupChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setGroupId(item.key.toString());
};
const _postPraise = () => {
const objPraise: IPraise = {
icon,
nominee,
comment,
groupId
};
setLoading(true);
props.yammerProvider.postPraise(objPraise).then(response => {
const threadId = response.data.messages[0].thread_id;
setThreadId(threadId);
clearControls();
setMessageBarStatus({
type: MessageBarType.success,
message: <span>Your priase now been successfully added.<a target="_blank" href={`https://www.yammer.com/messages/${threadId}`}>See the praise on Yammer.</a></span>,
show: true
});
setLoading(false);
}).catch(error => {
setMessageBarStatus({
type: MessageBarType.error,
message: <span>"Unfortunately we could not post your praise. Please try again later."</span>,
show: true
});
setLoading(false);
});
};
const clearControls = () => {
setNominee("");
setIcon("");
setComment("");
};
const _onRenderOption = (option: IDropdownOption): JSX.Element => {
return (
<div>
<img style={{ marginRight: '8px' }} src={require(`./assets/${option.key}.png`)} width={24} height={24} />
</div>
);
};
const _onRenderTitle = (options: IDropdownOption[]): JSX.Element => {
const option = options[0];
return (
<div>
<img style={{ marginTop: '4px' }} src={require(`./assets/${option.key}.png`)} width={24} height={24} />
</div>
);
};
return (
<div className={styles.reactYammer}>
<div>
{
loading && <LoadingImage />
}
</div>
<div>
{
(messageBarStatus.show && !loading) &&
<MessageBar messageBarType={messageBarStatus.type}>{
messageBarStatus.message
}</MessageBar>
}
</div>
{
!loading &&
<div>
<div>
<PeoplePicker isRequired
context={props.context}
titleText="Who do you want to praise?"
ensureUser={true}
personSelectionLimit={1}
groupName=""
selectedItems={_getPeoplePickerItems}
defaultSelectedUsers={[nominee]}
showHiddenInUI={false}
principalTypes={[PrincipalType.User]}
resolveDelay={1000} />
<TextField required maxLength={250} placeholder="Describe what they've done." label="What they've done" value={comment} multiline={true} rows={6} onChanged={(value) => setComment(value)} />
<Dropdown required label="Group"
options={groups} onChange={_onGroupChange} />
<Dropdown required label="Icon"
style={{width:"70px"}}
onRenderOption={_onRenderOption}
onRenderTitle={_onRenderTitle}
options={badges} onChange={_onIconChange} />
</div>
<div className={styles.previewContainer} hidden={comment === "" && icon === "" && nominee === ""}>
<span>Preview:</span>
<div className={styles.previewBox}>
<div>
<img className={styles.previewIcon} src={require(`./assets/${icon ? icon : "star"}.png`)} width={32} height={32} />
<p className={styles.previewTitle}>
Praised {nomineeName}
</p>
<p className={styles.previewComment}>
{
comment
}
</p>
</div>
</div>
</div>
<PrimaryButton className={styles.btnSubmit} text="Post" title="Please fill in all required fields" onClick={_postPraise} disabled={comment === "" || icon === "" || nominee === "" || groupId === ""} />
</div>
}
</div>
);
};
export default ReactYammer;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,22 @@
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
const options: IDropdownOption[] = [
{ key: 'thumbsup', text: 'thumbsup'},
{ key: 'glasses', text: 'glasses'},
{ key: 'doublerainbow', text: 'double rainbow'},
{ key: 'money', text: 'money'},
{ key: 'ninja', text: 'ninja'},
{ key: 'coffee', text: 'coffee'},
{ key: 'checkeredflag', text: 'checkeredflag'},
{ key: 'pie', text: 'pie'},
{ key: 'monocle', text: 'monocle'},
{ key: 'heart', text: 'heart'},
{ key: 'ace', text: 'ace'},
{ key: 'trophy', text: 'trophy'},
{ key: 'gift', text: 'gift'},
{ key: 'graduationcap', text: 'graduationcap'},
{ key: 'lightbulb', text: 'lightbulb'},
{ key: 'star', text: 'star'},
{ key: 'diamond', text: 'diamond'}
];
export default options;

View File

@ -0,0 +1,21 @@
const icons = [
{ src: './assets/thumbsup.png', key: 'thumbsup' },
{ src: './assets/glasses.png', key: 'glasses' },
{ src: './assets/doublerainbow.png', key: 'doublerainbow' },
{ src: './assets/money.png', key: 'money' },
{ src: './assets/ninja.png', key: 'ninja' },
{ src: './assets/coffee.png', key: 'coffee' },
{ src: './assets/checkeredflag.png', key: 'checkeredflag' },
{ src: './assets/pie.png', key: 'pie' },
{ src: './assets/monocle.png', key: 'monocle' },
{ src: './assets/heart.png', key: 'heart' },
{ src: './assets/ace.png', key: 'ace' },
{ src: './assets/trophy.png', key: 'trophy' },
{ src: './assets/gift.png', key: 'gift' },
{ src: './assets/graduationcap.png', key: 'graduationcap' },
{ src: './assets/lightbulb.png', key: 'lightbulb' },
{ src: './assets/star.png', key: 'star' },
{ src: './assets/diamond.png', key: 'diamond' },
];
export default icons;

View File

@ -0,0 +1,11 @@
import * as React from 'react';
const loadingImage: any = require("./assets/loading.gif");
const loading = ()=>{
return(
<>
<img src={loadingImage} width={350} />
</>
);
};
export default loading;

View File

@ -0,0 +1,7 @@
export default interface IPraise{
threadId?:string;
comment:string;
icon:string;
nominee:string;
groupId:string;
}

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 IReactYammerWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'ReactYammerWebPartStrings' {
const strings: IReactYammerWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,8 @@
import IPraise from "../interface/IPraise";
export interface IYammerProvider {
postPraise(praise:IPraise):Promise<any>;
getGroups():Promise<any>;
}

View File

@ -0,0 +1,51 @@
import axios from "axios";
import IPraise from "../interface/IPraise";
import { IYammerProvider } from './IYammerProvider';
export default class YammerProvider implements IYammerProvider {
private readonly _apiUrl: string = "https://api.yammer.com/api/v1/";
constructor(private aadToken:string,private currentUser:string){}
private async getUserId(email: string) {
const reqHeaders = {
"Authorization": `Bearer ${this.aadToken}`
};
const result = await axios.get(`${this._apiUrl}users/by_email.json?email=${email}`,{headers:reqHeaders});
return result.data[0].id;
}
public async getGroups(){
const userId = await this.getUserId(this.currentUser);
const reqHeaders = {
"content-type": "application/json",
"Authorization": `Bearer ${this.aadToken}`
};
return axios.get(`${this._apiUrl}groups/for_user/${userId}`, { headers: reqHeaders });
}
public async postPraise(praise: IPraise) {
const userId = await this.getUserId(praise.nominee);
const reqHeaders = {
"content-type": "multipart/form-data",
"Authorization": `Bearer ${this.aadToken}`
};
const form = new FormData();
form.append("body", "posting as praise");
form.append("group_id", praise.groupId);
form.append("skip_body_notifications", "true");
form.append("praise", `{"comment":"${praise.comment}","icon":"${praise.icon}","praised_user_ids":[${userId}]}`);
return axios.post(`${this._apiUrl}messages.json`, form, { headers: reqHeaders });
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,38 @@
{
"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"
],
"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
}
}