fix: allow testing claim mapping rules before saving and keep rows editable after test
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m27s
CI / docker (push) Successful in 1m10s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 41s

The test endpoint now accepts inline rules from the client instead of reading
from the database, so unsaved rules can be tested. Matched rows show the
checkmark alongside action buttons instead of replacing them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-14 18:52:18 +02:00
parent 891abbfcfd
commit 9ac8e3604c
3 changed files with 46 additions and 19 deletions

View File

@@ -90,12 +90,22 @@ export function useDeleteClaimMappingRule() {
});
}
export interface TestRuleInput {
id: string;
claim: string;
matchType: string;
matchValue: string;
action: string;
target: string;
priority: number;
}
export function useTestClaimMappingRules() {
return useMutation({
mutationFn: (claims: Record<string, unknown>) =>
mutationFn: ({ rules, claims }: { rules: TestRuleInput[]; claims: Record<string, unknown> }) =>
adminFetch<TestResponse>('/claim-mappings/test', {
method: 'POST',
body: JSON.stringify(claims),
body: JSON.stringify({ rules, claims }),
}),
});
}

View File

@@ -250,7 +250,11 @@ export default function ClaimMappingRulesModal({ open, onClose }: Props) {
setTestError('Invalid JSON — paste a decoded JWT claims object');
return;
}
testRules.mutate(claims, {
const rules = localRules.map((r) => ({
id: r.id, claim: r.claim, matchType: r.matchType,
matchValue: r.matchValue, action: r.action, target: r.target, priority: r.priority,
}));
testRules.mutate({ rules, claims }, {
onSuccess: (result) => setTestResult(result),
onError: (e) => setTestError(e.message),
});
@@ -333,16 +337,13 @@ export default function ClaimMappingRulesModal({ open, onClose }: Props) {
<td><span className={`${styles.pill} ${actionPillClass(rule.action)}`}>{actionLabel(rule.action)}</span></td>
<td className={styles.targetCell}>{rule.target}</td>
<td>
{isMatched ? (
<span className={styles.matchCheck}><Check size={14} /></span>
) : (
<div className={styles.actions}>
<button className={styles.iconBtn} onClick={() => handleMove(rule, 'up')} disabled={idx === 0} title="Move up"><ChevronUp size={14} /></button>
<button className={styles.iconBtn} onClick={() => handleMove(rule, 'down')} disabled={idx === sorted.length - 1} title="Move down"><ChevronDown size={14} /></button>
<button className={styles.iconBtn} onClick={() => startEdit(rule)} title="Edit"><Pencil size={14} /></button>
<button className={styles.iconBtn} onClick={() => setDeleteTarget(rule)} title="Delete"><X size={14} /></button>
</div>
)}
<div className={styles.actions}>
{isMatched && <span className={styles.matchCheck}><Check size={14} /></span>}
<button className={styles.iconBtn} onClick={() => handleMove(rule, 'up')} disabled={idx === 0} title="Move up"><ChevronUp size={14} /></button>
<button className={styles.iconBtn} onClick={() => handleMove(rule, 'down')} disabled={idx === sorted.length - 1} title="Move down"><ChevronDown size={14} /></button>
<button className={styles.iconBtn} onClick={() => startEdit(rule)} title="Edit"><Pencil size={14} /></button>
<button className={styles.iconBtn} onClick={() => setDeleteTarget(rule)} title="Delete"><X size={14} /></button>
</div>
</td>
</>
)}