Open extensions added to store metadata to saved mail

This commit is contained in:
Markus Moeller 2020-04-06 18:14:16 +02:00
parent e4e1a044b1
commit 6fd6866903
16 changed files with 229 additions and 96 deletions

View File

@ -1,4 +1,4 @@
## outlook-2-teams-spfx
## outlook-2-sp-spfx
## Summary
@ -8,7 +8,7 @@ Furthermore it shows you how to retrieve a complete mail as a mimestream via Mic
* Writing normal files smaller 4MB
* Writing big files with an UploadSession when bigger than 4MB
## outlook-2-teams-spfx in action
## outlook-2-sp-spfx in action
![WebPartInAction](https://mmsharepoint.files.wordpress.com/2020/01/addin_overall.png)
A detailed functionality and technical description can be found in the [author's blog series](https://mmsharepoint.wordpress.com/2020/01/11/an-outlook-add-in-with-sharepoint-framework-spfx-introduction/)
@ -25,13 +25,14 @@ A detailed functionality and technical description can be found in the [author's
Solution|Author(s)
--------|---------
outlook-2-teams-spfx| Markus Moeller ([@moeller2_0](http://www.twitter.com/moeller2_0))
outlook-2-sp-spfx| Markus Moeller ([@moeller2_0](http://www.twitter.com/moeller2_0))
## Version history
Version|Date|Comments
-------|----|--------
1.0|February 05, 2020|Initial release
1.0|January 29, 2020|Initial release
1.1|April 06, 2020|Open extensions to store metadata added
## Disclaimer
@ -67,6 +68,5 @@ This Outlook Add-In shows the following capabilities on top of the SharePoint Fr
* Use Microsoft Graph to retrieve joined Groups and Teams
* Use Microsoft Graph to retrieve folders and subfolders for OneDrive or Teams/Group drives
* Use Microsoft Graph to retrieve complete mail mimestream by given ID
* Use Microsoft Graph to save normal or big files (in size bigger 4MB) with different concepts
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-outlook-copy2teams" />
* Use Microsoft Graph to save normal or big files (in size bigger 4MB) with different concepts
* Optionally store metadata of save operation to copied mail with open extension (configure line 15 Outlook2SharePoint.tsx)

View File

@ -26,8 +26,8 @@
},
{
"resource": "Microsoft Graph",
"scope": "Mail.Read"
},
"scope": "Mail.ReadWrite"
},
{
"resource": "Microsoft Graph",
"scope": "Sites.ReadWrite.All"

View File

@ -2,19 +2,27 @@ import { MSGraphClient, MSGraphClientFactory } from '@microsoft/sp-http';
import Utilities from './Utilities';
import { IFolder } from '../model/IFolder';
import { IMail } from '../model/IMail';
import { IMailMetadata } from '../model/IMailMetadata';
export default class GraphController {
private client: MSGraphClient;
private metadataExtensionName = 'mmsharepoint.onmicrosoft.MailStorage';
private saveMetadata: boolean;
constructor (graphFactory: MSGraphClientFactory, callback: () => void) {
graphFactory
.getClient()
.then((client: MSGraphClient) => {
this.client = client;
callback();
});
this.retrieveMimeMail = this.retrieveMimeMail.bind(this);
constructor (saveMetadata: boolean) {
this.saveMetadata = saveMetadata;
}
public init(graphFactory: MSGraphClientFactory): Promise<boolean> {
return graphFactory
.getClient()
.then((client: MSGraphClient) => {
this.client = client;
return true;
})
.catch((error) => {
return false;
});
}
public getClient() {
@ -28,12 +36,13 @@ export default class GraphController {
return this.client
.api('me/drive/root/children')
.version('v1.0')
.filter('folder ne null')
.filter('folder ne null')
.select('id, name, parentReference, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
response.value.forEach((item) => {
folders.push({ id: item.id, name: item.name, driveID: item.parentReference.driveId, parentFolder: null});
folders.push({ id: item.id, name: item.name, driveID: item.parentReference.driveId, parentFolder: null, webUrl: item.webUrl });
});
return folders;
});
@ -43,12 +52,13 @@ export default class GraphController {
return this.client
.api(`drives/${group.driveID}/root/children`)
.version('v1.0')
.filter('folder ne null')
.filter('folder ne null')
.select('id, name, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
response.value.forEach((item) => {
folders.push({ id: item.id, name: item.name, driveID: group.driveID, parentFolder: group});
folders.push({ id: item.id, name: item.name, driveID: group.driveID, parentFolder: group, webUrl: item.webUrl});
});
return folders;
});
@ -58,12 +68,13 @@ export default class GraphController {
return this.client
.api(`drives/${folder.driveID}/items/${folder.id}/children`)
.version('v1.0')
.filter('folder ne null')
.filter('folder ne null')
.select('id, name, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
response.value.forEach((item) => {
folders.push({ id: item.id, name: item.name, driveID: folder.driveID, parentFolder: folder});
folders.push({ id: item.id, name: item.name, driveID: folder.driveID, parentFolder: folder, webUrl: item.webUrl});
});
return folders;
});
@ -75,7 +86,8 @@ export default class GraphController {
public getJoinedGroups(): Promise<IFolder[]> {
return this.client
.api('me/memberOf')
.version('v1.0')
.version('v1.0')
.select('id, displayName, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
@ -83,7 +95,7 @@ export default class GraphController {
// Show unified Groups but NO Teams
if (item['@odata.type'] === '#microsoft.graph.group') {
if(!item.resourceProvisioningOptions || item.resourceProvisioningOptions.indexOf('Team') === -1) {
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null, webUrl: item.webUrl});
}
}
});
@ -97,12 +109,13 @@ export default class GraphController {
public getJoinedTeams(): Promise<IFolder[]> {
return this.client
.api('me/joinedTeams')
.version('v1.0')
.version('v1.0')
.select('id, displayName, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
response.value.forEach((item) => {
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null, webUrl: item.webUrl});
});
return folders;
});
@ -114,43 +127,47 @@ export default class GraphController {
public getGroupDrives(group: IFolder): Promise<IFolder[]> {
return this.client
.api(`groups/${group.id}/drives`)
.version('v1.0')
.version('v1.0')
.select('id, name, webUrl')
.get()
.then((response): any => {
let folders: Array<IFolder> = new Array<IFolder>();
response.value.forEach((item) => {
folders.push({ id: item.id, name: item.name, driveID: item.id, parentFolder: group});
folders.push({ id: item.id, name: item.name, driveID: item.id, parentFolder: group, webUrl: item.webUrl});
});
return folders;
});
}
public retrieveMimeMail(driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise<string> {
public retrieveMimeMail = (driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise<string> => {
return this.client
.api(`me/messages/${mail.id}/$value`)
.version('v1.0')
.responseType('TEXT')
.get((err: any, response, rawResponse): any => {
.get()
.then((response): any => {
if (response.length < (4 * 1024 * 1024)) // If Mail size bigger 4MB use resumable upload
{
this.saveNormalMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
return this.saveNormalMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
}
else {
this.saveBigMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
return this.saveBigMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
}
});
}
private saveNormalMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void) {
private saveNormalMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void): Promise<string> {
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/content` : `drives/${driveID}/root:/${fileName}.eml:/content`;
this.client
return this.client
.api(apiUrl)
.put(mimeStream)
.then((response) => {
clientCallback('Success');
return 'Success';
})
.catch((error) => {
clientCallback('Error');
return null;
});
}
@ -161,32 +178,30 @@ export default class GraphController {
}
};
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/createUploadSession` : `drives/${driveID}/root:/${fileName}.eml:/createUploadSession`;
this.client
return this.client
.api(apiUrl)
.post(JSON.stringify(sessionOptions))
.then(async (response):Promise<any> => {
console.log(response.uploadUrl);
console.log(response.expirationDateTime);
try {
const resp = await this.uploadMailSlices(mimeStream, response.uploadUrl);
console.log(resp);
clientCallback('Success');
return 'Success';
}
catch(err) {
console.log(err);
clientCallback('Error');
return null;
}
});
}
private async uploadMailSlices(mimeStream: string, uploadUrl: string) {
let minSize=0;
let maxSize=327680; // 320kb slices
let maxSize=5*327680; // 5*320kb slices --> MUST be a multiple of 320 KiB (327,680 bytes)
while(mimeStream.length > minSize) {
const fileSlice = mimeStream.slice(minSize, maxSize);
const resp = await this.uploadMailSlice(uploadUrl, minSize, maxSize, mimeStream.length, fileSlice);
minSize = maxSize;
maxSize += 327680;
maxSize += 5*327680;
if (maxSize > mimeStream.length) {
maxSize = mimeStream.length;
}
@ -210,12 +225,53 @@ export default class GraphController {
.put(fileSlice);
}
private saveMailCallback(error: any, response: any, rawResponse?: any): void {
if (error !== null) {
console.log(error);
}
else {
console.log(response);
}
public saveMailMetadata(mailId: string, displayName: string, url: string, savedDate: Date) {
if (this.saveMetadata) {
const apiUrl = `/me/messages/${mailId}/extensions`;
const metadataBody = {
"@odata.type" : "microsoft.graph.openTypeExtension",
"extensionName" : this.metadataExtensionName,
"saveDisplayName" : displayName,
"saveUrl" : url,
"savedDate" : savedDate.toISOString()
};
this.client
.api(apiUrl)
.version('v1.0')
.post(JSON.stringify(metadataBody))
.then((response) => {
console.log(response);
});
}
}
public retrieveMailMetadata(mailId: string): Promise<any> {
const apiUrl = `/me/messages/${mailId}`;
const expand = `Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.${this.metadataExtensionName}')`;
return this.client
.api(apiUrl)
.version('v1.0')
.expand(expand)
.select('id,subject,extensions')
.get()
.then((response) => {
if (typeof response.extensions !== 'undefined' && response.extensions !== null) {
const metadata: IMailMetadata = {
extensionName: response.extensions[0].extensionName,
saveDisplayName: response.extensions[0].saveDisplayName,
saveUrl: response.extensions[0].saveUrl,
savedDate: new Date(response.extensions[0].savedDate)
};
return metadata;
}
else {
return null;
}
},
(error) => {
console.log(error);
return null;
});
}
}

View File

@ -4,4 +4,5 @@ export interface IFolder {
id: string;
driveID: string;
parentFolder: IFolder;
webUrl: string;
}

View File

@ -0,0 +1,6 @@
export interface IMailMetadata {
extensionName: string;
saveDisplayName: string;
saveUrl: string;
savedDate: Date;
}

View File

@ -3,7 +3,7 @@ import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
PropertyPaneToggle
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
@ -13,22 +13,23 @@ import Outlook2SharePoint from './components/Outlook2SharePoint';
import { IOutlook2SharePointProps } from './components/IOutlook2SharePointProps';
export interface IOutlook2SharePointWebPartProps {
description: string;
}
export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IOutlook2SharePointWebPartProps> {
public render(): void {
let mail: IMail = null;
if (this.context.sdks.office) {
const item = this.context.sdks.office.context.mailbox.item;
const item = this.context.sdks.office.context.mailbox.item;
const itemId = this.context.sdks.office.context.mailbox.convertToRestId(item.itemId, 'v2.0');
if (item !== null) {
mail = { id: item.itemId,subject: item.subject };
mail = { id: itemId, subject: item.subject };
}
}
const element: React.ReactElement<IOutlook2SharePointProps> = React.createElement(
Outlook2SharePoint,
{
{
msGraphClientFactory: this.context.msGraphClientFactory,
mail: mail
}
@ -56,8 +57,8 @@ export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IO
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
PropertyPaneToggle('saveMetadata', {
label: strings.SaveMetadataFieldLabel
})
]
}

View File

@ -5,6 +5,7 @@ import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import Breadcrumb from './controls/Breadcrumb';
import Folder from './Folder';
import styles from './Groups.module.scss';
import * as strings from 'Outlook2SharePointWebPartStrings';
import { IGroupsProps } from './IGroupsProps';
import { IGroupsState } from './IGroupsState';
import { IFolder } from '../../../model/IFolder';
@ -16,6 +17,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
folders: [],
grandParentFolder: null,
parentFolder: null,
selectedGroupName: '',
showSpinner: false
};
}
@ -50,7 +52,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
<div>
<PrimaryButton
className={styles.saveBtn}
text="Save here"
text={strings.SaveLabel}
onClick={this.saveMailTo}
disabled={this.state.parentFolder === null}
allowDisabledFocus={true}
@ -58,7 +60,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
{ this.state.showSpinner && (
<div className={styles.spinnerContainer}>
<Overlay >
<Spinner size={ SpinnerSize.large } label='Processing request' />
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
</Overlay>
</div>
) }
@ -77,20 +79,15 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
});
}
private getGroupDrives = (group: IFolder) => {
let nextParent: IFolder = null;
this.state.folders.forEach((fldr) => {
if (fldr.id === group.id) {
nextParent = fldr;
}
});
private getGroupDrives = (group: IFolder) => {
this.props.graphController.getGroupDrives(group).then((folders) => {
if (folders.length > 0) {
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
return {
folders: folders,
grandParentFolder: null,
parentFolder: group
parentFolder: group,
selectedGroupName: group.name
};
});
}
@ -122,7 +119,6 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
}
}
private showRoot = () => {
this.getGroups();
}
@ -142,7 +138,11 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
showSpinner: true
};
});
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
.then((response: string) => {
const saveLocationDisplayName = `${this.state.selectedGroupName} ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
});
}
private saveMailCallback = (message: string) => {
@ -152,10 +152,10 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
};
});
if (message.indexOf('Success') > -1) {
this.props.successCallback(message);
this.props.successCallback(strings.SuccessMessage);
}
else {
this.props.errorCallback(message);
this.props.errorCallback(strings.ErrorMessage);
}
}
}

View File

@ -4,5 +4,6 @@ export interface IGroupsState {
folders: IFolder[];
grandParentFolder: IFolder;
parentFolder: IFolder;
selectedGroupName: string;
showSpinner: boolean;
}

View File

@ -1,7 +1,9 @@
import GraphController from '../../../controller/GraphController';
import { IMailMetadata } from '../../../model/IMailMetadata';
export interface IOutlook2SharePointState {
graphController: GraphController;
mailMetadata: IMailMetadata;
showSuccess: boolean;
showError: boolean;
showOneDrive: boolean;

View File

@ -4,5 +4,6 @@ export interface ITeamsState {
folders: IFolder[];
grandParentFolder: IFolder;
parentFolder: IFolder;
selectedTeamName: string;
showSpinner: boolean;
}

View File

@ -4,6 +4,7 @@ import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import Folder from './Folder';
import styles from './Groups.module.scss';
import * as strings from 'Outlook2SharePointWebPartStrings';
import Breadcrumb from './controls/Breadcrumb';
import { IOneDriveProps } from './IOneDriveProps';
import { IOneDriveState } from './IOneDriveState';
@ -49,14 +50,14 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
<div>
<PrimaryButton
className={styles.saveBtn}
text="Save here"
text={strings.SaveLabel}
onClick={this.saveMailTo}
allowDisabledFocus={true}
/>
{ this.state.showSpinner && (
<div className={styles.spinnerContainer}>
<Overlay >
<Spinner size={ SpinnerSize.large } label='Processing request' />
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
</Overlay>
</div>
) }
@ -103,7 +104,11 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
showSpinner: true
};
});
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
.then((response: string) => {
const saveLocationDisplayName = `OneDrive ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
});
}
private saveMailCallback = (message: string) => {
@ -113,10 +118,10 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
};
});
if (message.indexOf('Success') > -1) {
this.props.successCallback(message);
this.props.successCallback(strings.SuccessMessage);
}
else {
this.props.errorCallback(message);
this.props.errorCallback(strings.ErrorMessage);
}
}
}

View File

@ -14,4 +14,19 @@
@include ms-fontSize-l;
margin-left: 3px;
}
.metadata {
margin-left: 6px;
margin-bottom: 8px;
}
.subMetadata {
margin-left: 6px;
a {
cursor: pointer;
text-decoration: none;
color: inherit;
}
a:hover {
@include ms-fontColor-themePrimary;
}
}
}

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import styles from './Outlook2SharePoint.module.scss';
import * as strings from 'Outlook2SharePointWebPartStrings';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import GraphController from '../../../controller/GraphController';
@ -11,11 +12,13 @@ import { IOutlook2SharePointState } from './IOutlook2SharePointState';
export default class Outlook2SharePoint extends React.Component<IOutlook2SharePointProps, IOutlook2SharePointState> {
private graphController: GraphController;
private saveMetadata = true; // For simplicity reasons and as I am not convinced with the current "Property handling" of Office Add-In we configure 'hard-coded'
constructor(props) {
super(props);
this.state = {
graphController: null,
mailMetadata: null,
showError: false,
showSuccess: false,
showOneDrive: false,
@ -24,13 +27,25 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
successMessage: '',
errorMessage: ''
};
this.graphController = new GraphController(this.props.msGraphClientFactory, this.graphClientReadyCallback);
this.graphController = new GraphController(this.saveMetadata);
this.graphController.init(this.props.msGraphClientFactory)
.then((controllerReady) => {
if (controllerReady) {
this.graphClientReady();
}
});
}
public render(): React.ReactElement<IOutlook2SharePointProps> {
return (
<div className={ styles.outlook2SharePoint }>
{this.state.mailMetadata !== null &&
<div className={styles.metadata}>
<div><Icon iconName="InfoSolid" /> {strings.SaveInfo}</div>
<div className={styles.subMetadata}>{strings.To} <a href={this.state.mailMetadata.saveUrl}>{this.state.mailMetadata.saveDisplayName}</a></div>
<div className={styles.subMetadata}>{strings.On} <span>{this.state.mailMetadata.savedDate.toLocaleDateString()}</span></div>
</div>}
{this.state.showSuccess && <div>
<MessageBar
messageBarType={MessageBarType.success}
@ -97,12 +112,15 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
/**
* This function first retrieves all OneDrive root folders from user
*/
private graphClientReadyCallback = () => {
private graphClientReady = () => {
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
return {
graphController: this.graphController
};
});
if (this.saveMetadata) {
this.getMetadata();
}
}
private showError = (message: string) => {
@ -172,4 +190,17 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
};
});
}
private getMetadata() {
this.state.graphController.retrieveMailMetadata(this.props.mail.id)
.then((response) => {
if (response !== null) {
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
return {
mailMetadata: response
};
});
}
});
}
}

