feat(campaigns): prefix test names with [Campaign] on add
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- From template: name is pre-filled as '[Campaign] {template.name}'
(user can edit before confirming).
- Existing test: renamed via PATCH /tests/{id} to prepend '[Campaign] '
before being linked to the campaign, consistent with the APT-generated
campaign flow.
Idempotent — skips rename if the name already starts with '[Campaign]'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,9 @@ import {
|
|||||||
ChevronRight,
|
ChevronRight,
|
||||||
Filter,
|
Filter,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { getTests } from "../api/tests";
|
import { getTests, createTestFromTemplate, updateTest } from "../api/tests";
|
||||||
import { addTestToCampaign } from "../api/campaigns";
|
import { addTestToCampaign } from "../api/campaigns";
|
||||||
import { getTemplates, getTemplateById } from "../api/test-templates";
|
import { getTemplates, getTemplateById } from "../api/test-templates";
|
||||||
import { createTestFromTemplate } from "../api/tests";
|
|
||||||
import type { Test, TestState, TestTemplateSummary } from "../types/models";
|
import type { Test, TestState, TestTemplateSummary } from "../types/models";
|
||||||
|
|
||||||
/* ── helpers ─────────────────────────────────────────────────────── */
|
/* ── helpers ─────────────────────────────────────────────────────── */
|
||||||
@@ -130,10 +129,13 @@ export default function AddTestToCampaignModal({
|
|||||||
enabled: !!selectedTemplateId,
|
enabled: !!selectedTemplateId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pre-fill form when full template loads
|
// Pre-fill form when full template loads — always prefix with [Campaign]
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fullTemplate) {
|
if (fullTemplate) {
|
||||||
setFormName(fullTemplate.name);
|
const baseName = fullTemplate.name.startsWith("[Campaign]")
|
||||||
|
? fullTemplate.name
|
||||||
|
: `[Campaign] ${fullTemplate.name}`;
|
||||||
|
setFormName(baseName);
|
||||||
setFormDescription(fullTemplate.description || "");
|
setFormDescription(fullTemplate.description || "");
|
||||||
setFormPlatform(fullTemplate.platform || "");
|
setFormPlatform(fullTemplate.platform || "");
|
||||||
setFormProcedure(fullTemplate.attack_procedure || "");
|
setFormProcedure(fullTemplate.attack_procedure || "");
|
||||||
@@ -143,14 +145,20 @@ export default function AddTestToCampaignModal({
|
|||||||
|
|
||||||
// ── mutations ─────────────────────────────────────────────────────
|
// ── mutations ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Add an existing test directly to the campaign */
|
/** Add an existing test to the campaign, renaming it with [Campaign] prefix first */
|
||||||
const addExistingMutation = useMutation({
|
const addExistingMutation = useMutation({
|
||||||
mutationFn: (testId: string) =>
|
mutationFn: async (test: Test) => {
|
||||||
addTestToCampaign(campaignId, { test_id: testId }),
|
// Rename the test to add [Campaign] prefix if not already present
|
||||||
onSuccess: (_data, testId) => {
|
if (!test.name.startsWith("[Campaign]")) {
|
||||||
setAddedIds((prev) => new Set(prev).add(testId));
|
await updateTest(test.id, { name: `[Campaign] ${test.name}` });
|
||||||
|
}
|
||||||
|
return addTestToCampaign(campaignId, { test_id: test.id });
|
||||||
|
},
|
||||||
|
onSuccess: (_data, test) => {
|
||||||
|
setAddedIds((prev) => new Set(prev).add(test.id));
|
||||||
setAddedCount((n) => n + 1);
|
setAddedCount((n) => n + 1);
|
||||||
queryClient.invalidateQueries({ queryKey: ["campaign", campaignId] });
|
queryClient.invalidateQueries({ queryKey: ["campaign", campaignId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["tests"] });
|
||||||
onSuccess();
|
onSuccess();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -528,11 +536,11 @@ export default function AddTestToCampaignModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => addExistingMutation.mutate(test.id)}
|
onClick={() => addExistingMutation.mutate(test)}
|
||||||
disabled={addExistingMutation.isPending && addExistingMutation.variables === test.id}
|
disabled={addExistingMutation.isPending && addExistingMutation.variables?.id === test.id}
|
||||||
className="ml-3 flex shrink-0 items-center gap-1.5 rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-3 py-1.5 text-xs font-medium text-cyan-400 hover:bg-cyan-500/20 disabled:opacity-50 transition-colors"
|
className="ml-3 flex shrink-0 items-center gap-1.5 rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-3 py-1.5 text-xs font-medium text-cyan-400 hover:bg-cyan-500/20 disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
{addExistingMutation.isPending && addExistingMutation.variables === test.id ? (
|
{addExistingMutation.isPending && addExistingMutation.variables?.id === test.id ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
|
|||||||
Reference in New Issue
Block a user