Last active
July 19, 2024 16:10
SharePoint Adobe integration library extension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Goal: replicate the deprecated SharePoint-Adobe Integration | |
# setup 2 text columns in SharePoint where this action needs to be performed: "Adobe Sign Status" and "AgreementId" | |
# start a regular typescript project for SharePoint with the command set for library extensions. | |
https://learn.microsoft.com/en-us/sharepoint/dev/spfx/extensions/get-started/building-simple-cmdset-with-dialog-api | |
# have a (supposedly deprecated) Adobe Integration key handy. | |
https://na3.documents.adobe.com/account/accountSettingsPage#pageId::ACCESS_TOKENS | |
# you can use the client-to-server authentication method for app id and thats fine. Be wary of user clicks. | |
# best thing to do is (server-to-server authentication method) to start a project in developer portal in Adobe, enable Adobe Sign API and make curl request auth for a key. | |
# copy and paste the following code (then update the AdobeIntegrationKey), debug and gulp serve. Remove console.log messages before gulp package ship | |
import { Log } from '@microsoft/sp-core-library'; | |
import { | |
BaseListViewCommandSet, | |
type Command, | |
type IListViewCommandSetExecuteEventParameters, | |
type ListViewStateChangedEventArgs | |
} from '@microsoft/sp-listview-extensibility'; | |
import axios from 'axios'; | |
import { spfi, SPFx as spSPFx } from "@pnp/sp"; | |
import "@pnp/sp/webs"; | |
import { graphfi, SPFx as graphSPFx } from "@pnp/graph"; | |
import "@pnp/sp/webs"; | |
import "@pnp/graph/users"; | |
export interface IAdobeSignCommandSetProperties { | |
sampleTextOne: string; | |
sampleTextTwo: string; | |
} | |
const LOG_SOURCE: string = 'AdobeSignCommandSet'; | |
export default class AdobeSignCommandSet extends BaseListViewCommandSet<IAdobeSignCommandSetProperties> { | |
private sp: ReturnType<typeof spfi>; | |
private graph: ReturnType<typeof graphfi>; | |
public onInit(): Promise<void> { | |
Log.info(LOG_SOURCE, 'Initialized AdobeSignCommandSet'); | |
this.sp = spfi().using(spSPFx(this.context)); | |
this.graph = graphfi().using(graphSPFx(this.context)); | |
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1'); | |
if (compareOneCommand) { | |
compareOneCommand.visible = false; | |
} | |
const compareTwoCommand: Command = this.tryGetCommand('COMMAND_2'); | |
if (compareTwoCommand) { | |
compareTwoCommand.visible = true; | |
} | |
const compareThreeCommand: Command = this.tryGetCommand('COMMAND_3'); | |
if (compareThreeCommand) { | |
compareThreeCommand.visible = true; | |
} | |
const compareFourCommand: Command = this.tryGetCommand('COMMAND_4'); | |
if (compareFourCommand) { | |
compareFourCommand.visible = true; | |
} | |
const compareFiveCommand: Command = this.tryGetCommand('COMMAND_5'); | |
if (compareFiveCommand) { | |
compareFiveCommand.visible = true; | |
} | |
const compareSixCommand: Command = this.tryGetCommand('COMMAND_6'); | |
if (compareSixCommand) { | |
compareSixCommand.visible = true; | |
} | |
this.context.listView.listViewStateChangedEvent.add(this, this._onListViewStateChanged); | |
document.addEventListener("DOMContentLoaded", async () => { | |
const urlParams = new URLSearchParams(window.location.search); | |
const documentUrl = decodeURIComponent(urlParams.get('state') || ''); | |
const documentName = decodeURIComponent(urlParams.get('documentName') || ''); | |
if (documentUrl) { | |
try { | |
const accessToken = await this._getAccessTokenMicrosoft(); | |
const response = await this._uploadTransientDocument(documentUrl, accessToken, documentName); | |
if (response.data && response.data.transientDocumentId) { | |
// window.location.href = `https://na3.documents.adobe.com/public/compose?transientDocumentId=${response.data.transientDocumentId}`; | |
} else { | |
Log.error(LOG_SOURCE, new Error('Failed to upload transient document to Adobe Sign')); | |
} | |
} catch (error) { | |
Log.error(LOG_SOURCE, new Error('Error during Adobe Sign integration: ' + error.message)); | |
} | |
} | |
}); | |
return Promise.resolve(); | |
} | |
public onExecute(event: IListViewCommandSetExecuteEventParameters): void { | |
console.log('Executing command: ', event.itemId); | |
switch (event.itemId) { | |
case 'COMMAND_1': | |
window.location.href = `https://www.adobe.com/`; | |
break; | |
case 'COMMAND_2': | |
window.location.href = `https://na3.documents.adobe.com/public/agreements`; | |
break; | |
case 'COMMAND_3': | |
console.log('COMMAND_3 is clicked'); | |
if (this.context.listView.selectedRows && this.context.listView.selectedRows.length > 0) { | |
const selectedRow = this.context.listView.selectedRows[0]; | |
if (selectedRow) { | |
console.log('Selected Row:', selectedRow); | |
const documentUrl = selectedRow.getValueByName('FileRef'); | |
console.log('Document URL:', documentUrl); | |
const spItemUrl = selectedRow.getValueByName('.spItemUrl'); | |
console.log('SP Item URL:', spItemUrl); | |
const documentNameWithExtension = selectedRow.getValueByName('FileLeafRef'); | |
const documentName = this._getFileNameWithoutExtension(documentNameWithExtension); | |
const author = selectedRow.getValueByName('Author'); | |
const agreementUserEmail = author[0].email; | |
console.log('agreementUserEmail:', agreementUserEmail); | |
const approverField = selectedRow.getValueByName('Approver'); | |
const approverEmail = approverField && approverField.length > 0 ? approverField[0].email : agreementUserEmail; | |
console.log('Approver Email:', approverEmail); | |
const agreementId = selectedRow.getValueByName('AgreementID'); | |
// if no agreementId - window.open(`https://na3.documents.adobe.com/public/compose?agrId=${agreementId}&client_id=UB7E5BXCXY`, '_blank'); | |
if (selectedRow.getValueByName('AgreementID') && documentUrl) { | |
window.open(`https://na3.documents.adobe.com/public/compose?agrId=${agreementId}&client_id=UB7E5BXCXY`, '_blank'); | |
} | |
// if there isn't an agreementId, AND it's a document set, then we need to create a set of transient documents | |
// first we need to determine if it's a document set by checking the content type value | |
// if it is a document set, we need to get all the documents in the set | |
const contentTypeId = selectedRow.getValueByName('ContentTypeId'); | |
console.log('Content Type ID:', contentTypeId); | |
// now we need to create a new agreement by uploading the document to Adobe Sign as a transient document - content type is not Document | |
if (!selectedRow.getValueByName('AgreementID') && documentUrl && selectedRow.getValueByName('ContentTypeId').startsWith('0x0120')) { | |
const accessToken = 'AdobeIntegrationKey' | |
// The documentUrl is the URL of the document set, so we need to get all the documents in the set | |
this._uploadTransientDocumentDocSet(documentUrl, accessToken, spItemUrl).then((response) => { | |
console.log('Response:', response); | |
console.log('Transient Document IDs:', response.data.transientDocumentIds); | |
if (response.data) { | |
const transientDocumentIds = response.data.transientDocumentIds; | |
this._createDocSetAgreement(transientDocumentIds, agreementUserEmail, documentNameWithExtension, approverEmail).then((agreementId) => { | |
console.log('Agreement ID:', agreementId); | |
this._updateSharePointFieldAgreementId(selectedRow, agreementId).then(() => { | |
console.log('Agreement ID:', agreementId); | |
Log.info(LOG_SOURCE, `Agreement ID updated to: ${agreementId}`); | |
window.location.reload(); | |
}).catch((error: { message: string; }) => { | |
console.log('Error:', error); | |
}); | |
window.open(`https://na3.documents.adobe.com/public/compose?agrId=${agreementId}&client_id=UB7E5BXCXY`, '_blank'); | |
}).catch((error) => { | |
Log.error(LOG_SOURCE, new Error('Failed to create agreement: ' + error.message)); | |
}); | |
} | |
}).catch((error) => { | |
Log.error(LOG_SOURCE, new Error('Failed to upload transient document: ' + error.message)); | |
}); | |
} | |
// this is the case where we have an agreementId, but it's not a document set | |
if (!selectedRow.getValueByName('AgreementID') && documentUrl && selectedRow.getValueByName('ContentTypeId').startsWith('0x0101')) { | |
const accessToken = 'AdobeIntegrationKey' | |
this._uploadTransientDocument(documentUrl, accessToken, documentName).then((response) => { | |
console.log('Response:', response); | |
console.log('Transient Document ID:', response.data.transientDocumentId); | |
if (response.data) { | |
console.log('Transient Document ID:', response.data.transientDocumentId); | |
const transientDocumentId = response.data.transientDocumentId; | |
this._createAgreement(transientDocumentId, agreementUserEmail, documentNameWithExtension, approverEmail).then((agreementId) => { | |
console.log('Agreement ID:', agreementId); | |
this._updateSharePointFieldAgreementId(selectedRow, agreementId).then(() => { | |
console.log('Agreement ID:', agreementId); | |
Log.info(LOG_SOURCE, `Agreement ID updated to: ${agreementId}`); | |
window.location.reload(); | |
}).catch((error: { message: string; }) => { | |
console.log('Error:', error); | |
}); | |
// new tab to open the agreement in Adobe Sign | |
// Open the agreement in Adobe Sign in a new tab | |
window.open(`https://na3.documents.adobe.com/public/compose?agrId=${agreementId}&client_id=UB7E5BXCXY`, '_blank'); | |
}).catch((error) => { | |
Log.error(LOG_SOURCE, new Error('Failed to create agreement: ' + error.message)); | |
}); | |
} | |
}).catch((error) => { | |
Log.error(LOG_SOURCE, new Error('Failed to upload transient document: ' + error.message)); | |
}); | |
} | |
} | |
} | |
break; | |
case 'COMMAND_4': | |
console.log('COMMAND_4 is clicked'); | |
if (this.context.listView.selectedRows && this.context.listView.selectedRows.length > 0) { | |
const selectedRow = this.context.listView.selectedRows[0]; | |
const agreementId = selectedRow.getValueByName('AgreementID'); | |
const author = selectedRow.getValueByName('Author'); | |
console.log('Author:', author); | |
const agreementUserEmail = author[0].email; | |
console.log('Agreement User Email:', agreementUserEmail); | |
if (agreementId) { | |
this._fetchAgreementStatus(agreementId, agreementUserEmail).then(status => { | |
this._updateSharePointField(selectedRow, status).then(() => { | |
Log.info(LOG_SOURCE, `Agreement status updated to: ${status}`); | |
window.location.reload(); | |
}).catch(error => { | |
Log.error(LOG_SOURCE, new Error('Failed to update agreement status in SharePoint: ' + error.message)); | |
}); | |
}).catch(error => { | |
Log.error(LOG_SOURCE, new Error('Failed to fetch agreement status from Adobe Sign: ' + error.message)); | |
}); | |
} else { | |
Log.error(LOG_SOURCE, new Error('No AgreementID found in the selected row')); | |
} | |
} | |
break; | |
case 'COMMAND_5': | |
console.log('COMMAND_5 is clicked'); | |
if (this.context.listView.selectedRows && this.context.pageContext.list && this.context.listView.selectedRows.length > 0) { | |
const selectedRow = this.context.listView.selectedRows[0]; | |
// determine the id of the sharepoint row | |
const itemId = selectedRow.getValueByName('ID'); | |
const listId = this.context.pageContext.list.id.toString(); | |
const siteId = this.context.pageContext.site.id.toString(); | |
const agreementId = selectedRow.getValueByName('AgreementID'); | |
const author = selectedRow.getValueByName('Author'); | |
const agreementUserEmail = author[0].email; | |
const documentUrl = selectedRow.getValueByName('FileRef'); | |
console.log('Document URL:', documentUrl); | |
const documentNameWithExtension = selectedRow.getValueByName('FileLeafRef'); | |
const documentName = this._getFileNameWithoutExtension(documentNameWithExtension); | |
console.log('Document Name:', documentName); | |
// let's figure out what metadata we need to update the file | |
const itemMetadata = this._getSharePointItemMetadata(listId, itemId, siteId); | |
console.log('Item Metadata:', itemMetadata); | |
this._fetchSignedAgreement(agreementId, documentName, documentUrl, agreementUserEmail, itemId, itemMetadata).then(() => { | |
Log.info(LOG_SOURCE, `Signed agreement fetched successfully`); | |
window.location.reload(); | |
}).catch(error => { | |
Log.error(LOG_SOURCE, new Error('Failed to fetch signed agreement: ' + error.message)); | |
}); | |
} | |
break; | |
default: | |
throw new Error('Unknown command'); | |
} | |
} | |
private async _getAccessTokenMicrosoft(): Promise<string> { | |
const tokenProvider = await this.context.aadTokenProviderFactory.getTokenProvider(); | |
return tokenProvider.getToken("https://graph.microsoft.com/"); | |
} | |
private async _uploadTransientDocument(documentUrl: string, accessToken: string, documentName: string): Promise<any> { | |
const url = 'https://api.na3.adobesign.com/api/rest/v6/transientDocuments'; | |
console.log('Document URL:', documentUrl); | |
try { | |
const documentResponse = await axios.get(documentUrl, { | |
responseType: 'arraybuffer' | |
}); | |
const formData = new FormData(); | |
formData.append('File', new Blob([documentResponse.data]), documentName); | |
formData.append('Mime-Type', 'application/pdf'); | |
formData.append('File-Name', documentName); | |
console.log('Uploading transient document...'); | |
console.log('formData:', formData); | |
const response = await axios.post(url, formData, { | |
headers: { | |
'Authorization': `Bearer ${accessToken}`, | |
'Content-Type': 'multipart/form-data' | |
} | |
}); | |
return { | |
data: { | |
transientDocumentId: response.data.transientDocumentId, | |
} | |
}; | |
} catch (error) { | |
console.error(error); | |
Log.error(LOG_SOURCE, error); | |
throw error; | |
} | |
} | |
private async _getAccessTokenSharePoint(): Promise<string> { | |
const tokenProvider = await this.context.aadTokenProviderFactory.getTokenProvider(); | |
return tokenProvider.getToken(this.context.pageContext.web.absoluteUrl); | |
} | |
// _uploadTransientDocumentDocSet should be called when the content type is Document Set. This is because we need to upload all the documents in the set to Adobe Sign | |
private async _uploadTransientDocumentDocSet(documentUrl: string, accessTokenAdobe: string, spItemUrl: string): Promise<any> { | |
console.log('Document URL:', documentUrl); | |
console.log('SP Item URL:', spItemUrl); | |
const getDocumentSetContentsUrl = `${spItemUrl}/children`; | |
const accessTokenSharePoint = await this._getAccessTokenSharePoint(); | |
try { | |
// Step 1: Fetch the list of documents within the document set | |
const documentSetResponse = await axios.get(getDocumentSetContentsUrl, { | |
headers: { | |
'Authorization': `Bearer ${accessTokenSharePoint}`, | |
'Accept': 'application/json' | |
} | |
}); | |
const documents = documentSetResponse.data.value; | |
console.log('Documents in the set:', documents); | |
// Step 2: Iterate through the documents and upload each one as a transient document | |
const transientDocumentIds = await Promise.all(documents.map(async (document: any) => { | |
const documentFileUrl = document['@microsoft.graph.downloadUrl']; | |
const documentFileName = document.name; | |
const documentResponse = await axios.get(documentFileUrl, { | |
responseType: 'arraybuffer' | |
}); | |
const formData = new FormData(); | |
formData.append('File', new Blob([documentResponse.data]), documentFileName); | |
formData.append('Mime-Type', 'application/pdf'); | |
formData.append('File-Name', documentFileName); | |
console.log('Uploading transient document:', documentFileName); | |
const response = await axios.post('https://api.na3.adobesign.com/api/rest/v6/transientDocuments', formData, { | |
headers: { | |
'Authorization': `Bearer ${accessTokenAdobe}`, | |
'Content-Type': 'multipart/form-data' | |
} | |
}); | |
return response.data.transientDocumentId; | |
})); | |
console.log('Transient Document IDs:', transientDocumentIds); | |
// Step 3: Return the list of transient document IDs | |
return { | |
data: { | |
transientDocumentIds: transientDocumentIds | |
} | |
}; | |
} catch (error) { | |
console.error(error); | |
Log.error(LOG_SOURCE, error); | |
throw error; | |
} | |
} | |
private async _createAgreement(transientDocumentId: string, agreementUserEmail: string, documentNameWithExtension: string, approverEmail: string): Promise<string> { | |
console.log('Transient Document ID:', transientDocumentId); | |
console.log('Agreement User Email:', agreementUserEmail); | |
console.log('Document Name:', documentNameWithExtension); | |
console.log('Approver Email:', approverEmail); | |
console.log('Attempting to create agreement...'); | |
const url = `https://api.na3.adobesign.com/api/rest/v6/agreements`; | |
// Define the agreement request body based on the schema | |
const agreementRequestBody = { | |
fileInfos: [ | |
{ | |
transientDocumentId: transientDocumentId | |
} | |
], | |
name: documentNameWithExtension, | |
participantSetsInfo: [ | |
{ | |
order: 1, | |
role: 'SIGNER', | |
memberInfos: [ | |
{ | |
email: approverEmail | |
} | |
] | |
} | |
], | |
signatureType: 'ESIGN', | |
state: 'DRAFT', | |
senderEmail: agreementUserEmail, | |
}; | |
console.log('Agreement Request Body:', agreementRequestBody); | |
try { | |
const response = await axios.post(url, agreementRequestBody, { | |
headers: { | |
'Authorization': `Bearer AdobeIntegrationKey`, | |
'Content-Type': 'application/json', | |
'x-api-user': `email:${agreementUserEmail}` | |
} | |
}); | |
console.log('Response:', response.data); | |
const agreementId = response.data.id; | |
console.log('Agreement ID:', agreementId); | |
return agreementId; | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
private async _createDocSetAgreement(transientDocumentIds: string[], agreementUserEmail: string, documentNameWithExtension: string, approverEmail: string): Promise<string> { | |
console.log('Transient Document IDs:', transientDocumentIds); | |
console.log('Agreement User Email:', agreementUserEmail); | |
console.log('Document Name:', documentNameWithExtension); | |
console.log('Approver Email:', approverEmail); | |
console.log('Attempting to create agreement...'); | |
const url = `https://api.na3.adobesign.com/api/rest/v6/agreements`; | |
// Define the agreement request body based on the schema | |
const agreementRequestBody = { | |
fileInfos: transientDocumentIds.map(transientDocumentId => ({ | |
transientDocumentId: transientDocumentId | |
})), | |
name: documentNameWithExtension, | |
participantSetsInfo: [ | |
{ | |
order: 1, | |
role: 'SIGNER', | |
memberInfos: [ | |
{ | |
email: approverEmail | |
} | |
] | |
} | |
], | |
signatureType: 'ESIGN', | |
state: 'DRAFT', | |
senderEmail: agreementUserEmail, | |
}; | |
console.log('Agreement Request Body:', agreementRequestBody); | |
try { | |
const response = await axios.post(url, agreementRequestBody, { | |
headers: { | |
'Authorization': `Bearer AdobeIntegrationKey`, | |
'Content-Type': 'application/json', | |
'x-api-user': `email:${agreementUserEmail}` | |
} | |
}); | |
console.log('Response:', response.data); | |
const agreementId = response.data.id; | |
console.log('Agreement ID:', agreementId); | |
return agreementId; | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
private async _fetchAgreementStatus(agreementId: string, agreementUserEmail: string): Promise<string> { | |
console.log('Agreement ID:', agreementId); | |
console.log('Agreement User Email:', agreementUserEmail); | |
console.log('Attempting to fetch agreement status...'); | |
const accessToken = 'AdobeIntegrationKey' | |
const url = `https://api.na3.adobesign.com/api/rest/v6/agreements/${agreementId}`; | |
try { | |
const response = await axios.get(url, { | |
headers: { | |
'Authorization': `Bearer ${accessToken}`, | |
'x-api-user': `email:${agreementUserEmail}` | |
} | |
}); | |
return response.data.status; | |
} catch (error) { | |
Log.error(LOG_SOURCE, error); | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
private async _updateSharePointField(row: any, fieldValue: string): Promise<void> { | |
const itemId = row.getValueByName('ID'); | |
console.log('Item ID:', itemId); | |
if (this.context.pageContext.list) { | |
const listId = this.context.pageContext.list.id.toString(); | |
console.log('List ID:', listId); | |
const siteId = this.context.pageContext.site.id.toString(); | |
const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items/${itemId}`; | |
console.log('URL:', url); | |
try { | |
const accessToken = await this._getAccessTokenMicrosoft(); | |
const response = await fetch(url, { | |
method: 'PATCH', | |
headers: { | |
'Authorization': `Bearer ${accessToken}`, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
fields: { | |
'Adobe_x0020_Sign_x0020_Status': fieldValue | |
} | |
}) | |
}); | |
if (!response.ok) { | |
console.error('Error:', response.statusText); | |
throw new Error(`Failed to update Status: ${response.status}`); | |
} | |
console.log(`updated successfully`); | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
} | |
private async _updateSharePointFieldAgreementId(row: any, agreementId: string): Promise<void> { | |
const itemId = row.getValueByName('ID'); | |
console.log('Item ID:', itemId); | |
console.log('Agreement ID:', agreementId); | |
if (this.context.pageContext.list) { | |
const listId = this.context.pageContext.list.id.toString(); | |
const siteId = this.context.pageContext.site.id.toString(); | |
console.log('List ID:', listId); | |
const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items/${itemId}`; | |
console.log('URL:', url); | |
try { | |
const accessToken = await this._getAccessTokenMicrosoft(); | |
const response = await fetch(url, { | |
method: 'PATCH', | |
headers: { | |
'Authorization': `Bearer ${accessToken}`, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
fields: { | |
'AgreementID': agreementId | |
} | |
}) | |
}); | |
if (!response.ok) { | |
console.error('Error:', response.statusText); | |
throw new Error(`Failed to update AgreementID. Status: ${response.status}`); | |
} | |
console.log('AgreementID updated successfully'); | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
} | |
private async _getSharePointItemMetadata(listId: string, itemId: string, siteId: string): Promise<any> { | |
const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listId}/items/${itemId}`; | |
try { | |
const accessToken = await this._getAccessTokenMicrosoft(); | |
const response = await fetch(url, { | |
method: 'GET', | |
headers: { | |
'Authorization': `Bearer ${accessToken}`, | |
'Content-Type': 'application/json' | |
} | |
}); | |
if (!response.ok) { | |
console.error('Error:', response.statusText); | |
throw new Error(`Failed to retrieve item metadata. Status: ${response.status}`); | |
} | |
const itemMetadata = await response.json(); | |
console.log('Item Metadata:', itemMetadata); | |
return itemMetadata.fields; | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
private async _getDriveId(siteId: string, accessToken: string): Promise<string> { | |
const drivesUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives`; | |
const response = await fetch(drivesUrl, { | |
headers: { | |
'Authorization': `Bearer ${accessToken}` | |
} | |
}); | |
if (!response.ok) { | |
throw new Error(`Failed to fetch drives. Status: ${response.status}`); | |
} | |
const data = await response.json(); | |
const driveId = data.value[0]?.id; // Assuming the first drive is the document library | |
if (!driveId) { | |
throw new Error('Drive ID not found'); | |
} | |
return driveId; | |
} | |
private async _getFolderId(siteId: string, driveId: string, folderPath: string, accessToken: string): Promise<string> { | |
const folderUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${driveId}/root:/${folderPath}`; | |
const response = await fetch(folderUrl, { | |
headers: { | |
'Authorization': `Bearer ${accessToken}` | |
} | |
}); | |
if (!response.ok) { | |
throw new Error(`Failed to fetch folder. Status: ${response.status}`); | |
} | |
const data = await response.json(); | |
const folderId = data.id; | |
if (!folderId) { | |
throw new Error('Folder ID not found'); | |
} | |
return folderId; | |
} | |
private async _fetchSignedAgreement( | |
agreementId: string, | |
documentName: string, | |
documentUrl: string, | |
agreementUserEmail: string, | |
itemId: string, | |
itemMetadata: any | |
): Promise<void> { | |
console.log('Agreement ID:', agreementId); | |
console.log('Agreement User Email:', agreementUserEmail); | |
console.log('Attempting to fetch signed agreement...'); | |
console.log('Document URL:', documentUrl); | |
console.log('Document Name:', documentName); | |
console.log('Item ID:', itemId); | |
const siteId = this.context.pageContext.site.id.toString(); | |
const accessTokenAdobe = 'AdobeIntegrationKey'; | |
const urlAdobe = `https://api.na3.adobesign.com/api/rest/v6/agreements/${agreementId}/combinedDocument`; | |
try { | |
// Step 1: Fetch the signed document from Adobe Sign | |
const response = await axios.get(urlAdobe, { | |
headers: { | |
'Authorization': `Bearer ${accessTokenAdobe}`, | |
'x-api-user': `email:${agreementUserEmail}`, | |
'Accept': 'application/pdf' | |
}, | |
responseType: 'arraybuffer' | |
}); | |
const signedAgreement = response.data; | |
console.log('Signed Agreement:', signedAgreement); | |
// Step 2: Retrieve Destination Drive and Folder IDs | |
const accessTokenMicrosoft = await this._getAccessTokenMicrosoft(); | |
const destinationDriveId = await this._getDriveId(siteId, accessTokenMicrosoft); | |
const destinationFolderPath = 'Invoices/.Signed Documents'; | |
const destinationFolderId = await this._getFolderId(siteId, destinationDriveId, destinationFolderPath, accessTokenMicrosoft); | |
const fileName = `${documentName}.pdf`; | |
// Step 3: Copy the original file to the designated folder | |
const originalFilePath = documentUrl.substring(documentUrl.indexOf('/sites'), documentUrl.lastIndexOf('/')); | |
console.log('Original File Path:', originalFilePath); | |
console.log('Destination Folder Path:', destinationFolderPath); | |
const copyFileUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${destinationDriveId}/items/${itemId}/copy`; | |
const copyRequestBody = { | |
parentReference: { | |
driveId: destinationDriveId, | |
id: destinationFolderId, | |
}, | |
name: fileName | |
}; | |
console.log('Copying file to:', destinationFolderPath); | |
console.log('fileName:', fileName); | |
console.log('copyRequestBody:', copyRequestBody); | |
const copyFileResponse = await fetch(copyFileUrl, { | |
method: 'POST', | |
headers: { | |
'Authorization': `Bearer ${accessTokenMicrosoft}`, | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(copyRequestBody) | |
}); | |
if (!copyFileResponse.ok) { | |
console.error('Error copying SharePoint file:', copyFileResponse.statusText); | |
throw new Error(`Failed to copy SharePoint file: ${copyFileResponse.status}`); | |
} | |
console.log('File copied successfully.'); | |
// Step 4: Update the copied file content with the signed document | |
const copiedFileId = (await copyFileResponse.json()).id; | |
const fileUploadUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${destinationDriveId}/items/${copiedFileId}/content`; | |
const fileUploadResponse = await fetch(fileUploadUrl, { | |
method: 'PUT', | |
headers: { | |
'Authorization': `Bearer ${accessTokenMicrosoft}`, | |
'Content-Type': 'application/pdf' | |
}, | |
body: signedAgreement | |
}); | |
if (!fileUploadResponse.ok) { | |
console.error('Error updating SharePoint file with signed agreement:', fileUploadResponse.statusText); | |
throw new Error(`Failed to update SharePoint file: ${fileUploadResponse.status}`); | |
} | |
const fileUploadResult = await fileUploadResponse.json(); | |
console.log('File Upload Result:', fileUploadResult); | |
// Step 5: Delete the original unsigned file | |
const deleteFileUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${destinationDriveId}/items/${itemId}`; | |
const deleteFileResponse = await fetch(deleteFileUrl, { | |
method: 'DELETE', | |
headers: { | |
'Authorization': `Bearer ${accessTokenMicrosoft}` | |
} | |
}); | |
if (!deleteFileResponse.ok) { | |
console.error('Error deleting original SharePoint file:', deleteFileResponse.statusText); | |
throw new Error(`Failed to delete original SharePoint file: ${deleteFileResponse.status}`); | |
} | |
console.log('Original file deleted successfully.'); | |
} catch (error) { | |
console.log('Error:', error); | |
throw error; | |
} | |
} | |
private _getFileNameWithoutExtension(fileNameWithExtension: string): string { | |
const lastDotPosition = fileNameWithExtension.lastIndexOf('.'); | |
if (lastDotPosition === -1) return fileNameWithExtension; // No extension found | |
return fileNameWithExtension.substring(0, lastDotPosition); | |
} | |
private _onListViewStateChanged = (args: ListViewStateChangedEventArgs): void => { | |
Log.info(LOG_SOURCE, 'List view state changed'); | |
const compareTwoCommand: Command = this.tryGetCommand('COMMAND_2'); | |
if (compareTwoCommand) { | |
compareTwoCommand.visible = this.context.listView.selectedRows?.length === 1; | |
} | |
const compareThreeCommand: Command = this.tryGetCommand('COMMAND_3'); | |
if (compareThreeCommand) { | |
compareThreeCommand.visible = this.context.listView.selectedRows?.length === 1; | |
} | |
const compareFourCommand: Command = this.tryGetCommand('COMMAND_4'); | |
if (compareFourCommand) { | |
compareFourCommand.visible = this.context.listView.selectedRows?.length === 1; | |
} | |
const compareFiveCommand: Command = this.tryGetCommand('COMMAND_5'); | |
if (compareFiveCommand) { | |
compareFiveCommand.visible = this.context.listView.selectedRows?.length === 1; | |
} | |
this.raiseOnChange(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment