Propague el calendario de vacaciones del equipo

Nivel de programación: Principiante
Duración: 15 min
Tipo de proyecto: Automatización con un activador basado en el tiempo

Objetivos

  • Comprender qué hace la solución
  • Comprender qué hacen los servicios de Apps Script dentro de la solución
  • Configura la secuencia de comandos.
  • Ejecuta la secuencia de comandos.

Acerca de esta solución

Un calendario de vacaciones compartido es una excelente herramienta para ayudar a tu equipo a colaborar. Cualquier persona puede determinar quién no está disponible de un vistazo. Esta solución te permite ver cuándo tus colegas no están en la oficina, sin necesidad de ingresar datos de forma manual.

ejemplo de calendario de vacaciones

Cómo funciona

Esta solución propaga un calendario de vacaciones compartido en función de los calendarios individuales de cada persona en un Grupo de Google. Cuando alguien reserva tiempo libre, agrega un evento a su Calendario de Google personal con una palabra clave, como "Vacaciones" o "Ausente".

Cada hora, la secuencia de comandos analiza los calendarios de los miembros del grupo y sincroniza los eventos adecuados con el calendario compartido. Puedes cambiar la frecuencia con la que la secuencia de comandos busca eventos nuevos.

Esta solución solo accede a los eventos del Calendario que tus colegas te hayan hecho visibles a través de su configuración de privacidad.

Servicios de Apps Script

En esta solución, se usan los siguientes servicios:

Requisitos previos

Para usar esta muestra, debes cumplir con los siguientes requisitos previos:

  • Una Cuenta de Google (es posible que las cuentas de Google Workspace requieran aprobación del administrador)
  • Un navegador web con acceso a Internet

Configura la secuencia de comandos

Crea un calendario de vacaciones del equipo

  1. Abre el Calendario de Google.
  2. Crea un calendario nuevo que se llame “Vacaciones del equipo”.
  3. En la configuración del calendario, en Integrar calendario, copia el ID del calendario.

Crea el proyecto de Apps Script

  1. Haz clic en el siguiente botón para abrir el proyecto de Apps Script Vacation Calendar.
    Abre el proyecto
  2. Haz clic en Resumen .
  3. En la página de descripción general, haz clic en Crear una copia El ícono para crear una copia.
  4. En el proyecto de Apps Script que copiaste, configura la variable TEAM_CALENDAR_ID en el ID del calendario que creaste antes.
  5. Establece la variable GROUP_EMAIL en la dirección de correo electrónico de un Grupo de Google que contenga a los miembros de tu equipo.
  6. Junto a Servicios, haz clic en Agregar un servicio .
  7. Selecciona API de Calendario de Google y haz clic en Agregar.

Ejecuta la secuencia de comandos:

  1. En el proyecto de Apps Script copiado, en el menú desplegable de funciones, selecciona setup.
  2. Haz clic en Ejecutar.
  3. Cuando se te solicite, autoriza la secuencia de comandos. Si la pantalla de consentimiento de OAuth muestra la advertencia Esta app no está verificada, continúa seleccionando Avanzado > Ir a {nombre del proyecto} (no seguro).

  4. Cuando termines, vuelve al Calendario para confirmar que el calendario de Team Vacations se haya propagado con los eventos.

Revisa el código

Para revisar el código de Apps Script de esta solución, haz clic en Ver código fuente a continuación:

Ver el código fuente

Code.gs

solutions/automations/vacation-calendar/Code.js
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/apps-script/samples/automations/vacation-calendar

/*
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.
*/

// Set the ID of the team calendar to add events to. You can find the calendar's
// ID on the settings page.
let TEAM_CALENDAR_ID = 'ENTER_TEAM_CALENDAR_ID_HERE';
// Set the email address of the Google Group that contains everyone in the team.
// Ensure the group has less than 500 members to avoid timeouts.
// Change to an array in order to add indirect members frrm multiple groups, for example:
// let GROUP_EMAIL = ['ENTER_GOOGLE_GROUP_EMAIL_HERE', 'ENTER_ANOTHER_GOOGLE_GROUP_EMAIL_HERE'];
let GROUP_EMAIL = 'ENTER_GOOGLE_GROUP_EMAIL_HERE';

