Copiar macros para outras planilhas

Nível de codificação: intermediário
Duração: 30 minutos
Tipo de projeto: complemento do Google Workspace

Objetivos

  • Entenda o que a solução faz.
  • Entenda o que os serviços do Apps Script fazem na solução.
  • Configure o ambiente.
  • Configure o script.
  • Execute o script.

Sobre esta solução

Copiar manualmente macros das Planilhas Google de uma planilha para outra pode ser demorado e propenso a erros. Esse complemento do Google Workspace copia automaticamente um projeto de script e o anexa a uma planilha especificada pelo usuário. Embora essa solução se concentre em macros das Planilhas, você pode usá-la para copiar e compartilhar qualquer script vinculado a um contêiner.

Captura de tela do complemento Share Macro do Google Workspace

Como funciona

O script copia o projeto do Apps Script vinculado à planilha original e cria um projeto duplicado do Apps Script vinculado à planilha especificada pelo usuário.

Serviços do Apps Script

Esta solução usa os seguintes serviços:

Pré-requisitos

Para usar este exemplo, você precisa dos seguintes pré-requisitos:

Configurar o ambiente

Abra seu projeto do Cloud no console do Google Cloud

Se ainda não estiver aberto, abra o projeto do Cloud que você pretende usar para este exemplo:

  1. No console do Google Cloud, acesse a página Selecionar um projeto.

    Selecionar um projeto do Cloud

  2. Selecione o projeto do Google Cloud que você quer usar. Ou clique em Criar projeto e siga as instruções na tela. Se você criar um projeto do Google Cloud, talvez seja necessário ativar o faturamento dele.

Ativar a API Google Apps Script

Este guia de início rápido usa a API Google Apps Script.

Antes de usar as APIs do Google, você precisa ativá-las em um projeto do Google Cloud. É possível ativar uma ou mais APIs em um único projeto do Google Cloud.
  • No projeto do Cloud, ative a API Google Apps Script.

    Ativar a API

Os complementos do Google Workspace exigem uma configuração de tela de consentimento. A configuração da tela de consentimento do OAuth do seu complemento define o que o Google mostra aos usuários.

  1. No console do Google Cloud, acesse Menu > > Marca.

    Acessar "Branding"

  2. Se você já tiver configurado o , poderá configurar as seguintes configurações da tela de consentimento do OAuth em Marca, Público-alvo e Acesso a dados. Se você receber a mensagem ainda não configurado, clique em Começar:
    1. Em Informações do app, em Nome do app, insira um nome para o app.
    2. Em E-mail para suporte do usuário, escolha um endereço de e-mail de suporte para que os usuários entrem em contato com você se tiverem dúvidas sobre o consentimento.
    3. Clique em Próxima.
    4. Em Público, selecione Interno.
    5. Clique em Próxima.
    6. Em Dados de contato, insira um endereço de e-mail para receber notificações sobre as mudanças no projeto.
    7. Clique em Próxima.
    8. Em Concluir, analise a Política de dados do usuário dos serviços de API do Google e, se concordar, selecione Concordo com a política de dados do usuário dos serviços de API do Google.
    9. Clique em Continuar.
    10. Clique em Criar.
  3. Por enquanto, você pode pular a adição de escopos. No futuro, quando você criar um app para uso fora da sua organização do Google Workspace, será necessário mudar o Tipo de usuário para Externo. Em seguida, adicione os escopos de autorização necessários para o app. Para saber mais, consulte o guia completo Configurar a permissão OAuth.

Configurar o script

Criar o projeto do Apps Script

  1. Clique no botão a seguir para abrir o projeto do Apps Script Compartilhar uma macro.
    Abrir o projeto
  2. Clique em Visão geral .
  3. Na página de visão geral, clique em Fazer uma cópia O ícone para fazer uma cópia.

Copiar o número do projeto do Cloud

  1. No console do Google Cloud, acesse Menu > IAM e administrador > Configurações.

    Acessar as configurações do IAM e do administrador

  2. No campo Número do projeto, copie o valor.

Definir o projeto do Cloud do Apps Script

  1. No projeto do Apps Script copiado, clique em Configurações do projeto Ícone das configurações do projeto.
  2. Em Projeto do Google Cloud Platform (GCP), clique em Mudar projeto.
  3. Em Número do projeto do GCP, cole o número do projeto do Google Cloud.
  4. Clique em Configurar projeto.

