Skip to content

[Cases] Add global reusable field definitions#269962

Open
lgestc wants to merge 32 commits into
elastic:mainfrom
lgestc:tasks/cases-global-reusable-fields
Open

[Cases] Add global reusable field definitions#269962
lgestc wants to merge 32 commits into
elastic:mainfrom
lgestc:tasks/cases-global-reusable-fields

Conversation

@lgestc
Copy link
Copy Markdown
Contributor

@lgestc lgestc commented May 19, 2026

Summary

Adds an applyToAllCases flag to field library definitions so that specific fields appear in every case — in both the create form and the case view sidebar — regardless of which template (if any) is selected. Values are stored in extended_fields using the same key format as template-specific fields, so no separate storage mechanism is needed.

Motivation

Teams with many templates often need the same contextual fields (e.g. incident type, severity tier, business unit) on every case, not just on cases created from a particular template. Without this flag, those fields had to be duplicated into every template definition. This flag lets a field be defined once in the library and surface everywhere automatically.

Changes

  • Domain & API schema: applyToAllCases?: boolean added to FieldDefinitionSchema and the GET /fields query filter; SO mapping updated to match.
  • Server validation: create.ts and validators.ts (bulk update path) now allow extended_fields keys that correspond to applyToAllCases: true definitions even when no template is set. Non-global keys without a template still reject as before.
  • Field library UI: new checkbox (with tooltip) in the field definition flyout; badge column on the all-definitions table. Both wired to the create/update mutations.
  • Case view: new GlobalCaseFields component renders global fields in the sidebar, independently of the existing TemplateFields component (which remains unchanged).
  • Case create form: CreateCaseTemplateFields always fetches and renders global fields; useTemplateFormSync preserves their values when the user switches or clears a template.

Risk & Migration

Score: 2 — Optional boolean field on a brand-new SO type with no existing production documents; no exported public API surfaces changed; all validation changes are backward-compatible (existing cases without applyToAllCases defs are unaffected). Gated behind the existing templates.enabled config flag.

Testing

Manual:

  1. Enable the templates.enabled flag in kibana.dev.yml.
  2. Open the field library, create a field definition, and check Apply to all cases. Confirm the badge appears in the table.
  3. Open the case create form with no template selected — the global field should appear under a "Global fields" heading.
  4. Select a template — the global field should still be visible alongside template-specific fields, and its value should survive switching templates or clearing the template selection.
  5. Open an existing case in the case view sidebar — the global field should appear above the template fields section.
  6. Submit a case with a global field value set and no template — confirm it saves without a 400 error.

Automated:

  • Unit tests added/updated:
    • server/services/field_definitions/index.test.ts — filter logic for applyToAllCases
    • server/client/cases/validators.test.ts — allow/reject logic for global vs. non-global keys
    • public/components/field_library/components/field_definition_flyout.test.tsx — checkbox state and onSave payload
    • public/components/create/template_fields.test.tsx — global fields render path
    • public/components/create/use_template_form_sync.test.ts — value preservation across template switches

Checklist

Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.

  • Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support
  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the docker list
  • This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The release_note:breaking label should be applied in these situations.
  • Flaky Test Runner was used on any tests changed
  • The PR description includes the appropriate Release Notes section, and the correct release_note:* label is applied per the guidelines
  • Review the backport guidelines and apply applicable backport:* labels.

lgestc and others added 4 commits May 19, 2026 15:58
Adds a `renderInAllCases` boolean to `FieldDefinition` so field-library
entries can be flagged as global — appearing in every case view and create
form regardless of template selection, with values stored in
`extended_fields` using the same key format as template-specific fields.

- Domain/API: `renderInAllCases?: boolean` on `FieldDefinitionSchema` and
  `FieldDefinitionsFindRequest` filter; SO mappings updated to match.
- Server: `create.ts` and `validators.ts` relax extended_fields validation
  to allow keys that correspond to `renderInAllCases: true` field defs even
  when no template is set; `resolveGlobalFieldKeys` helper fetches and
  parses global defs server-side.
- Field library UI: `renderInAllCases` checkbox (with tooltip) in the
  definition flyout; badge column on the all-definitions page.
- Case view: new `GlobalCaseFields` component renders global fields in the
  sidebar, independent of `TemplateFields`.
