Skip to content

feat(logs): instant draft alerts with inline configuration#57271

Merged
jonmcwest merged 2 commits intomasterfrom
05-01-feat_logs_instant_draft_alerts_with_inline_configuration
May 1, 2026
Merged

feat(logs): instant draft alerts with inline configuration#57271
jonmcwest merged 2 commits intomasterfrom
05-01-feat_logs_instant_draft_alerts_with_inline_configuration

Conversation

@jonmcwest
Copy link
Copy Markdown
Contributor

@jonmcwest jonmcwest commented May 1, 2026

Problem

Creating a logs alert required navigating to a separate /logs/alerts/new page, filling out a form, and then being redirected to the detail page. This two-step flow was awkward and the "New alert" page added unnecessary routing complexity. Additionally, alerts had no concept of a "draft" state — every alert was immediately active or disabled, with no way to distinguish an alert that had never been enabled from one that had been explicitly disabled.

Changes

2026-05-01 12.09.17.gif

Draft alert concept

  • Added first_enabled_at field to LogsAlertConfiguration model and API. null means the alert has never been enabled (draft); a timestamp means it has been enabled at least once.
  • Alerts with first_enabled_at == null and enabled == false are shown with a "Draft" badge instead of "Disabled".
  • Draft alerts bypass filter validation on create/update, allowing empty-filter stubs to be saved.
  • name and filters are now optional on create (defaulting to "Untitled alert" and {} respectively). threshold_count defaults to 100.

Click-to-create flow

  • Removed the LogsAlertNew scene, route (/logs/alerts/new), and all associated logic/files.
  • "New alert" button in the alert list now calls createAlertAndOpen, which POSTs a minimal { enabled: false } draft and immediately navigates to the detail page for that new alert.
  • The detail page detects the draft state (isDraft selector) and renders an "Enable alert" primary button instead of "Save", with the toggle-enabled button hidden.

Pre-enable checks refactored

  • withEnableNotificationGuard replaced by runPreEnableChecks + dispatchPreEnableCheck, which return a typed result (ok | blocked | warning) and are independently unit-testable.
  • Blocked state (no filters) shows a toast; warning state (no destinations) shows a confirmation dialog with "Enable anyway" / "Configure notifications" options.
  • enableAlert action on the detail scene handles the save-then-enable chain: if the form is dirty, it saves first and then enables on submitAlertFormSuccess.

Other fixes

  • renameAlert now also updates the form value so the name field stays in sync without a full reload.
  • destinationsChanged action replaces the loadExistingHogFunctionsSuccess listener to avoid reloading the alert (and wiping unsaved form changes) on every hog-function fetch.
  • Blank/whitespace alert names are coerced to "Untitled alert" on both create and update.
  • first_enabled_at is backfilled to created_at for all existing alerts via a data migration.

How did you test this code?

  • New and updated Playwright E2E tests covering the click-to-create flow, the "Enable anyway" dialog path, and the banner-enable regression for dirty drafts.
  • New unit tests for runPreEnableChecks covering all result branches (ok, blocked, warning, both).
  • New unit tests for logsAlertingLogic.createAlertAndOpen covering success and error paths.
  • New unit tests for logsAlertDetailSceneLogic covering the markPendingEnablesubmitAlertFormSuccessapplyEnabledChange chain.
  • Backend tests added for draft creation, first_enabled_at stamping, re-enable idempotency, enabling with empty filters (400), and blank-name fallback.

Publish to changelog?

No

Copy link
Copy Markdown
Contributor Author

jonmcwest commented May 1, 2026

@jonmcwest jonmcwest marked this pull request as ready for review May 1, 2026 11:14
@assign-reviewers-posthog assign-reviewers-posthog Bot requested a review from a team May 1, 2026 11:15
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Size Change: +2.06 kB (0%)

Total Size: 134 MB

