Search code examples
reactjsazure-devopsspfx

Issue with file attachment upload and download in Azure DevOps integration with SharePoint SPFx Form Customizer


I'm currently working on integrating my ticketing system, which is based on SharePoint with SPFx Form Customizer, with Azure DevOps. I'm facing an issue with file attachments: when I upload files, they appear to be broken and cannot be opened in Azure DevOps.

type export default class AzureDevOpsService {
  constructor(organization, project, pat) {
    this.organization = organization;
    this.project = project;
    this.pat = pat;
  }
  async createTask(title, description, attachments) {
    const url = `https://dev.azure.com/${this.organization}/${this.project}/_apis/wit/workitems/$Task?api-version=7.1-preview.3`;

    const taskData = [
        {
            op: 'add',
            path: '/fields/System.Title',
            value: title
        },
        {
            op: 'add',
            path: '/fields/System.Description',
            value: description
        }
    ];

    try {
        // Създаване на таска в Azure DevOps
        const responseTask = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json-patch+json',
                'Authorization': `Basic ${btoa(`:${this.pat}`)}`
            },
            body: JSON.stringify(taskData)
        });

        if (!responseTask.ok) {
            const errorText = await responseTask.text();
            console.error('Error creating a task:', errorText);
            throw new Error('Error creating a task');
        }

        const resultTask = await responseTask.json();

        for (const attachment of attachments) {

            const attachmentUrl = `https://dev.azure.com/${this.organization}/${this.project}/_apis/wit/attachments?fileName=${encodeURIComponent(attachment.FileName)}&uploadType=simple&api-version=7.1-preview.3`;

            const responseAttachment = await fetch(attachmentUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/octet-stream; charset=utf-8',
                    'Authorization': `Basic ${btoa(`:${this.pat}`)}`
                },
                body: attachment.Content
            });

            if (!responseAttachment.ok) {
                const errorText = await responseAttachment.text();
                console.error('Error uploading attachment:', errorText);
                throw new Error('Error uploading attachment');
            }

            const resultAttachment = await responseAttachment.json();
            const linkUrl = `https://dev.azure.com/${this.organization}/${this.project}/_apis/wit/workitems/${resultTask.id}?api-version=7.1-preview.3`;
            const linkData = [
                {
                    op: 'add',
                    path: '/relations/-',
                    value: {
                        rel: 'AttachedFile',
                        url: resultAttachment.url,
                        attributes: {
                            comment: 'Attached File'
                        }
                    }
                }
            ];

            await fetch(linkUrl, {
                method: 'PATCH',
                headers: {
                    'Content-Type': 'application/json-patch+json',
                    'Authorization': `Basic ${btoa(`:${this.pat}`)}`
                },
                body: JSON.stringify(linkData)
            });
        }

        return resultTask;
    } catch (error) {
        console.error('Unexpected error:', error);
        throw error;
    }
}
}

i tried converting to blob, but it didn`t work


Solution

  • Found a solution that worked, thanks to Bright Ran-MSFT's help! First I convert to Base64 string, then convert the Base64 string back into a Blob:

    for (const attachment of attachments) {
        // Fetch the file from the URL
        const response = await fetch(attachment.Content.DecodedUrl);
        const arrayBuffer = await response.arrayBuffer();
        const bytes = new Uint8Array(arrayBuffer);
    
        // Convert the file content into a Base64 string
        let base64content = '';
        const len = bytes.byteLength;
        for (let i = 0; i < len; i++) {
            base64content += String.fromCharCode(bytes[i]);
        }
        base64content = window.btoa(base64content);
    
        // Convert the Base64 string back into a Blob
        const byteCharacters = atob(base64content);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        const blob = new Blob([byteArray], {type: 'application/octet-stream'});
    
        const attachmentUrl = `https://dev.azure.com/${this.organization}/${this.project}/_apis/wit/attachments?fileName=${encodeURIComponent(attachment.FileName)}&uploadType=simple&api-version=7.1-preview.3`;
    
        const responseAttachment = await fetch(attachmentUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/octet-stream',
                'Authorization': `Basic ${btoa(`:${this.pat}`)}`
            },
            body: blob
        });
    }