Merge pull request #1853 from ReactIntern/react-groups-teams

This commit is contained in:
Hugo Bernier 2021-05-06 01:05:24 -04:00 committed by GitHub
commit 4945d129df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 19419 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-groups-teams/.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.9.1",
"libraryName": "react-groups",
"libraryId": "e562b13d-7e90-4a1e-810c-ccde0ff27866",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,112 @@
## All Microsoft 365 Groups and Teams with SPFx
### Summary
Web part pulls all Microsoft 365 Groups and Teams that the logged in user has access to view.
1. The Microsoft Groups view has filter option for private or public groups and can switch between viewing all groups or just my groups.
- Group Name (hover for group description)
- Link to email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- Group privacy
2. The Microsoft Teams view has filter option for private or public Teams.
- Team Name (hover for group description)
- Link to email
- Link to SharePoint site
- Link to calendar
- Link to Planner plan (if available)
- Link to Team
- Team privacy
Each Team o Uses SharePoint theme.
![picture of the web part in action](./assets/Groups-in-my-organization.png)
![picture of the web part in action](./assets/My-Teams-Teams-Side-By-Side-Theme.png)
![picture of the web part in action](./assets/My-Groups-Public-Filter.png)
![picture of the web part in action](./assets/My-Teams-Teams-With-Tooltip.png)
## Compatibility
![SPFx 1.10](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
![Node.js LTS 8.x | LTS 10.x](https://img.shields.io/badge/Node.js-LTS%208.x%20%7C%20LTS%210.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Hosted: Only after API permissions granted](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Only after API permissions granted")
## Applies to
* [SharePoint Framework Developer Preview](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
## Solution
Solution|Author(s)
--------|---------
React-Groups-Teams | [Alison Collins](https://github.com/ReactIntern) |
## Version history
| Version | Date | Comments |
| ------- | ---------------- | --------------- |
| 1.0.0 | April 16, 2021 | Initial release |
# Prerequisites
- Administrative access to Azure AD of Microsoft 365 tenant
- SharePoint Online tenant
- You need following set of permissions in order to manage Microsoft 365 Groups and Teams
```
"webApiPermissionRequests": [{
"resource": "Microsoft Graph",
"scope": "Groups.Read.All"
}, {
"resource": "Team",
"scope": "Teams.ReadBasic.All"
}]
```
# Minimal Path to Awesome
- Clone this repo
- Navigate to the folder with current sample
- Restore dependencies: `$ npm i`
- Bundle the solution: `$ gulp bundle --ship`
- Package the solution: `$ gulp package-solution --ship`
- Upload to SharePoint tenant app catalog
- You will see a message saying that solution has pending permissions which need to be approved
- Approve the permission requests.
- Run `$ gulp serve --nobrowser`
- Open hosted workbench, i.e. `https://<tenant>.sharepoint.com/sites/<your site>/_layouts/15/workbench.aspx`
- Search and add `O365 Groups Manager` web part to see it in action
# Features
This project contains sample client-side web part built on the SharePoint Framework illustrating possibilities to quickly gain access to features in Microsoft 365 Groups and Teams using React and MS Graph.
This sample illustrates the following concepts on top of the SharePoint Framework:
- Explore MS Graph APIs for Microsoft 365 Group
- Using the MSGraphClient in a SharePoint Framework web part
- Requesting API permissions in a SharePoint Framework package
- Communicating with the Microsoft Graph using its REST API
- Using Office UI Fabric controls for building SharePoint Framework client-side web parts
- Passing web part properties to React components
# 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.**
## Support
We do not support samples, but we do use GitHub to track issues and constantly want to improve these samples.
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-groups-teams&authors=@ReactIntern&title=react-groups-teams%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-groups-teams&authors=@ReactIntern&title=react-groups-teams%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-groups-teams&authors=@ReactIntern&title=react-groups-teams%20-%20).
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-groups-teams" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -0,0 +1,68 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-groups-teams",
"source": "pnp",
"title": "All Microsoft 365 Groups and Teams",
"shortDescription": "Web part pulls all Microsoft 365 Groups and Teams that the logged in user has access to view.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-groups-teams",
"longDescription": [
"Web part pulls all Microsoft 365 Groups and Teams that the logged in user has access to view."
],
"creationDateTime": "2021-05-06",
"updateDateTime": "2021-05-06",
"products": [
"SharePoint",
"Office"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.10.0"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-groups-teams/assets/Groups-in-my-organization.png",
"alt": "Web Part Preview"
},
{
"type": "image",
"order": 101,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-groups-teams/assets/My-Groups-Public-Filter.png",
"alt": "Web Part Preview"
},
{
"type": "image",
"order": 102,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-groups-teams/assets/My-Teams-Teams-Side-By-Side-Theme.png",
"alt": "Web Part Preview"
},
{
"type": "image",
"order": 102,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-groups-teams/assets/My-Teams-Teams-With-Tooltip.png",
"alt": "Web Part Preview"
}
],
"authors": [
{
"gitHubAccount": "ReactIntern",
"pictureUrl": "https://github.com/ReactIntern.png",
"name": "Alison Collens"
}
],
"references": [
{
"name": "Build your first SharePoint client-side web part",
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
}
]
}
]

