לאכלס יומן חופשות של הצוות

רמת הקידוד: מתחילים
משך: 15 דקות
סוג הפרויקט: אוטומציה עם טריגר מבוסס-זמן

מטרות

  • להבין מה הפתרון עושה.
  • הסבר על הפעולות של שירותי Apps Script בפתרון.
  • מגדירים את הסקריפט.
  • מריצים את הסקריפט.

מידע על הפתרון הזה

יומן חופשות משותף הוא כלי מצוין לשיתוף פעולה בצוות. כל אחד יכול לראות במבט חטוף מי לא נמצא במשרד. הפתרון הזה מאפשר לכם לראות מתי הקולגות לא נמצאים במשרד, בלי שתצטרכו להזין את המידע באופן ידני.

דוגמה ליומן חופשות

איך זה עובד

כשמשתמשים בפתרון הזה, המערכת מאכלסת יומן חופשות משותף על סמך היומנים האישיים של כל אחד מחברי קבוצת Google. כשמישהו קובע לעצמו חופשה, הוא מוסיף אירוע ליומן Google האישי שלו באמצעות מילת מפתח כמו 'חופשה' או 'לא במשרד'.

כל שעה, הסקריפט סורק את היומנים של חברי הקבוצה ומסנכרן את האירועים המתאימים עם היומן המשותף. אתם יכולים לשנות את התדירות שבה הסקריפט סורק אירועים חדשים.

הפתרון הזה מקבל גישה רק לאירועים ביומן שהקולגות שלכם הגדירו כגלויים לכם דרך הגדרות הפרטיות שלהם.

שירותי Apps Script

הפתרון הזה משתמש בשירותים הבאים:

דרישות מוקדמות

כדי להשתמש בדוגמה הזו, אתם צריכים לעמוד בדרישות המוקדמות הבאות:

  • חשבון Google (יכול להיות שחשבונות Google Workspace ידרשו אישור אדמין).
  • דפדפן אינטרנט עם גישה לאינטרנט.

הגדרת הסקריפט

יצירת יומן חופשות של הצוות

  1. פותחים את יומן Google.
  2. יוצרים יומן חדש בשם 'חופשות של הצוות'.
  3. בהגדרות היומן, בקטע שילוב היומן, מעתיקים את מזהה היומן.

יצירת פרויקט Apps Script

  1. לוחצים על הלחצן הבא כדי לפתוח את פרויקט Vacation Calendar Apps Script.
    פתיחת הפרויקט
  2. לוחצים על סקירה כללית .
  3. בדף הסקירה הכללית, לוחצים על 'יצירת עותק' הסמל ליצירת עותק.
  4. בפרויקט Apps Script שהעתקתם, מגדירים את המשתנה TEAM_CALENDAR_ID למזהה של היומן שיצרתם קודם.
  5. מגדירים את המשתנה GROUP_EMAIL לכתובת האימייל של קבוצת Google שכוללת את חברי הצוות.
  6. לצד שירותים, לוחצים על סמל הוספת שירות .
  7. בוחרים באפשרות Google Calendar API ולוחצים על הוספה.

הפעלת הסקריפט

  1. בפרויקט Apps Script שהעתקתם, בתפריט הנפתח של הפונקציה, בוחרים באפשרות setup.
  2. לוחצים על Run.
  3. כשמוצגת בקשה, מאשרים את הסקריפט. אם במסך ההסכמה ל-OAuth מוצגת האזהרה האפליקציה הזו לא אומתה, ממשיכים בתהליך על ידי בחירה באפשרות מתקדם > מעבר אל {שם הפרויקט} (לא בטוח).

  4. אחרי שמסיימים, חוזרים ליומן כדי לוודא שהאירועים מופיעים ביומן של חופשות הצוות.

בדיקת הקוד

כדי לבדוק את קוד Apps Script של הפתרון הזה, לוחצים על הצגת קוד המקור למטה:

הצגת קוד המקור

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.
const 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'];
const GROUP_EMAIL = "ENTER_GOOGLE_GROUP_EMAIL_HERE";

const ONLY_DIRECT_MEMBERS = false;

const KEYWORDS = ["vacation", "ooo", "out of office", "offline"];
const MONTHS_IN_ADVANCE = 3;

/**
 * Sets up the script to run automatically every hour.
 */
function setup() {
  const 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.
  const today = new Date();
  const 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;
  for (const user of users) {
    const username = user.getEmail().split("@")[0];
    const events = findEvents(user, today, maxDate, lastRun);
    for (const event of events) {
      importEvent(username, event);
      count++;
    }
  }

  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";
    event.outOfOfficeProperties = undefined;
    event.focusTimeProperties = undefined;
  }

  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, start, end, optSince) {
  const params = {
    eventTypes: "outOfOffice",
    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);
    pageToken = response.nextPageToken;
  } while (pageToken);
  return events;
}

/**
 * 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) {
  const group = GroupsApp.getGroupByEmail(groupEmail);
  let users = group.getUsers();
  const childGroups = group.getGroups();
  for (let i = 0; i < childGroups.length; i++) {
    const childGroup = childGroups[i];
    users = users.concat(getAllMembers(childGroup.getEmail()));
  }
  // Remove duplicate members
  const uniqueUsers = [];
  const userEmails = {};
  for (let i = 0; i < users.length; i++) {
    const 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) {
  const users = [];
  for (const groupEmail of groupEmails) {
    const groupUsers = GroupsApp.getGroupByEmail(groupEmail).getUsers();
    for (const user of groupUsers) {
      if (!users.some((u) => u.getEmail() === user.getEmail())) {
        users.push(user);
      }
    }
  }
  return users;
}

שינויים

אתם יכולים לערוך את האוטומציה של יומן החופשות של הצוות כמה שתרצו כדי להתאים אותה לצרכים שלכם. בהמשך מופיע שינוי אופציונלי לשינוי הטריגר.

שינוי התדירות שבה הסקריפט סורק אירועים חדשים

כדי לשנות את התדירות שבה הסקריפט פועל, מבצעים את השלבים הבאים:

  1. בפרויקט Apps Script, לוחצים על Triggers (טריגרים) .
  2. לצד הטריגר, לוחצים על סמל העריכה של הטריגר .
  3. בוחרים את השינויים הרצויים ולוחצים על שמירה.

תורמים

הדוגמה הזו מתוחזקת על ידי Google בעזרת מומחי Google לפיתוח.

השלבים הבאים