Копировать макросы в другие таблицы Google Таблиц

Уровень владения программированием : Средний
Продолжительность : 30 минут
Тип проекта : Дополнение для Google Workspace

Цели

  • Поймите, для чего предназначено это решение.
  • Разберитесь, что делают службы Apps Script в рамках данного решения.
  • Настройте среду.
  • Настройте скрипт.
  • Запустите скрипт.

Об этом решении

Ручное копирование макросов Google Sheets из одной электронной таблицы в другую может быть трудоемким и чреватым ошибками процессом. Это дополнение для Google Workspace автоматически копирует проект скрипта и прикрепляет его к указанной пользователем электронной таблице. Хотя это решение ориентировано на макросы Sheets, вы можете использовать его для копирования и обмена любыми скриптами, привязанными к контейнеру.

Скриншот дополнения Share Macro для Google Workspace

Как это работает

Скрипт копирует проект Apps Script, привязанный к исходной электронной таблице, и создает дубликат проекта Apps Script, привязанный к указанной пользователем электронной таблице.

Сервисы Apps Script

Данное решение использует следующие сервисы:

Предварительные требования

Для использования этого примера необходимы следующие условия:

Настройте свою среду

Откройте свой облачный проект в консоли Google Cloud.

Если проект Cloud, который вы собираетесь использовать в этом примере, еще не открыт, откройте его:

  1. В консоли Google Cloud перейдите на страницу «Выберите проект» .

    Выберите облачный проект

  2. Выберите проект Google Cloud, который хотите использовать. Или нажмите «Создать проект» и следуйте инструкциям на экране. Если вы создаете проект Google Cloud, вам может потребоваться включить оплату для этого проекта .

Включите API Google Apps Script.

В этом кратком руководстве используется API Google Apps Script.

Перед использованием API Google необходимо включить их в проекте Google Cloud. В одном проекте Google Cloud можно включить один или несколько API.
  • В своем облачном проекте включите API Google Apps Script.

    Включите API

Для работы дополнений Google Workspace требуется настройка экрана согласия. Настройка экрана согласия OAuth для вашего дополнения определяет, что Google отображает пользователям.

  1. В консоли Google Cloud перейдите в >Google Auth platform > Брендинг .

    Перейти к разделу «Брендинг»

  2. Если вы уже настроили Google Auth platformВы можете настроить следующие параметры экрана согласия OAuth в разделах «Брендинг» , «Аудитория» и «Доступ к данным» . Если вы видите сообщение, в котором говорится... Google Auth platform Если конфигурация еще не выполнена , нажмите «Начать» :
    1. В разделе «Информация о приложении» , в поле «Название приложения» , введите название для приложения.
    2. В разделе «Электронная почта службы поддержки пользователей» выберите адрес электронной почты, по которому пользователи смогут связаться с вами, если у них возникнут вопросы относительно их согласия.
    3. Нажмите «Далее» .
    4. В разделе «Аудитория» выберите «Внутренняя» .
    5. Нажмите «Далее» .
    6. В поле «Контактная информация» укажите адрес электронной почты , на который вы сможете получать уведомления об изменениях в вашем проекте.
    7. Нажмите «Далее» .
    8. В разделе «Завершить» ознакомьтесь с Политикой использования пользовательских данных сервисов Google API и, если вы согласны, выберите «Я согласен с Политикой использования пользовательских данных сервисов Google API» .
    9. Нажмите «Продолжить» .
    10. Нажмите «Создать» .
  3. На данный момент добавление областей действия можно пропустить. В будущем, при создании приложения для использования за пределами вашей организации Google Workspace, необходимо изменить тип пользователя на «Внешний» . Затем добавьте необходимые для вашего приложения области авторизации. Для получения дополнительной информации см. полное руководство по настройке согласия OAuth .

Настройте скрипт

Создайте проект Apps Script.

  1. Нажмите следующую кнопку, чтобы открыть проект «Поделиться макросом в Apps Script».
    Откройте проект
  2. Нажмите «Обзор .
  3. На странице обзора нажмите «Создать копию». Значок для создания копии .

Скопируйте номер облачного проекта.

  1. В консоли Google Cloud перейдите в > IAM и администрирование > Настройки .

    Перейдите в раздел «Настройки IAM и администрирования».

  2. В поле «Номер проекта» скопируйте значение.

