Skip to content

[Alerting v2] Compose Discover: stepped Edit Form flyout with RHF form state#268774

Merged
jasonrhodes merged 53 commits into
elastic:mainfrom
jasonrhodes:rna/pr-b-rhf-form
May 12, 2026
Merged

[Alerting v2] Compose Discover: stepped Edit Form flyout with RHF form state#268774
jasonrhodes merged 53 commits into
elastic:mainfrom
jasonrhodes:rna/pr-b-rhf-form

Conversation

@jasonrhodes
Copy link
Copy Markdown
Member

@jasonrhodes jasonrhodes commented May 11, 2026

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) 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 [Alerting v2] Add quick edit rule flyout to the rules list #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:

cd ~/__projects__/kibana-dev/pr-b-rhf-form
node scripts/es snapshot --license=trial

Start Kibana (Node 24 required):

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:

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 NextDetails & Artifacts: fill in rule name and tags via the standard field group
  • Click NextNotifications: 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

@jasonrhodes jasonrhodes added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting v9.5.0 labels May 11, 2026
@github-actions github-actions Bot added the author:actionable-obs PRs authored by the actionable obs team label May 11, 2026
jasonrhodes and others added 12 commits May 11, 2026 16:43
…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>
@jasonrhodes jasonrhodes marked this pull request as ready for review May 12, 2026 03:26
@jasonrhodes jasonrhodes requested a review from a team as a code owner May 12, 2026 03:26
kibanamachine and others added 5 commits May 12, 2026 03:32
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>
@jasonrhodes
Copy link
Copy Markdown
Member Author

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.

jasonrhodes and others added 4 commits May 12, 2026 14:28
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>
@jasonrhodes
Copy link
Copy Markdown
Member Author

Addressing all items from your manual testing review:

Bugs fixed:

  1. Edit mode blank query → 9d50ab4: sandbox.query is now seeded from the rule's evaluation.query.base on mount in edit mode. Also renamed fullQuerysandbox.query across the state to make it clear these are pending/draft values, not committed to RHF until "Apply changes".
  2. metadata.tags undefined → 6ea8457: added ?? [] guard in mapMetadata.
  3. Group fields no options + no auto-populate → 68c6a0d: options now come from getEsqlColumns (| LIMIT 0 schema fetch) on the committed query — returns the pipeline's output columns, not source index fields. Works in edit mode without opening the sandbox. On first commit, auto-populates from the STATS BY clause using Parser from @elastic/esql.
  4. Group fields options prop → same commit.
  5. Duplicate result count → d0e117f: removed the redundant count above the grid.
  6. injectTimeFilter single-line → e0470e7: see inline comment reply.

UX:
7. Sandbox closes on Apply → intentional for now; see PR comment above for rationale and the YAML mode exception.
8. Next while sandbox open → same.
9. Missing spacer above execution fields → a9c701e: dropped the RuleExecutionFieldGroup wrapper entirely, inlined Schedule and Lookback alongside Time field and Group fields with consistent spacing.

Testing gap (#10): tracked in #268958.

jasonrhodes and others added 2 commits May 12, 2026 15:28
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>
baileycash-elastic

This comment was marked as duplicate.

Copy link
Copy Markdown
Contributor

@baileycash-elastic baileycash-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

@yiannisnikolopoulos yiannisnikolopoulos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 } : {})

@jasonrhodes
Copy link
Copy Markdown
Member Author

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.

@kibanamachine
Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] Scout Lane #8 - stateful-classic / default / local-stateful-classic - Profiling is setup and data is loaded - Admin user

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
alertingVTwo 1006 1016 +10

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
alertingVTwo 748.7KB 771.0KB +22.3KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
alertingVTwo 44.8KB 44.9KB +69.0B

History

@jasonrhodes jasonrhodes merged commit e9e242a into elastic:main May 12, 2026
32 checks passed
@jasonrhodes jasonrhodes deleted the rna/pr-b-rhf-form branch May 12, 2026 22:59
patrykkopycinski pushed a commit to patrykkopycinski/kibana that referenced this pull request May 13, 2026
…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>
jasonrhodes added a commit that referenced this pull request May 13, 2026
…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>
DennisKo pushed a commit to DennisKo/kibana that referenced this pull request May 13, 2026
…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>
DennisKo pushed a commit to DennisKo/kibana that referenced this pull request May 13, 2026
…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>
kelvtanv pushed a commit to kelvtanv/kibana that referenced this pull request May 14, 2026
…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>
kelvtanv pushed a commit to kelvtanv/kibana that referenced this pull request May 14, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author:actionable-obs PRs authored by the actionable obs team backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Alerting v2] Stepped Edit Form flyout — form state foundation

4 participants