View File

@ -5,6 +5,7 @@ import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import Breadcrumb from './controls/Breadcrumb';
import Folder from './Folder';
import styles from './Groups.module.scss';
import * as strings from 'Outlook2SharePointWebPartStrings';
import { ITeamsProps } from './ITeamsProps';
import { ITeamsState } from './ITeamsState';
import { IFolder } from '../../../model/IFolder';
@ -16,6 +17,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
folders: [],
grandParentFolder: null,
parentFolder: null,
selectedTeamName: '',
showSpinner: false
};
}
@ -50,7 +52,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
<div>
<PrimaryButton
className={styles.saveBtn}
text="Save here"
text={strings.SaveLabel}
onClick={this.saveMailTo}
disabled={this.state.parentFolder === null}
allowDisabledFocus={true}
@ -58,7 +60,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
{ this.state.showSpinner && (
<div className={styles.spinnerContainer}>
<Overlay >
<Spinner size={ SpinnerSize.large } label='Processing request' />
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
</Overlay>
</div>
) }
@ -77,20 +79,15 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
});
}
private getGroupDrives = (group: IFolder) => {
let nextParent: IFolder = null;
this.state.folders.forEach((fldr) => {
if (fldr.id === group.id) {
nextParent = fldr;
}
});
private getGroupDrives = (group: IFolder) => {
this.props.graphController.getGroupDrives(group).then((folders) => {
if (folders.length > 0) {
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
return {
folders: folders,
grandParentFolder: null,
parentFolder: group
parentFolder: group,
selectedTeamName: group.name
};
});
}
@ -122,7 +119,6 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
}
}
private showRoot = () => {
this.getTeams();
}
@ -142,7 +138,11 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
showSpinner: true
};
});
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
.then((response: string) => {
const saveLocationDisplayName = `${this.state.selectedTeamName} ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
});
}
private saveMailCallback = (message: string) => {
@ -152,10 +152,10 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
};
});
if (message.indexOf('Success') > -1) {
this.props.successCallback(message);
this.props.successCallback(strings.SuccessMessage);
}
else {
this.props.errorCallback(message);
this.props.errorCallback(strings.ErrorMessage);
}
}
}

View File

@ -1,7 +1,14 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
"PropertyPaneDescription": "Copy to OneDrive/Teams",
"BasicGroupName": "Configuration",
"SaveMetadataFieldLabel": "Save Metadata on copied mail",
"SaveInfo": "You already saved this mail",
"To": "to",
"On": "on",
"SaveLabel": "Save here",
"SpinnerLabel": "Processing request",
"SuccessMessage": "Success",
"ErrorMessage": "Error occured"
}
});

View File

@ -1,7 +1,14 @@
declare interface IOutlook2SharePointWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
SaveMetadataFieldLabel: string;
SaveInfo: string;
To: string;
On: string;
SaveLabel: string;
SpinnerLabel: string;
SuccessMessage: string;
ErrorMessage: string;
}
declare module 'Outlook2SharePointWebPartStrings' {