📦 View Changed
Filename Size Change
frontend/dist/exporter 21.4 MB +1.48 kB (+0.01%)
frontend/dist/exporter.js 21.4 MB +1.48 kB (+0.01%)
frontend/dist/LogsAlertNewScene 0 B -3.87 kB (removed) 🏆
frontend/dist/render-query.js 21.1 MB +1.48 kB (+0.01%)
frontend/dist/toolbar.js 11.1 MB +1.48 kB (+0.01%)
ℹ️ View Unchanged
Filename Size
frontend/dist/368Hedgehogs 5.41 kB
frontend/dist/abap 14.2 kB
frontend/dist/AccountConnected 2.89 kB
frontend/dist/Action 24 kB
frontend/dist/Actions 1.16 kB
frontend/dist/AdvancedActivityLogsScene 37.1 kB
frontend/dist/AgenticAuthorize 5.6 kB
frontend/dist/apex 3.99 kB
frontend/dist/ApprovalDetail 16.4 kB
frontend/dist/array.full.es5.js 346 kB
frontend/dist/array.full.js 438 kB
frontend/dist/array.js 190 kB
frontend/dist/AsyncMigrations 13.3 kB
frontend/dist/AuthorizationStatus 867 B
frontend/dist/azcli 885 B
frontend/dist/bat 1.88 kB
frontend/dist/BatchExportScene 60.6 kB
frontend/dist/bicep 2.59 kB
frontend/dist/Billing 629 B
frontend/dist/BillingSection 20.9 kB
frontend/dist/BoxPlot 5.18 kB
frontend/dist/browserAll-0QZMN1W2 37.4 kB
frontend/dist/ButtonPrimitives 698 B
frontend/dist/CalendarHeatMap 4.93 kB
frontend/dist/cameligo 2.23 kB
frontend/dist/changeRequestsLogic 680 B
frontend/dist/CLIAuthorize 11.5 kB
frontend/dist/CLILive 4.16 kB
frontend/dist/clojure 9.68 kB
frontend/dist/coffee 3.63 kB
frontend/dist/Cohort 24.9 kB
frontend/dist/CohortCalculationHistory 6.37 kB
frontend/dist/Cohorts 9.54 kB
frontend/dist/ConfirmOrganization 4.66 kB
frontend/dist/conversations.js 67.3 kB
frontend/dist/Coupons 861 B
frontend/dist/cpp 5.33 kB
frontend/dist/Create 796 B
frontend/dist/crisp-chat-integration.js 1.97 kB
frontend/dist/csharp 4.56 kB
frontend/dist/csp 1.45 kB
frontend/dist/css 4.54 kB
frontend/dist/cssMode 4.2 kB
frontend/dist/CustomCssScene 3.7 kB
frontend/dist/CustomerAnalyticsConfigurationScene 2.21 kB
frontend/dist/CustomerAnalyticsScene 26.7 kB
frontend/dist/CustomerJourneyBuilderScene 1.97 kB
frontend/dist/CustomerJourneyTemplatesScene 7.65 kB
frontend/dist/customizations.full.js 18 kB
frontend/dist/CyclotronJobInputAssignee 1.47 kB
frontend/dist/CyclotronJobInputBusinessHours 2.85 kB
frontend/dist/CyclotronJobInputTicketTags 852 B
frontend/dist/cypher 3.42 kB
frontend/dist/dart 4.29 kB
frontend/dist/Dashboard 1.31 kB
frontend/dist/Dashboards 22.2 kB
frontend/dist/DashboardTemplateCopyScene 5.85 kB
frontend/dist/DataManagementScene 782 B
frontend/dist/DataPipelinesNewScene 2.45 kB
frontend/dist/DataWarehouseScene 1.42 kB
frontend/dist/Deactivated 1.27 kB
frontend/dist/dead-clicks-autocapture.js 13.2 kB
frontend/dist/DeadLetterQueue 5.53 kB
frontend/dist/DebugScene 20.1 kB
frontend/dist/decompressionWorker 2.85 kB
frontend/dist/decompressionWorker.js 2.85 kB
frontend/dist/DefinitionEdit 8.71 kB
frontend/dist/DefinitionView 24 kB
frontend/dist/DestinationsScene 2.82 kB
frontend/dist/dist 643 B
frontend/dist/dockerfile 1.91 kB
frontend/dist/EarlyAccessFeature 889 B
frontend/dist/EarlyAccessFeatures 2.99 kB
frontend/dist/ecl 5.38 kB
frontend/dist/EditorScene 1.25 kB
frontend/dist/elixir 10.3 kB
frontend/dist/elk.bundled 1.44 MB
frontend/dist/EmailMFAVerify 3.16 kB
frontend/dist/EndpointScene 39.2 kB
frontend/dist/EndpointsScene 21.5 kB
frontend/dist/ErrorTrackingIssueFingerprintsScene 7.13 kB
frontend/dist/ErrorTrackingIssueScene 99.2 kB
frontend/dist/ErrorTrackingScene 22.7 kB
frontend/dist/EvaluationTemplates 711 B
frontend/dist/EventsScene 2.71 kB
frontend/dist/exception-autocapture.js 11.8 kB
frontend/dist/Experiment 195 kB
frontend/dist/Experiments 18.6 kB
frontend/dist/ExportsScene 4.13 kB
frontend/dist/FeatureFlag 131 kB
frontend/dist/FeatureFlags 742 B
frontend/dist/FeatureFlagTemplatesScene 7.18 kB
frontend/dist/FlappyHog 5.92 kB
frontend/dist/flow9 1.85 kB
frontend/dist/freemarker2 16.7 kB
frontend/dist/fsharp 3.02 kB
frontend/dist/go 2.69 kB
frontend/dist/graphql 2.3 kB
frontend/dist/Group 14.9 kB
frontend/dist/Groups 4.05 kB
frontend/dist/GroupsNew 7.49 kB
frontend/dist/handlebars 7.38 kB
frontend/dist/hcl 3.63 kB
frontend/dist/HealthCategoryDetailScene 7.38 kB
frontend/dist/HealthScene 10.7 kB
frontend/dist/HeatmapNewScene 4.76 kB
frontend/dist/HeatmapRecordingScene 4.14 kB
frontend/dist/HeatmapScene 6.77 kB
frontend/dist/HeatmapsScene 4.02 kB
frontend/dist/hls 394 kB
frontend/dist/HogFunctionScene 59.4 kB
frontend/dist/HogRepl 7.51 kB
frontend/dist/html 5.62 kB
frontend/dist/htmlMode 4.65 kB
frontend/dist/image-blob-reduce.esm 49.5 kB
frontend/dist/InboxScene 59.9 kB
frontend/dist/index 258 kB
frontend/dist/index.js 258 kB
frontend/dist/ini 1.14 kB
frontend/dist/InsightQuickStart 5.57 kB
frontend/dist/InsightScene 28.9 kB
frontend/dist/IntegrationsRedirect 874 B
frontend/dist/intercom-integration.js 2.03 kB
frontend/dist/InviteSignup 15.1 kB
frontend/dist/java 3.26 kB
frontend/dist/javascript 1.02 kB
frontend/dist/jsonMode 13.9 kB
frontend/dist/julia 7.26 kB
frontend/dist/kotlin 3.44 kB
frontend/dist/lazy 159 kB
frontend/dist/LegacyPluginScene 20.8 kB
frontend/dist/LegalDocumentNewScene 59.5 kB
frontend/dist/LegalDocumentsScene 4.78 kB
frontend/dist/LemonTextAreaMarkdown 638 B
frontend/dist/less 3.93 kB
frontend/dist/lexon 2.47 kB
frontend/dist/lib 2.25 kB
frontend/dist/Link 604 B
frontend/dist/LinkScene 25 kB
frontend/dist/LinksScene 4.34 kB
frontend/dist/liquid 4.57 kB
frontend/dist/LiveDebugger 19.3 kB
frontend/dist/LiveEventsTable 5.37 kB
frontend/dist/LLMAnalyticsClusterScene 19 kB
frontend/dist/LLMAnalyticsClustersScene 51.9 kB
frontend/dist/LLMAnalyticsDatasetScene 19.8 kB
frontend/dist/LLMAnalyticsDatasetsScene 3.42 kB
frontend/dist/LLMAnalyticsEvaluation 59.5 kB
frontend/dist/LLMAnalyticsEvaluationsScene 30 kB
frontend/dist/LLMAnalyticsPlaygroundScene 37.5 kB
frontend/dist/LLMAnalyticsScene 118 kB
frontend/dist/LLMAnalyticsSessionScene 13.5 kB
frontend/dist/LLMAnalyticsTraceScene 129 kB
frontend/dist/LLMAnalyticsUsers 662 B
frontend/dist/LLMASessionFeedbackDisplay 4.98 kB
frontend/dist/LLMPromptScene 17.7 kB
frontend/dist/LLMPromptsScene 4.62 kB
frontend/dist/LLMSkillScene 725 B
frontend/dist/LLMSkillsScene 742 B
frontend/dist/Login 8.76 kB
frontend/dist/Login2FA 4.39 kB
frontend/dist/logs.js 38.5 kB
frontend/dist/LogsAlertDetailScene 17.1 kB
frontend/dist/LogsSamplingDetailScene 3.4 kB
frontend/dist/LogsSamplingNewScene 1.9 kB
frontend/dist/LogsScene 15.6 kB
frontend/dist/lua 2.16 kB
frontend/dist/m3 2.85 kB
frontend/dist/main 819 kB
frontend/dist/ManagedMigration 14.3 kB
frontend/dist/markdown 3.83 kB
frontend/dist/MarketingAnalyticsScene 40.1 kB
frontend/dist/MaterializedColumns 10.3 kB
frontend/dist/Max 937 B
frontend/dist/mdx 5.43 kB
frontend/dist/memlens.lib.bundle 27.9 kB
frontend/dist/MessageTemplate 16.4 kB
frontend/dist/MetricsScene 974 B
frontend/dist/mips 2.62 kB
frontend/dist/ModelsScene 14.5 kB
frontend/dist/MonacoDiffEditor 471 B
frontend/dist/monacoEditorWorker 288 kB
frontend/dist/monacoEditorWorker.js 288 kB
frontend/dist/monacoJsonWorker 419 kB
frontend/dist/monacoJsonWorker.js 419 kB
frontend/dist/monacoTsWorker 7.02 MB
frontend/dist/monacoTsWorker.js 7.02 MB
frontend/dist/MoveToPostHogCloud 4.6 kB
frontend/dist/msdax 4.95 kB
frontend/dist/mysql 11.3 kB
frontend/dist/NavTabChat 4.82 kB
frontend/dist/NewSourceScene 919 B
frontend/dist/NewTabScene 783 B
frontend/dist/NodeDetailScene 16.9 kB
frontend/dist/NotebookCanvasScene 3.58 kB
frontend/dist/NotebookPanel 5.55 kB
frontend/dist/NotebookScene 8.59 kB
frontend/dist/NotebooksScene 7.74 kB
frontend/dist/OAuthAuthorize 709 B
frontend/dist/objective-c 2.44 kB
frontend/dist/Onboarding 734 kB
frontend/dist/OnboardingCouponRedemption 1.34 kB
frontend/dist/pascal 3.03 kB
frontend/dist/pascaligo 2.04 kB
frontend/dist/passkeyLogic 620 B
frontend/dist/PasswordReset 4.5 kB
frontend/dist/PasswordResetComplete 3.13 kB
frontend/dist/PendingDeletion 2.35 kB
frontend/dist/perl 8.29 kB
frontend/dist/PersonScene 18.2 kB
frontend/dist/PersonsScene 4.84 kB
frontend/dist/pgsql 13.5 kB
frontend/dist/php 8.06 kB
frontend/dist/PipelineStatusScene 9.24 kB
frontend/dist/pla 1.72 kB
frontend/dist/posthog 145 kB
frontend/dist/postiats 7.9 kB
frontend/dist/powerquery 17 kB
frontend/dist/powershell 3.31 kB
frontend/dist/PreflightCheck 5.71 kB
frontend/dist/product-tours.js 115 kB
frontend/dist/ProductTour 274 kB
frontend/dist/ProductTours 4.82 kB
frontend/dist/ProjectHomepage 41 kB
frontend/dist/protobuf 9.09 kB
frontend/dist/pug 4.86 kB
frontend/dist/python 4.8 kB
frontend/dist/qsharp 3.23 kB
frontend/dist/QueryPerformance 7.43 kB
frontend/dist/r 3.16 kB
frontend/dist/razor 9.38 kB
frontend/dist/react-json-view 121 kB
frontend/dist/recorder-v2.js 112 kB
frontend/dist/recorder.js 112 kB
frontend/dist/redis 3.59 kB
frontend/dist/redshift 11.8 kB
frontend/dist/RegionMap 29.6 kB
frontend/dist/render-query 21.1 MB
frontend/dist/ResourceTransfer 9.32 kB
frontend/dist/restructuredtext 3.94 kB
frontend/dist/RevenueAnalyticsScene 25.8 kB
frontend/dist/ruby 8.54 kB
frontend/dist/rust 4.2 kB
frontend/dist/SavedInsights 800 B
frontend/dist/sb 1.86 kB
frontend/dist/scala 7.36 kB
frontend/dist/schema 698 kB
frontend/dist/SchemaScene 21.1 kB
frontend/dist/scheme 1.8 kB
frontend/dist/scss 6.45 kB
frontend/dist/SdkDoctorScene 9.56 kB
frontend/dist/SessionAttributionExplorerScene 6.77 kB
frontend/dist/SessionGroupSummariesTable 4.77 kB
frontend/dist/SessionGroupSummaryScene 17.1 kB
frontend/dist/SessionProfileScene 15.2 kB
frontend/dist/SessionRecordingDetail 1.9 kB
frontend/dist/SessionRecordingFilePlaybackScene 4.61 kB
frontend/dist/SessionRecordings 878 B
frontend/dist/SessionRecordingsKiosk 8.98 kB
frontend/dist/SessionRecordingsPlaylistScene 4.29 kB
frontend/dist/SessionRecordingsSettingsScene 2.04 kB
frontend/dist/SessionsScene 4.12 kB
frontend/dist/SettingsScene 3.12 kB
frontend/dist/sharedChunkAnchors 235 kB
frontend/dist/sharedChunkAnchors.js 235 kB
frontend/dist/SharedMetric 5.08 kB
frontend/dist/SharedMetrics 685 B
frontend/dist/shell 3.11 kB
frontend/dist/SignupContainer 26 kB
frontend/dist/Site 1.33 kB
frontend/dist/solidity 18.6 kB
frontend/dist/sophia 2.8 kB
frontend/dist/SourceScene 860 B
frontend/dist/SourcesScene 6.1 kB
frontend/dist/sparql 2.59 kB
frontend/dist/sql 10.3 kB
frontend/dist/SqlVariableEditScene 7.39 kB
frontend/dist/st 7.44 kB
frontend/dist/StartupProgram 21.3 kB
frontend/dist/StripeConfirmInstall 3.67 kB
frontend/dist/SubscriptionScene 13.4 kB
frontend/dist/SubscriptionsScene 5.04 kB
frontend/dist/SupportSettingsScene 1.57 kB
frontend/dist/SupportTicketScene 25.9 kB
frontend/dist/SupportTicketsScene 869 B
frontend/dist/Survey 984 B
frontend/dist/SurveyFormBuilder 1.68 kB
frontend/dist/Surveys 18.4 kB
frontend/dist/surveys.js 95.4 kB
frontend/dist/SurveyWizard 72.3 kB
frontend/dist/swift 5.3 kB
frontend/dist/SystemStatus 17 kB
frontend/dist/systemverilog 7.65 kB
frontend/dist/TaskDetailScene 22.5 kB
frontend/dist/TaskTracker 14.3 kB
frontend/dist/tcl 3.61 kB
frontend/dist/TextCardMarkdownEditor 11.1 kB
frontend/dist/toolbar 11.1 MB
frontend/dist/ToolbarLaunch 2.66 kB
frontend/dist/tracing-headers.js 1.74 kB
frontend/dist/TracingScene 31.6 kB
frontend/dist/TransformationsScene 2.06 kB
frontend/dist/TrendsLineChart 42.5 kB
frontend/dist/tsMode 24 kB
frontend/dist/twig 6.01 kB
frontend/dist/TwoFactorReset 4.16 kB
frontend/dist/typescript 274 B
frontend/dist/typespec 2.86 kB
frontend/dist/Unsubscribe 1.8 kB
frontend/dist/UserInterview 4.68 kB
frontend/dist/UserInterviews 2.16 kB
frontend/dist/vb 5.83 kB
frontend/dist/VercelConnect 5.13 kB
frontend/dist/VercelLinkError 2.4 kB
frontend/dist/VerifyEmail 4.92 kB
frontend/dist/vimMode 211 kB
frontend/dist/VisualReviewIndexScene 2.35 kB
frontend/dist/VisualReviewRunScene 42.3 kB
frontend/dist/VisualReviewRunsScene 7.11 kB
frontend/dist/VisualReviewSettingsScene 10.9 kB
frontend/dist/VisualReviewSnapshotHistoryScene 8.23 kB
frontend/dist/VisualReviewSnapshotOverviewScene 17.4 kB
frontend/dist/web-vitals.js 6.39 kB
frontend/dist/WebAnalyticsScene 5.91 kB
frontend/dist/WebGLRenderer-DYjOwNoG 60.4 kB
frontend/dist/WebGPURenderer-B_wkl_Ja 36.3 kB
frontend/dist/WebScriptsScene 2.68 kB
frontend/dist/webworkerAll-puPV1rBA 397 B
frontend/dist/wgsl 7.38 kB
frontend/dist/Wizard 4.59 kB
frontend/dist/WorkflowScene 109 kB
frontend/dist/WorkflowsScene 58.5 kB
frontend/dist/WorldMap 4.91 kB
frontend/dist/xml 3.02 kB
frontend/dist/yaml 4.64 kB

