feat(composio): format trigger slugs into human-readable labels (#1129) (#1179)

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
oxoxDev
2026-05-04 12:34:38 -07:00
committed by GitHub
co-authored by google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
parent 45a04da6e2
commit 34a6b4021f
5 changed files with 122 additions and 16 deletions
@@ -69,7 +69,7 @@ describe('<TriggerToggles>', () => {
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="gmail" toolkitName="Gmail" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="gmail" toolkitName="Gmail" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="slack" toolkitName="Slack" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="gmail" toolkitName="Gmail" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="github" toolkitName="GitHub" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="gmail" toolkitName="Gmail" connectionId="c1" />);
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('<TriggerToggles>', () => {
render(<TriggerToggles toolkitSlug="gmail" toolkitName="Gmail" connectionId="c1" />);
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()
);
});
+13 -4
View File
@@ -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 (
<li
@@ -198,7 +207,7 @@ export default function TriggerToggles({
type="button"
role="switch"
aria-checked={enabled}
aria-label={`${enabled ? 'Disable' : 'Enable'} ${entry.slug}`}
aria-label={ariaLabel}
disabled={disabled}
onClick={() => 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 ${
@@ -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}
</span>
<span className="rounded-full bg-sage-50 px-2.5 py-1 text-xs font-medium text-sage-700">
{entry.trigger}
{formatTriggerLabel(entry.trigger)}
</span>
<span className="text-xs text-stone-500">
{formatTimestamp(entry.received_at_ms)}
+46
View File
@@ -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('');
});
});
+50
View File
@@ -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, string> }
): 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(' ');
}