Skip to content

Commit 794f1b4

Browse files
[8.19] [Index management] Support DS failure store configuration (elastic#223389) (elastic#224356)
# Backport This will backport the following commits from `main` to `8.19`: - [[Index management] Support DS failure store configuration (elastic#223389)](elastic#223389) <!--- Backport version: 10.0.1 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Ignacio Rivas","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-06-17T13:11:22Z","message":"[Index management] Support DS failure store configuration (elastic#223389)","sha":"d67c52710ef98ca4b641794e0e67a2b7960bf0ad","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Index Management","Team:Kibana Management","release_note:skip","Team:obs-ux-management","backport:version","v9.1.0","v8.19.0"],"title":"[Index management] Support DS failure store configuration","number":223389,"url":"https://github.com/elastic/kibana/pull/223389","mergeCommit":{"message":"[Index management] Support DS failure store configuration (elastic#223389)","sha":"d67c52710ef98ca4b641794e0e67a2b7960bf0ad"}},"sourceBranch":"main","suggestedTargetBranches":["8.19"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/223389","number":223389,"mergeCommit":{"message":"[Index management] Support DS failure store configuration (elastic#223389)","sha":"d67c52710ef98ca4b641794e0e67a2b7960bf0ad"}},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: Elastic Machine <[email protected]>
1 parent 1a477b3 commit 794f1b4

File tree

18 files changed

+499
-20
lines changed

18 files changed

+499
-20
lines changed

x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ export const createDataStreamPayload = (dataStream: Partial<DataStream>): DataSt
308308
privileges: {
309309
delete_index: true,
310310
manage_data_stream_lifecycle: true,
311+
read_failure_store: true,
311312
},
312313
hidden: false,
313314
lifecycle: {

x-pack/platform/plugins/shared/index_management/__jest__/client_integration/home/data_streams_tab.test.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,19 @@ describe('Data Streams tab', () => {
458458

459459
const ds1 = createDataStreamPayload({
460460
name: 'dataStream1',
461-
privileges: { delete_index: true, manage_data_stream_lifecycle: true },
461+
privileges: {
462+
delete_index: true,
463+
manage_data_stream_lifecycle: true,
464+
read_failure_store: true,
465+
},
462466
});
463467
const ds2 = createDataStreamPayload({
464468
name: 'dataStream2',
465-
privileges: { delete_index: true, manage_data_stream_lifecycle: true },
469+
privileges: {
470+
delete_index: true,
471+
manage_data_stream_lifecycle: true,
472+
read_failure_store: true,
473+
},
466474
});
467475

468476
setLoadDataStreamsResponse([ds1, ds2]);
@@ -1215,20 +1223,36 @@ describe('Data Streams tab', () => {
12151223

12161224
const dataStreamFullPermissions = createDataStreamPayload({
12171225
name: 'dataStreamFullPermissions',
1218-
privileges: { delete_index: true, manage_data_stream_lifecycle: true },
1226+
privileges: {
1227+
delete_index: true,
1228+
manage_data_stream_lifecycle: true,
1229+
read_failure_store: true,
1230+
},
12191231
});
12201232
const dataStreamNoDelete = createDataStreamPayload({
12211233
name: 'dataStreamNoDelete',
1222-
privileges: { delete_index: false, manage_data_stream_lifecycle: true },
1234+
privileges: {
1235+
delete_index: false,
1236+
manage_data_stream_lifecycle: true,
1237+
read_failure_store: true,
1238+
},
12231239
});
12241240
const dataStreamNoEditRetention = createDataStreamPayload({
12251241
name: 'dataStreamNoEditRetention',
1226-
privileges: { delete_index: true, manage_data_stream_lifecycle: false },
1242+
privileges: {
1243+
delete_index: true,
1244+
manage_data_stream_lifecycle: false,
1245+
read_failure_store: true,
1246+
},
12271247
});
12281248

12291249
const dataStreamNoPermissions = createDataStreamPayload({
12301250
name: 'dataStreamNoPermissions',
1231-
privileges: { delete_index: false, manage_data_stream_lifecycle: false },
1251+
privileges: {
1252+
delete_index: false,
1253+
manage_data_stream_lifecycle: false,
1254+
read_failure_store: false,
1255+
},
12321256
});
12331257

12341258
describe('delete', () => {

x-pack/platform/plugins/shared/index_management/common/types/data_streams.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type TimestampField = TimestampFieldFromEs;
2323
interface PrivilegesFromEs {
2424
delete_index: boolean;
2525
manage_data_stream_lifecycle: boolean;
26+
read_failure_store: boolean;
2627
}
2728

2829
type Privileges = PrivilegesFromEs;
@@ -46,6 +47,7 @@ export interface EnhancedDataStreamFromEs extends IndicesDataStream {
4647
privileges: {
4748
delete_index: boolean;
4849
manage_data_stream_lifecycle: boolean;
50+
read_failure_store: boolean;
4951
};
5052
index_mode?: string | null;
5153
}
@@ -68,6 +70,7 @@ export interface DataStream {
6870
privileges: Privileges;
6971
hidden: boolean;
7072
nextGenerationManagedBy: string;
73+
failureStoreEnabled?: boolean;
7174
lifecycle?: IndicesDataStreamLifecycleWithRollover & {
7275
enabled?: boolean;
7376
effective_retention?: string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import {
10+
EuiModal,
11+
EuiModalBody,
12+
EuiModalFooter,
13+
EuiModalHeader,
14+
EuiModalHeaderTitle,
15+
EuiButtonEmpty,
16+
EuiButton,
17+
EuiSpacer,
18+
} from '@elastic/eui';
19+
import { i18n } from '@kbn/i18n';
20+
import { FormattedMessage } from '@kbn/i18n-react';
21+
import { configureFailureStoreFormSchema } from './schema';
22+
import {
23+
useForm,
24+
useFormIsModified,
25+
Form,
26+
UseField,
27+
ToggleField,
28+
} from '../../../../../shared_imports';
29+
30+
import { DataStream } from '../../../../../../common';
31+
import { useAppContext } from '../../../../app_context';
32+
import { updateDSFailureStore } from '../../../../services/api';
33+
34+
interface Props {
35+
dataStreams: DataStream[];
36+
ilmPolicyName?: string;
37+
ilmPolicyLink?: string;
38+
onClose: (data?: { hasUpdatedFailureStore: boolean }) => void;
39+
}
40+
41+
export const ConfigureFailureStoreModal: React.FunctionComponent<Props> = ({
42+
dataStreams,
43+
onClose,
44+
}) => {
45+
// We will support multiple data streams in the future, but for now we only support one.
46+
const dataStream = dataStreams[0];
47+
48+
const {
49+
services: { notificationService },
50+
} = useAppContext();
51+
52+
const { form } = useForm({
53+
defaultValue: {
54+
dsFailureStore: dataStream?.failureStoreEnabled ?? false,
55+
},
56+
schema: configureFailureStoreFormSchema,
57+
id: 'configureFailureStoreForm',
58+
});
59+
const isDirty = useFormIsModified({ form });
60+
61+
const formHasErrors = form.getErrors().length > 0;
62+
const disableSubmit = formHasErrors || !isDirty || form.isValid === false;
63+
64+
const onSubmitForm = async () => {
65+
const { isValid, data } = await form.submit();
66+
67+
if (!isValid) {
68+
return;
69+
}
70+
71+
return updateDSFailureStore([dataStream.name], data).then(({ data: responseData, error }) => {
72+
if (responseData) {
73+
if (responseData.warning) {
74+
notificationService.showWarningToast(responseData.warning);
75+
return onClose({ hasUpdatedFailureStore: true });
76+
}
77+
78+
const successMessage = i18n.translate(
79+
'xpack.idxMgmt.dataStreams.configureFailureStoreModal.successFailureStoreNotification',
80+
{
81+
defaultMessage:
82+
'Failure store {disabledFailureStore, plural, one { disabled } other { enabled } }',
83+
values: { disabledFailureStore: !data.dsFailureStore ? 1 : 0 },
84+
}
85+
);
86+
87+
notificationService.showSuccessToast(successMessage);
88+
89+
return onClose({ hasUpdatedFailureStore: true });
90+
}
91+
92+
if (error) {
93+
const errorMessage = i18n.translate(
94+
'xpack.idxMgmt.dataStreams.configureFailureStoreModal.errorFailureStoreNotification',
95+
{
96+
defaultMessage: "Error configuring failure store: ''{error}''",
97+
values: { error: error.message },
98+
}
99+
);
100+
notificationService.showDangerToast(errorMessage);
101+
}
102+
103+
onClose();
104+
});
105+
};
106+
107+
return (
108+
<EuiModal
109+
onClose={() => onClose()}
110+
data-test-subj="configureFailureStoreModal"
111+
css={{ width: 650 }}
112+
>
113+
<Form form={form} data-test-subj="configureFailureStoreForm">
114+
<EuiModalHeader>
115+
<EuiModalHeaderTitle>
116+
<FormattedMessage
117+
id="xpack.idxMgmt.dataStreams.configureFailureStoreModal.modalTitleText"
118+
defaultMessage="Configure failure store"
119+
/>
120+
</EuiModalHeaderTitle>
121+
</EuiModalHeader>
122+
123+
<EuiModalBody>
124+
<FormattedMessage
125+
id="xpack.idxMgmt.dataStreams.configureFailureStoreModal.modalDescriptionText"
126+
defaultMessage="A failure store is a secondary index within a data stream, used to store failed documents."
127+
/>
128+
<EuiSpacer />
129+
130+
<UseField
131+
path="dsFailureStore"
132+
component={ToggleField}
133+
data-test-subj="enableDataStreamFailureStoreToggle"
134+
euiFieldProps={{
135+
label: i18n.translate(
136+
'xpack.idxMgmt.dataStreams.configureFailureStoreModal.infiniteRetentionPeriodField',
137+
{
138+
defaultMessage: 'Enable data stream failure store',
139+
}
140+
),
141+
}}
142+
/>
143+
144+
<EuiSpacer />
145+
</EuiModalBody>
146+
147+
<EuiModalFooter>
148+
<EuiButtonEmpty data-test-subj="cancelButton" onClick={() => onClose()}>
149+
<FormattedMessage
150+
id="xpack.idxMgmt.dataStreams.configureFailureStoreModal.cancelButtonLabel"
151+
defaultMessage="Cancel"
152+
/>
153+
</EuiButtonEmpty>
154+
155+
<EuiButton
156+
fill
157+
type="submit"
158+
isLoading={false}
159+
disabled={disableSubmit}
160+
data-test-subj="saveButton"
161+
onClick={onSubmitForm}
162+
>
163+
<FormattedMessage
164+
id="xpack.idxMgmt.dataStreams.configureFailureStoreModal.saveButtonLabel"
165+
defaultMessage="Save"
166+
/>
167+
</EuiButton>
168+
</EuiModalFooter>
169+
</Form>
170+
</EuiModal>
171+
);
172+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { ConfigureFailureStoreModal } from './configure_failure_store_modal';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { FIELD_TYPES, FormSchema } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
9+
10+
export const configureFailureStoreFormSchema: FormSchema = {
11+
dsFailureStore: {
12+
type: FIELD_TYPES.TOGGLE,
13+
defaultValue: false,
14+
},
15+
};

x-pack/platform/plugins/shared/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { SectionError, Error, DataHealth } from '../../../../components';
4343
import { useLoadDataStream } from '../../../../services/api';
4444
import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
4545
import { EditDataRetentionModal } from '../edit_data_retention_modal';
46+
import { ConfigureFailureStoreModal } from '../configure_failure_store_modal';
4647
import { humanizeTimeStamp } from '../humanize_time_stamp';
4748
import { ILM_PAGES_POLICY_EDIT } from '../../../../constants';
4849
import {
@@ -129,6 +130,7 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
129130
const [isManagePopOverOpen, setManagePopOver] = useState<boolean>(false);
130131
const [isDeleting, setIsDeleting] = useState<boolean>(false);
131132
const [isEditingDataRetention, setIsEditingDataRetention] = useState<boolean>(false);
133+
const [isConfiguringFailureStore, setIsConfiguringFailureStore] = useState<boolean>(false);
132134

133135
const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName);
134136

@@ -400,6 +402,23 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
400402
</ConditionalWrap>
401403
),
402404
dataTestSubj: 'dataRetentionDetail',
405+
},
406+
{
407+
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.failureStoreTitle', {
408+
defaultMessage: 'Failure store',
409+
}),
410+
toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.failureStoreToolTip', {
411+
defaultMessage:
412+
'The failure store provides a mechanism to store documents that fail to be indexed.',
413+
}),
414+
content: dataStream.failureStoreEnabled
415+
? i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.failureStoreEnabledText', {
416+
defaultMessage: 'Enabled',
417+
})
418+
: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.failureStoreDisabledText', {
419+
defaultMessage: 'Disabled',
420+
}),
421+
dataTestSubj: 'failureStoreDetail',
403422
}
404423
);
405424