compressed-size-action

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 1, 2026

Comments Outside Diff (2)

  1. products/logs/frontend/scenes/LogsAlertDetailScene/logsAlertDetailSceneLogic.ts, line 267-277 (link)

    P2 Form name stays stale after server normalizes blank to "Untitled alert"

    setAlertFormValue('name', name) is called with the raw user input (which may be "") before the API call. The API normalizes a blank name to "Untitled alert" and patchAlertLocally({ name: updated.name }) updates the displayed title, but alertForm.name remains "", leaving the form perpetually dirty.

    On a draft alert (forceEdit=true), the next "Enable alert" click sees alertFormChanged=true, re-submits the form with name: "", and the server normalizes again — correct but wasteful. Adding actions.setAlertFormValue('name', updated.name) after patchAlertLocally would keep the form in sync with the server's canonical value.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: products/logs/frontend/scenes/LogsAlertDetailScene/logsAlertDetailSceneLogic.ts
    Line: 267-277
    
    Comment:
    **Form name stays stale after server normalizes blank to "Untitled alert"**
    
    `setAlertFormValue('name', name)` is called with the raw user input (which may be `""`) before the API call. The API normalizes a blank name to `"Untitled alert"` and `patchAlertLocally({ name: updated.name })` updates the displayed title, but `alertForm.name` remains `""`, leaving the form perpetually dirty.
    
    On a draft alert (`forceEdit=true`), the next "Enable alert" click sees `alertFormChanged=true`, re-submits the form with `name: ""`, and the server normalizes again — correct but wasteful. Adding `actions.setAlertFormValue('name', updated.name)` after `patchAlertLocally` would keep the form in sync with the server's canonical value.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. products/logs/backend/alerts_api.py, line 420-423 (link)

    P2 if "first_enabled_at" not in validated_data guard is always true

    first_enabled_at is declared read_only=True in the serializer, so DRF strips it from validated_data during deserialization — the inner guard is therefore always truthy. More importantly, super().update() calls instance.save() without update_fields, so the direct assignment instance.first_enabled_at = timezone.now() is sufficient to persist the value. Adding it to validated_data is redundant and the guard adds misleading noise.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: products/logs/backend/alerts_api.py
    Line: 420-423
    
    Comment:
    **`if "first_enabled_at" not in validated_data` guard is always true**
    
    `first_enabled_at` is declared `read_only=True` in the serializer, so DRF strips it from `validated_data` during deserialization — the inner guard is therefore always truthy. More importantly, `super().update()` calls `instance.save()` without `update_fields`, so the direct assignment `instance.first_enabled_at = timezone.now()` is sufficient to persist the value. Adding it to `validated_data` is redundant and the guard adds misleading noise.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