Instalar uma implantação de teste

  1. No projeto do Apps Script copiado, clique em Editor .
  2. Abra o arquivo UI.gs e clique em Run. Quando solicitado, autorize o script.
  3. Clique em Implantar > Testar implantações.
  4. Clique em Instalar > Concluído.

Conseguir informações sobre o script de macro e a planilha

  1. Abra uma planilha do Google Planilhas que tenha uma macro e que você tenha permissão para editar. Para usar uma planilha de exemplo, faça uma cópia da planilha Sample macro.
  2. Clique em Extensões > Apps Script.
  3. No projeto do Apps Script, clique em Configurações do projeto Ícone das configurações do projeto.
  4. Em "ID do script", clique em Copiar.
  5. Anote o ID do script para usar em uma etapa posterior.
  6. Abra ou crie uma planilha em que você quer adicionar a macro. Você precisa ter permissão para editar a planilha.
  7. Copie o URL da planilha e deixe-o de lado para usar em uma etapa posterior.

Executar o script

Verifique se a API Google Apps Script está ativada nas configurações do painel. Siga as etapas nas seções a seguir para executar o script.

Copiar a macro

  1. No app Planilhas, na barra lateral à direita, abra o complemento Share Macro O ícone para fazer uma cópia.
  2. Em Macro de origem, cole o ID do script.
  3. Em Planilha de destino, cole o URL da planilha.
  4. Clique em Compartilhar macro.
  5. Clique em Autorizar acesso e autorize o complemento.
  6. Repita as etapas 2 a 4.

Abrir a macro copiada

  1. Abra a planilha para a qual você copiou a macro, se ainda não estiver aberta.
  2. Clique em Extensões > Apps Script.
  3. Se o projeto copiado do Apps Script não aparecer, verifique se a API Google Apps Script está ativada nas configurações do painel e repita as etapas listadas em Copiar a macro.

Revisar o código

Para revisar o código do Apps Script para essa solução, clique em Ver código-fonte abaixo:

Acessar o código-fonte

Code.gs

solutions/add-on/share-macro/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.devsite.corp.google.com/apps-script/add-ons/share-macro

/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Uses Apps Script API to copy source Apps Script project 
 * to destination Google Spreadsheet container.
 * 
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL if the target spreadsheet.
 */
function shareMacro_(sourceScriptId, targetSpreadsheetUrl) {

  // Gets the source project content using the Apps Script API.
  const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
  const sourceFiles = APPS_SCRIPT_API.getContent(sourceScriptId);

  // Opens the target spreadsheet and gets its ID.
  const parentSSId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();

  // Creates an Apps Script project that's bound to the target spreadsheet.
  const targetProjectObj = APPS_SCRIPT_API.create(sourceProject.title, parentSSId);

  // Updates the Apps Script project with the source project content.
  APPS_SCRIPT_API.updateContent(targetProjectObj.scriptId, sourceFiles);

}

/**
 * Function that encapsulates Apps Script API project manipulation. 
*/
const APPS_SCRIPT_API = {
  accessToken: ScriptApp.getOAuthToken(),

  /* APPS_SCRIPT_API.get
   * Gets Apps Script source project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of source project.
   */
  get: function (scriptId) {
    const url = ('https://script.googleapis.com/v1/projects/' + scriptId);
    const options = {
      "method": 'get',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "muteHttpExceptions": true,
    };
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      return JSON.parse(res);
    } else {
      console.log('An error occurred gettting the project details');
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },

  /* APPS_SCRIPT_API.create
   * Creates new Apps Script project in the target spreadsheet.
   * @param {string} title - Name of Apps Script project.
   * @param {string} parentId - Internal ID of target spreadsheet.
   * @return {Object} - JSON representation completed project creation.
   */
  create: function (title, parentId) {
    const url = 'https://script.googleapis.com/v1/projects';
    const options = {
      "headers": {
        "Authorization": "Bearer " + this.accessToken,
        "Content-Type": "application/json"
      },
      "muteHttpExceptions": true,
      "method": "POST",
      "payload": { "title": title }
    }
    if (parentId) {
      options.payload.parentId = parentId;
    }
    options.payload = JSON.stringify(options.payload);
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      res = JSON.parse(res);
      return res;
    } else {
      console.log("An error occurred while creating the project");
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },
   /* APPS_SCRIPT_API.getContent
   * Gets the content of the source Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @return {Object} - JSON representation of Apps Script project content.
   */
   getContent: function (scriptId) {
    const url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
    const options = {
      "method": 'get',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "muteHttpExceptions": true,
    };
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      res = JSON.parse(res);
      return res['files'];
    } else {
      console.log('An error occurred obtaining the content from the source script');
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  },

  /* APPS_SCRIPT_API.updateContent
   * Updates (copies) content from source to target Apps Script project.
   * @param {string} scriptId - Script ID of the source project.
   * @param {Object} files - JSON representation of Apps Script project content.
   * @return {boolean} - Result status of the function.
   */
  updateContent: function (scriptId, files) {
    const url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
    const options = {
      "method": 'put',
      "headers": {
        "Authorization": "Bearer " + this.accessToken
      },
      "contentType": "application/json",
      "payload": JSON.stringify({ "files": files }),
      "muteHttpExceptions": true,
    };
    let res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() == 200) {
      return true;
    } else {
      console.log(`An error occurred updating content of script ${scriptId}`);
      console.log(res.getResponseCode());
      console.log(res.getContentText());
      console.log(res);
      return false;
    }
  }
}

