Mateusz: Niech pierwszy rzuci kamieniem ten, kto nie korzysta z Kalendarza Google.
Ludzie: ...
Mateusz: Ktokolwiek?
Ludzie: ...
Mateusz: Tak myślałem 😅
Chodzi o to, że AppSheet pozwala podłączyć Kalendarz Google jako źródło danych i to jest w porządku, ale ja wolę coś bardziej zaawansowanego. Co powiesz na zagregowany widok wszystkich wydarzeń z różnych kalendarzy i możliwość aktualizacji wszystkich tych wydarzeń zarówno z interfejsu kalendarza Google, jak i aplikacji AppSheet? Brzmi interesująco? Zostań ze mną...
Wyobraźmy sobie, że prowadzimy firmę i każdy z pracowników ma swój własny „służbowy kalendarz Google”. Aby zarządzać zadaniami lub wydarzeniami Tomka, musisz otworzyć jego kalendarz, a kiedy on zarządza swoimi zadaniami, zobaczysz to tylko wtedy, gdy otworzysz aplikację Kalendarz i włączysz wyświetlanie jego kalendarza. A gdybyś mógł to wszystko zrobić w aplikacji AppSheet stworzonej specjalnie dla Twoich potrzeb? Powyższy obrazek przedstawia aplikację „PA Management” zbudowaną przy użyciu platformy AppSheet, w której zaimplementowałem to rozwiązanie.
W skrócie, wszystkie niezbędne kalendarze są współdzielone z kontem Google właściciela aplikacji, który wykonuje operacje integracyjne napisane w Apps Script.
Główne punkty to:
Struktura danych dla tego rozwiązania zawiera obecnie 3 tabele:
USERS - tutaj przechowujemy dane specyficzne dla użytkownika oraz relację pomiędzy użytkownikiem a synchronizowanym kalendarzem Google.
SYNCED_GOOGLE_CALENDARS - tutaj przechowujemy informacje o zsynchronizowanych kalendarzach Google, takie jak calendarId, calendarName, calendarDescription itp.
EVENTS - tutaj przechowujemy wszystkie wydarzenia ze wszystkich zsynchronizowanych Kalendarzy Google.
Projekt Apps Script, który działa jako pomost między API kalendarza a API AppSheet.
Instalowalne wyzwalacze Apps Script dla wszystkich Kalendarzy Google, które nasłuchują wszystkich zmian po stronie Kalendarza Google.
Właściwości skryptu, które przechowują pary klucz-wartość zawierające identyfikator Kalendarza Google i jego ostatni syncToken.
Boty AppSheet, które nasłuchują zdarzeń ADD, EDIT i DELETE w tabeli EVENTS i uruchamiają funkcję Apps Script, która wykonuje różne operacje na określonym kalendarzu za pomocą interfejsu API kalendarza.
Boty AppSheet, które nasłuchują zdarzeń ADD & DELETE w tabeli SYNCED_GOOGLE_CALENDARS i uruchamiają funkcję Apps Script, która tworzy lub usuwa instalowalny wyzwalacz dla określonego kalendarza i wykonuje pierwszą pełną synchronizację za pomocą interfejsu API kalendarza.
Nie podam tutaj całego kodu, ponieważ uważam, że GitHub jest lepszym miejscem do jego przechowywania, ale postaram się nakreślić kluczowe kwestie związane z tym projektem. Pewnego dnia w przyszłości utworzę również moje publiczne repozytoria na GitHubie. Daj mi trochę czasu, proszę.
Po pierwsze, jeśli chcesz wykonać jakiekolwiek operacje na dowolnym kalendarzu, potrzebujesz dostępu do tego kalendarza. Jeśli nie jesteś właścicielem, nadal możesz to zrobić, o ile uzyskasz najwyższy możliwy poziom uprawnień. Następną częścią jest zrozumienie CalendarAPI i wreszcie integracja z AppSheet, która obejmuje również zrozumienie AppSheet API. Gdy poczujesz się pewnie i zrozumiesz, jak pracować z tymi zasobami, możemy rozpocząć proces rozwoju 🙂
Google Apps Script to świetny sposób na zbudowanie takiej integracji, ponieważ ma wbudowaną zaawansowaną metodę CalendarAPI, która znacznie przyspiesza pracę.
Aby móc pracować z zaawansowanym CalendarAPI w Apps Script, musisz włączyć tę usługę w swoim projekcie. Gdy już to zrobisz, tak to będzie wyglądać:
Teraz, aby otrzymywać żądania po zaktualizowaniu kalendarzy, potrzebujesz jakiegoś rodzaju wyzwalacza/webhooka. Apps Script ma wbudowaną funkcję zwaną instalowalnymi wyzwalaczami, w której można tworzyć takie wyzwalacze (pamiętaj o sprawdzeniu limitów) i definiować, co będzie źródłem do ich uruchomienia. W naszym przypadku będzie to Kalendarz Google. Gdy to zrobisz, jedna z funkcji w projekcie zostanie uruchomiona, gdy ktoś dokona aktualizacji w określonym Kalendarzu Google. Rzecz w tym, że kalendarze mogą zawierać dziesiątki wydarzeń, więc nie ma sensu wywoływać API, aby uzyskać wszystkie wydarzenia przy każdej aktualizacji. Znacznie lepiej jest przechowywać gdzieś ostatni syncToken dla każdego zsynchronizowanego kalendarza i pobierać tylko ostatnie wydarzenia bez starych, ponieważ ich nie potrzebujemy. Zrobiłem to za pomocą właściwości skryptu, gdzie przechowuję pary calendarId-lastsyncToken dla każdego zsynchronizowanego kalendarza. Za każdym razem, gdy uruchamiam funkcję integracji, aktualizuję te pary, aby były aktualne. Ma to kluczowe znaczenie dla szybkiego działania.
function makeTriggersAndInitialSyncSingleCalendar(calId) {
try {
const t = ScriptApp.newTrigger("gCalEventUpdated")
.forUserCalendar(calId)
.onEventUpdated()
.create()
const syncToken = getInitialSyncToken(calId)
console.log({
messageTriggers: "Created event trigger for calendar " + calId,
messageTokens: "Saved initial syncToken for calendar: " + calId + " syncToken is: " + syncToken,
});
return true
} catch (err) {
console.error("There was an error: ", err);
logErrorInAppSheet(properties().appID, properties().accessKey, `Wystąpił błąd podczas tworzenia reguły dla nowego kalendarza: ${calId}. Oryginalna treść błędu: ${err}`);
return false
}
}
function deleteInitialSync(calId) {
try {
const triggers = ScriptApp.getProjectTriggers();
const matchingTrigger = triggers.find(function (trigger) {
return trigger.getHandlerFunction() === "gCalEventUpdated" && trigger.getTriggerSource() === ScriptApp.TriggerSource.CALENDAR && trigger.getTriggerSourceId() === calId;
});
if (matchingTrigger) {
ScriptApp.deleteTrigger(matchingTrigger);
Logger.log("Trigger deleted successfully.");
} else {
Logger.log("No matching trigger found.");
}
} catch (err) {
console.error("There was an error: ", err);
logErrorInAppSheet(properties().appID, properties().accessKey, `Wystąpił błąd podczas usuwania reguły dla kalendarza: ${calId}. Oryginalna treść błędu: ${err}`);
}
}
Te dwie funkcje są odpowiedzialne za tworzenie i usuwanie instalowalnych wyzwalaczy dla kalendarza określonego w zmiennej calId, która jest przekazywana do tych funkcji. Są one uruchamiane po dodaniu lub usunięciu rekordu z tabeli SYNCED_GOOGLE_CALEDARS. Tak może wyglądać widok aplikacji (gdzie zielone kółko ze strzałkami wskazuje, że wyzwalacz dla tego kalendarza został utworzony):
W tym przypadku aplikacja AppSheet działa jako główne oprogramowanie do zarządzania w firmie, więc widok zbiorczy jest kluczowy dla menedżera, aby zobaczyć wszystkie spotkania swoich pracowników, prace naprawcze itp. Istnieje tabela o nazwie EVENTS, która przechowuje wszystkie informacje o wydarzeniach ze wszystkich zsynchronizowanych kalendarzy. Możesz zdefiniować własną strukturę i przechowywać to, czego potrzebujesz z CalendarAPI.
Te 3 boty w połączeniu z Apps Script są odpowiedzialne za wykonywanie operacji na kalendarzu przypisanym do użytkownika:
Tak wyglądają boty od środka:
Jak widać, używam wartości zwracanej z funkcji Apps Script, która została uruchomiona w celu połączenia rekordu z tabeli EVENTS z nowo utworzonym wydarzeniem w Kalendarzu Google.
W przypadku jakiegokolwiek błędu, most integracyjny informuje administratora o takiej sytuacji, wysyłając żądanie API AppSheet do już istniejącego modułu informacyjnego w aplikacji, który wysyła powiadomienia push do określonych użytkowników. Gdy żądanie API z komunikatem o błędzie nie powiedzie się, wysyłana jest wiadomość e-mail do użytkownika z informacją, że wystąpił błąd w całym scenariuszu i że główna funkcja obsługi błędów również nie powiodła się.
/**
* Logs an error to AppSheet and sends an email notification if AppSheet logging fails.
*
* This function attempts to create a new record in the "your_table_name" table of the specified AppSheet application
* to log the error. If this fails, an email is sent to the specified address with details about both errors.
*
* @param {string} aid The AppSheet application ID.
* @param {string} ak The AppSheet application access key.
* @param {Error} err The error object to be logged.
*/
function logErrorInAppSheet(aid, ak, err) {
try {
const url = `https://api.appsheet.com/api/v2/apps/${aid}/tables/${your_table_name}/Action`;
const headers = {
"ApplicationAccessKey": ak
};
const payload = {
"Action": "Add",
"Properties": {
"Locale": "pl-PL",
"Timezone": "Central European Standard Time",
},
"Rows": [{
"your_column_name1": Utilities.getUuid(),
"your_column_name2": err,
"your_column_name3": "your_email@email.com",
"your_column_name4": "your_email@email.com",
"your_column_name5": "Error !!!",
"your_column_name6": "your_email@email.com",
}]
}
const response = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
headers: headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
})
} catch (e) {
console.error("There was an error: ", err);
MailApp.sendEmail("your_email@email.com", "[ERROR] your subject", `This is why the error notification API request didn't work: ${e}\nThis is the main error that happened before sending the error notification API request: ${err}`)
}
}
Szczerze mówiąc, ten projekt jest dość złożony i naprawdę trudno go w pełni wyjaśnić w jednym artykule, ale mam nadzieję, że zainspiruje to kogoś do zgłębienia tematu, ponieważ moim zdaniem ta integracja może zaoszczędzić wiele niepotrzebnie zmarnowanego czasu.
Mam nadzieję, że ten artykuł okazał się dla Ciebie interesujący.
Do następnego! 👋🏼