Установите проект Cloud для проекта Apps Script.

  1. В скопированном проекте Apps Script нажмите «Настройки проекта» . Значок для настроек проекта .
  2. В разделе «Проект Google Cloud Platform (GCP)» нажмите «Изменить проект» .
  3. В поле "Номер проекта GCP" вставьте номер проекта Google Cloud.
  4. Нажмите «Установить проект» .

Установите тестовую среду развертывания.

  1. В скопированном проекте Apps Script нажмите «Редактор .
  2. Откройте файл UI.gs и нажмите «Запустить» . При появлении запроса авторизуйте скрипт.
  3. Нажмите «Развернуть» > «Проверить развертывания» .
  4. Нажмите «Установить» > «Готово» .

Получите информацию о скрипте макроса и электронной таблице.

  1. Откройте электронную таблицу Google Sheets, содержащую макрос и на редактирование которой у вас есть права. Чтобы использовать пример электронной таблицы, создайте копию таблицы «Пример макроса» .
  2. Нажмите «Расширения» > «Скрипты приложений» .
  3. В проекте Apps Script нажмите «Настройки проекта» . Значок для настроек проекта .
  4. Под идентификатором скрипта нажмите «Копировать» .
  5. Отложите идентификатор скрипта для использования на следующем этапе.
  6. Откройте или создайте новую электронную таблицу, в которую хотите добавить макрос. У вас должны быть права на редактирование этой электронной таблицы.
  7. Скопируйте URL-адрес электронной таблицы и сохраните его для использования на следующем этапе.

Запустите скрипт

Убедитесь, что API Google Apps Script включен в настройках вашей панели управления . Выполните действия, описанные в следующих разделах, чтобы запустить скрипт.

Скопируйте макрос

  1. В Google Sheets, в правой боковой панели, откройте надстройку «Поделиться макросом». Значок для создания копии .
  2. В поле «Источник макроса» вставьте идентификатор скрипта.
  3. В поле «Целевая таблица» вставьте URL-адрес таблицы.
  4. Нажмите «Поделиться» (макрос) .
  5. Нажмите «Авторизовать доступ» и авторизуйте дополнение.
  6. Повторите шаги 2-4.

Откройте скопированный макрос

  1. Если электронная таблица, в которую вы скопировали макрос, еще не открыта, откройте ее.
  2. Нажмите «Расширения» > «Скрипты приложений» .
  3. Если вы не видите скопированный проект Apps Script, убедитесь, что API Google Apps Script включен в настройках панели управления , и повторите шаги, описанные в разделе «Скопировать макрос» .

Просмотрите код

Чтобы просмотреть код Apps Script для этого решения, нажмите «Просмотреть исходный код» ниже:

Просмотреть исходный код

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);
    }
    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;
    }
    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;
    }
    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,
    };
    const res = UrlFetchApp.fetch(url, options);
    if (res.getResponseCode() === 200) {
      return true;
    }
    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.
  const 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?.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.
  const 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.
  const 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.
  const 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.
  const cardFooter = CardService.newFixedFooter().setPrimaryButton(
    CardService.newTextButton()
      .setText("Share macro")
      .setOnClickAction(
        CardService.newAction().setFunctionName("onClickFunction_"),
      ),
  );

  // Begins building the card.
  const 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?.length) {
    // Redisplays form if inputs are missing or invalid.
    return createSelectionCard(e, sourceScriptId, targetSpreadsheetUrl, errors);
  }
  // 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.
  const 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.
  const 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),
        ),
    );
  const 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.
  const cardFooter = CardService.newFixedFooter().setPrimaryButton(
    CardService.newTextButton()
      .setText("Share another")
      .setOnClickAction(CardService.newAction().setFunctionName("onHomepage")),
  );

  const builder = CardService.newCardBuilder()
    .setHeader(cardHeader)
    .addSection(sectionBody1)
    .addSection(sectionBody2)
    .setFixedFooter(cardFooter);

  return builder.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": {}
  }
}

Авторы

Данный пример поддерживается компанией Google при содействии экспертов-разработчиков Google.

Следующие шаги