View File

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

View File

@ -0,0 +1,23 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-groups-client-side-solution",
"id": "e562b13d-7e90-4a1e-810c-ccde0ff27866",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"webApiPermissionRequests":[{
"resource": "Microsoft Graph",
"scope": "Groups.Read.All"
}, {
"resource": "Team",
"scope": "Teams.ReadBasic.All"
}]
},
"paths": {
"zippedPackage": "solution/react-groups.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 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);

17984
samples/react-groups-teams/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
{
"name": "react-groups",
"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.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"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.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"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": "5ced32db-af85-469a-a3cb-39f3986e1f1a",
"alias": "MicrosoftGroupsWebPart",
"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": "Microsoft Groups" },
"description": { "default": "Microsoft Groups description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "Microsoft Groups"
}
}]
}

View File

@ -0,0 +1,59 @@
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 'MicrosoftGroupsWebPartStrings';
import MicrosoftGroups from './components/MicrosoftGroups';
export interface IMicrosoftGroupsWebPartProps {
description: string;
}
export default class MicrosoftGroupsWebPart extends BaseClientSideWebPart<IMicrosoftGroupsWebPartProps> {
public render(): void {
const element: React.ReactElement<IMicrosoftGroupsWebPartProps > = React.createElement(
MicrosoftGroups,
{
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
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,241 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import { Modal, TooltipHostBase } from 'office-ui-fabric-react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { iconClass } from './MyTeams';
export interface IGraphConsumerProps {
context: WebPartContext;
}
export interface IUserItem {
Topic: string;
DeliveryDate: Date;
}
export interface IGraphConsumerState {
AllGroupsresults: any;
MyGroupResults: any;
plannerId: string;
either: string;
Allgroupsdisplay: number;
mygroupsdisplay: number;
mode: string;
title: string;
isOpen: boolean;
MoreDetails: any;
Name: string;
Description: string;
TenantUrl: string;
MyGroupResultsFiltered: any;
AllGroupsresultsFiltered: any;
}
export default class MicrosoftGroups extends React.Component<IGraphConsumerProps, IGraphConsumerState> {
public _plannerIDs = [];
public _arr = [];
private graphClient: MSGraphClient = null;
public Tenant = this.props.context.pageContext.web.absoluteUrl.split('.')[0].split('//')[1];
constructor(props) {
super(props);
this.GetPlanner = this.GetPlanner.bind(this);
this.GetGroups = this.GetGroups.bind(this);
this.state = {
AllGroupsresults: [],
MyGroupResults: [],
plannerId: '',
either: '',
Allgroupsdisplay: 0,
mygroupsdisplay: 0,
mode: 'All',
title: 'Groups In My Organization',
isOpen: false,
MoreDetails: [],
Name: '',
Description: '',
TenantUrl: '',
MyGroupResultsFiltered: [],
AllGroupsresultsFiltered: []
};
}
public SwitchGroupList() {
if (this.state.title === 'Groups In My Organization') {
this.setState({ title: "My Groups" });
}
else {
this.setState({ title: 'Groups In My Organization' });
}
}
public SwitchGroupList2(Switch) {
if (Switch === 'All') {
this.setState({ AllGroupsresultsFiltered: this.state.AllGroupsresults });
this.setState({ MyGroupResultsFiltered: this.state.MyGroupResults });
}
else {
const SwitchedALL = this.state.AllGroupsresults.filter(item => item.Visibility === Switch);
this.setState({ AllGroupsresultsFiltered: SwitchedALL });
const SwitchedMY = this.state.MyGroupResults.filter(item => item.Visibility === Switch);
this.setState({ MyGroupResultsFiltered: SwitchedMY });
}
this.setState({ mode: Switch });
}
public OpenModal(GroupInfo) {
var array = [];
array.push(GroupInfo);
this.setState({ isOpen: true, MoreDetails: array, Name: GroupInfo.Name, Description: GroupInfo.Description });
}
public async GetPlanner() {
this.state.MyGroupResults.map(async GroupId => {
try {
this.graphClient = await this.props.context.msGraphClientFactory.getClient();
const results: any = await this.graphClient
.api(`/groups/${GroupId.Id}/planner/plans`)
.version('v1.0')
.get();
if (results.value.length > 0) {
var ID; results.value.map(Items => {
ID = Items.id;
});
var URL = `https://tasks.office.com/${this.Tenant}.com/EN-US/Home/Planner#/plantaskboard?groupId=${GroupId.Id}&planId=${ID}`;
var Planner = { Planner: URL };
var Results = Object.assign(GroupId, Planner);
GroupId = Results;
this.state.AllGroupsresults.map(Group => {
if (Group.Name === GroupId.Name) {
var Results2 = Object.assign(Group, Planner);
Group = Results2;
}
});
}
return this.setState({ MyGroupResultsFiltered: this.state.MyGroupResults });
} catch (error) {
}
});
}
public GetGroups() {
var
GroupIDListAll = [],
GroupIDListMy = [],
myarray = [];
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`me/transitiveMemberOf/microsoft.graph.group?$filter=groupTypes/any(a:a eq 'unified')`)
.version("v1.0")
.get((err, res) => {
if (err) {
}
if (res) {
res.value.map((item) => {
myarray.push({ Name: item.displayName, Id: item.id, Description: item.description, Mail: item.mail, Visibility: item.visibility });
GroupIDListMy.push({ Id: item.id });
});
this.setState({ MyGroupResultsFiltered: myarray, MyGroupResults: myarray });
this.GetPlanner();
}
});
});
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`groups?$filter=groupTypes/any(a:a eq 'unified')`)
.version("v1.0")
.get((err, res) => {
if (err) {
// Do nothing
}
if (res) {
res.value.map((item) => {
this._arr.push({ Name: item.displayName, Id: item.id, Description: item.description, Mail: item.mail, Visibility: item.visibility });
GroupIDListAll.push([item.id]);
});
this.setState({ AllGroupsresults: this._arr, AllGroupsresultsFiltered: this._arr });
this.GetPlanner();
}
});
});
}
public componentDidMount() {
this.GetGroups();
}
public render(): React.ReactElement<IGraphConsumerProps> {
var Replaceregex = /\s+/g;
return <div className={styles.test}>
<div className={styles.tableCaptionStyle}>{this.state.title}
<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Public')}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Public')}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('All')}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('All')}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList2('Private')}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList2('Private')}>Private</button>}
</div>
<button className={styles.SwitchGroups} onClick={() => this.SwitchGroupList()}> View {this.state.title === 'My Groups' ?
'Groups in my Organization' : 'My Groups'}</button>
</div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle}>
<div className={styles.Center}>Group</div>
<div className={styles.Center}>Mail</div>
<div className={styles.Center}>Site</div>
<div className={styles.Center}>Calendar</div>
<div className={styles.Center}>Planner</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>Visibility</div>
</div>
{this.state.title === 'My Groups' ? this.state.MyGroupResultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
return <div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Group.Name}<span className={styles.ToolTip}>{Group.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Group.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Group.Planner === undefined ? <div></div> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon></a>}
</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>;
}) : this.state.AllGroupsresultsFiltered.map(Group => {
var GroupEmailSplit = Group.Mail.split("@");
Group.Mail = GroupEmailSplit[0];
return <div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Group.Name}<span className={styles.ToolTip}>{Group.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Group.Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon>
</a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Group.Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Group.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Group.Planner === undefined ? <div></div> : <a href={Group.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon></a>}
</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Group.Visibility}</div>
</div>;
})}
</div>;
</div>;
}
}

