Cấp độ lập trình: Người mới bắt đầu
Thời lượng: 15 phút
Loại dự án: Tự động hoá bằng trình kích hoạt dựa trên thời gian
Mục tiêu
- Tìm hiểu chức năng của giải pháp.
- Tìm hiểu chức năng của các dịch vụ Apps Script trong giải pháp.
- Thiết lập tập lệnh.
- Chạy tập lệnh.
Giới thiệu về giải pháp này
Lịch nghỉ phép dùng chung là một công cụ hiệu quả để giúp nhóm của bạn cộng tác; mọi người đều có thể nhanh chóng xác định được ai đang nghỉ phép. Giải pháp này cho phép bạn biết thời điểm đồng nghiệp không có mặt tại văn phòng mà không cần phải nhập theo cách thủ công.
Cách hoạt động
Giải pháp này điền sẵn lịch nghỉ phép dùng chung dựa trên các lịch cá nhân của từng người trong một Nhóm trên Google. Khi đặt lịch nghỉ, người dùng sẽ thêm một sự kiện vào Lịch Google cá nhân bằng cách sử dụng từ khoá như "Kỳ nghỉ" hoặc "Nghỉ phép".
Mỗi giờ, tập lệnh sẽ quét lịch của các thành viên trong nhóm và đồng bộ hoá các sự kiện thích hợp với lịch dùng chung. Bạn có thể thay đổi tần suất tập lệnh quét các sự kiện mới.
Giải pháp này chỉ truy cập vào các sự kiện trên Lịch mà đồng nghiệp của bạn đã cho phép bạn xem thông qua chế độ cài đặt quyền riêng tư của họ.
Dịch vụ Apps Script
Giải pháp này sử dụng các dịch vụ sau:
Điều kiện tiên quyết
Để sử dụng mẫu này, bạn cần có các điều kiện tiên quyết sau:
- Tài khoản Google (có thể cần có sự phê duyệt của quản trị viên đối với tài khoản Google Workspace).
- Một trình duyệt web có quyền truy cập Internet.
Thiết lập tập lệnh
Tạo lịch nghỉ phép của nhóm
- Mở Lịch Google.
- Tạo một lịch mới có tên là "Kỳ nghỉ của nhóm".
- Trong phần cài đặt của lịch, trong mục Tích hợp lịch, hãy sao chép Mã lịch.
Tạo dự án Apps Script
- Nhấp vào nút sau để mở dự án Apps Script Vacation Calendar (Lịch nghỉ phép).
Mở dự án
- Nhấp vào biểu tượng Tổng quan info_outline.
- Trên trang tổng quan, hãy nhấp vào biểu tượng Tạo bản sao
.
- Trong dự án Apps Script đã sao chép, hãy đặt biến
TEAM_CALENDAR_ID
thành mã nhận dạng của lịch bạn đã tạo trước đó.
- Đặt biến
GROUP_EMAIL
thành địa chỉ email của một Nhóm Google chứa các thành viên trong nhóm của bạn.
- Bên cạnh Dịch vụ, hãy nhấp vào biểu tượng Thêm dịch vụ add.
- Chọn Google Calendar API rồi nhấp vào Thêm.
Chạy tập lệnh
- Trong dự án Apps Script đã sao chép, trong trình đơn thả xuống về hàm, hãy chọn setup (thiết lập).
- Nhấp vào Chạy.
Khi được nhắc, hãy cho phép tập lệnh chạy.
Nếu màn hình đồng ý OAuth hiển thị cảnh báo Ứng dụng này chưa được xác minh, hãy tiếp tục bằng cách chọn Nâng cao >
Chuyển đến {Project Name} (không an toàn).
Khi hoàn tất, hãy quay lại Lịch để xác nhận rằng lịch Team Vacations đã được điền sẵn các sự kiện.
Xem lại mã
Để xem xét mã Apps Script cho giải pháp này, hãy nhấp vào Xem mã nguồn bên dưới:
Xem mã nguồn
Code.gs
// 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;
}
Sửa đổi
Bạn có thể chỉnh sửa lịch tự động của lịch nghỉ phép của nhóm theo ý muốn để phù hợp với nhu cầu của mình. Dưới đây là một thay đổi không bắt buộc để sửa đổi điều kiện kích hoạt.
Thay đổi tần suất tập lệnh quét các sự kiện mới
Để thay đổi tần suất chạy tập lệnh, hãy làm theo các bước sau:
- Trong dự án Apps Script, hãy nhấp vào biểu tượng Trình kích hoạt
alarm.
- Bên cạnh điều kiện kích hoạt, hãy nhấp vào biểu tượng Chỉnh sửa điều kiện kích hoạt edit.
- Chọn nội dung thay đổi rồi nhấp vào Lưu.
Người đóng góp
Mẫu này do Google duy trì với sự trợ giúp của Chuyên gia phát triển của Google.
Các bước tiếp theo