- Case create: `CreateCaseTemplateFields` always renders global fields;
  `useTemplateFormSync` preserves their values across template switches.
- Tests: flyout checkbox, service filter, validators allow-logic, create
  form global fields path — all green; i18n check passes (4 new keys).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When `template: null` clears a template on update, extended_fields
containing only renderInAllCases keys were incorrectly rejected.
The no-template path now applies uniformly — global keys are always
permitted regardless of whether the template is absent or being cleared;
non-global keys are still rejected in both cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs prevented renderInAllCases fields from appearing in the create
case form when no template was picked:

1. useTemplateFormSync returned isLoading:true indefinitely when no
   templateId was set, because react-query keeps a disabled query in
   loading state when there is no cached data.  Fixed by short-circuiting
   isLoading to false whenever templateId is absent.

2. CreateCaseTemplateFields rendered the "Template not selected" callout
   even when global fields were already visible, suppressing the
   FormProvider wrapper.  Fixed by skipping the callout when global
   inline fields are present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lgestc lgestc changed the title [Cases] Add renderInAllCases flag to reusable field definitions [Cases] Add applyToAllCases flag to reusable field definitions May 20, 2026
- Fix createFormSerializer reading the wrong key: the form stores
  extended fields under the camelCase key 'extendedFields' (schema
  field) but the serializer was destructuring 'extended_fields'. Also
  fix createFormDeserializer to map 'extended_fields' → 'extendedFields'
  when loading existing case data. Tighten CaseFormFieldsSchemaProps to
  Omit 'extended_fields' from the CasePostRequest base to avoid the
  ambiguity entirely.
- Change "Render in all cases" label/column text to "Apply to all
  cases" to match the applyToAllCases field name, and rename the TS
  translation constants accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lgestc lgestc added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Team:Cases Security Solution Cases team Cases: Templates Development of cases templates system labels May 20, 2026
lgestc and others added 3 commits May 20, 2026 15:35
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n, tests

- Extract `parseFieldDefinitionsToInlineFields` to `common/utils/template_fields.ts`
  so the YAML→InlineField parsing is shared between client and server instead of
  duplicated in three places
- Fix N+1 query: `validateExtendedFieldsInRequest` now receives a pre-resolved
  `globalKeys: Set<string>` instead of calling `fieldDefinitionsService` per case
  inside `Promise.all`; `bulk_update.ts` resolves keys once per unique owner
- Add `modelVersion1` to the `case-field-definition` saved-object type to register
  the `applyToAllCases` mapping with Elasticsearch
- Add unit tests for `parseFieldDefinitionsToInlineFields`, `GlobalCaseFields`
  component, and `create.ts` extended_fields validation paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lgestc lgestc marked this pull request as ready for review May 20, 2026 20:22
@lgestc lgestc requested a review from a team as a code owner May 20, 2026 20:22
@infra-vault-gh-plugin-prod
Copy link
Copy Markdown

Pinging @elastic/kibana-cases (Team:Cases)

lgestc and others added 4 commits May 21, 2026 10:02
The jest.mock for './template_fields' only stubbed TemplateFields but
not GlobalCaseFields, which was added in this PR. Rendering undefined
as a component triggered the error boundary in the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- client.ts: throw 400 when getFieldDefinitions is called with no owner,
  preventing trivially-passing auth check on empty entities array
- client.ts: add comment documenting that field definition mutations
  intentionally produce no UserAction (library-level objects, not case mutations)
- get_field_definitions_route.ts: coerce applyToAllCases query param from
  string "true" to boolean — HTTP query params arrive as strings, causing
  the service filter to never apply
- use_get_field_definitions.ts: skip query when no owner provided, preventing
  unnecessary 400 errors when template has not loaded yet
- model_version_1.ts: add data_backfill to set applyToAllCases: false on
  pre-existing field definition documents
- template_fields.tsx: check isError in GlobalCaseFields and
  CreateCaseTemplateFields to distinguish query error from empty results
- use_template_form_sync.test.ts: add tests covering globalFieldKeys
  preservation across template switch and template deselection
- template_fields.test.tsx: add test for GlobalCaseFields isError state
- client.test.ts: add getFieldDefinitions tests covering the empty-owner guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… the active template