products/logs/frontend/scenes/LogsAlertDetailScene/logsAlertDetailSceneLogic.ts:249-254
**Race condition: `loadAlert` fires before the enable PATCH completes**

When `pendingEnableAfterSave` is true, `submitAlertFormSuccess` dispatches `applyEnabledChange(true)` and then immediately calls `loadAlert()`. The GET from `loadAlert` will complete before the PATCH from `applyEnabledChange`, so `loadAlertSuccess` resets the local alert state to the not-yet-enabled record — briefly flashing the "Enable alert" button and disabled banner back before `applyEnabledChange` finishes.

`applyEnabledChange` already calls `patchAlertLocally(updated)` and `resetAlertForm(buildFormDefaults(updated))` from the PATCH response, so `loadAlert()` is redundant in the pending-enable path. The call should be conditional:

```typescript
submitAlertFormSuccess: () => {
    if (values.pendingEnableAfterSave) {
        actions.applyEnabledChange(true)
    } else {
        actions.loadAlert()
    }
},
```

### Issue 2 of 3
products/logs/frontend/scenes/LogsAlertDetailScene/logsAlertDetailSceneLogic.ts:267-277
**Form name stays stale after server normalizes blank to "Untitled alert"**

`setAlertFormValue('name', name)` is called with the raw user input (which may be `""`) before the API call. The API normalizes a blank name to `"Untitled alert"` and `patchAlertLocally({ name: updated.name })` updates the displayed title, but `alertForm.name` remains `""`, leaving the form perpetually dirty.

