diff --git a/app/src/components/composio/TriggerToggles.test.tsx b/app/src/components/composio/TriggerToggles.test.tsx index 904639933..be848fe6d 100644 --- a/app/src/components/composio/TriggerToggles.test.tsx +++ b/app/src/components/composio/TriggerToggles.test.tsx @@ -69,7 +69,7 @@ describe('', () => { expect(screen.getByText('Loading…')).toBeInTheDocument(); await waitFor(() => - expect(screen.getByLabelText(/Enable GMAIL_NEW_GMAIL_MESSAGE/)).toBeInTheDocument() + expect(screen.getByLabelText(/Enable Gmail New Gmail Message/)).toBeInTheDocument() ); expect(mockListAvailable).toHaveBeenCalledWith('gmail', 'c1'); expect(mockListTriggers).toHaveBeenCalledWith('gmail'); @@ -111,7 +111,7 @@ describe('', () => { render(); - const sw = await screen.findByLabelText(/Disable GMAIL_NEW_GMAIL_MESSAGE/); + const sw = await screen.findByLabelText(/Disable Gmail New Gmail Message/); expect(sw).toHaveAttribute('aria-checked', 'true'); }); @@ -127,7 +127,7 @@ describe('', () => { render(); - const sw = await screen.findByLabelText(/Enable GMAIL_NEW_GMAIL_MESSAGE/); + const sw = await screen.findByLabelText(/Enable Gmail New Gmail Message/); expect(sw).toHaveAttribute('aria-checked', 'false'); }); @@ -139,7 +139,7 @@ describe('', () => { render(); - const sw = await screen.findByLabelText(/Enable SLACK_NEW_MESSAGE/); + const sw = await screen.findByLabelText(/Enable Slack New Message/); expect(sw).toBeDisabled(); expect(screen.getByText('Needs configuration')).toBeInTheDocument(); }); @@ -159,11 +159,11 @@ describe('', () => { render(); - const sw = await screen.findByLabelText(/Enable GMAIL_NEW_GMAIL_MESSAGE/); + const sw = await screen.findByLabelText(/Enable Gmail New Gmail Message/); fireEvent.click(sw); await waitFor(() => - expect(screen.getByLabelText(/Disable GMAIL_NEW_GMAIL_MESSAGE/)).toHaveAttribute( + expect(screen.getByLabelText(/Disable Gmail New Gmail Message/)).toHaveAttribute( 'aria-checked', 'true' ) @@ -192,7 +192,7 @@ describe('', () => { render(); expect(await screen.findByText('acme/api')).toBeInTheDocument(); - fireEvent.click(screen.getByLabelText(/Enable GITHUB_PUSH_EVENT/)); + fireEvent.click(screen.getByLabelText(/Enable GitHub Push Event/)); await waitFor(() => expect(mockEnable).toHaveBeenCalled()); expect(mockEnable).toHaveBeenCalledWith('c1', 'GITHUB_PUSH_EVENT', { @@ -214,11 +214,11 @@ describe('', () => { render(); - const sw = await screen.findByLabelText(/Disable GMAIL_NEW_GMAIL_MESSAGE/); + const sw = await screen.findByLabelText(/Disable Gmail New Gmail Message/); fireEvent.click(sw); await waitFor(() => - expect(screen.getByLabelText(/Enable GMAIL_NEW_GMAIL_MESSAGE/)).toHaveAttribute( + expect(screen.getByLabelText(/Enable Gmail New Gmail Message/)).toHaveAttribute( 'aria-checked', 'false' ) @@ -235,11 +235,11 @@ describe('', () => { render(); - fireEvent.click(await screen.findByLabelText(/Enable GMAIL_NEW_GMAIL_MESSAGE/)); + fireEvent.click(await screen.findByLabelText(/Enable Gmail New Gmail Message/)); await waitFor(() => expect( - screen.getByText(/Enable failed for GMAIL_NEW_GMAIL_MESSAGE: upstream 500/) + screen.getByText(/Enable failed for Gmail New Gmail Message: upstream 500/) ).toBeInTheDocument() ); }); diff --git a/app/src/components/composio/TriggerToggles.tsx b/app/src/components/composio/TriggerToggles.tsx index 20d23d4f4..1be736433 100644 --- a/app/src/components/composio/TriggerToggles.tsx +++ b/app/src/components/composio/TriggerToggles.tsx @@ -6,6 +6,7 @@ import { listAvailableTriggers, listTriggers, } from '../../lib/composio/composioApi'; +import { formatTriggerLabel } from '../../lib/composio/formatters'; import type { ComposioActiveTrigger, ComposioAvailableTrigger } from '../../lib/composio/types'; /** @@ -122,7 +123,9 @@ export default function TriggerToggles({ } } catch (err) { const msg = err instanceof Error ? err.message : String(err); - setRowError(`${existing ? 'Disable' : 'Enable'} failed for ${entry.slug}: ${msg}`); + setRowError( + `${existing ? 'Disable' : 'Enable'} failed for ${formatTriggerLabel(entry.slug)}: ${msg}` + ); } finally { setPendingSignature(null); } @@ -177,13 +180,19 @@ export default function TriggerToggles({ const label = entry.scope === 'github_repo' && entry.repo ? `${entry.repo.owner}/${entry.repo.repo}` - : entry.slug; + : formatTriggerLabel(entry.slug); const sub = entry.scope === 'github_repo' - ? entry.slug + ? formatTriggerLabel(entry.slug) : requiresConfig ? 'Needs configuration' : ''; + const action = enabled ? 'Disable' : 'Enable'; + const triggerName = formatTriggerLabel(entry.slug); + const ariaLabel = + entry.scope === 'github_repo' && entry.repo + ? `${action} ${triggerName} for ${entry.repo.owner}/${entry.repo.repo}` + : `${action} ${triggerName}`; return (
  • void handleToggle(entry)} className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 ${ diff --git a/app/src/components/webhooks/ComposeioTriggerHistory.tsx b/app/src/components/webhooks/ComposeioTriggerHistory.tsx index 261ec799b..dec1d2e59 100644 --- a/app/src/components/webhooks/ComposeioTriggerHistory.tsx +++ b/app/src/components/webhooks/ComposeioTriggerHistory.tsx @@ -1,3 +1,4 @@ +import { formatTriggerLabel } from '../../lib/composio/formatters'; import type { ComposioTriggerHistoryEntry } from '../../utils/tauriCommands'; interface ComposeioTriggerHistoryProps { @@ -52,7 +53,7 @@ export default function ComposeioTriggerHistory({ entries }: ComposeioTriggerHis {entry.toolkit} - {entry.trigger} + {formatTriggerLabel(entry.trigger)} {formatTimestamp(entry.received_at_ms)} diff --git a/app/src/lib/composio/formatters.test.ts b/app/src/lib/composio/formatters.test.ts new file mode 100644 index 000000000..947b42485 --- /dev/null +++ b/app/src/lib/composio/formatters.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; + +import { formatTriggerLabel } from './formatters'; + +describe('formatTriggerLabel', () => { + it('formats GOOGLECALENDAR_GOOGLE_CALENDAR_EVENT_CREATED_TRIGGER correctly', () => { + expect(formatTriggerLabel('GOOGLECALENDAR_GOOGLE_CALENDAR_EVENT_CREATED_TRIGGER')).toBe( + 'Google Calendar Event Created' + ); + }); + + it('formats GITHUB_ISSUE_OPENED correctly', () => { + expect(formatTriggerLabel('GITHUB_ISSUE_OPENED')).toBe('GitHub Issue Opened'); + }); + + it('formats SLACK_MESSAGE_RECEIVED_TRIGGER correctly', () => { + expect(formatTriggerLabel('SLACK_MESSAGE_RECEIVED_TRIGGER')).toBe('Slack Message Received'); + }); + + it('handles empty string and undefined', () => { + expect(formatTriggerLabel('')).toBe(''); + expect(formatTriggerLabel(undefined as any)).toBe(''); + expect(formatTriggerLabel(null as any)).toBe(''); + }); + + it('respects overrides', () => { + const overrides = { GITHUB_ISSUE_OPENED: 'New Issue on GitHub' }; + expect(formatTriggerLabel('GITHUB_ISSUE_OPENED', { overrides })).toBe('New Issue on GitHub'); + }); + + it('dedupes leading provider prefix when it matches next token', () => { + expect(formatTriggerLabel('SLACK_SLACK_MESSAGE_RECEIVED')).toBe('Slack Message Received'); + }); + + it('handles case-insensitivity for _TRIGGER suffix', () => { + expect(formatTriggerLabel('GITHUB_PUSH_trigger')).toBe('GitHub Push'); + }); + + it('handles tokens with multiple consecutive underscores correctly', () => { + expect(formatTriggerLabel('LINEAR__ISSUE___CREATED')).toBe('Linear Issue Created'); + }); + + it('honors explicit empty-string override (hasOwnProperty path)', () => { + expect(formatTriggerLabel('NOOP', { overrides: { NOOP: '' } })).toBe(''); + }); +}); diff --git a/app/src/lib/composio/formatters.ts b/app/src/lib/composio/formatters.ts new file mode 100644 index 000000000..54dd78afc --- /dev/null +++ b/app/src/lib/composio/formatters.ts @@ -0,0 +1,50 @@ +/** + * Formats a Composio trigger slug into a human-readable label. + * + * Example: GOOGLECALENDAR_GOOGLE_CALENDAR_EVENT_CREATED_TRIGGER + * -> Google Calendar Event Created + * + * Rules: + * 1. empty/null input -> return '' + * 2. opts.overrides[slug] wins if present + * 3. strip trailing _TRIGGER (case-insensitive) + * 4. dedupe leading provider prefix when it reappears + * 5. split on _, title-case each token, join with space + */ +export function formatTriggerLabel( + slug: string | null | undefined, + opts?: { overrides?: Record } +): string { + if (!slug) return ''; + if (opts?.overrides && Object.prototype.hasOwnProperty.call(opts.overrides, slug)) { + return opts.overrides[slug] ?? ''; + } + + // Strip trailing _TRIGGER (case-insensitive) + const workingSlug = slug.replace(/_TRIGGER$/i, ''); + + const tokens = workingSlug.split('_').filter(t => t.length > 0); + + // Dedupe leading provider prefix + // e.g. GOOGLECALENDAR_GOOGLE_CALENDAR_EVENT_CREATED -> drop GOOGLECALENDAR + if (tokens.length > 1) { + const first = tokens[0].toUpperCase(); + const second = tokens[1].toUpperCase(); + + if (first === second) { + tokens.shift(); + } else if (tokens.length > 2) { + const third = tokens[2].toUpperCase(); + if (first === second + third) { + tokens.shift(); + } + } + } + + return tokens + .map(token => { + if (token.toUpperCase() === 'GITHUB') return 'GitHub'; + return token.charAt(0).toUpperCase() + token.slice(1).toLowerCase(); + }) + .join(' '); +}