When a field definition has both applyToAllCases: true and is referenced
via \$ref in the active template, it was previously rendered twice — once in
the global fields section and once in the template section. The template may
apply name/default overrides via the \$ref entry, so the template section owns
display for those fields. GlobalCaseFields and the global section in the
create form now exclude any field whose name appears as a \$ref in the
active template.

globalFieldKeys remains unfiltered so useTemplateFormSync still preserves
values for those fields across template switches.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lgestc lgestc changed the title [Cases] Add applyToAllCases flag to reusable field definitions [Cases] Add global reusable field definitions May 21, 2026
lgestc and others added 9 commits May 21, 2026 14:48
Incorporate main's HiddenField registration for CASE_EXTENDED_FIELDS
while preserving the branch's expanded loading condition and refactored
globalFieldsFragment/templateFieldsFragment structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nded fields heading in case view

- Gate isLoadingFields on templateId to avoid react-query v4's indefinite
  loading state for disabled queries blocking global-field render
- Add "Extended fields" section header in case view to match "Global fields"
- Fix GlobalCaseFields excessive padding by wrapping in a single div so it
  appears as one flex item in the gutterSize="xl" column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eedback

- Remove client-side merge in processExtendedFields; each form section
  now sends only its own fields to prevent last-write-wins race conditions
- Add server-side merge in createPatchCasesPayload using originalCase
  attributes so concurrent GlobalCaseFields / TemplateFields saves don't
  overwrite each other
- Fix boolean coercion in getFieldDefinitionsRoute (false was treated as
  undefined)
- Add JSDoc to resolveGlobalFieldKeys explaining unsecured SO access is safe
- Add visibility comment to casesRedesign config block in server/index.ts
- Add explanatory comment to modelVersion1 data_backfill defaulting logic
- Update tests to assert only section-own fields are sent (not merged set)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add API integration tests for applyToAllCases filter, create/patch
  cases with global extended_fields, and server-side merge behavior
- Add comment to FieldDefinitionsFindRequestSchema explaining the
  boolean/string coercion gap at the HTTP route layer
- Add comment to FieldDefinitionsService explaining that applyToAllCases
  false and undefined are both no-op (no filter added)
- Add comment in CreateCaseTemplateFields explaining why owner[0] is safe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michaelolo24 michaelolo24 added the ci:cloud-deploy Create or update a Cloud deployment label May 26, 2026
…renders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lgestc lgestc requested a review from michaelolo24 May 27, 2026 09:46
lgestc and others added 9 commits May 27, 2026 15:37
… fix save and dedup

- GlobalCaseFields renders without a section title and is moved to appear
  before CustomFields in the sidebar
- TemplateFields filters out inline fields whose name collides with a global
  field definition, preventing duplicate entries when a template and the
  global field library share the same field name
- validateExtendedFields gains a partial option (used in the update path)
  that skips the required check for fields absent from the request, fixing
  400 errors when saving only global fields on a case that has a template
  with required fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anges

- TemplateFields tests: add default mockUseGetFieldDefinitions return value
  in beforeEach now that TemplateFields calls useGetFieldDefinitions
- GlobalCaseFields test: update assertion to expect no 'Global fields' title
- validators test: update required-field test to explicitly clear the field
  (empty string) rather than omitting it; add a test covering the partial
  update case where only global fields are provided

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iew sidebar

Both were direct children of the xl-gutter flex column, causing an
oversized gap. Wrapping them in a div makes them a single flex item.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kibanamachine
Copy link
Copy Markdown
Contributor

kibanamachine commented May 31, 2026

💔 Build Failed

Failed CI Steps

Test Failures

  • [job] [logs] Scout Lane #5 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Privileges - should show privileges callout and disable switch for user without risk engine privileges
  • [job] [logs] Scout Lane #5 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should discard changes when clicking discard button
  • [job] [logs] Scout Lane #5 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should show save bar when toggling closed alerts switch
  • [job] [logs] Scout Lane #5 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should show save bar when toggling retain checkbox

Metrics [docs]

Async chunks

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

id before after diff
cases 2.4MB 2.4MB +4.3KB

Page load bundle

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

id before after diff
cases 205.0KB 205.1KB +83.0B

History

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

Labels

backport:skip This PR does not require backporting Cases: Templates Development of cases templates system ci:cloud-deploy Create or update a Cloud deployment release_note:skip Skip the PR/issue when compiling release notes Team:Cases Security Solution Cases team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants