diff --git a/components/google_calendar/package.json b/components/google_calendar/package.json index e8520f19d1e96..222e4d995ecdc 100644 --- a/components/google_calendar/package.json +++ b/components/google_calendar/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/google_calendar", - "version": "0.5.13", + "version": "0.6.0", "description": "Pipedream Google_calendar Components", "main": "google_calendar.app.mjs", "keywords": [ diff --git a/components/google_calendar/sources/upcoming-event-alert-polling/test-event.mjs b/components/google_calendar/sources/upcoming-event-alert-polling/test-event.mjs new file mode 100644 index 0000000000000..c9b9a9e57c7d2 --- /dev/null +++ b/components/google_calendar/sources/upcoming-event-alert-polling/test-event.mjs @@ -0,0 +1,33 @@ +export default { + "kind": "calendar#event", + "etag": "\"3442838491454000\"", + "id": "0dip62r3f3d85o35jjnjcmqbmo", + "status": "confirmed", + "htmlLink": "https://www.google.com/calendar/event?eid=MGRpcDYycjNW8zNWgbWljaGVsbGUucGlwZWRyZWFtQG0", + "created": "2024-07-19T20:00:45.000Z", + "updated": "2024-07-19T20:00:45.727Z", + "summary": "Upcoming Meeting", + "creator": { + "email": "test@sample.com", + "self": true + }, + "organizer": { + "email": "test@sample.com", + "self": true + }, + "start": { + "dateTime": "2024-07-19T16:07:00-04:00", + "timeZone": "America/Detroit" + }, + "end": { + "dateTime": "2024-07-19T17:07:00-04:00", + "timeZone": "America/Detroit" + }, + "iCalUID": "0dip62r35jjnjcmqbmo@google.com", + "sequence": 0, + "reminders": { + "useDefault": true + }, + "eventType": "default" +} + diff --git a/components/google_calendar/sources/upcoming-event-alert-polling/upcoming-event-alert-polling.mjs b/components/google_calendar/sources/upcoming-event-alert-polling/upcoming-event-alert-polling.mjs new file mode 100644 index 0000000000000..519eca4bccc4d --- /dev/null +++ b/components/google_calendar/sources/upcoming-event-alert-polling/upcoming-event-alert-polling.mjs @@ -0,0 +1,153 @@ +import common from "../common/common.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "google_calendar-upcoming-event-alert-polling", + name: "New Upcoming Event Alert (Polling)", + 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)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + db: "$.service.db", + pollingInfo: { + type: "alert", + alertType: "info", + 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.", + }, + calendarId: { + propDefinition: [ + common.props.googleCalendar, + "calendarId", + ], + }, + eventTypes: { + propDefinition: [ + common.props.googleCalendar, + "eventTypes", + ], + }, + minutesBefore: { + type: "integer", + label: "Minutes Before", + description: "Number of minutes to trigger before the start of the calendar event.", + min: 0, + default: 30, + }, + }, + methods: { + ...common.methods, + _getEmittedEvents() { + return this.db.get("emittedEvents") || {}; + }, + _setEmittedEvents(emittedEvents) { + this.db.set("emittedEvents", emittedEvents); + }, + _cleanupEmittedEvents(now) { + const emittedEvents = this._getEmittedEvents(); + const cleanedEvents = {}; + let cleanedCount = 0; + + // Keep only events that haven't passed yet + for (const [ + eventId, + startTime, + ] of Object.entries(emittedEvents)) { + if (startTime > now.getTime()) { + cleanedEvents[eventId] = startTime; + } else { + cleanedCount++; + } + } + + if (cleanedCount > 0) { + console.log(`Cleaned up ${cleanedCount} past event(s) from emitted events tracker`); + this._setEmittedEvents(cleanedEvents); + } + + return cleanedEvents; + }, + getConfig({ now }) { + // Get events starting from now up to the alert window + const timeMin = now.toISOString(); + // Look ahead to find events within our alert window + const alertWindowMs = this.minutesBefore * 60 * 1000; + const timeMax = new Date(now.getTime() + alertWindowMs).toISOString(); + + return { + calendarId: this.calendarId, + timeMin, + timeMax, + eventTypes: this.eventTypes, + singleEvents: true, + orderBy: "startTime", + }; + }, + isRelevant(event, { now }) { + // Skip cancelled events + if (event.status === "cancelled") { + return false; + } + + // Get event start time + const startTime = event.start + ? new Date(event.start.dateTime || event.start.date) + : null; + + if (!startTime) { + return false; + } + + // Calculate time remaining until event starts (in milliseconds) + const timeRemaining = startTime.getTime() - now.getTime(); + + // Skip past events + if (timeRemaining < 0) { + return false; + } + + // Convert minutesBefore to milliseconds + const alertThresholdMs = this.minutesBefore * 60 * 1000; + + // Clean up old emitted events and get the current list + const emittedEvents = this._cleanupEmittedEvents(now); + + // Check if we've already emitted this event + if (emittedEvents[event.id]) { + return false; + } + + // Emit if time remaining is less than or equal to the alert threshold + if (timeRemaining <= alertThresholdMs) { + // Mark this event as emitted with its start time for future cleanup + emittedEvents[event.id] = startTime.getTime(); + this._setEmittedEvents(emittedEvents); + return true; + } + + return false; + }, + generateMeta(event) { + const { + summary, + id, + } = event; + return { + summary: `Upcoming: ${summary || `Event ID: ${id}`}`, + id: `${id}-${Date.now()}`, + ts: Date.now(), + }; + }, + }, + hooks: { + async deploy() { + // On initial deploy, don't emit historical events + // Just initialize the emitted events tracker + this._setEmittedEvents({}); + }, + }, + sampleEmit, +}; + diff --git a/components/microsoft_outlook_calendar/package.json b/components/microsoft_outlook_calendar/package.json index 20c3e66ad15f5..f2acfc9d4a498 100644 --- a/components/microsoft_outlook_calendar/package.json +++ b/components/microsoft_outlook_calendar/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/microsoft_outlook_calendar", - "version": "0.3.4", + "version": "0.4.0", "description": "Pipedream Microsoft Outlook Calendar Components", "main": "microsoft_outlook_calendar.app.mjs", "keywords": [ diff --git a/components/microsoft_outlook_calendar/sources/new-upcoming-event-polling/new-upcoming-event-polling.mjs b/components/microsoft_outlook_calendar/sources/new-upcoming-event-polling/new-upcoming-event-polling.mjs new file mode 100644 index 0000000000000..357a33361f1af --- /dev/null +++ b/components/microsoft_outlook_calendar/sources/new-upcoming-event-polling/new-upcoming-event-polling.mjs @@ -0,0 +1,134 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import microsoftOutlook from "../../microsoft_outlook_calendar.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "microsoft_outlook_calendar-new-upcoming-event-polling", + name: "New Upcoming Calendar Event (Polling)", + 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)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + microsoftOutlook, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + pollingInfo: { + type: "alert", + alertType: "info", + 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.", + }, + minutesBefore: { + type: "integer", + label: "Minutes Before", + description: "Number of minutes to trigger before the start of the calendar event.", + min: 0, + default: 30, + }, + }, + methods: { + _getEmittedEvents() { + return this.db.get("emittedEvents") || {}; + }, + _setEmittedEvents(emittedEvents) { + this.db.set("emittedEvents", emittedEvents); + }, + _cleanupEmittedEvents(now) { + const emittedEvents = this._getEmittedEvents(); + const cleanedEvents = {}; + let cleanedCount = 0; + + // Keep only events that haven't passed yet + for (const [ + eventId, + startTime, + ] of Object.entries(emittedEvents)) { + if (startTime > now.getTime()) { + cleanedEvents[eventId] = startTime; + } else { + cleanedCount++; + } + } + + if (cleanedCount > 0) { + console.log(`Cleaned up ${cleanedCount} past event(s) from emitted events tracker`); + this._setEmittedEvents(cleanedEvents); + } + + return cleanedEvents; + }, + generateMeta(event) { + return { + id: `${event.id}-${Date.now()}`, + summary: `Upcoming: ${event.subject || `Event ID: ${event.id}`}`, + ts: Date.now(), + }; + }, + }, + hooks: { + async deploy() { + // On initial deploy, don't emit historical events + // Just initialize the emitted events tracker + this._setEmittedEvents({}); + }, + }, + async run() { + const now = new Date(); + const alertWindowMs = this.minutesBefore * 60 * 1000; + const timeMax = new Date(now.getTime() + alertWindowMs).toISOString(); + + // Clean up old emitted events + const emittedEvents = this._cleanupEmittedEvents(now); + + // Fetch events within the alert window + const { value: events } = await this.microsoftOutlook.listCalendarView({ + params: { + startDateTime: now.toISOString(), + endDateTime: timeMax, + $orderby: "start/dateTime", + }, + }); + + if (!events || events.length === 0) { + console.log("No upcoming events found in the alert window"); + return; + } + + for (const event of events) { + // Skip if already emitted + if (emittedEvents[event.id]) { + continue; + } + + const startTime = event.start + ? new Date(event.start.dateTime) + : null; + + if (!startTime) { + continue; + } + + const timeRemaining = startTime.getTime() - now.getTime(); + if (timeRemaining < 0) { + continue; + } + + const alertThresholdMs = this.minutesBefore * 60 * 1000; + + // Emit if time remaining is less than or equal to the alert threshold + if (timeRemaining <= alertThresholdMs) { + emittedEvents[event.id] = startTime.getTime(); + this._setEmittedEvents(emittedEvents); + + this.$emit(event, this.generateMeta(event)); + } + } + }, + sampleEmit, +}; +