On a draft alert (`forceEdit=true`), the next "Enable alert" click sees `alertFormChanged=true`, re-submits the form with `name: ""`, and the server normalizes again — correct but wasteful. Adding `actions.setAlertFormValue('name', updated.name)` after `patchAlertLocally` would keep the form in sync with the server's canonical value.

### Issue 3 of 3
products/logs/backend/alerts_api.py:420-423
**`if "first_enabled_at" not in validated_data` guard is always true**

`first_enabled_at` is declared `read_only=True` in the serializer, so DRF strips it from `validated_data` during deserialization — the inner guard is therefore always truthy. More importantly, `super().update()` calls `instance.save()` without `update_fields`, so the direct assignment `instance.first_enabled_at = timezone.now()` is sufficient to persist the value. Adding it to `validated_data` is redundant and the guard adds misleading noise.

Reviews (1): Last reviewed commit: "feat(logs): instant draft alerts with in..." | Re-trigger Greptile

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Migration SQL Changes

Hey 👋, we've detected some migrations on this PR. Here's the SQL output for each migration, make sure they make sense:

products/logs/backend/migrations/0010_logsalertconfiguration_first_enabled_at.py

BEGIN;
--
-- Add field first_enabled_at to logsalertconfiguration
--
ALTER TABLE "logs_logsalertconfiguration" ADD COLUMN "first_enabled_at" timestamp with time zone NULL;
--
-- Alter field threshold_count on logsalertconfiguration
--
-- (no-op)
COMMIT;

