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 @@ class CampaignCreate(BaseModel):
|
||||
target_platform: Optional[str] = None
|
||||
tags: Optional[list[str]] = Field(default_factory=list)
|
||||
scheduled_at: Optional[str] = None
|
||||
start_date: Optional[str] = None # ISO date — campaign won't activate before this
|
||||
|
||||
class CampaignUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
@@ -62,6 +63,7 @@ class CampaignUpdate(BaseModel):
|
||||
target_platform: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
scheduled_at: Optional[str] = None
|
||||
start_date: Optional[str] = None # ISO date — can be updated while still in draft
|
||||
|
||||
class AddTestPayload(BaseModel):
|
||||
test_id: str
|
||||
@@ -125,6 +127,7 @@ def create_campaign(
|
||||
target_platform=payload.target_platform,
|
||||
tags=payload.tags,
|
||||
scheduled_at=payload.scheduled_at,
|
||||
start_date=payload.start_date,
|
||||
)
|
||||
campaign_id = result["id"]
|
||||
log_action(
|
||||
@@ -273,7 +276,27 @@ def activate_campaign(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Activate a campaign, moving it from draft to active."""
|
||||
"""Activate a campaign, moving it from draft to active.
|
||||
|
||||
If the campaign has a start_date in the future, manual activation is blocked —
|
||||
the campaign will be auto-activated by the scheduler when the date arrives.
|
||||
"""
|
||||
# Guard: start_date must have been reached before manual activation
|
||||
campaign_obj = db.query(Campaign).filter(Campaign.id == campaign_id).first()
|
||||
if campaign_obj and campaign_obj.start_date:
|
||||
now = datetime.utcnow()
|
||||
if campaign_obj.start_date > now:
|
||||
from fastapi import HTTPException
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=(
|
||||
f"This campaign is scheduled to start on "
|
||||
f"{campaign_obj.start_date.strftime('%Y-%m-%d')}. "
|
||||
f"It will be activated automatically on that date. "
|
||||
f"To activate it now, remove the start date first."
|
||||
),
|
||||
)
|
||||
|
||||
with UnitOfWork(db) as uow:
|
||||
campaign = crud_activate(db, campaign_id)
|
||||
notify_role(
|
||||
@@ -314,6 +337,7 @@ def activate_campaign(
|
||||
auto_create_test_issue(
|
||||
db, ct.test, current_user,
|
||||
parent_ticket_override=campaign_jira_key,
|
||||
campaign_start_date=campaign.start_date,
|
||||
)
|
||||
db.commit()
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user