0e22ae0521
## Context Twenty can import calendar events and send emails, but cannot create calendar events. This adds calendar event creation on connected **Google** and **Microsoft** accounts, mirroring the existing email-send architecture (`message-outbound-manager`). ## What it adds The capability is exposed three ways, all backed by the same composer → driver → persist pipeline: - **GraphQL mutation** `createCalendarEvent` (metadata API) - **AI agent tool** `create_calendar_event` (flows to MCP automatically), gated by a new `CREATE_CALENDAR_EVENT_TOOL` permission flag - **Workflow builder node** "Create Calendar Event" in the **Core** section, with a full settings form (variable interpolation supported) CalDAV/IMAP is intentionally out of scope for now (different long pole). ## Design notes - **Reuse over reinvention** — the created event is run through the existing inbound formatters (`formatGoogleCalendarEvents` / `formatMicrosoftCalendarEvents`) and persisted immediately via the existing `CalendarSaveEventsService`, so it appears in Twenty right away and is reconciled by the next provider sync (dedup on external id). Persistence is best-effort. - **OAuth scopes** — Google already requests `calendar.events` (read+write), so no change there. Microsoft moves `Calendars.Read` → `Calendars.ReadWrite`; existing Microsoft accounts must re-consent (surfaced as a clear "reconnect" error via a missing-scope check). - **Deliberate invitation semantics** — `sendInvitations` is off by default. When off, the event is created with **no attendees** on either provider, so creating an event never silently emails external people. When on, attendees are attached and notified (Google `sendUpdates: all`, Microsoft's default). This sidesteps Microsoft Graph having no per-request suppression. - **Timezone correctness** — Microsoft Graph interprets `dateTime` as wall-clock in the supplied `timeZone` and ignores the offset, so the absolute instant is converted to its wall-clock form before sending (Google honors the offset directly). Both providers end up scheduling the same instant. - **Conferencing** — optional Google Meet (`conferenceData.createRequest`, with a follow-up `events.get` to resolve the async link) / Microsoft Teams (`isOnlineMeeting`). - Attendees are a comma-separated string everywhere (tool input, GraphQL DTO, workflow input), consistent with `send_email` recipients; the composer parses to its internal list. ## Test plan - **Unit**: 45 tests covering the composer (validation, all-day boundaries, offset enforcement, timezone, scope checks, default-account resolution), both provider drivers, the dispatcher, and the workflow step-log builder. - **Integration**: `createCalendarEvent` on the `/metadata` API fails closed with a structured error for a non-existent account (the auth/ownership/validation path that doesn't require provider mocking). - **Manual**: verified the workflow node appears in the Core section, the settings form renders and round-trips (edit → autosave → reload), and the live mutation returns a structured failure for a bogus account. ## Open question for reviewers The metadata mutation `createCalendarEvent` shares a name with the core schema's auto-generated `createCalendarEvent(data:)` CRUD mutation for the CalendarEvent object — they live on different endpoints (`/metadata` vs `/graphql`) so there's no runtime conflict, but it's a potential point of confusion for API consumers. Happy to rename (e.g. `createCalendarEventOnConnectedAccount`) if preferred. ## Out of scope / follow-ups - CalDAV/IMAP support - Event update/delete and recurrence - Existing Microsoft accounts need re-consent for the widened scope <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22231?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: neo773 <neo773@protonmail.com>
31 lines
1.6 KiB
TypeScript
31 lines
1.6 KiB
TypeScript
import type { PermissionFlagType } from './PermissionFlagType';
|
|
|
|
export const SystemPermissionFlag = {
|
|
API_KEYS_AND_WEBHOOKS: 'beb99803-9a0e-588d-9c4b-3dd14d9cdf21',
|
|
WORKSPACE: '86a58859-a539-5fa8-ba18-a6aaddc811b6',
|
|
WORKSPACE_MEMBERS: 'ec342166-2df7-53e7-aa9b-64992d6cb35f',
|
|
ROLES: '80f1e5ab-484c-5660-a5a1-0ee7413d8190',
|
|
DATA_MODEL: 'f0c08013-3c9a-5489-bd45-a641fe5f085d',
|
|
SECURITY: '4a8b6016-1d3d-5c94-bbe8-a74c5f32335a',
|
|
WORKFLOWS: '6189e7bd-4051-5752-b6b1-5f31358fbaf1',
|
|
IMPERSONATE: 'dd2274d1-bbd5-551d-bcf2-3147539cef11',
|
|
SSO_BYPASS: '2e9cfdc0-4669-58ab-b241-b6c3c14d96df',
|
|
APPLICATIONS: '9faa55f7-2606-566b-9e1d-aefbedbd0136',
|
|
MARKETPLACE_APPS: '3a37e2f5-d3e4-56d6-b541-adf0237468ff',
|
|
LAYOUTS: '458d1ee5-ba12-5f4e-9d17-6f8c11fcd7fd',
|
|
BILLING: '24b66701-ed97-51e4-aa63-794e1b31414c',
|
|
AI_SETTINGS: 'e8cc4a30-c120-5925-8f5d-73d6a2247fee',
|
|
AI: '1c7f09a7-367d-568b-a0e8-c274eccd6ee3',
|
|
VIEWS: 'c68354c4-535d-53a9-848a-e3a6774b5fb8',
|
|
UPLOAD_FILE: 'dc6931a9-44bd-5164-a983-3db5db837f54',
|
|
DOWNLOAD_FILE: '6edf252f-228e-5e2f-9706-7bab7a796fd3',
|
|
SEND_EMAIL_TOOL: '19f382a8-689d-5792-a18c-737f0ce9025f',
|
|
CREATE_CALENDAR_EVENT_TOOL: '5b8c104e-24d3-4e89-986b-1d9208df6cf4',
|
|
HTTP_REQUEST_TOOL: '1bf49cf1-bb8f-5eb9-809a-5f34ea79fc6a',
|
|
CODE_INTERPRETER_TOOL: '11f31c3a-7042-54b7-a2dd-ae31dd9e9fa2',
|
|
IMPORT_CSV: 'c4912536-a4db-5af9-85ac-be2919c24c5e',
|
|
EXPORT_CSV: '88efced9-ca6d-5316-a695-f1b9158e7370',
|
|
CONNECTED_ACCOUNTS: 'e5f63b2d-5369-5df6-8f32-a8bd9e79e653',
|
|
PROFILE_INFORMATION: '7c13f23f-78d8-5e7d-8963-a7c8772fb4e8',
|
|
} as const satisfies Record<PermissionFlagType, string>;
|