Files
twenty/packages/twenty-shared/src/constants/SystemPermissionFlag.ts
T
Félix Malfait 0e22ae0521 feat: create calendar events on Google and Microsoft accounts (#22231)
## 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>
2026-06-27 14:05:58 +02:00

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>;