Skip to content

Commit 8031f71

Browse files
authored
Google Calendar & Microsoft Outlook Calendar - adding polling sources for upcoming events (#18976)
* Adding upcoming-event-alert-polling source * Adding microsoft calendar source * Removing unused test event import * Adjusting id for deduping
1 parent b5e83dc commit 8031f71

File tree

5 files changed

+325
-2
lines changed

5 files changed

+325
-2
lines changed

components/google_calendar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/google_calendar",
3-
"version": "0.5.13",
3+
"version": "0.6.0",
44
"description": "Pipedream Google_calendar Components",
55
"main": "google_calendar.app.mjs",
66
"keywords": [
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export default {
2+
"kind": "calendar#event",
3+
"etag": "\"3442838491454000\"",
4+
"id": "0dip62r3f3d85o35jjnjcmqbmo",
5+
"status": "confirmed",
6+
"htmlLink": "https://www.google.com/calendar/event?eid=MGRpcDYycjNW8zNWgbWljaGVsbGUucGlwZWRyZWFtQG0",
7+
"created": "2024-07-19T20:00:45.000Z",
8+
"updated": "2024-07-19T20:00:45.727Z",
9+
"summary": "Upcoming Meeting",
10+
"creator": {
11+
"email": "test@sample.com",
12+
"self": true
13+
},
14+
"organizer": {
15+
"email": "test@sample.com",
16+
"self": true
17+
},
18+
"start": {
19+
"dateTime": "2024-07-19T16:07:00-04:00",
20+
"timeZone": "America/Detroit"
21+
},
22+
"end": {
23+
"dateTime": "2024-07-19T17:07:00-04:00",
24+
"timeZone": "America/Detroit"
25+
},
26+
"iCalUID": "0dip62r35jjnjcmqbmo@google.com",
27+
"sequence": 0,
28+
"reminders": {
29+
"useDefault": true
30+
},
31+
"eventType": "default"
32+
}
33+
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import common from "../common/common.mjs";
2+
import sampleEmit from "./test-event.mjs";
3+
4+
export default {
5+
...common,
6+
key: "google_calendar-upcoming-event-alert-polling",
7+
name: "New Upcoming Event Alert (Polling)",
8+
description: "Emit new event based on a time interval before an upcoming event in the calendar. [See the documentation](https://developers.google.com/calendar/api/v3/reference/events/list)",
9+
version: "0.0.1",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
...common.props,
14+
db: "$.service.db",
15+
pollingInfo: {
16+
type: "alert",
17+
alertType: "info",
18+
content: "Since this source executes based on a timer, event emission may be slightly delayed. For example, if the source runs every 5 minutes, the delay may be up to 5 minutes. You can use the `upcoming-event-alert` source for instant event emission.",
19+
},
20+
calendarId: {
21+
propDefinition: [
22+
common.props.googleCalendar,
23+
"calendarId",
24+
],
25+
},
26+
eventTypes: {
27+
propDefinition: [
28+
common.props.googleCalendar,
29+
"eventTypes",
30+
],
31+
},
32+
minutesBefore: {
33+
type: "integer",
34+
label: "Minutes Before",
35+
description: "Number of minutes to trigger before the start of the calendar event.",
36+
min: 0,
37+
default: 30,
38+
},
39+
},
40+
methods: {
41+
...common.methods,
42+
_getEmittedEvents() {
43+
return this.db.get("emittedEvents") || {};
44+
},
45+
_setEmittedEvents(emittedEvents) {
46+
this.db.set("emittedEvents", emittedEvents);
47+
},
48+
_cleanupEmittedEvents(now) {
49+
const emittedEvents = this._getEmittedEvents();
50+
const cleanedEvents = {};
51+
let cleanedCount = 0;
52+
53+
// Keep only events that haven't passed yet
54+
for (const [
55+
eventId,
56+
startTime,
57+
] of Object.entries(emittedEvents)) {
58+
if (startTime > now.getTime()) {
59+
cleanedEvents[eventId] = startTime;
60+
} else {
61+
cleanedCount++;
62+
}
63+
}
64+
65+
if (cleanedCount > 0) {
66+
console.log(`Cleaned up ${cleanedCount} past event(s) from emitted events tracker`);
67+
this._setEmittedEvents(cleanedEvents);
68+
}
69+
70+
return cleanedEvents;
71+
},
72+
getConfig({ now }) {
73+
// Get events starting from now up to the alert window
74+
const timeMin = now.toISOString();
75+
// Look ahead to find events within our alert window
76+
const alertWindowMs = this.minutesBefore * 60 * 1000;
77+
const timeMax = new Date(now.getTime() + alertWindowMs).toISOString();
78+
79+
return {
80+
calendarId: this.calendarId,
81+
timeMin,
82+
timeMax,
83+
eventTypes: this.eventTypes,
84+
singleEvents: true,
85+
orderBy: "startTime",
86+
};
87+
},
88+
isRelevant(event, { now }) {
89+
// Skip cancelled events
90+
if (event.status === "cancelled") {
91+
return false;
92+
}
93+
94+
// Get event start time
95+
const startTime = event.start
96+
? new Date(event.start.dateTime || event.start.date)
97+
: null;
98+
99+
if (!startTime) {
100+
return false;
101+
}
102+
103+
// Calculate time remaining until event starts (in milliseconds)
104+
const timeRemaining = startTime.getTime() - now.getTime();
105+
106+
// Skip past events
107+
if (timeRemaining < 0) {
108+
return false;
109+
}
110+
111+
// Convert minutesBefore to milliseconds
112+
const alertThresholdMs = this.minutesBefore * 60 * 1000;
113+
114+
// Clean up old emitted events and get the current list
115+
const emittedEvents = this._cleanupEmittedEvents(now);
116+
117+
// Check if we've already emitted this event
118+
if (emittedEvents[event.id]) {
119+
return false;
120+
}
121+
122+
// Emit if time remaining is less than or equal to the alert threshold
123+
if (timeRemaining <= alertThresholdMs) {
124+
// Mark this event as emitted with its start time for future cleanup
125+
emittedEvents[event.id] = startTime.getTime();
126+
this._setEmittedEvents(emittedEvents);
127+
return true;
128+
}
129+
130+
return false;
131+
},
132+
generateMeta(event) {
133+
const {
134+
summary,
135+
id,
136+
} = event;
137+
return {
138+
summary: `Upcoming: ${summary || `Event ID: ${id}`}`,
139+
id: `${id}-${Date.now()}`,
140+
ts: Date.now(),
141+
};
142+
},
143+
},
144+
hooks: {
145+
async deploy() {
146+
// On initial deploy, don't emit historical events
147+
// Just initialize the emitted events tracker
148+
this._setEmittedEvents({});
149+
},
150+
},
151+
sampleEmit,
152+
};
153+

components/microsoft_outlook_calendar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/microsoft_outlook_calendar",
3-
"version": "0.3.4",
3+
"version": "0.4.0",
44
"description": "Pipedream Microsoft Outlook Calendar Components",
55
"main": "microsoft_outlook_calendar.app.mjs",
66
"keywords": [
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
2+
import microsoftOutlook from "../../microsoft_outlook_calendar.app.mjs";
3+
4+
export default {
5+
key: "microsoft_outlook_calendar-new-upcoming-event-polling",
6+
name: "New Upcoming Calendar Event (Polling)",
7+
description: "Emit new event based on a time interval before an upcoming calendar event. [See the documentation](https://docs.microsoft.com/en-us/graph/api/user-list-events)",
8+
version: "0.0.1",
9+
type: "source",
10+
dedupe: "unique",
11+
props: {
12+
microsoftOutlook,
13+
db: "$.service.db",
14+
timer: {
15+
type: "$.interface.timer",
16+
default: {
17+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
18+
},
19+
},
20+
pollingInfo: {
21+
type: "alert",
22+
alertType: "info",
23+
content: "Since this source executes based on a timer, event emission may be slightly delayed. For example, if the source runs every 5 minutes, the delay may be up to 5 minutes. You can use the `new-upcoming-event` source for instant event emission.",
24+
},
25+
minutesBefore: {
26+
type: "integer",
27+
label: "Minutes Before",
28+
description: "Number of minutes to trigger before the start of the calendar event.",
29+
min: 0,
30+
default: 30,
31+
},
32+
},
33+
methods: {
34+
_getEmittedEvents() {
35+
return this.db.get("emittedEvents") || {};
36+
},
37+
_setEmittedEvents(emittedEvents) {
38+
this.db.set("emittedEvents", emittedEvents);
39+
},
40+
_cleanupEmittedEvents(now) {
41+
const emittedEvents = this._getEmittedEvents();
42+
const cleanedEvents = {};
43+
let cleanedCount = 0;
44+
45+
// Keep only events that haven't passed yet
46+
for (const [
47+
eventId,
48+
startTime,
49+
] of Object.entries(emittedEvents)) {
50+
if (startTime > now.getTime()) {
51+
cleanedEvents[eventId] = startTime;
52+
} else {
53+
cleanedCount++;
54+
}
55+
}
56+
57+
if (cleanedCount > 0) {
58+
console.log(`Cleaned up ${cleanedCount} past event(s) from emitted events tracker`);
59+
this._setEmittedEvents(cleanedEvents);
60+
}
61+
62+
return cleanedEvents;
63+
},
64+
generateMeta(event) {
65+
const ts = event.start?.dateTime
66+
? Date.parse(event.start.dateTime)
67+
: Date.now();
68+
return {
69+
id: `${event.uid}-${ts}`,
70+
summary: `Upcoming: ${event.subject || "(untitled)"}`,
71+
ts,
72+
};
73+
},
74+
},
75+
hooks: {
76+
async deploy() {
77+
// On initial deploy, don't emit historical events
78+
// Just initialize the emitted events tracker
79+
this._setEmittedEvents({});
80+
},
81+
},
82+
async run() {
83+
const now = new Date();
84+
const alertWindowMs = this.minutesBefore * 60 * 1000;
85+
const timeMax = new Date(now.getTime() + alertWindowMs).toISOString();
86+
87+
// Clean up old emitted events
88+
const emittedEvents = this._cleanupEmittedEvents(now);
89+
90+
// Fetch events within the alert window
91+
const { value: events } = await this.microsoftOutlook.listCalendarView({
92+
params: {
93+
startDateTime: now.toISOString(),
94+
endDateTime: timeMax,
95+
$orderby: "start/dateTime",
96+
},
97+
});
98+
99+
if (!events || events.length === 0) {
100+
console.log("No upcoming events found in the alert window");
101+
return;
102+
}
103+
104+
for (const event of events) {
105+
// Skip if already emitted
106+
if (emittedEvents[event.uid]) {
107+
continue;
108+
}
109+
110+
const startTime = event.start
111+
? new Date(event.start.dateTime)
112+
: null;
113+
114+
if (!startTime) {
115+
continue;
116+
}
117+
118+
const timeRemaining = startTime.getTime() - now.getTime();
119+
if (timeRemaining < 0) {
120+
continue;
121+
}
122+
123+
const alertThresholdMs = this.minutesBefore * 60 * 1000;
124+
125+
// Emit if time remaining is less than or equal to the alert threshold
126+
if (timeRemaining <= alertThresholdMs) {
127+
emittedEvents[event.uid] = startTime.getTime();
128+
129+
const meta = this.generateMeta(event);
130+
this.$emit(event, meta);
131+
}
132+
}
133+
134+
this._setEmittedEvents(emittedEvents);
135+
},
136+
};
137+

0 commit comments

Comments
 (0)