@@ -520,6 +539,22 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
520539
},
521540
]
522541
: []),
542+
...(dataStream?.privileges?.read_failure_store
543+
? [
544+
{
545+
key: 'configureFailureStore',
546+
name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.configureFailureStore', {
547+
defaultMessage: 'Configure failure store',
548+
}),
549+
'data-test-subj': 'configureFailureStoreButton',
550+
icon: <EuiIcon type="gear" size="m" />,
551+
onClick: () => {
552+
closePopover();
553+
setIsConfiguringFailureStore(true);
554+
},
555+
},
556+
]
557+
: []),
523558
...(dataStream?.privileges?.delete_index
524559
? [
525560
{
@@ -571,6 +606,19 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({
571606
/>
572607
)}
573608

609+
{isConfiguringFailureStore && dataStream && (
610+
<ConfigureFailureStoreModal
611+
onClose={(data) => {
612+
if (data && data?.hasUpdatedFailureStore) {
613+
onClose(true);
614+
} else {
615+
setIsConfiguringFailureStore(false);
616+
}
617+
}}
618+
dataStreams={[dataStream]}
619+
/>
620+
)}
621+
574622
<EuiFlyout
575623
onClose={() => onClose()}
576624
data-test-subj="dataStreamDetailPanel"

x-pack/platform/plugins/shared/index_management/public/application/services/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ export async function updateDataRetention(
113113
});
114114
}
115115

116+
export async function updateDSFailureStore(
117+
dataStreams: string[],
118+
data: {
119+
dsFailureStore: boolean;
120+
}
121+
) {
122+
const body = { dsFailureStore: data.dsFailureStore, dataStreams };
123+
124+
return sendRequest({
125+
path: `${API_BASE_PATH}/data_streams/configure_failure_store`,
126+
method: 'put',
127+
body,
128+
});
129+
}
130+
116131
export async function loadIndices() {
117132
const response = await httpService.httpClient.get<any>(`${API_BASE_PATH}/indices`);
118133
return response.data ? response.data : response;

0 commit comments

Comments
 (0)