View File

@ -0,0 +1,425 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.test {
text-align: center;
vertical-align: middle;
}
.none {
display:none;
}
.Center {
padding-top: 10px;
padding-bottom: 10px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding-right: 13px;
padding-left: 13px;
border-bottom: 2px solid white;
/*
position: relative;
padding-bottom: 0px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding: 8px 13px 8px;
border-bottom: 2px solid white;
max-width: 132px;
word-break: break-word;*/
}
.SwitchGroups{
position:absolute;
bottom:2px;
right:10px;
font-size:16px;
background-color: transparent;
border: none;
cursor:pointer;
color:white;
}
.SwitchGroups:hover{
text-decoration: underline;
}
.headerStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
display: table-row ;
border: solid ;
text-align : center ;
width : 100px ;
height : 30px ;
padding-top : 10px ;
color : white ;
}
.tableStyle{
display: table;
min-width: 100%;
margin-right: auto;
margin-left: auto;
}
.panelStyle{
background: lightblue ;
}
.tableCaptionStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
font-size: 20px ;
font-weight: bold ;
border: solid ;
text-align:center ;
height:79px;
padding-top:3px ;
border-radius:25px ;
color:white;
border-radius: 25px;
margin-left: auto;
margin-top: auto;
min-width: 100%;
text-align: center;
vertical-align: middle;
position: relative;
font-weight:normal;
}
.rowStyle{
display : table-row ;
background: #eee ;
padding: 20px ;
margin: 20px ;
font-weight : bold ;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.CellStyle{
display: table-cell ;
border: solid ;
border-color : white ;
text-align : center ;
min-width : 75px ;
height : 30px ;
width: fit-content;
padding-top : 10px ;
}
.buttons {
display: table-cell ;
text-align : center ;
width : 160px ;
height : 30px ;
padding-top : 10px ;
}
.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;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
// @import '~office-ui-fabric-react/dist/sass/References.scss';
.test {
text-align: center;
vertical-align: middle;
}
.none {
display:none;
}
.CenterRight {
border-right:none;
padding: 5px 10px 3px;
margin-left: 26px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
//border-bottom: 2px solid white;
max-width: 113px;
word-break: break-word;
}
.Center {
padding: 5px 10px 3px;
margin-left: 26px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
border-bottom: 2px solid white;
max-width: 113px;
word-break: break-word;
}
.ToolTipName {
position: relative;
padding-bottom: 0px;
display: table-cell;
vertical-align: middle;
text-align: center;
border-right: 2px solid white;
padding: 8px 13px 8px;
border-bottom: 2px solid white;
max-width: 132px;
word-break: break-word;
}
.ToolTip {
visibility: hidden;
padding: 7px;
background-color: $ms-color-themeDark;
color: #fff;
text-align: center;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 86%;
right: 0%;
left: 0%;
padding-left: 7px;
padding-right: 7px;
}
.ToolTipName:hover .ToolTip {
visibility: visible;
padding: 5px 0px 5px 0px;
background-color: "[theme: themeLight, default: #0078d7]";
}
.MainViewCenter {
width: 50%;
padding-top: 10px;
padding-bottom: 10px;
display: table-cell;
vertical-align: middle;
text-align: center;
padding-right: 13px;
padding-left: 13px;
cursor:pointer;
background-color: "[theme: themeDark, default: #0078d7]";
border: none;
color: white;
border-right: 2px solid white;
}
.MainViewCenter :hover{
text-decoration: underline;
}
.Filters {
cursor: pointer;
background-color: "[theme: themePrimary, default: #0078d7]";
color: #fff;
border: none;
border: 2px solid;
border-color: "[theme: themeDark, default: #0078d7]";
margin-right: 4px;
margin-top: 9px;
}
.Filters:focus {
outline: none;
}
.SelectedFilter {
cursor: pointer;
background-color: "[theme: themeDark, default: #0078d7]";
color: #fff;
border: none;
border: 2px solid;
border-color: "[theme: themeDark, default: #0078d7]";
margin-right: 4px;
margin-top: 9px;}
.SelectedFilter:focus{
outline:none;
}
.OneNoteIcon {
height: 32px;
width: 32px;
cursor: pointer;
}
.SwitchGroups{
position:absolute;
bottom:2px;
right:10px;
font-size:16px;
background-color: transparent;
border: none;
cursor:pointer;
color:white;
}
.SwitchGroups:hover{
text-decoration: underline;
}
.headerStyle{
background-color: "[theme: themeSecondary, default: #0078d7]"; //themeSecondary
display: table-row ;
border: solid ;
text-align : center ;
width : 100px ;
height : 30px ;
padding-top : 10px ;
color : white ;
}
.tableStyle{
display: table;
min-width: 100%;
margin-right: auto;
margin-left: auto;
}
.panelStyle{
background: lightblue ;
}
.tableCaptionStyle{
background-color: "[theme: themePrimary, default: #0078d7]";
// display: block ;
font-size: 20px ;
font-weight: bold ;
border: solid ;
text-align:center ;
height:79px;
padding-top:3px ;
border-radius:25px ;
color:white;
border-radius: 25px;
margin-left: auto;
margin-top: auto;
min-width: 100%;
text-align: center;
vertical-align: middle;
position: relative;
font-weight:normal;
}
.rowStyle{
display : table-row ;
background: #eee ;
padding: 20px ;
margin: 20px ;
font-weight : bold ;
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.CellStyle{
display: table-cell ;
border: solid ;
border-color : white ;
text-align : center ;
min-width : 75px ;
height : 30px ;
width: fit-content;
padding-top : 10px ;
}
.buttons {
display: table-cell ;
text-align : center ;
width : 160px ;
height : 30px ;
padding-top : 10px ;
}
.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;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import TeamsGraphConsumer from './MyTeams';
import GraphConsumer from './Microsoft365Groups';
export interface IMicrosoftGroupsProps {
context: WebPartContext;
}
export interface IMicrosoftGroupsState {
title: string;
}
export default class MicrosoftGroups extends React.Component<IMicrosoftGroupsProps, IMicrosoftGroupsState> {
constructor(props) {
super(props);
this.state = {
title: ''
};
}
public SwitchGroupList(ID) {
this.setState({title: ID});
}
public render(): React.ReactElement<IMicrosoftGroupsProps> {
return <div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle} >
<button className={styles.MainViewCenter} id={'Microsoft 365 Groups'} onClick={() => this.SwitchGroupList('Microsoft 365 Groups')}>Microsoft 365 Groups</button>
<button className={styles.MainViewCenter} id={'My Teams'} onClick={() => this.SwitchGroupList('My Teams')}>My Teams</button>
</div></div>
{this.state.title === 'Microsoft 365 Groups' ? <GraphConsumer context={this.props.context}/> : <div></div>}
{this.state.title === 'My Teams' ? <TeamsGraphConsumer context={this.props.context} hidden={false}/>: <TeamsGraphConsumer context={this.props.context} hidden={true}/>}
</div>;
}
}

View File

@ -0,0 +1,200 @@
import * as React from 'react';
import { MSGraphClient } from "@microsoft/sp-http";
import { WebPartContext } from '@microsoft/sp-webpart-base';
import styles from './MicrosoftGroups.module.scss';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
export const iconClass = mergeStyles({
fontSize: 32,
height: 32,
width: 32
});
export interface IMyTeamsProps {
context: WebPartContext;
hidden: Boolean;
}
export interface IUserItem {
Topic: string;
DeliveryDate: Date;
}
export interface IMyTeamsState {
MyGroupResults: any;
MyGroupsresultsFiltered: any;
plannerId: string;
Allgroupsdisplay: number;
mygroupsdisplay: number;
mode: string;
title: string;
TenantURL: String;
}
export default class MyTeams extends React.Component<IMyTeamsProps, IMyTeamsState> {
public Tenant = this.props.context.pageContext.web.absoluteUrl.split('.')[0].split('//')[1];
private graphClient: MSGraphClient = null;
constructor(props) {
super(props);
this.state = {
MyGroupResults: [],
MyGroupsresultsFiltered: [],
plannerId: '',
Allgroupsdisplay: 0,
mygroupsdisplay: 0,
mode: 'All',
title: 'Teams in Microsoft Teams In My Organization',
TenantURL: ''
};
}
public SwitchGroupList(Switch) {
if (Switch === 'All') {
this.setState({ MyGroupsresultsFiltered: this.state.MyGroupResults });
}
else {
const SwitchedMY = this.state.MyGroupResults.filter(item => item.Visibility === Switch);
this.setState({ MyGroupsresultsFiltered: SwitchedMY });
}
this.setState({ mode: Switch });
}
public async GetPlanner() {
this.state.MyGroupResults.map(async GroupId => {
try {
this.graphClient = await this.props.context.msGraphClientFactory.getClient();
const results: any = await this.graphClient
.api(`/groups/${GroupId.Id}/planner/plans`)
.version('v1.0')
.get();
if (results.value.length > 0) {
var ID; results.value.map(Items => {
ID = Items.id;
});
var URL = `https://tasks.office.com/${this.Tenant}.com/EN-US/Home/Planner#/plantaskboard?groupId=${GroupId.Id}&planId=${ID}`;
var Planner = { Planner: URL };
var Results = Object.assign(GroupId, Planner);
GroupId = Results;
}
return results.value;
} catch (error) {
}
});
}
public GetTeamsURL() {
this.state.MyGroupResults.map(Group => {
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`/teams/${Group.Id}/?$select=webUrl`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
var Results = Object.assign(Group, { WebUrl: res.webUrl });
Group = Results;
this.setState({ MyGroupsresultsFiltered: this.state.MyGroupResults });
}
});
});
});
}
public async GetMail() {
this.state.MyGroupResults.map(Group => {
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`groups/${Group.Id}/`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
var Results = Object.assign(Group, { Mail: res.mail });
Group = Results;
}
});
});
});
}
public componentDidMount() {
this.setState({ TenantURL: this.props.context.pageContext.web.absoluteUrl });
var array = [];
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient): void => {
client
.api(`me/joinedTeams`)
.version("v1.0")
.get((err, res) => {
if (err) {
return;
}
if (res) {
res.value.map((item) => {
array.push({ Name: item.displayName, Id: item.id, Description: item.description, Visibility: item.visibility });
});
this.setState({ MyGroupResults: array });
this.GetMail();
this.GetPlanner();
this.GetTeamsURL();
}
});
});
}
public render(): React.ReactElement<IMyTeamsProps> {
var Replaceregex = /\s+/g;
return this.props.hidden ? <div></div> : <div className={styles.test}>
<div className={styles.tableCaptionStyle} style={{ borderRight: 'none' }}>My Teams Teams<div>
{this.state.mode === 'Public' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('Public')}>Public</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('Public')}>Public</button>}
{this.state.mode === 'All' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('All')}>All</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('All')}>All</button>}
{this.state.mode === 'Private' ? <button className={styles.SelectedFilter} onClick={() => this.SwitchGroupList('Private')}>Private</button> :
<button className={styles.Filters} onClick={() => this.SwitchGroupList('Private')}>Private</button>}</div>
</div>
<div className={styles.tableStyle}>
<div className={styles.headerStyle}>
<div className={styles.Center}>Team</div>
<div className={styles.Center}>Mail</div>
<div className={styles.Center}>Site</div>
<div className={styles.Center}>Calendar</div>
<div className={styles.Center}>Planner</div>
<div className={styles.Center}>WebUrl</div>
<div className={styles.Center} style={{ borderRight: 'none' }}>Visibility</div>
</div>
{this.state.MyGroupsresultsFiltered.map(Team => {
Team.Visibility = Team.Visibility.substr(0, 1).toUpperCase() + Team.Visibility.substr(1);
var GroupEmailSplit = Team.Mail.split("@");
var Mail = GroupEmailSplit[0];
return (
<div className={styles.rowStyle}>
<div className={styles.ToolTipName}>{Team.Name}<span className={styles.ToolTip}>{Team.Description}</span></div>
<a className={styles.Center} href={`https://outlook.office365.com/mail/group/${this.Tenant}.com/${Mail.toLowerCase()}/email`}>
<Icon className={iconClass} style={{ color: '#087CD7' }} iconName="OutlookLogo"></Icon></a>
<a className={styles.Center} href={`https://${this.Tenant}.sharepoint.com/sites/${Mail}`}>
<Icon className={iconClass} style={{ color: '#068B90' }} iconName="SharePointLogo"></Icon>
</a>
<a className={styles.Center} href={`https://outlook.office365.com/calendar/group/${this.Tenant}.com/${Team.Name.replace(Replaceregex, '')}/view/week`}>
<Icon className={iconClass} style={{ color: '#119AE2' }} iconName="Calendar"></Icon>
</a>
<div className={styles.Center}>
{Team.Planner === undefined ? <div></div> : <a href={Team.Planner}>
<Icon className={iconClass} style={{ color: '#077D3F' }} iconName="ViewListTree"></Icon>
</a>}
</div>
<a className={styles.Center} href={`${Team.WebUrl}`}>
<Icon className={iconClass} style={{ color: '#424AB5' }} iconName="TeamsLogo"></Icon>
</a>
<div className={styles.Center} style={{ borderRight: 'none' }}>{Team.Visibility}</div>
</div>
);
})}</div></div>;
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,36 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/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": [
"es2015","es2017","dom"
]
},
"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
}
}