let ONLY_DIRECT_MEMBERS = false;

let KEYWORDS = ['vacation', 'ooo', 'out of office', 'offline'];
let MONTHS_IN_ADVANCE = 3;

/**
 * Sets up the script to run automatically every hour.
 */
function setup() {
  let triggers = ScriptApp.getProjectTriggers();
  if (triggers.length > 0) {
    throw new Error('Triggers are already setup.');
  }
  ScriptApp.newTrigger('sync').timeBased().everyHours(1).create();
  // Runs the first sync immediately.
  sync();
}

/**
 * Looks through the group members' public calendars and adds any
 * 'vacation' or 'out of office' events to the team calendar.
 */
function sync() {
  // Defines the calendar event date range to search.
  let today = new Date();
  let maxDate = new Date();
  maxDate.setMonth(maxDate.getMonth() + MONTHS_IN_ADVANCE);

  // Determines the time the the script was last run.
  let lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
  lastRun = lastRun ? new Date(lastRun) : null;

  // Gets the list of users in the Google Group.
  let users = getAllMembers(GROUP_EMAIL);
  if (ONLY_DIRECT_MEMBERS){
    users = GroupsApp.getGroupByEmail(GROUP_EMAIL).getUsers();
  } else if (Array.isArray(GROUP_EMAIL)) {
    users = getUsersFromGroups(GROUP_EMAIL);
  }

  // For each user, finds events having one or more of the keywords in the event
  // summary in the specified date range. Imports each of those to the team
  // calendar.
  let count = 0;
  users.forEach(function(user) {
    let username = user.getEmail().split('@')[0];
    KEYWORDS.forEach(function(keyword) {
      let events = findEvents(user, keyword, today, maxDate, lastRun);
      events.forEach(function(event) {
        importEvent(username, event);
        count++;
      }); // End foreach event.
    }); // End foreach keyword.
  }); // End foreach user.

  PropertiesService.getScriptProperties().setProperty('lastRun', today);
  console.log('Imported ' + count + ' events');
}

/**
 * Imports the given event from the user's calendar into the shared team
 * calendar.
 * @param {string} username The team member that is attending the event.
 * @param {Calendar.Event} event The event to import.
 */
function importEvent(username, event) {
  event.summary = '[' + username + '] ' + event.summary;
  event.organizer = {
    id: TEAM_CALENDAR_ID,
  };
  event.attendees = [];

  // If the event is not of type 'default', it can't be imported, so it needs
  // to be changed.
  if (event.eventType != 'default') {
    event.eventType = 'default';
    delete event.outOfOfficeProperties;
    delete event.focusTimeProperties;
  }

  console.log('Importing: %s', event.summary);
  try {
    Calendar.Events.import(event, TEAM_CALENDAR_ID);
  } catch (e) {
    console.error('Error attempting to import event: %s. Skipping.',
        e.toString());
  }
}

/**
 * In a given user's calendar, looks for occurrences of the given keyword
 * in events within the specified date range and returns any such events
 * found.
 * @param {Session.User} user The user to retrieve events for.
 * @param {string} keyword The keyword to look for.
 * @param {Date} start The starting date of the range to examine.
 * @param {Date} end The ending date of the range to examine.
 * @param {Date} optSince A date indicating the last time this script was run.
 * @return {Calendar.Event[]} An array of calendar events.
 */
