feat(campaigns): campaign start date — scheduled activation, Jira start_date
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
DB: migration b047 adds start_date (DateTime nullable) + index to campaigns. Backend: - Campaign model: start_date field - CampaignCreate/Update schemas: accept start_date (ISO string) - CRUD service: persist + serialize start_date in both serializers - Activation endpoint: blocks manual activation if start_date is in the future (campaign will auto-activate via scheduler) - Scheduler: new hourly job _run_scheduled_campaign_activation — finds draft campaigns with start_date <= now, activates them, creates Jira tickets, notifies red_tech team - Jira: campaign + test tickets now include JIRA_START_DATE_FIELD (configurable, default customfield_10015). Campaign uses start_date if set, else created_at. Tests inherit campaign start_date. - config.py: JIRA_START_DATE_FIELD setting Frontend: - Campaign type: start_date field on Campaign + CampaignSummary - CampaignCreatePayload: start_date optional field - Create form: date picker with min=today, warning message explaining behavior - Campaign detail header: start_date badge showing days remaining or started date Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,7 @@ export default function CampaignsPage() {
|
||||
description: "",
|
||||
type: "custom",
|
||||
target_platform: "",
|
||||
start_date: "",
|
||||
});
|
||||
|
||||
const canCreate = user?.role === "admin" || user?.role === "red_lead" || user?.role === "blue_lead";
|
||||
@@ -73,7 +74,7 @@ export default function CampaignsPage() {
|
||||
onSuccess: (campaign) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
|
||||
setShowCreateForm(false);
|
||||
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "" });
|
||||
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "", start_date: "" });
|
||||
navigate(`/campaigns/${campaign.id}`);
|
||||
},
|
||||
});
|
||||
@@ -224,6 +225,29 @@ export default function CampaignsPage() {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Start date */}
|
||||
<div className="mt-4">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||
Start date
|
||||
<span className="ml-2 text-xs font-normal text-gray-500">
|
||||
(optional — campaign activates automatically on this date)
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={newCampaign.start_date}
|
||||
min={new Date().toISOString().split("T")[0]}
|
||||
onChange={(e) => setNewCampaign((c) => ({ ...c, start_date: e.target.value }))}
|
||||
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 focus:border-cyan-500 focus:outline-none [color-scheme:dark]"
|
||||
/>
|
||||
{newCampaign.start_date && (
|
||||
<p className="mt-1.5 flex items-center gap-1.5 text-xs text-amber-400">
|
||||
<span>⏰</span>
|
||||
Tests won't be queued until {new Date(newCampaign.start_date + "T00:00:00").toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" })}. Manual activation before that date is blocked.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user