products/logs/backend/migrations/0011_backfill_first_enabled_at.py

BEGIN;
--
-- Raw Python operation
--
-- THIS OPERATION CANNOT BE WRITTEN AS SQL
COMMIT;

Last updated: 2026-05-01 15:14 UTC (796c4f3)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🔍 Migration Risk Analysis

We've analyzed your migrations for potential risks.

Summary: 0 Safe | 2 Needs Review | 0 Blocked

⚠️ Needs Review

May have performance impact

logs.0010_logsalertconfiguration_first_enabled_at
  └─ #1 ✅ AddField
     Adding nullable field requires brief lock
     model: logsalertconfiguration, field: first_enabled_at
  └─ #2 ⚠️ AlterField
     Field alteration may cause table locks or data loss (check if changing type or constraints)
     model: logsalertconfiguration, field: threshold_count, field_type: PositiveIntegerField
logs.0011_backfill_first_enabled_at
  └─ #1 ⚠️ RunPython: RunPython data migration needs review for performance

📚 How to Deploy These Changes Safely

AddField:

This operation acquires a brief lock but doesn't rewrite the table.

Deployment uses lock timeouts with automatic retries, so lock contention will cause retries rather than connection pile-up.

RunPython:

Use batching for large data migrations:

  • Use .iterator() to avoid loading all rows into memory
  • Use .bulk_update() instead of saving individual objects
  • Batch size: 1,000-10,000 rows per batch
  • Add pauses between batches
  • Consider background jobs for very large updates (millions of rows)