function findEvents(user, keyword, start, end, optSince) {
  let params = {
    q: keyword,
    timeMin: formatDateAsRFC3339(start),
    timeMax: formatDateAsRFC3339(end),
    showDeleted: true,
  };
  if (optSince) {
    // This prevents the script from examining events that have not been
    // modified since the specified date (that is, the last time the
    // script was run).
    params.updatedMin = formatDateAsRFC3339(optSince);
  }
  let pageToken = null;
  let events = [];
  do {
    params.pageToken = pageToken;
    let response;
    try {
      response = Calendar.Events.list(user.getEmail(), params);
    } catch (e) {
      console.error('Error retriving events for %s, %s: %s; skipping',
          user, keyword, e.toString());
      continue;
    }
    events = events.concat(response.items.filter(function(item) {
      return shouldImportEvent(user, keyword, item);
    }));
    pageToken = response.nextPageToken;
  } while (pageToken);
  return events;
}

/**
 * Determines if the given event should be imported into the shared team
 * calendar.
 * @param {Session.User} user The user that is attending the event.
 * @param {string} keyword The keyword being searched for.
 * @param {Calendar.Event} event The event being considered.
 * @return {boolean} True if the event should be imported.
 */
function shouldImportEvent(user, keyword, event) {
  // Filters out events where the keyword did not appear in the summary
  // (that is, the keyword appeared in a different field, and are thus
  // is not likely to be relevant).
  if (event.summary.toLowerCase().indexOf(keyword) < 0) {
    return false;
  }
  if (!event.organizer || event.organizer.email == user.getEmail()) {
    // If the user is the creator of the event, always imports it.
    return true;
  }
  // Only imports events the user has accepted.
  if (!event.attendees) return false;
  let matching = event.attendees.filter(function(attendee) {
    return attendee.self;
  });
  return matching.length > 0 && matching[0].responseStatus == 'accepted';
}

/**
 * Returns an RFC3339 formated date String corresponding to the given
 * Date object.
 * @param {Date} date a Date.
 * @return {string} a formatted date string.
 */
function formatDateAsRFC3339(date) {
  return Utilities.formatDate(date, 'UTC', 'yyyy-MM-dd\'T\'HH:mm:ssZ');
}

/**
* Get both direct and indirect members (and delete duplicates).
* @param {string} the e-mail address of the group.
* @return {object} direct and indirect members.
*/
function getAllMembers(groupEmail) {
  var group = GroupsApp.getGroupByEmail(groupEmail);
  var users = group.getUsers();
  var childGroups = group.getGroups();
  for (var i = 0; i < childGroups.length; i++) {
    var childGroup = childGroups[i];
    users = users.concat(getAllMembers(childGroup.getEmail()));
  }
  // Remove duplicate members
  var uniqueUsers = [];
  var userEmails = {};
  for (var i = 0; i < users.length; i++) {
    var user = users[i];
    if (!userEmails[user.getEmail()]) {
      uniqueUsers.push(user);
      userEmails[user.getEmail()] = true;
    }
  }
  return uniqueUsers;
}

/**
* Get indirect members from multiple groups (and delete duplicates).
* @param {array} the e-mail addresses of multiple groups.
* @return {object} indirect members of multiple groups.
*/
function getUsersFromGroups(groupEmails) {
  let users = [];
  for (let groupEmail of groupEmails) {
    let groupUsers = GroupsApp.getGroupByEmail(groupEmail).getUsers();
    for (let user of groupUsers) {
      if (!users.some(u => u.getEmail() === user.getEmail())) {
        users.push(user);
      }
    }
  }
  return users;
}

Modificaciones

Puedes editar la automatización del calendario de vacaciones del equipo tanto como desees para adaptarla a tus necesidades. A continuación, se muestra un cambio opcional para modificar el activador.

Cambia la frecuencia con la que la secuencia de comandos busca eventos nuevos

Para cambiar la frecuencia con la que se ejecuta la secuencia de comandos, sigue estos pasos:

  1. En el proyecto de Apps Script, haz clic en Activadores .
  2. Junto al activador, haz clic en Editar activador .
  3. Selecciona los cambios y haz clic en Guardar.

Colaboradores

Google mantiene este ejemplo con la ayuda de expertos en desarrollo de Google.

Próximos pasos