UI.gs

solutions/add-on/share-macro/UI.js
/**
 * Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Change application logo here (and in manifest) as desired.
const ADDON_LOGO = 'https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png';

/**
 * Callback function for rendering the main card.
 * @return {CardService.Card} The card to show the user.
 */
function onHomepage(e) {
  return createSelectionCard(e);
}

/**
 * Builds the primary card interface used to collect user inputs.
 * 
 * @param {Object} e - Add-on event object.
 * @param {string} sourceScriptId - Script ID of the source project.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 * @param {string[]} errors - Array of error messages. 
 * 
 * @return {CardService.Card} The card to show to the user for inputs.
 */
function createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors) {

  // Configures card header.
  let cardHeader = CardService.newCardHeader()
    .setTitle('Share macros with other spreadheets!')
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // If form errors exist, configures section with error messages.
  let showErrors = false;

  if (errors && errors.length) {
    showErrors = true;
    let msg = errors.reduce((str, err) => `${str}${err}<br>`, '');
    msg = `<b>Form submission errors:</b><br><font color="#ba0000">${msg}</font>`;

    // Builds error message section.
    sectionErrors = CardService.newCardSection()
      .addWidget(CardService.newDecoratedText()
        .setText(msg)
        .setWrapText(true));
  }

  // Configures source project section.
  let sectionSource = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b>Source macro</b><br>The Apps Script project to copy'))

    .addWidget(CardService.newTextInput()
      .setFieldName('sourceScriptId')
      .setValue(sourceScriptId || '')
      .setTitle('Script ID of the source macro')
      .setHint('You must have at least edit permission for the source spreadsheet to access its script project'))

    .addWidget(CardService.newTextButton()
      .setText('Find the script ID')
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://developers.google.com/apps-script/api/samples/execute')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.NOTHING)));

  // Configures target spreadsheet section.
  let sectionTarget = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b>Target spreadsheet</b>'))

    .addWidget(CardService.newTextInput()
      .setFieldName('targetSpreadsheetUrl')
      .setValue(targetSpreadsheetUrl || '')
      .setHint('You must have at least edit permission for the target spreadsheet')
      .setTitle('Target spreadsheet URL'));

  // Configures help section.
  let sectionHelp = CardService.newCardSection()
    .addWidget(CardService.newDecoratedText()
      .setText('<b><font color=#c80000>NOTE: </font></b>' +
        'The Apps Script API must be turned on.')
      .setWrapText(true))

    .addWidget(CardService.newTextButton()
      .setText('Turn on Apps Script API')
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://script.google.com/home/usersettings')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.NOTHING)));

  // Configures card footer with action to copy the macro.
  var cardFooter = CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
      .setText('Share macro')
      .setOnClickAction(CardService.newAction()
        .setFunctionName('onClickFunction_')));

  // Begins building the card.
  let builder = CardService.newCardBuilder()
    .setHeader(cardHeader);

  // Adds error section if applicable.
  if (showErrors) {
    builder.addSection(sectionErrors)
  }

  // Adds final sections & footer.
  builder
    .addSection(sectionSource)
    .addSection(sectionTarget)
    .addSection(sectionHelp)
    .setFixedFooter(cardFooter);

  return builder.build();
}

/**
 * Action handler that validates user inputs and calls shareMacro_
 * function to copy Apps Script project to target spreadsheet.
 * 
 * @param {Object} e - Add-on event object.
 * 
 * @return {CardService.Card} Responds with either a success or error card.
 */
function onClickFunction_(e) {

  const sourceScriptId = e.formInput.sourceScriptId;
  const targetSpreadsheetUrl = e.formInput.targetSpreadsheetUrl;

  // Validates inputs for errors.
  const errors = [];

  // Pushes an error message if the Script ID parameter is missing.
  if (!sourceScriptId) {
    errors.push('Missing script ID');
  } else {

    // Gets the Apps Script project if the Script ID parameter is valid.
    const sourceProject = APPS_SCRIPT_API.get(sourceScriptId);
    if (!sourceProject) {
      // Pushes an error message if the Script ID parameter isn't valid.
      errors.push('Invalid script ID');
    }
  }

  // Pushes an error message if the spreadsheet URL is missing.
  if (!targetSpreadsheetUrl) {
    errors.push('Missing Spreadsheet URL');
  } else
    try {
      // Tests for valid spreadsheet URL to get the spreadsheet ID.
      const ssId = SpreadsheetApp.openByUrl(targetSpreadsheetUrl).getId();
    } catch (err) {
      // Pushes an error message if the spreadsheet URL parameter isn't valid.
      errors.push('Invalid spreadsheet URL');
    }

  if (errors && errors.length) {
    // Redisplays form if inputs are missing or invalid.
    return createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors);

  } else {
    // Calls shareMacro function to copy the project.
    shareMacro_(sourceScriptId, targetSpreadsheetUrl);

    // Creates a success card to display to users.
    return buildSuccessCard(e, targetSpreadsheetUrl);
  }
}

