[Alerting v2] Compose Discover: stepped Edit Form flyout with RHF form state#268774
Conversation
…xecutionFieldGroup)
…tion (follow-up PR) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…cement
RHF bridge (was not connected):
- Add useEffect hooks in ComposeDiscoverFlyout to sync fullQuery,
timeField, and groupFields from the UI reducer into RHF via setValue()
whenever they change. This ensures mapFormValuesToCreateRequest/
UpdateRequest receives actual user input instead of default values.
- Replace `rule as { id: string }` cast with explicit `ruleId` prop.
Stubs (were silent no-ops):
- DetailsAndArtifactsStep: remove hardcoded no-data select; mark
runbook/dashboard fields as disabled with TODO comment (elastic#268770).
- NotificationsStep: replace all uncontrolled/hardcoded fields with an
explicit EuiCallOut placeholder — no longer silently discards input.
console.debug:
- Remove two console.debug calls in use_query_execution.ts that were
suppressed with eslint-disable-next-line comments.
Quick Edit replacement + Create in Flyout:
- Add onEditInFlyout prop to RulesListTableContainer; when provided,
pencil icon routes to that handler instead of QuickEditRuleFlyout.
- RulesListPage passes onEditInFlyout to open ComposeDiscoverFlyout
in edit mode (setEditRule + setFlyoutOpen) from the pencil icon.
- Replace the single "Create rule" button with two buttons:
"Create rule" (fill, navigates to full-page form — existing flow)
"Create in flyout" (opens ComposeDiscoverFlyout in create mode).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Previously, ComposeDiscoverForm routed to the correct step component by
comparing `stepTitles[state.step]` against hardcoded string literals:
if (currentStepName === 'Alert Condition') { ... }
if (currentStepName === 'Details & Artifacts') { ... }
This is fragile: renaming a step title in getStepTitles() would silently
break rendering — the component would fall through to `return null` with
no error. The user would see a blank flyout body with no indication of
what went wrong.
Replace with a switch on the numeric step index. The step index is the
authoritative source of which step is active (it lives in uiState.step),
so routing on it is both more correct and more resilient to copy changes.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…leton
The previous implementation used a module-level singleton:
let registeredDisposables: monaco.IDisposable[] | null = null;
function registerProviders(callbacks) {
if (registeredDisposables) return; // ← the problem
...
}
This caused two bugs:
1. React Strict Mode double-invokes effects (mount → unmount → mount in dev).
The cleanup on first unmount set registeredDisposables = null, but the
guard on re-mount re-registered correctly. However, if a second component
instance mounted before the first unmounted, the guard caused the second
instance to skip registration entirely, leaving it with no autocomplete.
2. Stale callbacks: the singleton captured callbacks from the first render.
If services changed after mount, the registered providers continued using
the old callbacks with no way to update them short of a full unmount.
Fix: register providers per-hook-instance with empty deps (register once,
clean up on unmount). Callbacks are kept current via a ref so providers
always delegate to the latest values without needing to be re-registered.
The stable wrapper object delegates getSources/getColumnsFor to the ref,
giving Monaco providers a stable reference while the underlying callbacks
can change freely.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The footer stat previously showed "N documents queried" and the results header showed "N results". Both were using totalRowCount from useQueryExecution, which is the number of rows in the ES|QL response — capped at MAX_ROWS (500). ES|QL does not return a true total count without a separate COUNT(*) query. The old labels implied to users that N was the total number of matching documents in the index, which is incorrect. A query matching 50,000 documents would show "500 documents queried" because ES|QL only returns the first 500 rows. Change both labels to "N rows returned" / "N rows" which accurately describes what the number represents: rows in the current response, not total matching documents. This is consistent with how Discover itself presents ES|QL result counts. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Amends the row count label to use 'result'/'results' which matches the exact terminology used by Discover's hits counter component (discover.hitsCounter.resultLabel / resultsLabel). ES|QL responses contain only the returned rows — there is no total hit count equivalent to hits.total in a regular ES search. Discover handles this the same way: it displays the count of rows in the current response. 'N results' is accurate and consistent with the rest of the product. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Previously, dateStart and dateEnd were local state in ComposeDiscoverChild. Every time the user closed the Sandbox (via Apply changes) and reopened it, the date range silently reset to 'now-15m / now', discarding whatever window the user had been exploring their data in. Move dateStart/dateEnd into the UI reducer as sandboxDateStart/sandboxDateEnd so the values survive Sandbox close/reopen. The child reads them from props (state.sandboxDateStart/End) and dispatches SET_SANDBOX_DATE_RANGE on change. The date range is intentionally NOT connected to FormValues.schedule.lookback. It is a preview window for testing the query in the Sandbox, not a rule configuration field. The rule's lookback is configured separately in the Evaluation section of the Edit Form. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
ComposeDiscoverTabs was copied in from the prototype branch where it handled the Base/Alert/Recovery tab layout in the Discover Sandbox. In this PR, the Sandbox renders a single CodeEditor directly (tab support is deferred to the custom recovery follow-up PR). The component was never imported or rendered anywhere — it was pure dead code carrying ~100 lines and unnecessary props (tabConfig, hideTabBar, services) that served no function. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
tabConfig (SandboxTabConfig) was in ComposeDiscoverChildProps and passed from the flyout, but was never destructured or used anywhere in the component body. It was a remnant of the prototype where tabs existed — in this PR the Sandbox always renders a single CodeEditor with no tabs. Also removes the now-unused getSandboxTabConfig import from the flyout and the tabConfig local variable that computed it. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
guessRecoveryBlock was exported but never imported or called anywhere in this PR. It was written for the custom recovery feature (where it would auto-suggest a recovery condition by inverting comparison operators in the alert block), but that feature is deferred to a follow-up PR. The implementation also had a correctness issue: it used sequential .replace() calls on plain strings rather than a single-pass substitution, meaning overlapping patterns (e.g. '>=' matched by both '>=' and '>') could produce wrong output. The correct implementation will ship with the custom recovery PR when it can be properly tested end-to-end. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Two bare useEffect calls (no dependency array) were syncing baseQuery and
search into refs:
useEffect(() => { baseQueryRef.current = baseQuery; });
useEffect(() => { searchRef.current = search; });
Bare effects run after every render. Using useEffect here served no purpose
beyond adding two effect invocations per render cycle. Refs are mutable
objects — assigning to ref.current during render is safe and idiomatic in
React for values that need to be readable in closures without causing
re-renders. The standard pattern is simply:
baseQueryRef.current = baseQuery;
searchRef.current = search;
This is a minor but meaningful cleanup: fewer effects = less React overhead,
and the code correctly conveys that this is a synchronous assignment, not
an async side effect.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
All fields that belong to the submitted form (timeField, groupFields, name, tags, schedule, lookback, alertDelayMode, recoveryDelayMode, etc.) are removed from ComposeDiscoverState and ComposeDiscoverAction. The reducer now owns only UI state: step, childOpen, fullQuery, activeTab, yamlMode, queryCommitted, sandboxDateStart/End. Components read and write timeField and grouping directly via useFormContext<FormValues>() — no dispatching, no bridge effects. The two stale useEffect syncs (timeField, groupFields) are removed from compose_discover_flyout.tsx; only the fullQuery sync remains (needed because the Sandbox editor is reducer-owned until Apply is clicked). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@kbn/code-editor is the public interface that re-exports from @kbn/monaco. Consumers should import through it rather than reaching directly into @kbn/monaco. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
Re UX #7 and #8 (sandbox close + Next while open): Intentional for now — 'Apply changes' is meant to signal 'I'm done with the Sandbox for this step', so closing it is by design. The Next-while-open restriction flows from the same assumption: if the Sandbox is open, the user is still mid-edit. The tension Yiannis is pointing out is real. We're tracking a broader question with Joana about whether to move toward a persistent side-by-side layout where the Sandbox content changes per step instead of opening and closing. That would resolve both issues cleanly but needs design sign-off before we commit to it. One exception already locked in: YAML mode (follow-up in #268772) will NOT close the Sandbox on Apply — noted in the issue AC — because users need to iterate between the query editor and the YAML view. Leaving current behavior unchanged for this PR. |
ComposeDiscoverChart and ComposeDiscoverChild now call useRuleFormServices() directly instead of receiving services (or lens/dataViews) as props. RuleFormProvider is already in the tree above both — no need to thread the bag down through the call chain. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
ES|QL already caps results at 10,000 server-side. Slicing again in the browser was inconsistent (footer showed ES total, grid showed only 500) and wasteful (all rows allocated then discarded). EuiDataGrid paginates over whatever ES returns — no client-side cap needed. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Monaco swallows unhandled rejections from provideCompletionItems already, but explicit try/catch prevents unhandled promise rejection warnings and makes the fallback intentional: return no suggestions on failure rather than letting the rejection propagate silently. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
#4 — options: uses getEsqlColumns (| LIMIT 0) to derive the output columns of the committed pipeline query. Works in edit mode (query seeded on mount) and create mode, with no dependency on the sandbox flyout having been opened or run. #3 — auto-populate: on first query commit, parses the AST with Parser from @elastic/esql to extract BY column names from the last STATS command and pre-fills grouping.fields if currently empty. Does not override existing grouping in edit mode. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
Addressing all items from your manual testing review: Bugs fixed:
UX: |
Parser.parse() from @elastic/esql gives us the exact character position where the FROM command ends via fromCmd.location.max, so the WHERE is inserted in the correct place regardless of pipes inside index names or string literals — the same reason we're moving away from the naive pipe scan in use_heuristic_split.ts. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Overall the architecture is solid — the RHF + reducer split is well-motivated, the autocomplete provider hoisting is a thoughtful detail, and the paramsRef/execRef pattern in useQueryExecution correctly avoids stale closures. A few things to address:
1. Duplicate imports from @kbn/code-editor (nit)
Both compose_discover_child.tsx and query_summary.tsx have:
import { CodeEditor } from '@kbn/code-editor';
import { ESQL_LANG_ID } from '@kbn/code-editor';These should be one import statement. ESLint's no-duplicate-imports rule normally catches this.
2. paths.ruleCreate inlined — minor inconsistency
The "Create rule" button in rules_list_page.tsx now uses:
application.navigateToUrl(http.basePath.prepend('/app/management/alertingV2/rules/create'))basePath.prepend is correct, but the path string was previously centralised in paths.ruleCreate (still used in rules_list_table_container.tsx for clone and rule_details_actions_menu.tsx). Two places now own the same URL literal. Prefer paths.ruleCreate to keep it consistent with the rest of the file.
3. Deleted Scout tests with no replacement or tracking issue — important
quick_edit_rule.spec.ts (136 lines) is deleted. The pencil-icon edit flow now routes through ComposeDiscoverFlyout, which has zero Scout UI coverage. The PR description says "rewriting them is deferred" but there is no linked issue and no placeholder spec file. The old tests were catching real regressions (the existing CI failure on this PR demonstrates that).
Please create a follow-up tracking issue for ComposeDiscoverFlyout Scout test coverage before or alongside merge, and link it in the PR description.
4. Inline AstNode type alias inside useEffect — minor
In compose_discover_form.tsx, the auto-populate effect has:
type AstNode = { type: string; name: string; args?: unknown[] };
const byOption = (statsCmd?.args as AstNode[] | undefined)?.find(...)@elastic/esql likely exports proper AST node types — using those would be safer if the AST shape changes. At minimum the as AstNode[] cast is hiding a type error from the compiler.
use_heuristic_split.ts is understood to be a placeholder pending the AST rewrite in #268776 — no issue there.
yiannisnikolopoulos
left a comment
There was a problem hiding this comment.
Approving to unblock merge
It turns out that the metadata.tags bug was pre-existing:
tags: metadata.tags ?? [] breaks rule creation when the user doesn't add tags. The API schema makes tags optional but requires .min(1) if present, so sending [] is rejected. Omit the key instead:
...(metadata.tags?.length ? { tags: metadata.tags } : {})
Oof, I’ll check with core engine team on this but it seems like a strange API design. For now I’ll revert. |
💛 Build succeeded, but was flaky
Failed CI Steps
Test Failures
Metrics [docs]Module Count
Async chunks
Page load bundle
History
|
…m state (elastic#268774) ## Summary Closes elastic#268771 Adds `ComposeDiscoverFlyout`, the stepped Edit Form flyout for v2 rule authoring. This is PR B in a series; PR A (elastic#268739) adds `HorizontalMinimalStepper` as a foundation. **What's in this PR:** - `ComposeDiscoverFlyout` component with a 3-step form: **Alert Condition → Details & Artifacts → Notifications** - Complete RHF migration: all submitted values (`name`, `tags`, `schedule`, `lookback`, `timeField`, `grouping`, `query`, delays) live in `useForm<FormValues>()`; reducer owns only UI state (`step`, `childOpen`, `yamlMode`, `queryCommitted`, etc.) - **Create mode** — wired to `useCreateRule` mutation; entry point is the new **"Create in flyout"** button on the rules list (alongside **"Create rule"** which still navigates to the full-page form) - **Edit mode** — wired to `useUpdateRule`; entry point is the edit icon on any rule row (replaces `QuickEditRuleFlyout`) - **Discover Sandbox** — child flyout with a single ES|QL editor, Lens histogram, and data grid; opens via "Open query editor" / "Edit query" in the Alert Condition step - **YAML preview** — toggle in header shows read-only CodeEditor with rule YAML; editing round-trip is deferred - Reuses `RuleDetailsFieldGroup` and `RuleExecutionFieldGroup` from PR elastic#268164 **What's NOT in this PR (deferred):** - No Recovery Condition step (PR E) - No tracking toggle or split-query support - Notifications step is a placeholder callout - Runbook URL / Dashboard link are disabled stubs - Sandbox UI unpolished (redesign in PR C) - YAML round-trip editing deferred ### State architecture ``` useForm<FormValues>() ← submitted values: name, tags, schedule, query, timeField, grouping, delays useReducer(uiReducer) ← UI only: step, childOpen, fullQuery, yamlMode, queryCommitted, sandboxDateStart/End ``` One bridge: when the user clicks **Apply changes** in the Sandbox, a `useEffect` syncs `uiState.fullQuery → setValue('evaluation.query.base', ...)`. Everything else writes directly to RHF via `useFormContext()`. --- ## Test plan ### Prerequisites Start Elasticsearch: ```bash cd ~/__projects__/kibana-dev/pr-b-rhf-form node scripts/es snapshot --license=trial ``` Start Kibana (Node 24 required): ```bash export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" && nvm use 24 KBN_USE_RSPACK=true yarn start \ --server.port=5603 \ --server.basePath=/ifh \ --server.rewriteBasePath=true ``` `config/kibana.dev.yml`: ```yaml xpack.alerting_v2.enabled: true dev.basePathProxyTarget: 5604 mockIdpPlugin.enabled: false ``` Navigate to: `http://localhost:5603/ifh/app/management/alertingV2/rules` --- ### Create flow - [ ] Click **Create in flyout** (next to the primary "Create rule" button) — flyout opens in create mode; Discover Sandbox opens alongside it automatically - [ ] Verify 3 steps in the header stepper: Alert Condition, Details & Artifacts, Notifications - [ ] In the Sandbox: type a query (e.g. `FROM logs-* | STATS count = COUNT(*) BY host.name | WHERE count > 0`), click **Search** — verify histogram and results grid appear - [ ] Click **Apply changes** — Sandbox closes; Alert Condition step shows the committed query summary - [ ] Change the **Time field** dropdown — verify it reflects in the form - [ ] Adjust **schedule** and **lookback** via the schedule fields - [ ] Click **Next** → **Details & Artifacts**: fill in rule name and tags via the standard field group - [ ] Click **Next** → **Notifications**: verify placeholder callout appears (not wired yet — expected) - [ ] Click **Create rule** — verify rule appears in the list with correct name, tags, and schedule - [ ] Verify the primary **Create rule** button (filled, left of "Create in flyout") still navigates to the full-page form ### Edit flow - [ ] Click the edit (pencil) icon on an existing rule — flyout opens in edit mode, pre-populated with that rule's values - [ ] Modify the rule name in Details & Artifacts - [ ] Click **Save rule** — verify the rule updates and the change appears in the list ### YAML preview - [ ] Toggle YAML mode in the flyout header — stepper collapses; CodeEditor shows read-only rule YAML - [ ] Fill in form fields, toggle YAML mode — verify YAML reflects the current values ### Edge cases - [ ] Open the Sandbox, edit the query, close without clicking **Apply changes** — verify the Alert Condition step still shows the previously committed query - [ ] Verify **Next** is disabled while the Sandbox is open --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Bailey Cash <bailey.cash@elastic.co>
…69011) ## Summary Follow-up to #268774 addressing feedback from Bailey and Yiannis left after merge. ## Changes **Bug fix (Yiannis):** `metadata.tags ?? []` revert — the API schema marks `tags` as optional but requires `.min(1)` if present, so sending `[]` is rejected. The key is now omitted entirely when tags is absent or empty. **Nits (Bailey):** - Merge duplicate `@kbn/code-editor` imports in `compose_discover_child.tsx` and `query_summary.tsx` - Use `paths.ruleCreate` constant in `rules_list_page.tsx` instead of the inline URL string (consistent with the rest of the plugin) - Replace the inline `AstNode` interface + unsafe cast in `compose_discover_form.tsx` with `isOptionNode` type guard from `@elastic/esql` ## Scout test tracking The deleted `quick_edit_rule.spec.ts` tests are tracked in #268958 (child of M2 epic rna-program#482). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…m state (elastic#268774) ## Summary Closes elastic#268771 Adds `ComposeDiscoverFlyout`, the stepped Edit Form flyout for v2 rule authoring. This is PR B in a series; PR A (elastic#268739) adds `HorizontalMinimalStepper` as a foundation. **What's in this PR:** - `ComposeDiscoverFlyout` component with a 3-step form: **Alert Condition → Details & Artifacts → Notifications** - Complete RHF migration: all submitted values (`name`, `tags`, `schedule`, `lookback`, `timeField`, `grouping`, `query`, delays) live in `useForm<FormValues>()`; reducer owns only UI state (`step`, `childOpen`, `yamlMode`, `queryCommitted`, etc.) - **Create mode** — wired to `useCreateRule` mutation; entry point is the new **"Create in flyout"** button on the rules list (alongside **"Create rule"** which still navigates to the full-page form) - **Edit mode** — wired to `useUpdateRule`; entry point is the edit icon on any rule row (replaces `QuickEditRuleFlyout`) - **Discover Sandbox** — child flyout with a single ES|QL editor, Lens histogram, and data grid; opens via "Open query editor" / "Edit query" in the Alert Condition step - **YAML preview** — toggle in header shows read-only CodeEditor with rule YAML; editing round-trip is deferred - Reuses `RuleDetailsFieldGroup` and `RuleExecutionFieldGroup` from PR elastic#268164 **What's NOT in this PR (deferred):** - No Recovery Condition step (PR E) - No tracking toggle or split-query support - Notifications step is a placeholder callout - Runbook URL / Dashboard link are disabled stubs - Sandbox UI unpolished (redesign in PR C) - YAML round-trip editing deferred ### State architecture ``` useForm<FormValues>() ← submitted values: name, tags, schedule, query, timeField, grouping, delays useReducer(uiReducer) ← UI only: step, childOpen, fullQuery, yamlMode, queryCommitted, sandboxDateStart/End ``` One bridge: when the user clicks **Apply changes** in the Sandbox, a `useEffect` syncs `uiState.fullQuery → setValue('evaluation.query.base', ...)`. Everything else writes directly to RHF via `useFormContext()`. --- ## Test plan ### Prerequisites Start Elasticsearch: ```bash cd ~/__projects__/kibana-dev/pr-b-rhf-form node scripts/es snapshot --license=trial ``` Start Kibana (Node 24 required): ```bash export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" && nvm use 24 KBN_USE_RSPACK=true yarn start \ --server.port=5603 \ --server.basePath=/ifh \ --server.rewriteBasePath=true ``` `config/kibana.dev.yml`: ```yaml xpack.alerting_v2.enabled: true dev.basePathProxyTarget: 5604 mockIdpPlugin.enabled: false ``` Navigate to: `http://localhost:5603/ifh/app/management/alertingV2/rules` --- ### Create flow - [ ] Click **Create in flyout** (next to the primary "Create rule" button) — flyout opens in create mode; Discover Sandbox opens alongside it automatically - [ ] Verify 3 steps in the header stepper: Alert Condition, Details & Artifacts, Notifications - [ ] In the Sandbox: type a query (e.g. `FROM logs-* | STATS count = COUNT(*) BY host.name | WHERE count > 0`), click **Search** — verify histogram and results grid appear - [ ] Click **Apply changes** — Sandbox closes; Alert Condition step shows the committed query summary - [ ] Change the **Time field** dropdown — verify it reflects in the form - [ ] Adjust **schedule** and **lookback** via the schedule fields - [ ] Click **Next** → **Details & Artifacts**: fill in rule name and tags via the standard field group - [ ] Click **Next** → **Notifications**: verify placeholder callout appears (not wired yet — expected) - [ ] Click **Create rule** — verify rule appears in the list with correct name, tags, and schedule - [ ] Verify the primary **Create rule** button (filled, left of "Create in flyout") still navigates to the full-page form ### Edit flow - [ ] Click the edit (pencil) icon on an existing rule — flyout opens in edit mode, pre-populated with that rule's values - [ ] Modify the rule name in Details & Artifacts - [ ] Click **Save rule** — verify the rule updates and the change appears in the list ### YAML preview - [ ] Toggle YAML mode in the flyout header — stepper collapses; CodeEditor shows read-only rule YAML - [ ] Fill in form fields, toggle YAML mode — verify YAML reflects the current values ### Edge cases - [ ] Open the Sandbox, edit the query, close without clicking **Apply changes** — verify the Alert Condition step still shows the previously committed query - [ ] Verify **Next** is disabled while the Sandbox is open --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Bailey Cash <bailey.cash@elastic.co>
…astic#269011) ## Summary Follow-up to elastic#268774 addressing feedback from Bailey and Yiannis left after merge. ## Changes **Bug fix (Yiannis):** `metadata.tags ?? []` revert — the API schema marks `tags` as optional but requires `.min(1)` if present, so sending `[]` is rejected. The key is now omitted entirely when tags is absent or empty. **Nits (Bailey):** - Merge duplicate `@kbn/code-editor` imports in `compose_discover_child.tsx` and `query_summary.tsx` - Use `paths.ruleCreate` constant in `rules_list_page.tsx` instead of the inline URL string (consistent with the rest of the plugin) - Replace the inline `AstNode` interface + unsafe cast in `compose_discover_form.tsx` with `isOptionNode` type guard from `@elastic/esql` ## Scout test tracking The deleted `quick_edit_rule.spec.ts` tests are tracked in elastic#268958 (child of M2 epic rna-program#482). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
…m state (elastic#268774) ## Summary Closes elastic#268771 Adds `ComposeDiscoverFlyout`, the stepped Edit Form flyout for v2 rule authoring. This is PR B in a series; PR A (elastic#268739) adds `HorizontalMinimalStepper` as a foundation. **What's in this PR:** - `ComposeDiscoverFlyout` component with a 3-step form: **Alert Condition → Details & Artifacts → Notifications** - Complete RHF migration: all submitted values (`name`, `tags`, `schedule`, `lookback`, `timeField`, `grouping`, `query`, delays) live in `useForm<FormValues>()`; reducer owns only UI state (`step`, `childOpen`, `yamlMode`, `queryCommitted`, etc.) - **Create mode** — wired to `useCreateRule` mutation; entry point is the new **"Create in flyout"** button on the rules list (alongside **"Create rule"** which still navigates to the full-page form) - **Edit mode** — wired to `useUpdateRule`; entry point is the edit icon on any rule row (replaces `QuickEditRuleFlyout`) - **Discover Sandbox** — child flyout with a single ES|QL editor, Lens histogram, and data grid; opens via "Open query editor" / "Edit query" in the Alert Condition step - **YAML preview** — toggle in header shows read-only CodeEditor with rule YAML; editing round-trip is deferred - Reuses `RuleDetailsFieldGroup` and `RuleExecutionFieldGroup` from PR elastic#268164 **What's NOT in this PR (deferred):** - No Recovery Condition step (PR E) - No tracking toggle or split-query support - Notifications step is a placeholder callout - Runbook URL / Dashboard link are disabled stubs - Sandbox UI unpolished (redesign in PR C) - YAML round-trip editing deferred ### State architecture ``` useForm<FormValues>() ← submitted values: name, tags, schedule, query, timeField, grouping, delays useReducer(uiReducer) ← UI only: step, childOpen, fullQuery, yamlMode, queryCommitted, sandboxDateStart/End ``` One bridge: when the user clicks **Apply changes** in the Sandbox, a `useEffect` syncs `uiState.fullQuery → setValue('evaluation.query.base', ...)`. Everything else writes directly to RHF via `useFormContext()`. --- ## Test plan ### Prerequisites Start Elasticsearch: ```bash cd ~/__projects__/kibana-dev/pr-b-rhf-form node scripts/es snapshot --license=trial ``` Start Kibana (Node 24 required): ```bash export NVM_DIR="$HOME/.nvm" && source "$NVM_DIR/nvm.sh" && nvm use 24 KBN_USE_RSPACK=true yarn start \ --server.port=5603 \ --server.basePath=/ifh \ --server.rewriteBasePath=true ``` `config/kibana.dev.yml`: ```yaml xpack.alerting_v2.enabled: true dev.basePathProxyTarget: 5604 mockIdpPlugin.enabled: false ``` Navigate to: `http://localhost:5603/ifh/app/management/alertingV2/rules` --- ### Create flow - [ ] Click **Create in flyout** (next to the primary "Create rule" button) — flyout opens in create mode; Discover Sandbox opens alongside it automatically - [ ] Verify 3 steps in the header stepper: Alert Condition, Details & Artifacts, Notifications - [ ] In the Sandbox: type a query (e.g. `FROM logs-* | STATS count = COUNT(*) BY host.name | WHERE count > 0`), click **Search** — verify histogram and results grid appear - [ ] Click **Apply changes** — Sandbox closes; Alert Condition step shows the committed query summary - [ ] Change the **Time field** dropdown — verify it reflects in the form - [ ] Adjust **schedule** and **lookback** via the schedule fields - [ ] Click **Next** → **Details & Artifacts**: fill in rule name and tags via the standard field group - [ ] Click **Next** → **Notifications**: verify placeholder callout appears (not wired yet — expected) - [ ] Click **Create rule** — verify rule appears in the list with correct name, tags, and schedule - [ ] Verify the primary **Create rule** button (filled, left of "Create in flyout") still navigates to the full-page form ### Edit flow - [ ] Click the edit (pencil) icon on an existing rule — flyout opens in edit mode, pre-populated with that rule's values - [ ] Modify the rule name in Details & Artifacts - [ ] Click **Save rule** — verify the rule updates and the change appears in the list ### YAML preview - [ ] Toggle YAML mode in the flyout header — stepper collapses; CodeEditor shows read-only rule YAML - [ ] Fill in form fields, toggle YAML mode — verify YAML reflects the current values ### Edge cases - [ ] Open the Sandbox, edit the query, close without clicking **Apply changes** — verify the Alert Condition step still shows the previously committed query - [ ] Verify **Next** is disabled while the Sandbox is open --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Bailey Cash <bailey.cash@elastic.co>
…astic#269011) ## Summary Follow-up to elastic#268774 addressing feedback from Bailey and Yiannis left after merge. ## Changes **Bug fix (Yiannis):** `metadata.tags ?? []` revert — the API schema marks `tags` as optional but requires `.min(1)` if present, so sending `[]` is rejected. The key is now omitted entirely when tags is absent or empty. **Nits (Bailey):** - Merge duplicate `@kbn/code-editor` imports in `compose_discover_child.tsx` and `query_summary.tsx` - Use `paths.ruleCreate` constant in `rules_list_page.tsx` instead of the inline URL string (consistent with the rest of the plugin) - Replace the inline `AstNode` interface + unsafe cast in `compose_discover_form.tsx` with `isOptionNode` type guard from `@elastic/esql` ## Scout test tracking The deleted `quick_edit_rule.spec.ts` tests are tracked in elastic#268958 (child of M2 epic rna-program#482). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Summary
Closes #268770
(May close PART of #268771 ??)
Adds
ComposeDiscoverFlyout, the stepped Edit Form flyout for v2 rule authoring. This is PR B in a series; PR A (#268739) addsHorizontalMinimalStepperas a foundation.What's in this PR:
ComposeDiscoverFlyoutcomponent with a 3-step form: Alert Condition → Details & Artifacts → Notificationsname,tags,schedule,lookback,timeField,grouping,query, delays) live inuseForm<FormValues>(); reducer owns only UI state (step,childOpen,yamlMode,queryCommitted, etc.)useCreateRulemutation; entry point is the new "Create in flyout" button on the rules list (alongside "Create rule" which still navigates to the full-page form)useUpdateRule; entry point is the edit icon on any rule row (replacesQuickEditRuleFlyout)RuleDetailsFieldGroupandRuleExecutionFieldGroupfrom PR [Alerting v2] Add quick edit rule flyout to the rules list #268164What's NOT in this PR (deferred):
State architecture
One bridge: when the user clicks Apply changes in the Sandbox, a
useEffectsyncsuiState.fullQuery → setValue('evaluation.query.base', ...). Everything else writes directly to RHF viauseFormContext().Test plan
Prerequisites
Start Elasticsearch:
Start Kibana (Node 24 required):
config/kibana.dev.yml:Navigate to:
http://localhost:5603/ifh/app/management/alertingV2/rulesCreate flow
FROM logs-* | STATS count = COUNT(*) BY host.name | WHERE count > 0), click Search — verify histogram and results grid appearEdit flow
YAML preview
Edge cases