From 6fd68669034e0895da37fc3bbe2d2349b3acd858 Mon Sep 17 00:00:00 2001 From: Markus Moeller Date: Mon, 6 Apr 2020 18:14:16 +0200 Subject: [PATCH] Open extensions added to store metadata to saved mail --- samples/react-outlook-copy2teams/README.md | 14 +- .../config/package-solution.json | 4 +- .../src/controller/GraphController.ts | 138 ++++++++++++------ .../src/model/IFolder.ts | 1 + .../src/model/IMailMetadata.ts | 6 + .../Outlook2SharePointWebPart.ts | 15 +- .../outlook2SharePoint/components/Groups.tsx | 28 ++-- .../components/IGroupsState.ts | 1 + .../components/IOutlook2SharePointState.ts | 2 + .../components/ITeamsState.ts | 1 + .../components/OneDrive.tsx | 15 +- .../components/Outlook2SharePoint.module.scss | 15 ++ .../components/Outlook2SharePoint.tsx | 35 ++++- .../outlook2SharePoint/components/Teams.tsx | 28 ++-- .../webparts/outlook2SharePoint/loc/en-us.js | 13 +- .../outlook2SharePoint/loc/mystrings.d.ts | 9 +- 16 files changed, 229 insertions(+), 96 deletions(-) create mode 100644 samples/react-outlook-copy2teams/src/model/IMailMetadata.ts diff --git a/samples/react-outlook-copy2teams/README.md b/samples/react-outlook-copy2teams/README.md index 82a81ca52..0fd2c4089 100644 --- a/samples/react-outlook-copy2teams/README.md +++ b/samples/react-outlook-copy2teams/README.md @@ -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 - - +* 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) diff --git a/samples/react-outlook-copy2teams/config/package-solution.json b/samples/react-outlook-copy2teams/config/package-solution.json index da76919db..ee6f3560e 100644 --- a/samples/react-outlook-copy2teams/config/package-solution.json +++ b/samples/react-outlook-copy2teams/config/package-solution.json @@ -26,8 +26,8 @@ }, { "resource": "Microsoft Graph", - "scope": "Mail.Read" - }, + "scope": "Mail.ReadWrite" + }, { "resource": "Microsoft Graph", "scope": "Sites.ReadWrite.All" diff --git a/samples/react-outlook-copy2teams/src/controller/GraphController.ts b/samples/react-outlook-copy2teams/src/controller/GraphController.ts index 1702354c4..78fa89005 100644 --- a/samples/react-outlook-copy2teams/src/controller/GraphController.ts +++ b/samples/react-outlook-copy2teams/src/controller/GraphController.ts @@ -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 { + 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 = new Array(); 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 = new Array(); 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 = new Array(); 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 { return this.client .api('me/memberOf') - .version('v1.0') + .version('v1.0') + .select('id, displayName, webUrl') .get() .then((response): any => { let folders: Array = new Array(); @@ -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 { return this.client .api('me/joinedTeams') - .version('v1.0') + .version('v1.0') + .select('id, displayName, webUrl') .get() .then((response): any => { let folders: Array = new Array(); 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 { 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 = new Array(); 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 { + public retrieveMimeMail = (driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise => { 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 { 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 => { - 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 { + 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; + }); + } } diff --git a/samples/react-outlook-copy2teams/src/model/IFolder.ts b/samples/react-outlook-copy2teams/src/model/IFolder.ts index e0878343a..49bef304f 100644 --- a/samples/react-outlook-copy2teams/src/model/IFolder.ts +++ b/samples/react-outlook-copy2teams/src/model/IFolder.ts @@ -4,4 +4,5 @@ export interface IFolder { id: string; driveID: string; parentFolder: IFolder; + webUrl: string; } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/model/IMailMetadata.ts b/samples/react-outlook-copy2teams/src/model/IMailMetadata.ts new file mode 100644 index 000000000..4e6d3f0e3 --- /dev/null +++ b/samples/react-outlook-copy2teams/src/model/IMailMetadata.ts @@ -0,0 +1,6 @@ +export interface IMailMetadata { + extensionName: string; + saveDisplayName: string; + saveUrl: string; + savedDate: Date; +} \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/Outlook2SharePointWebPart.ts b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/Outlook2SharePointWebPart.ts index 20560cfb6..2c208a31f 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/Outlook2SharePointWebPart.ts +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/Outlook2SharePointWebPart.ts @@ -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 { 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 = React.createElement( Outlook2SharePoint, - { + { msGraphClientFactory: this.context.msGraphClientFactory, mail: mail } @@ -56,8 +57,8 @@ export default class Outlook2SharePointWebPart extends BaseClientSideWebPart folders: [], grandParentFolder: null, parentFolder: null, + selectedGroupName: '', showSpinner: false }; } @@ -50,7 +52,7 @@ export default class Groups extends React.Component
{ this.state.showSpinner && (
- +
) } @@ -77,20 +79,15 @@ export default class Groups extends React.Component }); } - 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 } } - private showRoot = () => { this.getGroups(); } @@ -142,7 +138,11 @@ export default class Groups extends React.Component 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 }; }); if (message.indexOf('Success') > -1) { - this.props.successCallback(message); + this.props.successCallback(strings.SuccessMessage); } else { - this.props.errorCallback(message); + this.props.errorCallback(strings.ErrorMessage); } } } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IGroupsState.ts b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IGroupsState.ts index 25927f411..10b81715e 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IGroupsState.ts +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IGroupsState.ts @@ -4,5 +4,6 @@ export interface IGroupsState { folders: IFolder[]; grandParentFolder: IFolder; parentFolder: IFolder; + selectedGroupName: string; showSpinner: boolean; } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IOutlook2SharePointState.ts b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IOutlook2SharePointState.ts index 353769d98..f2ba5c7cf 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IOutlook2SharePointState.ts +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/IOutlook2SharePointState.ts @@ -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; diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/ITeamsState.ts b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/ITeamsState.ts index 1fd5c522f..9f0d5ca86 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/ITeamsState.ts +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/ITeamsState.ts @@ -4,5 +4,6 @@ export interface ITeamsState { folders: IFolder[]; grandParentFolder: IFolder; parentFolder: IFolder; + selectedTeamName: string; showSpinner: boolean; } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/OneDrive.tsx b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/OneDrive.tsx index cae07fa4a..c8423602f 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/OneDrive.tsx +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/OneDrive.tsx @@ -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 { this.state.showSpinner && (
- +
) } @@ -103,7 +104,11 @@ export default class OneDrive extends React.Component { + 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 -1) { - this.props.successCallback(message); + this.props.successCallback(strings.SuccessMessage); } else { - this.props.errorCallback(message); + this.props.errorCallback(strings.ErrorMessage); } } } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.module.scss b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.module.scss index f97e0bf43..cfb9589e1 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.module.scss +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.module.scss @@ -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; + } + } } diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.tsx b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.tsx index a07f1f956..f3646f09e 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.tsx +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Outlook2SharePoint.tsx @@ -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 { 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 { + if (controllerReady) { + this.graphClientReady(); + } + }); } public render(): React.ReactElement { return (
+ {this.state.mailMetadata !== null && +
+
{strings.SaveInfo}
+ +
{strings.On} {this.state.mailMetadata.savedDate.toLocaleDateString()}
+
} {this.state.showSuccess &&
{ + 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 { + if (response !== null) { + this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => { + return { + mailMetadata: response + }; + }); + } + }); + } } diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Teams.tsx b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Teams.tsx index 80937b65a..1e40c7dc9 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Teams.tsx +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/components/Teams.tsx @@ -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 { folders: [], grandParentFolder: null, parentFolder: null, + selectedTeamName: '', showSpinner: false }; } @@ -50,7 +52,7 @@ export default class Teams extends React.Component {
{ { this.state.showSpinner && (
- +
) } @@ -77,20 +79,15 @@ export default class Teams extends React.Component { }); } - 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 { } } - private showRoot = () => { this.getTeams(); } @@ -142,7 +138,11 @@ export default class Teams extends React.Component { 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 { }; }); if (message.indexOf('Success') > -1) { - this.props.successCallback(message); + this.props.successCallback(strings.SuccessMessage); } else { - this.props.errorCallback(message); + this.props.errorCallback(strings.ErrorMessage); } } } \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js index 89f98bc1e..2b6d4536f 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/en-us.js @@ -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" } }); \ No newline at end of file diff --git a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts index 42c888ca3..5a64a0a41 100644 --- a/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts +++ b/samples/react-outlook-copy2teams/src/webparts/outlook2SharePoint/loc/mystrings.d.ts @@ -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' {