0

I am facing a strange and inconsistent issue with theme application when generating presentations using the Slides API and an Apps Script helper function.

My Goal:

I'm building a service to generate quotes by merging a series of predefined slides into a new presentation. The process starts from a template that has a specific theme (colors, fonts, master layouts).

The Workflow:

  • Create Presentation: My Node.js backend creates a new presentation by copying a template using driveApi.files.copy. This template has our company's theme and a single master.
  • Delete Placeholder Slide: I remove the initial slide from the newly created presentation.
  • Batch Insert Slides: I have a list of source presentation IDs (each being a single slide). I process this list in parallel batches (e.g., 20 slides at a time using Promise.all).
  • Call Apps Script: For each slide to be inserted, I call an Apps Script function mergeSingleSlide via the REST API. This function is wrapped in a retry-logic handler in my Node.js code to manage transient server errors (like 5xx).
  • Merge via appendSlide: The Apps Script function mergeSingleSlide opens the source and target presentations and uses targetPresentation.appendSlide(sourceSlide) to perform the merge.

The Problem:

The process works, but the theme application is inconsistent. Often, the slides in the first batch are inserted without the correct theme applied. They appear with a simple, blank/white theme.

Here are the key details:

  • The Master Exists: When I inspect the final presentation's properties using the API (presentations.get), the correct master from the template is present in the list of masters.
  • Slides Aren't Attached: The newly inserted slides that have the wrong appearance are attached to a default, blank master instead of the correct one that was copied from the template.
  • Inconsistency: The most confusing part is that subsequent batches in the same execution often work perfectly. All slides in the second or third batch will correctly inherit the theme. Sometimes the first batch works, and a later one fails. This leads me to believe there might be a race condition or a timing issue after the initial presentation is created. It seems like the presentation isn't "ready" to apply its theme correctly, even though the driveApi.files.copy call has completed.

My Questions:

  • Is there a known race condition or replication delay when a new presentation is created via drive.files.copy, which might affect its ability to correctly apply its master theme to newly inserted slides immediately afterward?
  • Could the highly parallel nature of Promise.all (making 20 simultaneous calls to the Apps Script API) be causing an issue on the backend, even if the individual API calls succeed?
  • Would a more robust approach be to insert all slides first (even with the wrong theme), and then run a final batchUpdate at the end to explicitly apply the correct layout from the correct master to every slide? This seems inefficient, but I'm looking for reliability.

Below is the relevant code for context. Any insights or suggestions for a more reliable workflow would be greatly appreciated.

Thank you!


Code Snippets :

Node.js Logic

// Function to call Apps Script with exponential backoff for 5xx errors
async function callAppsScriptWithRetry(scriptClient, functionName, parameters, maxRetries = 5) {
    // ... (The code you provided is perfect here)
}

async function mergeSlides(slideIds, /*...other params*/) {
    try {
        // --- Setup: Authenticate, get presentation IDs, etc. ---
        const oAuth2Client = new google.auth.OAuth2(/*...*/);
        oAuth2Client.setCredentials({ refresh_token: refreshToken });
        const slidesApi = google.slides({ version: "v1", auth: oAuth2Client });
        const driveApi = google.drive({ version: "v3", auth: oAuth2Client });
        const scriptApi = google.script({ version: "v1", auth: oAuth2Client });

        // 1. Copy the template presentation
        const newPresentationResponse = await driveApi.files.copy({
            fileId: "1FNlEEgBPZHVwrV4ydDmjV3U5qwBiwx4OBNKJ0eS6too", // My template
            resource: { name: "New Quote" },
            supportsAllDrives: true,
        });
        const newPresentationId = newPresentationResponse.data.id;

        // 2. Delete the first placeholder slide
        const firstSlide = (await slidesApi.presentations.get({ presentationId: newPresentationId, fields: 'slides' })).data.slides[0];
        await slidesApi.presentations.batchUpdate({
            presentationId: newPresentationId,
            resource: { requests: [{ deleteObject: { objectId: firstSlide.objectId } }] },
        });

        // 3. Process all slides in batches
        const batchSize = 20;
        for (let i = 0; i < googleSlidesIds.length; i += batchSize) {
            const chunk = googleSlidesIds.slice(i, i + batchSize);
            console.log(`Processing batch ${Math.floor(i / batchSize) + 1}...`);

            const promisesInChunk = chunk.map(googleSlidesId => {
                return callAppsScriptWithRetry(
                    scriptApi, 
                    "mergeSingleSlide", 
                    [googleSlidesId, newPresentationId, /* some index */]
                );
            });

            await Promise.all(promisesInChunk);
        }

        console.log("All slides processed.");

    } catch (error) {
        console.error("An error occurred during the merge process:", error);
    }
}

Google Apps Script Function (mergeSingleSlide.gs)

function mergeSingleSlide(sourcePresentationId, targetPresentationId, finalIndex) {
  const MAX_RETRIES = 5;
  let waitingTime = 500;

  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    try {
      const targetPresentation = SlidesApp.openById(targetPresentationId);
      const sourcePresentation = SlidesApp.openById(sourcePresentationId.trim());
      const sourceSlide = sourcePresentation.getSlides()[0];

      if (sourceSlide) {
        // Using appendSlide, which should also copy the master if not present
        const newSlide = targetPresentation.appendSlide(sourceSlide);
        newSlide.getNotesPage().getSpeakerNotesShape().getText().setText("ORDER_INDEX::" + finalIndex);
        
        targetPresentation.saveAndClose();
        
        console.log(`Attempt ${attempt}/${MAX_RETRIES}: Slide ${finalIndex} inserted successfully.`);
        return { status: "SUCCESS" };

      } else {
        return { status: "ERROR", message: `No slide found in presentation ${sourcePresentationId}.` };
      }

    } catch (e) {
      console.error(`Attempt ${attempt}/${MAX_RETRIES} failed for presentation ${sourcePresentationId}: ${e.message}`);
      if (attempt === MAX_RETRIES) {
        throw new Error(`Failed to merge after ${MAX_RETRIES} attempts. Last error: ${e.message}`);
      }
      Utilities.sleep(waitingTime);
      waitingTime *= 2; 
    }
  }
}
1
  • Welcome to Stack Overflow. This post is not a good fit for this site, as the questions should be specific (only one question). Don't make open questions like "any insights or suggestions..." Commented Oct 3 at 13:24

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.