/**
 * Builds success card to inform user & let them open the spreadsheet.
 * 
 * @param {Object} e - Add-on event object.
 * @param {string} targetSpreadsheetUrl - URL of the target spreadsheet.
 * 
 * @return {CardService.Card} Returns success card.
 */function buildSuccessCard(e, targetSpreadsheetUrl) {

  // Configures card header.
  let cardHeader = CardService.newCardHeader()
    .setTitle('Share macros with other spreadsheets!')
    .setImageUrl(ADDON_LOGO)
    .setImageStyle(CardService.ImageStyle.SQUARE);

  // Configures card body section with success message and open button.
  let sectionBody1 = CardService.newCardSection()
    .addWidget(CardService.newTextParagraph()
      .setText('Sharing process is complete!'))
    .addWidget(CardService.newTextButton()
      .setText('Open spreadsheet')
      .setOpenLink(CardService.newOpenLink()
        .setUrl(targetSpreadsheetUrl)
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.RELOAD_ADD_ON)));
  let sectionBody2 = CardService.newCardSection()
    .addWidget(CardService.newTextParagraph()
      .setText('If you don\'t see the copied project in your target spreadsheet,' +
       ' make sure you turned on the Apps Script API in the Apps Script dashboard.'))
    .addWidget(CardService.newTextButton()
      .setText("Check API")
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://script.google.com/home/usersettings')
        .setOpenAs(CardService.OpenAs.FULL_SIZE)
        .setOnClose(CardService.OnClose.RELOAD_ADD_ON)));

  // Configures the card footer with action to start new process.
  let cardFooter = CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
      .setText('Share another')
      .setOnClickAction(CardService.newAction()
        .setFunctionName('onHomepage')));

  return builder = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(sectionBody1)
    .addSection(sectionBody2)
    .setFixedFooter(cardFooter)
    .build();
 }

appsscript.json

solutions/add-on/share-macro/appsscript.json
{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/drive.readonly",
    "https://www.googleapis.com/auth/script.projects"
  ],
    "urlFetchWhitelist": [
    "https://script.googleapis.com/"
  ],
  "addOns": {
    "common": {
      "name": "Share Macro",
      "logoUrl": "https://www.gstatic.com/images/branding/product/2x/apps_script_48dp.png",
      "layoutProperties": {
        "primaryColor": "#188038",
        "secondaryColor": "#34a853"
      },
      "homepageTrigger": {
        "runFunction": "onHomepage"
      }
    },
    "sheets": {}
  }
}

Colaboradores

Esse exemplo é mantido pelo Google com a ajuda de especialistas em desenvolvimento do Google.

Próximas etapas