pnp / pnpjs

Fluent JavaScript API for SharePoint and Microsoft Graph REST APIs
https://pnp.github.io/pnpjs/
Other
753 stars 305 forks source link

TypeError: Failed to fetch at c when trying to upload files to document library. #3090

Closed ameyaaklekar closed 4 weeks ago

ameyaaklekar commented 1 month ago

Major Version

3.x

Minor Version Number

3.24.0

Target environment

SharePoint Framework

Additional environment details

Node Version: 18.20.0 SPFX Version: 1.19.0 Typescript: 4.7.4

Expected or Desired Behavior

We have developed an app to handle Daily Business Operations. There are 100s of users using the app at the same time. But only some of them are facing this issue. We have multiple forms that adds data to Sharepoint lists and if there are any attachments they gets added to the document library. This is the expected behavior.

Observed Behavior

When submitting the request, the data gets added to the list correctly but while adding the files to the document library some users get the error and the documents don't get added to the document library.

[{"FileName":"Complete_with_Docusign.pdf","StackTrace":"TypeError: Failed to fetch\n at c (https://publiccdn.sharepointonline.com/site.sharepoint.com/sites/AppStore/ClientSideAssets/f25c455d-16ce-453c-8f3f-538e4eaf03c3/webpart-web-part_bbd2b95a37cd14bf4fb8.js:2:1329754)\n at c (https://publiccdn.sharepointonline.com/site.sharepoint.com/sites/AppStore/ClientSideAssets/f25c455d-16ce-453c-8f3f-538e4eaf03c3/webpart-web-part_bbd2b95a37cd14bf4fb8.js:2:1329833)\n at async https://publiccdn.sharepointonline.com/site.sharepoint.com/sites/AppStore/ClientSideAssets/f25c455d-16ce-453c-8f3f-538e4eaf03c3/webpart-web-part_bbd2b95a37cd14bf4fb8.js:2:1326512","Message":"Failed to fetch"}]

Steps to Reproduce

I have attached the code below that handles the file upload.

/**
 * Function to handle file upload to the document library.
 * This function is called when there is an attachment added to upload in the PS and MS forms.
 * It uploads the files to the document library and updates the list item with the new files names and URLs.
 *
 * @param context SPFX Context
 * @param requestId Request ID
 * @param attachedFiles Files to upload if any
 * @param requestType Request Type can be "MS" or "PS"
 * @returns void
 */
const handleFileUploadToDocumentLibrary = async (
  context: WebPartContext,
  requestId: number,
  attachedFiles: File[],
  requestType: "MS" | "PS"
): Promise<void> => {
  /**
   * Creating a new instance of the SPFx object to upload the file to the
   * document library of the site where the App is hosted.
   *
   * This is to ensure that the document library and the list are in different sites,
   * and the context of the list is not used to upload the files for security reasons.
   *
   * NOTE: PLease do not create a new instance for any other function, use the getSP function from pnpjsConfig.ts
   * This is only for this function as it is a special case.
   */
  const siteSp = getNewInstaceOfSpfx(context);

  // Setting up the location for the files in Document Library, based on the request type
  const destination = requestId.toString();

  // Setting up the document library based on the request type
  const documentLibrary = getDocumentLibraryUrl(requestType);

  // Checking if the folder exists
  const folder = await siteSp.web
    .getFolderByServerRelativePath(`${documentLibrary}/${destination}`)
    .select("Exists")();

  // If the folder does not exist, create the folder
  if (folder.Exists === false) {
    await siteSp.web.folders
      .getByUrl(documentLibrary)
      .addSubFolderUsingPath(destination);
  }

  /**
   * Check if the attached files newly uploaded or already existing in the document library
   * Since we are using the same component for both new and existing files, we need to differentiate between the two
   * If the file is already in the document library, it will have a URL and type of AttachmentsUrl,
   * if not, it will be a file object
   */
  const filesToUpload = attachedFiles.filter(
    (file) => !isTypeAttachementsUrl(file)
  );

  /**
   * Error log to store the files that could not be uploaded
   * Since we are uploading multiple files, we need to store the error for each files
   * and return the error at the end of the function to show the user which files could not be uploaded
   * and to aviod the function from breaking if one file fails to upload.
   */
  const errorLog: any = [];

  /**
   * Map through the files to upload and upload them to the document library.
   * If the file is uploaded successfully, return the file name and URL.
   * If the file fails to upload, store the error in the error log.
   * The reason for try and catch inside the map is to avoid the
   * function from breaking if one file fails to upload.
   * This way, the user can be notified that some files failed to upload
   * and the function will continue to upload the rest of the files.
   */
  const filesUploadPromise = filesToUpload.map(async (file) => {
    const fileNamePath = file.name;
    try {
      const data = await siteSp.web
        .getFolderByServerRelativePath(`${documentLibrary}/${destination}`)
        .files.addUsingPath(fileNamePath, file, { Overwrite: true });

      return {
        Name: data.data.Name,
        Url: data.data.ServerRelativeUrl,
      };
    } catch (error) {
      errorLog.push({
        FileName: fileNamePath ? fileNamePath : "Unknown or Undefined",
        StackTrace: error.stack,
        Message: error.message,
      });
    }
  });

  /**
   * If there are files to upload, upload them
   * If not, return an empty array
   *
   * returns an array of the uploaded files with their name and URL in the document library
   * This will be used to update the Requests list item with the new files names and URLs.
   */
  const fileData = filesUploadPromise
    ? await Promise.all(filesUploadPromise)
    : [];

  // Combine the attached files and the newly uploaded files
  const requestFilesData = [
    ...attachedFiles.filter((file) => isTypeAttachementsUrl(file)),
    ...fileData.filter((file) => file !== undefined),
  ];

  // Set the list name based on the request type
  const listName =
    requestType === "MS" ? "DC-SS-Requests-MS" : "DC-SS-Requests";

  /**
   * Creating a new instance of the SPFx object for the site where the data is saved.
   * This is to becuase the document library and the list are in different sites,
   * we need to update the list item with the new files names and URLs.
   */
  const sp = getSP(context);

  // Update the Requests list item with the new files names and URLs
  await sp.web.lists
    .getByTitle(listName)
    .items.getById(requestId)
    .update({
      AttachmentsUrl:
        requestFilesData.length > 0 ? JSON.stringify(requestFilesData) : null,
    });

  // Get the current user ID to log the error
  const userID = await getCurrentUserId(context);

  // If there are files that could not be uploaded, log the error
  if (errorLog.length > 0) {
    await sp.web.lists.getByTitle("DC-SS-Logs").items.add({
      Message: "File upload failed.",
      LogType: "Error",
      CodeFileName: "CommonServices.ts",
      MethodName: "handleFileUploadToDocumentLibrary",
      StackTrace: JSON.stringify(errorLog),
      AffectedUserId: userID,
    });

    // Throw an error to notify the user that some files could not be uploaded
    throw new Error(
      "Some files could not be uploaded. Please contact the administrator."
    );
  }

  return;
};
bcameron1231 commented 1 month ago

Can you provide what you see in the Dev Tools Network Tab for this request?

ameyaaklekar commented 1 month ago

@bcameron1231 we are not able to replicate this issue on our end, this is happening for few users and it gets recorded in our logs. And the result of this error is there are no files in the document library for those requests.

bcameron1231 commented 1 month ago

@ameyaaklekar Okay, that makes this rather difficult for us to help without knowing more. The code looks fine to me. If you're unable to reproduce it, it feels like it's an issue outside of the library. Can they upload successfully without using this code (that is, directly on the list?)

What permissions do these users have? I believe Edit rights are required for uploading Attachments to list items.

ameyaaklekar commented 1 month ago

@bcameron1231 Yup, they all have read and write permissions.

bcameron1231 commented 1 month ago

@ameyaaklekar Ok thanks. I'm not sure at the moment, it would be really helpful if your end users could capture the Network calls just to see what is happening. Because it's intermittent and you can't reproduce it yourself, it's hard to say it's a PnPjs issue or not.

ameyaaklekar commented 1 month ago

@bcameron1231 thank you, I will keep an eye on this.

bcameron1231 commented 4 weeks ago

Hi. We will close this issue for now as it is an intermittent issue. When you're able to reproduce and provide some more diagnostic information, please open a new issue and reference this issue. Thanks!

github-actions[bot] commented 3 weeks ago

This issue is locked for inactivity or age. If you have a related issue please open a new issue and reference this one. Closed issues are not tracked.