Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e9854fe
Create edit ILM flow
SoniaSanzV Feb 17, 2026
7e048d1
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Feb 17, 2026
86da194
fix type
SoniaSanzV Feb 17, 2026
8d1fe22
Add missing prop in tests
SoniaSanzV Feb 17, 2026
64aa842
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 17, 2026
ab3e4e7
Fix left padding
SoniaSanzV Feb 18, 2026
90390ca
fix text jump
SoniaSanzV Feb 18, 2026
9076f68
Fix on save validation
SoniaSanzV Feb 18, 2026
8c50e6f
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 18, 2026
a72141d
Fix as any lint error
SoniaSanzV Feb 18, 2026
f38fd55
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 18, 2026
974e31d
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 19, 2026
6dbde72
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 19, 2026
68ea503
Fix problem on save
SoniaSanzV Feb 20, 2026
e5c6f2c
not display donwsampling for non metrics streams
SoniaSanzV Feb 20, 2026
7675f16
Fix type check
SoniaSanzV Feb 20, 2026
1f9f187
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 20, 2026
924c5c8
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 20, 2026
ac82385
Fix stream type in test
SoniaSanzV Feb 20, 2026
8f559b3
Fix stream type in test
SoniaSanzV Feb 20, 2026
aa7cd64
Update callout copy
SoniaSanzV Feb 20, 2026
fc5c22c
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 23, 2026
7d3984a
normalizeIlmPhases
SoniaSanzV Feb 23, 2026
17a2191
Merge branch 'main' into downsampling/edit_ilm_action
SoniaSanzV Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.
Comment thread
damian-polewski marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In some instances, I was receiving a validation errors when one shouldn't exist. For example, in the attached screenshot, 3d is larger than 0ms, but it's shown as an error. Not entirely sure, but it feels like it might have something to do with the speed at which the user is entering a value in the number input.

Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @MichaelMarcialis, could you let me know what was the configuration of this policy?

Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
SoniaSanzV marked this conversation as resolved.
Comment thread
SoniaSanzV marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('Converter Helpers', () => {
read_failure_store: true,
manage_failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
effective_failure_store: {
disabled: {},
Expand Down Expand Up @@ -142,6 +143,7 @@ describe('Converter Helpers', () => {
read_failure_store: true,
manage_failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
effective_lifecycle: {
dsl: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface IngestStreamPrivileges {
read_failure_store: boolean;
// User can manage failure store information
manage_failure_store: boolean;
// User can create snapshot repositories (needed for frozen phase searchable snapshots)
create_snapshot_repository: boolean;
}

const ingestStreamPrivilegesSchema: z.Schema<IngestStreamPrivileges> = z.object({
Expand All @@ -48,6 +50,7 @@ const ingestStreamPrivilegesSchema: z.Schema<IngestStreamPrivileges> = z.object(
text_structure: z.boolean(),
read_failure_store: z.boolean(),
manage_failure_store: z.boolean(),
create_snapshot_repository: z.boolean(),
});

export interface IngestBase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ describe('ClassicStream', () => {
text_structure: true,
read_failure_store: true,
manage_failure_store: true,
create_snapshot_repository: true,
view_index_metadata: true,
},
data_stream_exists: true,
Expand Down Expand Up @@ -157,6 +158,7 @@ describe('ClassicStream', () => {
text_structure: true,
failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
data_stream_exists: true,
dashboards: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ describe('WiredStream', () => {
read_failure_store: true,
manage_failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
effective_lifecycle: {
dsl: {},
Expand Down Expand Up @@ -165,6 +166,7 @@ describe('WiredStream', () => {
text_structure: true,
failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
dashboards: [],
queries: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,8 @@ export class StreamsClient {
REQUIRED_MANAGE_PRIVILEGES.push('monitor_text_structure');
}

const CREATE_SNAPSHOT_REPOSITORY_CLUSTER_PRIVILEGE = 'cluster:admin/repository/put';

const REQUIRED_INDEX_PRIVILEGES = [
'read',
'write',
Expand All @@ -557,7 +559,7 @@ export class StreamsClient {

const privileges =
await this.dependencies.scopedClusterClient.asCurrentUser.security.hasPrivileges({
cluster: REQUIRED_MANAGE_PRIVILEGES,
cluster: [...REQUIRED_MANAGE_PRIVILEGES, CREATE_SNAPSHOT_REPOSITORY_CLUSTER_PRIVILEGE],
index: [
{
names,
Expand Down Expand Up @@ -588,6 +590,8 @@ export class StreamsClient {
text_structure: isServerless ? true : privileges.cluster.monitor_text_structure,
read_failure_store: names.every((name) => privileges.index[name].read_failure_store),
manage_failure_store: names.every((name) => privileges.index[name].manage_failure_store),
create_snapshot_repository:
privileges.cluster[CREATE_SNAPSHOT_REPOSITORY_CLUSTER_PRIVILEGE] === true,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,39 @@ const lifecycleIlmPoliciesUpdateRoute = createServerRoute({
},
});

const lifecycleSnapshotRepositoriesRoute = createServerRoute({
endpoint: 'GET /internal/streams/lifecycle/_snapshot_repositories',
options: {
access: 'internal',
},
security: {
authz: {
requiredPrivileges: [STREAMS_API_PRIVILEGES.read],
},
},
params: z.object({}),
handler: async ({
request,
getScopedClients,
}): Promise<{ repositories: Array<{ name: string; type: string }> }> => {
const { scopedClusterClient } = await getScopedClients({ request });
const repositoriesByName = await scopedClusterClient.asCurrentUser.snapshot.getRepository({
name: '*',
});

const repositories = Object.entries(repositoriesByName).map(([name, { type }]) => ({
name,
type: type ?? '',
}));

return { repositories };
},
});

export const internalLifecycleRoutes = {
...lifecycleStatsRoute,
...lifecycleIlmExplainRoute,
...lifecycleIlmPoliciesRoute,
...lifecycleIlmPoliciesUpdateRoute,
...lifecycleSnapshotRepositoriesRoute,
};
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const privileges = {
text_structure: true,
read_failure_store: true,
manage_failure_store: true,
create_snapshot_repository: true,
};

const buildWiredDefinition = (): Streams.WiredStream.GetResponse => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const createMockClassicStreamDefinition = (
read_failure_store: true,
manage_failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
data_stream_exists: true,
effective_lifecycle: { dsl: {} },
Expand Down Expand Up @@ -81,6 +82,7 @@ export const createMockWiredStreamDefinition = (
read_failure_store: true,
manage_failure_store: true,
view_index_metadata: true,
create_snapshot_repository: true,
},
inherited_fields: {
'attributes.inherited_field': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ describe('utils', () => {
text_structure: true,
read_failure_store: true,
manage_failure_store: true,
create_snapshot_repository: true,
});

const createWiredDefinition = (): Streams.WiredStream.GetResponse => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import { DataLifecycleSummary } from './data_lifecycle_summary';
import { type LifecyclePhase } from './lifecycle_types';

describe('DataLifecycleSummary', () => {
const defaultProps = {
model: {
phases: [],
},
capabilities: { canManageLifecycle: true },
showDownsampling: true,
};
describe('Loading State', () => {
it('should show skeleton when data is being fetched', () => {
const phases: LifecyclePhase[] = [];
render(<DataLifecycleSummary phases={phases} loading={true} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ loading: true, phases: [] }} />);

expect(screen.getByTestId('dataLifecycleSummary-title')).toBeInTheDocument();
expect(screen.getByTestId('dataLifecycleSummary-skeleton')).toBeInTheDocument();
Expand All @@ -23,8 +29,7 @@ describe('DataLifecycleSummary', () => {

describe('Empty State', () => {
it('should render title with no phases when phases array is empty', () => {
const phases: LifecyclePhase[] = [];
render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} />);

expect(screen.getByTestId('dataLifecycleSummary-title')).toBeInTheDocument();
expect(screen.queryByTestId('dataLifecycleSummary-skeleton')).not.toBeInTheDocument();
Expand Down Expand Up @@ -62,7 +67,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByText('0d')).toBeInTheDocument();
expect(screen.getByTestId('lifecyclePhase-hot-name')).toBeInTheDocument();
Expand Down Expand Up @@ -93,7 +98,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByText('0s')).toBeInTheDocument();
});
Expand All @@ -119,7 +124,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByTestId('lifecyclePhase-Main phase-name')).toBeInTheDocument();
expect(screen.getByTestId('lifecyclePhase-Main phase-size')).toHaveTextContent('2.0 GB');
Expand All @@ -139,7 +144,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByTestId('lifecyclePhase-Main phase-name')).toBeInTheDocument();
expect(screen.queryByTestId('dataLifecycle-delete-icon')).not.toBeInTheDocument();
Expand All @@ -156,7 +161,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByText('∞')).toBeInTheDocument();
});
Expand All @@ -179,7 +184,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByTestId('downsamplingPhase-1h-label')).toBeInTheDocument();
expect(screen.getByTestId('downsamplingPhase-1h-interval')).toHaveTextContent('1h');
Expand Down Expand Up @@ -208,16 +213,14 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.getByTestId('downsamplingPhase-1h-label')).toBeInTheDocument();
expect(screen.getByTestId('downsamplingPhase-1h-interval')).toHaveTextContent('1h');
expect(screen.queryByTestId('downsamplingPhase-delete-label')).not.toBeInTheDocument();
});
});

describe('DSL Downsampling', () => {
it('should render multiple downsampling steps in a single bar for DSL', () => {
it('should not render downsampling bar when show downsampling is false', () => {
const phases: LifecyclePhase[] = [
{
color: '#FF0000',
Expand All @@ -235,12 +238,38 @@ describe('DataLifecycleSummary', () => {

render(
<DataLifecycleSummary
phases={phases}
downsampleSteps={downsampleSteps}
canManageLifecycle
{...defaultProps}
model={{ phases, downsampleSteps }}
showDownsampling={false}
/>
);

expect(screen.queryByTestId('downsamplingBar-label')).not.toBeInTheDocument();
expect(screen.queryByTestId('downsamplingPhase-1d-label')).not.toBeInTheDocument();
expect(screen.queryByText('20d')).not.toBeInTheDocument();
expect(screen.queryByText('40d')).not.toBeInTheDocument();
});
});

describe('DSL Downsampling', () => {
it('should render multiple downsampling steps in a single bar for DSL', () => {
const phases: LifecyclePhase[] = [
{
color: '#FF0000',
name: 'hot',
label: 'hot',
size: '1.0 MB',
grow: true,
},
];

const downsampleSteps = [
{ fixed_interval: '1d', after: '20d' },
{ fixed_interval: '5d', after: '40d' },
];

render(<DataLifecycleSummary {...defaultProps} model={{ phases, downsampleSteps }} />);

expect(screen.getByTestId('downsamplingPhase-1d-label')).toBeInTheDocument();
expect(screen.getByTestId('downsamplingPhase-1d-interval')).toHaveTextContent('1d');
expect(screen.getByTestId('downsamplingPhase-5d-label')).toBeInTheDocument();
Expand All @@ -261,7 +290,7 @@ describe('DataLifecycleSummary', () => {
},
];

render(<DataLifecycleSummary phases={phases} canManageLifecycle />);
render(<DataLifecycleSummary {...defaultProps} model={{ phases }} />);

expect(screen.queryByTestId('downsamplingPhase-1d-label')).not.toBeInTheDocument();
});
Expand Down Expand Up @@ -290,11 +319,10 @@ describe('DataLifecycleSummary', () => {

render(
<DataLifecycleSummary
phases={phases}
isIlm
onRemovePhase={jest.fn()}
onRemoveDownsampleStep={jest.fn()}
canManageLifecycle
{...defaultProps}
model={{ phases }}
phaseActions={{ onRemovePhase: jest.fn() }}
downsamplingActions={{ onRemoveDownsampleStep: jest.fn() }}
/>
);

Expand All @@ -308,4 +336,55 @@ describe('DataLifecycleSummary', () => {
expect(screen.getByTestId('lifecyclePhase-warm-removeButton')).toBeInTheDocument();
});
});

describe('Edit flyout open behavior', () => {
it('should navigate to phase when edit flyout is open (no popover)', () => {
const onEditPhase = jest.fn();
const phases: LifecyclePhase[] = [
{ grow: 5, name: 'hot', label: 'hot', color: '#FF0000', min_age: '0d' },
{ grow: 3, name: 'warm', label: 'warm', color: '#FFA500', min_age: '30d' },
];

render(
<DataLifecycleSummary
{...defaultProps}
model={{ phases }}
phaseActions={{ onEditPhase }}
uiState={{ isEditLifecycleFlyoutOpen: true }}
/>
);

fireEvent.click(screen.getByTestId('lifecyclePhase-warm-button'));

expect(onEditPhase).toHaveBeenCalledWith(phases[1].label);
expect(screen.queryByTestId('lifecyclePhase-warm-popoverTitle')).not.toBeInTheDocument();
});

it('should navigate to downsampling step when edit flyout is open (no popover)', () => {
const onEditDownsampleStep = jest.fn();
const phases: LifecyclePhase[] = [
{
color: '#FF0000',
name: 'hot',
label: 'hot',
grow: 5,
downsample: { after: '0d', fixed_interval: '1h' },
},
];

render(
<DataLifecycleSummary
{...defaultProps}
model={{ phases }}
downsamplingActions={{ onEditDownsampleStep }}
uiState={{ isEditLifecycleFlyoutOpen: true }}
/>
);

fireEvent.click(screen.getByTestId('downsamplingPhase-1h-label'));

expect(onEditDownsampleStep).toHaveBeenCalledWith(1, 'hot');
expect(screen.queryByTestId('downsamplingPopover-step1-title')).not.toBeInTheDocument();
});
});
});
Loading
Loading