Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions tests/unit/web/triggerAgentMapping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, expect, it } from 'vitest';
import { getTriggersForAgent } from '../../../web/src/lib/trigger-agent-mapping.js';

describe('getTriggersForAgent', () => {
it('returns all triggers when no opts given (backward compatibility)', () => {
const triggers = getTriggersForAgent('review');
expect(triggers).toHaveLength(3);
expect(triggers.map((t) => t.key)).toEqual([
'reviewTrigger.ownPrsOnly',
'reviewTrigger.externalPrs',
'reviewTrigger.onReviewRequested',
]);
});

it('returns empty array for review agent with category: pm', () => {
const triggers = getTriggersForAgent('review', { category: 'pm' });
expect(triggers).toHaveLength(0);
});

it('returns 3 review triggers for review agent with category: scm', () => {
const triggers = getTriggersForAgent('review', { category: 'scm' });
expect(triggers).toHaveLength(3);
for (const t of triggers) {
expect(t.category).toBe('scm');
}
});

it('returns PM-only triggers for briefing with category: pm and pmProvider: trello', () => {
const triggers = getTriggersForAgent('briefing', { category: 'pm', pmProvider: 'trello' });
expect(triggers.length).toBeGreaterThan(0);
for (const t of triggers) {
expect(t.category).toBe('pm');
// Should not include JIRA-only triggers
if (t.pmProvider) {
expect(t.pmProvider).toBe('trello');
}
}
const keys = triggers.map((t) => t.key);
expect(keys).toContain('cardMovedToBriefing');
expect(keys).toContain('readyToProcessLabel.briefing');
expect(keys).not.toContain('issueTransitioned.briefing');
});

it('returns empty array for briefing with category: scm', () => {
const triggers = getTriggersForAgent('briefing', { category: 'scm' });
expect(triggers).toHaveLength(0);
});

it('filters by pmProvider without category', () => {
const jiraTriggers = getTriggersForAgent('briefing', { pmProvider: 'jira' });
const trelloTriggers = getTriggersForAgent('briefing', { pmProvider: 'trello' });
// JIRA provider should exclude cardMovedToBriefing (trello-only)
expect(jiraTriggers.map((t) => t.key)).not.toContain('cardMovedToBriefing');
// Trello provider should exclude issueTransitioned.briefing (jira-only)
expect(trelloTriggers.map((t) => t.key)).not.toContain('issueTransitioned.briefing');
});

it('returns empty array for unknown agent type', () => {
const triggers = getTriggersForAgent('unknown-agent', { category: 'pm' });
expect(triggers).toHaveLength(0);
});
});

describe('getTriggersForAgent — review trigger dot-notation keys and defaults', () => {
it('returns dot-notation keys for review SCM triggers', () => {
const triggerDefs = getTriggersForAgent('review', { category: 'scm' });

// Verify that the trigger definitions have the expected dot-notation keys
expect(triggerDefs.map((t) => t.key)).toEqual([
'reviewTrigger.ownPrsOnly',
'reviewTrigger.externalPrs',
'reviewTrigger.onReviewRequested',
]);

// Verify each trigger has the correct category
for (const t of triggerDefs) {
expect(t.category).toBe('scm');
}
});

it('returns correct defaultValues for review triggers', () => {
const triggers = getTriggersForAgent('review', { category: 'scm' });
const defaults = Object.fromEntries(triggers.map((t) => [t.key, t.defaultValue]));
expect(defaults['reviewTrigger.ownPrsOnly']).toBe(false);
expect(defaults['reviewTrigger.externalPrs']).toBe(false);
expect(defaults['reviewTrigger.onReviewRequested']).toBe(false);
});
});
9 changes: 3 additions & 6 deletions web/src/components/projects/project-agent-configs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ function AgentSection({
const [pmSaved, setPmSaved] = useState(false);
const [scmSaved, setScmSaved] = useState(false);

const agentPmTriggers = getTriggersForAgent(agentType, pmProvider);
const agentScmTriggers = getTriggersForAgent(agentType).filter((t) => t.category === 'scm');
const agentPmTriggers = getTriggersForAgent(agentType, { pmProvider, category: 'pm' });
const agentScmTriggers = getTriggersForAgent(agentType, { category: 'scm' });

const hasTriggers = agentPmTriggers.length > 0 || agentScmTriggers.length > 0;

Expand Down Expand Up @@ -144,10 +144,7 @@ function AgentSection({
const handleSaveScm = async () => {
setScmSaving(true);
try {
const relevant: Record<string, unknown> = {};
for (const t of agentScmTriggers) {
relevant[t.key] = localScmTriggers[t.key] ?? t.defaultValue;
}
const relevant = extractRelevantTriggers(agentScmTriggers, localScmTriggers);
await onSaveTriggers('scm', relevant, agentType);
setScmSaved(true);
setTimeout(() => setScmSaved(false), 2000);
Expand Down
10 changes: 7 additions & 3 deletions web/src/lib/trigger-agent-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,16 @@ export const AGENT_TRIGGER_MAP: Record<string, TriggerDef[]> = {
};

/**
* Get trigger definitions for a specific agent type, filtered by PM provider.
* Get trigger definitions for a specific agent type, filtered by PM provider and/or category.
*/
export function getTriggersForAgent(agentType: string, pmProvider?: string): TriggerDef[] {
export function getTriggersForAgent(
agentType: string,
opts?: { pmProvider?: string; category?: 'pm' | 'scm' },
): TriggerDef[] {
const triggers = AGENT_TRIGGER_MAP[agentType] ?? [];
return triggers.filter((t) => {
if (t.pmProvider && pmProvider && t.pmProvider !== pmProvider) return false;
if (opts?.category && t.category !== opts.category) return false;
if (t.pmProvider && opts?.pmProvider && t.pmProvider !== opts.pmProvider) return false;
return true;
});
}
Expand Down