See the migration safety guide

Last updated: 2026-05-01 15:14 UTC (796c4f3)

@jonmcwest jonmcwest force-pushed the 05-01-feat_logs_instant_draft_alerts_with_inline_configuration branch 2 times, most recently from 1a73893 to 225e0e1 Compare May 1, 2026 11:32
@jonmcwest jonmcwest changed the base branch from 05-01-feat_logs-alerting_split_alert_save_ms_into_postgres_sub-stage_metrics to graphite-base/57271 May 1, 2026 11:46
@jonmcwest jonmcwest force-pushed the 05-01-feat_logs_instant_draft_alerts_with_inline_configuration branch from 225e0e1 to b136f8e Compare May 1, 2026 11:51
@jonmcwest jonmcwest changed the base branch from graphite-base/57271 to master May 1, 2026 11:51
@jonmcwest jonmcwest force-pushed the 05-01-feat_logs_instant_draft_alerts_with_inline_configuration branch 2 times, most recently from 1a7be31 to 7bd9168 Compare May 1, 2026 14:40
@jonmcwest jonmcwest force-pushed the 05-01-feat_logs_instant_draft_alerts_with_inline_configuration branch from 80b0e44 to 796c4f3 Compare May 1, 2026 15:00
@jonmcwest jonmcwest merged commit 29db143 into master May 1, 2026
238 checks passed
Copy link
Copy Markdown
Contributor Author

Merge activity

@jonmcwest jonmcwest deleted the 05-01-feat_logs_instant_draft_alerts_with_inline_configuration branch May 1, 2026 15:32
inkeep Bot added a commit to PostHog/posthog.com that referenced this pull request May 1, 2026
Covers the new draft alert workflow introduced in PostHog/posthog#57271:
- Creating alerts via click-to-create flow
- Draft state concept
- Filter, threshold, and notification configuration
- Pre-enable checks (blocked/warning states)
- Alert management (disable, snooze, reset, delete)
@deployment-status-posthog
Copy link
Copy Markdown

deployment-status-posthog Bot commented May 1, 2026

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-05-01 15:56 UTC Run
prod-us ✅ Deployed 2026-05-01 16:14 UTC Run
prod-eu ✅ Deployed 2026-05-01 16:17 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants