diff --git a/cypress/integration/data_streams.js b/cypress/integration/data_streams.js index ac5f6fe75..193112db5 100644 --- a/cypress/integration/data_streams.js +++ b/cypress/integration/data_streams.js @@ -41,7 +41,8 @@ describe("Data stream", () => { describe("can create a data stream", () => { it("successfully", () => { cy.get('[data-test-subj="Create data streamButton"]').click(); - cy.get('[data-test-subj="form-row-name"]').type(`ds-{enter}`); + cy.get('[data-test-subj="form-row-name"] [data-test-subj="comboBoxSearchInput"]').type(`ds-{enter}`); + cy.get("body").click(); cy.get('[data-test-subj="CreateDataStreamCreateButton"]').click(); cy.contains("ds- has been successfully created."); }); diff --git a/public/components/DescriptionListHoz/DescriptionListHoz.tsx b/public/components/DescriptionListHoz/DescriptionListHoz.tsx index 70725850b..12d97e8d4 100644 --- a/public/components/DescriptionListHoz/DescriptionListHoz.tsx +++ b/public/components/DescriptionListHoz/DescriptionListHoz.tsx @@ -1,4 +1,4 @@ -import { EuiDescriptionList, EuiDescriptionListProps, EuiFlexGrid, EuiFlexGridProps, EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; +import { EuiDescriptionList, EuiDescriptionListProps, EuiFlexGrid, EuiFlexGridProps, EuiFlexItem } from "@elastic/eui"; import React from "react"; const DisplayItem = ( diff --git a/public/components/FormGenerator/built_in_components/index.tsx b/public/components/FormGenerator/built_in_components/index.tsx index 2b302816e..c725d7da0 100644 --- a/public/components/FormGenerator/built_in_components/index.tsx +++ b/public/components/FormGenerator/built_in_components/index.tsx @@ -6,7 +6,7 @@ import EuiComboBox from "../../ComboBoxWithoutWarning"; export type ComponentMapEnum = "Input" | "Number" | "Switch" | "Select" | "Text" | "ComboBoxSingle" | "CheckBox"; export interface IFieldComponentProps extends IEuiToolTipWrapperProps { - onChange: (val: IFieldComponentProps["value"]) => void; + onChange: (val: IFieldComponentProps["value"], ...args: any) => void; value?: any; [key: string]: any; } @@ -78,7 +78,7 @@ const componentMap: Record { if (selectedOptions && selectedOptions[0]) { - onChange(selectedOptions[0].label); + onChange(selectedOptions[0].label, selectedOptions[0]); } else { onChange(undefined); } diff --git a/public/pages/CreateDataStream/components/IndexSettings/IndexSettings.tsx b/public/pages/CreateDataStream/components/IndexSettings/IndexSettings.tsx index 3e5285784..e6f999247 100644 --- a/public/pages/CreateDataStream/components/IndexSettings/IndexSettings.tsx +++ b/public/pages/CreateDataStream/components/IndexSettings/IndexSettings.tsx @@ -45,24 +45,6 @@ export default function IndexSettings(props: SubDetailProps) { @@ -76,24 +58,6 @@ export default function IndexSettings(props: SubDetailProps) { diff --git a/public/pages/CreateDataStream/containers/BackingIndices/BackingIndices.tsx b/public/pages/CreateDataStream/containers/BackingIndices/BackingIndices.tsx index 60ac29dce..fc64c82f8 100644 --- a/public/pages/CreateDataStream/containers/BackingIndices/BackingIndices.tsx +++ b/public/pages/CreateDataStream/containers/BackingIndices/BackingIndices.tsx @@ -1,11 +1,12 @@ import React, { useContext, useEffect, useState } from "react"; -import { EuiBasicTable, EuiHealth, EuiLink, EuiSpacer } from "@elastic/eui"; +import { EuiBasicTable, EuiHealth, EuiLink, EuiSpacer, EuiTitle } from "@elastic/eui"; import { ServicesContext } from "../../../../services"; import { BrowserServices } from "../../../../models/interfaces"; import { ManagedCatIndex } from "../../../../../server/models/interfaces"; import { DataStreamInEdit, SubDetailProps } from "../../interface"; import { ROUTES } from "../../../../utils/constants"; import { ContentPanel } from "../../../../components/ContentPanel"; +import CustomFormRow from "../../../../components/CustomFormRow"; const renderNumber = (value: string) => { return value || "-"; @@ -48,7 +49,21 @@ export default function BackingIndices(props: SubDetailProps) { }, [values.name]); const writingIndex = (values.indices || [])[(values.indices?.length || 0) - 1]?.index_name; return ( - + + + Backing indexes + + + <> + + + } + > {item.extraStatus || status}; }, }, + { + field: "rep", + name: "Writing index", + textOnly: true, + render: (value: string, record: ManagedCatIndex) => { + return record.index === writingIndex ? "Yes" : "No"; + }, + }, { field: "store.size", name: "Total size", @@ -152,14 +174,6 @@ export default function BackingIndices(props: SubDetailProps) { textOnly: true, dataType: "number", }, - { - field: "rep", - name: "Writing index", - textOnly: true, - render: (value: string, record: ManagedCatIndex) => { - return record.index === writingIndex ? "Yes" : "No"; - }, - }, ]} /> diff --git a/public/pages/CreateDataStream/containers/CreateDataStream/__snapshots__/CreateDataStream.test.tsx.snap b/public/pages/CreateDataStream/containers/CreateDataStream/__snapshots__/CreateDataStream.test.tsx.snap index 19e46190f..4139954d2 100644 --- a/public/pages/CreateDataStream/containers/CreateDataStream/__snapshots__/CreateDataStream.test.tsx.snap +++ b/public/pages/CreateDataStream/containers/CreateDataStream/__snapshots__/CreateDataStream.test.tsx.snap @@ -28,7 +28,7 @@ exports[` spec it goes to data streams page when click cance style="padding-top: 0px; padding-bottom: 4px;" >
- A data stream is internally composed of multiple backing indices. Search requests are routed to all the backing indices, while indexing requests are routed to the latest write index. + Data streams simplify the management of time-series data. Data streams are composed of multiple backing indices. Search requests are routed to all backing indexes, while indexing requests are routed to the latest write index.
) >([]); const [isSubmitting, setIsSubmitting] = useState(false); const field = useField({ - values: {} as Partial, + values: {} as Partial, }); const destroyRef = useRef(false); const onSubmit = async () => { @@ -97,7 +97,7 @@ const DataStreamDetail = (props: DataStreamDetailProps, ref: Ref) destroyRef.current = true; }; }, []); - const values: DataStreamInEdit & { matchedTemplate?: string } = field.getValues(); + const values: DataStreamInEdit = field.getValues(); const subCompontentProps = { ...props, isEdit, @@ -115,8 +115,8 @@ const DataStreamDetail = (props: DataStreamDetailProps, ref: Ref) label="" helpText={
- A data stream is internally composed of multiple backing indices. Search requests are routed to all the backing indices, - while indexing requests are routed to the latest write index.{" "} + Data streams simplify the management of time-series data. Data streams are composed of multiple backing indices. Search + requests are routed to all backing indexes, while indexing requests are routed to the latest write index.{" "} Learn more. diff --git a/public/pages/CreateDataStream/containers/DataStreamDetail/__snapshots__/DataStreamDetail.test.tsx.snap b/public/pages/CreateDataStream/containers/DataStreamDetail/__snapshots__/DataStreamDetail.test.tsx.snap index b3ad86f59..96a257e2a 100644 --- a/public/pages/CreateDataStream/containers/DataStreamDetail/__snapshots__/DataStreamDetail.test.tsx.snap +++ b/public/pages/CreateDataStream/containers/DataStreamDetail/__snapshots__/DataStreamDetail.test.tsx.snap @@ -25,7 +25,7 @@ exports[` spec render component 1`] = ` style="padding-top: 0px; padding-bottom: 4px;" >
- A data stream is internally composed of multiple backing indices. Search requests are routed to all the backing indices, while indexing requests are routed to the latest write index. + Data streams simplify the management of time-series data. Data streams are composed of multiple backing indices. Search requests are routed to all backing indexes, while indexing requests are routed to the latest write index.
>(null); const values: DataStreamInEdit = field.getValues(); + const searchValue = values.name || ""; const Component = isEdit ? AllBuiltInComponents.Text : AllBuiltInComponents.ComboBoxSingle; + const allDataStreamOrderedByPriority = useMemo(() => { + const newDataStream = [...allDataStreamTemplates]; + newDataStream.sort((a, b) => (a.index_template.priority > b.index_template.priority ? -1 : 1)); + return newDataStream; + }, [allDataStreamTemplates]); const matchedList = allDataStreamTemplates.filter((item) => { if (!searchValue) { - return false; + return true; + } else if (item.index_template.index_patterns.some((item) => item.match(new RegExp(searchValue, "i")))) { + return true; } return filterByMinimatch(searchValue || "", item.index_template.index_patterns); @@ -40,9 +44,42 @@ export default function DefineTemplate( pattern: INDEX_NAMING_PATTERN, message: "Invalid data stream name.", }, + { + validator: (rule, value) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (field.getValue("matchedTemplate")) { + resolve(""); + } else { + reject(`No matching index template found for data stream [${value || ""}]`); + } + }, 0); + }); + }, + }, ], props: {}, }); + const onBlurCallback = () => { + const nameValue = field.getValue("name") || ""; + // find the matched template + const findMatchedTemplate = allDataStreamOrderedByPriority.find((item) => { + const { findMatchesPattern, findStringPattern } = findPatternMatchesString(nameValue, item); + return findMatchesPattern || findStringPattern; + }); + suggestionRegister.onChange(nameValue); + if (findMatchedTemplate) { + setMatchedTemplate({ + matchedTemplate: findMatchedTemplate, + field, + }); + } else { + field.setValues({ + matchedTemplate: "", + template: {}, + }); + } + }; return isEdit ? ( @@ -54,7 +91,18 @@ export default function DefineTemplate( }, { title: "Status", - description: values.status, + description: ((health: string) => { + const healthLowerCase = health?.toLowerCase() as "green" | "yellow" | "red"; + const color = health ? HEALTH_TO_COLOR[healthLowerCase] : "subdued"; + const text = (health || "").toLowerCase(); + return ( + + + {text} + + + ); + })(values.status), }, { title: "Template name", @@ -74,44 +122,89 @@ export default function DefineTemplate( ) : ( - + + Enter a data stream name. It must match an index pattern from a data stream template.{" "} + + Manage templates + + + } + > ({ - label: searchValue, - value: item.name, + label: item.name, + value: item, }))} - renderOption={(option: { value: string }) => { + renderOption={(option: { value: TemplateItem; label: string }) => { return ( -

- Matched template: {option.value} -

+ + {option.value.index_template.index_patterns.join(",")} + + {option.label} + + ); }} async {...suggestionRegister} - onCreateOption={undefined} + ref={comboBoxRef} + onCreateOption={() => {}} + customOptionText={ + searchValue + ? `{searchValue} doesn’t match index patterns from any templates. Specify another name or create a data stream template.` + : `There are no data stream templates. Please create a data stream template.` + } onSearchChange={(dataStreamName: string) => { - setSearchValue(dataStreamName); + if (!dataStreamName) { + return; + } + field.setValue("name", dataStreamName); }} - onChange={(value) => { + onFocus={() => { + comboBoxRef.current?.setState({ + searchValue: field.getValue("name") || "", + }); + }} + onBlur={() => { + onBlurCallback(); + comboBoxRef.current?.setState({ + searchValue: "", + }); + }} + onChange={(value: string, selectedOption: { value: TemplateItem }) => { if (!value) { field.resetValues({ name: "", }); } else { - suggestionRegister.onChange(value); - const template = matchedList[0]?.index_template?.template; - const payload = { - matchedTemplate: matchedList[0]?.name, - template: { - ...template, - settings: flatten(template.settings || {}), - }, - }; - - set(payload, "template.mappings.properties", transformObjectToArray(get(payload, "template.mappings.properties", {}))); - - field.setValues(payload); + const findItem = selectedOption.value; + const { findMatchesPattern, findWordLikePattern } = findPatternMatchesString(searchValue, findItem); + let finalSearchValue = searchValue; + if (findMatchesPattern) { + // do nothing + } else if (findWordLikePattern) { + const finalReplaceValue = getStringBeforeStar(findWordLikePattern); + finalSearchValue = finalReplaceValue; + field.setValue("name", finalReplaceValue); + } + setTimeout(() => { + comboBoxRef.current?.setState( + { + searchValue: finalSearchValue, + hasFocus: true, + isListOpen: true, + }, + () => { + onBlurCallback(); + } + ); + }, 100); } }} /> diff --git a/public/pages/CreateDataStream/containers/IndexAlias/IndexAlias.tsx b/public/pages/CreateDataStream/containers/IndexAlias/IndexAlias.tsx index 08c05af7c..d14f29b91 100644 --- a/public/pages/CreateDataStream/containers/IndexAlias/IndexAlias.tsx +++ b/public/pages/CreateDataStream/containers/IndexAlias/IndexAlias.tsx @@ -5,12 +5,11 @@ import CustomFormRow from "../../../../components/CustomFormRow"; import { ServicesContext } from "../../../../services"; import { BrowserServices } from "../../../../models/interfaces"; import DescriptionListHoz from "../../../../components/DescriptionListHoz"; -import { ALIAS_SELECT_RULE } from "../../../../utils/constants"; import { getCommonFormRowProps } from "../../hooks"; import { SubDetailProps } from "../../interface"; export default function IndexAlias(props: SubDetailProps) { - const { readonly, field } = props; + const { isEdit, field } = props; const values = field.getValues(); const services = useContext(ServicesContext) as BrowserServices; return ( @@ -28,7 +27,7 @@ export default function IndexAlias(props: SubDetailProps) { <>
- {readonly ? ( + {!isEdit ? ( <> services?.commonService.apiCaller({ diff --git a/public/pages/CreateDataStream/containers/TemplateMappings/TemplateMappings.tsx b/public/pages/CreateDataStream/containers/TemplateMappings/TemplateMappings.tsx index e68f71c3e..ce68cc41c 100644 --- a/public/pages/CreateDataStream/containers/TemplateMappings/TemplateMappings.tsx +++ b/public/pages/CreateDataStream/containers/TemplateMappings/TemplateMappings.tsx @@ -1,12 +1,12 @@ import React, { useContext, useRef } from "react"; import { EuiFormRow, EuiLink, EuiSpacer, EuiTitle } from "@elastic/eui"; +import { CoreStart } from "opensearch-dashboards/public"; import { SubDetailProps } from "../../interface"; import IndexMapping, { IIndexMappingsRef } from "../../../../components/IndexMapping"; import { CoreServicesContext } from "../../../../components/core_services"; -import { CoreStart } from "opensearch-dashboards/public"; export default function TemplateMappings(props: SubDetailProps) { - const { readonly, field, isEdit } = props; + const { field, isEdit } = props; const mappingsRef = useRef(null); const coreServices = useContext(CoreServicesContext) as CoreStart; return ( @@ -40,19 +40,6 @@ export default function TemplateMappings(props: SubDetailProps) { { - if (result) { - return Promise.reject(result); - } - - return Promise.resolve(""); - }); - }, - }, - ], })} readonly isEdit={isEdit} diff --git a/public/pages/CreateDataStream/hooks.tsx b/public/pages/CreateDataStream/hooks.tsx index 8560a5e1b..f8dbeb74b 100644 --- a/public/pages/CreateDataStream/hooks.tsx +++ b/public/pages/CreateDataStream/hooks.tsx @@ -1,5 +1,10 @@ import { EuiFormRowProps } from "@elastic/eui"; +import { flatten } from "flat"; +import { get, set } from "lodash"; import { FieldInstance, transformNameToString } from "../../lib/field"; +import { TemplateItem } from "./interface"; +import { transformObjectToArray } from "../../components/IndexMapping"; +import { filterByMinimatch } from "../../../utils/helper"; export const getCommonFormRowProps = (name: string | string[], field: FieldInstance): Partial => { return { @@ -8,3 +13,37 @@ export const getCommonFormRowProps = (name: string | string[], field: FieldInsta "data-test-subj": `form-row-${transformNameToString(name)}`, }; }; + +export const setMatchedTemplate = ({ matchedTemplate, field }: { matchedTemplate: TemplateItem; field: FieldInstance }) => { + const payload = { + matchedTemplate: matchedTemplate.name, + template: JSON.parse( + JSON.stringify({ + ...matchedTemplate.index_template.template, + settings: flatten(matchedTemplate.index_template.template.settings || {}), + }) + ), + }; + + set(payload, "template.mappings.properties", transformObjectToArray(get(payload, "template.mappings.properties", {}))); + + field.setValues(payload); +}; + +export const getStringBeforeStar = (string: string) => string.replace(/^([^*]*).*$/, "$1"); + +export const findPatternMatchesString = (value: string, template: TemplateItem) => { + const { index_patterns } = template.index_template; + // matched by wildcard + const findMatchesPattern = index_patterns.find((item) => filterByMinimatch(value, [item])); + // matched by letters + const findWordLikePattern = index_patterns.find((item) => item.match(new RegExp(value, "i"))); + // exactly the same + const findStringPattern = index_patterns.find((item) => item === value); + + return { + findMatchesPattern, + findWordLikePattern, + findStringPattern, + }; +}; diff --git a/public/pages/CreateDataStream/interface.ts b/public/pages/CreateDataStream/interface.ts index 237f690d8..9401a5d8d 100644 --- a/public/pages/CreateDataStream/interface.ts +++ b/public/pages/CreateDataStream/interface.ts @@ -1,6 +1,7 @@ import { RouteComponentProps } from "react-router-dom"; import { FieldInstance } from "../../lib/field"; import { DataStream } from "../../../server/models/interfaces"; +import { TemplateItemRemote } from "../../../models/interfaces"; export interface DataStreamDetailProps { templateName?: string; @@ -18,3 +19,8 @@ export interface SubDetailProps extends DataStreamDetailProps { export interface DataStreamInEdit extends DataStream { matchedTemplate?: string; } + +export type TemplateItem = { + name: string; + index_template: TemplateItemRemote; +}; diff --git a/public/pages/CreateIndexTemplate/components/DefineTemplate/DefineTemplate.tsx b/public/pages/CreateIndexTemplate/components/DefineTemplate/DefineTemplate.tsx index 2b89f64cf..3127f4349 100644 --- a/public/pages/CreateIndexTemplate/components/DefineTemplate/DefineTemplate.tsx +++ b/public/pages/CreateIndexTemplate/components/DefineTemplate/DefineTemplate.tsx @@ -33,7 +33,7 @@ export default function DefineTemplate(props: SubDetailProps) { }, { title: "Index patterns", - description: values.index_patterns?.join(","), + description: values.index_patterns?.join(", "), }, { title: "Priority", diff --git a/public/pages/CreateIndexTemplate/components/TemplateType/TemplateType.tsx b/public/pages/CreateIndexTemplate/components/TemplateType/TemplateType.tsx index b52d7b8ab..ba927f6b1 100644 --- a/public/pages/CreateIndexTemplate/components/TemplateType/TemplateType.tsx +++ b/public/pages/CreateIndexTemplate/components/TemplateType/TemplateType.tsx @@ -1,9 +1,15 @@ -import { EuiRadio } from "@elastic/eui"; +import { EuiRadio, EuiSpacer } from "@elastic/eui"; import { TEMPLATE_TYPE } from "../../../../utils/constants"; import React from "react"; +import { AllBuiltInComponents } from "../../../../components/FormGenerator"; +import CustomFormRow from "../../../../components/CustomFormRow"; export interface ITemplateTypeProps { - value?: {}; + value?: { + timestamp_field?: { + name: string; + }; + }; onChange: (val: ITemplateTypeProps["value"]) => void; } @@ -19,10 +25,36 @@ export default function TemplateType(props: ITemplateTypeProps) { /> e.target.checked && onChange({})} + onChange={(e) => + e.target.checked && + onChange({ + timestamp_field: { + name: "@timestamp", + }, + }) + } label={TEMPLATE_TYPE.DATA_STREAM} checked={value !== undefined} /> + + {value !== undefined ? ( + + { + if (!val) { + onChange({}); + } else { + onChange({ + timestamp_field: { + name: val, + }, + }); + } + }} + /> + + ) : null} ); } diff --git a/public/pages/CreateIndexTemplate/containers/CreateIndexTemplate/CreateIndexTemplate.tsx b/public/pages/CreateIndexTemplate/containers/CreateIndexTemplate/CreateIndexTemplate.tsx index 916bbc3a4..2747d7a7c 100644 --- a/public/pages/CreateIndexTemplate/containers/CreateIndexTemplate/CreateIndexTemplate.tsx +++ b/public/pages/CreateIndexTemplate/containers/CreateIndexTemplate/CreateIndexTemplate.tsx @@ -5,10 +5,10 @@ import React, { Component } from "react"; import { RouteComponentProps } from "react-router-dom"; +import { isEqual } from "lodash"; import TemplateDetail from "../TemplateDetail"; import { BREADCRUMBS, ROUTES } from "../../../../utils/constants"; import { CoreServicesContext } from "../../../../components/core_services"; -import { isEqual } from "lodash"; interface CreateIndexTemplateProps extends RouteComponentProps<{ template?: string; mode?: string }> {} @@ -62,6 +62,7 @@ export default class CreateIndexTemplate extends Component ) - } /> + } + /> <>This is {ROUTES.TEMPLATES}} /> diff --git a/public/pages/CreateIndexTemplate/containers/TemplateDetail/TemplateDetail.tsx b/public/pages/CreateIndexTemplate/containers/TemplateDetail/TemplateDetail.tsx index 0176986aa..9aff6a45d 100644 --- a/public/pages/CreateIndexTemplate/containers/TemplateDetail/TemplateDetail.tsx +++ b/public/pages/CreateIndexTemplate/containers/TemplateDetail/TemplateDetail.tsx @@ -5,6 +5,7 @@ import React, { forwardRef, useContext, useEffect, useImperativeHandle, useRef, Ref, useState } from "react"; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiTitle } from "@elastic/eui"; +import queryString from "query-string"; import { transformArrayToObject } from "../../../../components/IndexMapping"; import { TemplateItem, TemplateItemRemote } from "../../../../../models/interfaces"; import useField, { FieldInstance } from "../../../../lib/field"; @@ -23,6 +24,7 @@ import DefineTemplate from "../../components/DefineTemplate"; import IndexSettings from "../../components/IndexSettings"; import IndexAlias from "../IndexAlias"; import TemplateMappings from "../TemplateMappings"; +import { merge } from "lodash"; export interface TemplateDetailProps { templateName?: string; @@ -30,6 +32,7 @@ export interface TemplateDetailProps { onSubmitSuccess?: (templateName: string) => void; readonly?: boolean; history: RouteComponentProps["history"]; + location: RouteComponentProps["location"]; } const TemplateDetail = (props: TemplateDetailProps, ref: Ref) => { @@ -40,8 +43,17 @@ const TemplateDetail = (props: TemplateDetailProps, ref: Ref) => const [visible, setVisible] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const oldValue = useRef(undefined); - const field = useField({ - values: { + const searchObject = queryString.parseUrl(props.location.search); + if (searchObject.query.values) { + try { + searchObject.query.values = JSON.parse((searchObject.query.values || "") as string); + } catch (e) { + // do nothing + } + } + const defaultValues = merge( + {}, + { priority: 0, template: { settings: { @@ -51,6 +63,10 @@ const TemplateDetail = (props: TemplateDetailProps, ref: Ref) => }, }, } as Partial, + searchObject.query.values + ); + const field = useField({ + values: defaultValues, onChange(name, value) { if (name === "data_stream" && value === undefined) { field.deleteValue(name); diff --git a/public/pages/CreateIndexTemplate/containers/TemplateDetail/__snapshots__/TemplateDetail.test.tsx.snap b/public/pages/CreateIndexTemplate/containers/TemplateDetail/__snapshots__/TemplateDetail.test.tsx.snap index 5cce65538..deced8894 100644 --- a/public/pages/CreateIndexTemplate/containers/TemplateDetail/__snapshots__/TemplateDetail.test.tsx.snap +++ b/public/pages/CreateIndexTemplate/containers/TemplateDetail/__snapshots__/TemplateDetail.test.tsx.snap @@ -194,6 +194,9 @@ exports[` spec render component 1`] = ` Data streams
+
{ static contextType = CoreServicesContext; constructor(props: DataStreamsProps) { @@ -270,8 +277,8 @@ class DataStreams extends Component { fullWidth helpText={
- A data stream is internally composed of multiple backing indices. Search requests are routed to all the backing indices, - while indexing requests are routed to the latest write index.{" "} + Data streams simplify the management of time-series data. Data streams are composed of multiple backing indices. Search + requests are routed to all backing indexes, while indexing requests are routed to the latest write index.{" "} Learn more. @@ -312,18 +319,33 @@ class DataStreams extends Component { name: "Status", sortable: true, render: (health: string, item) => { - const color = health ? HEALTH_TO_COLOR[health.toLowerCase()] : "subdued"; + const healthLowerCase = health?.toLowerCase() as "green" | "yellow" | "red"; + const color = health ? HEALTH_TO_COLOR[healthLowerCase] : "subdued"; const text = (health || item.status || "").toLowerCase(); return ( - - {text} - + + + {text} + + + ); + }, + }, + { + field: "template", + name: "Template", + sortable: true, + render: (value: unknown) => { + return ( + + {value} + ); }, }, { field: "backing_indices", - name: "Backing indices count", + name: "Backing indexes count", sortable: true, align: "right", }, @@ -336,18 +358,6 @@ class DataStreams extends Component { return <>{record.store_size || ""}; }, }, - { - field: "template", - name: "Template", - sortable: true, - render: (value: unknown) => { - return ( - - {value} - - ); - }, - }, ]} isSelectable={true} itemId="name" diff --git a/public/pages/DataStreams/containers/DataStreams/__snapshots__/DataStreams.test.tsx.snap b/public/pages/DataStreams/containers/DataStreams/__snapshots__/DataStreams.test.tsx.snap index aa299fb27..6a732d219 100644 --- a/public/pages/DataStreams/containers/DataStreams/__snapshots__/DataStreams.test.tsx.snap +++ b/public/pages/DataStreams/containers/DataStreams/__snapshots__/DataStreams.test.tsx.snap @@ -29,7 +29,7 @@ exports[` spec renders the component 1`] = ` id="some_html_id-help-0" >
- A data stream is internally composed of multiple backing indices. Search requests are routed to all the backing indices, while indexing requests are routed to the latest write index. + Data streams simplify the management of time-series data. Data streams are composed of multiple backing indices. Search requests are routed to all backing indexes, while indexing requests are routed to the latest write index. @@ -329,7 +329,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_store_size_bytes_3" + data-test-subj="tableHeaderCell_backing_indices_3" role="columnheader" scope="col" > @@ -343,9 +343,9 @@ exports[` spec renders the component 1`] = ` > - Total size + Backing indexes count @@ -354,7 +354,7 @@ exports[` spec renders the component 1`] = ` aria-live="polite" aria-sort="none" class="euiTableHeaderCell" - data-test-subj="tableHeaderCell_template_4" + data-test-subj="tableHeaderCell_store_size_bytes_4" role="columnheader" scope="col" > @@ -364,13 +364,13 @@ exports[` spec renders the component 1`] = ` type="button" > - Template + Total size diff --git a/public/pages/DataStreams/containers/DeleteDataStreamsModal/DeleteDataStreamsModal.tsx b/public/pages/DataStreams/containers/DeleteDataStreamsModal/DeleteDataStreamsModal.tsx index b211fd810..7a5dbe030 100644 --- a/public/pages/DataStreams/containers/DeleteDataStreamsModal/DeleteDataStreamsModal.tsx +++ b/public/pages/DataStreams/containers/DeleteDataStreamsModal/DeleteDataStreamsModal.tsx @@ -42,7 +42,7 @@ export default function DeleteTemplateModal(props: DeleteTemplateModalProps) { return (

- The following data streams will be permanently deleted. This action cannot be undone. + The following data streams will be permanently deleted. The backing indexes belong to the data streams will also be deleted.

    spec renders the component 1`] = ` data-test-subj="tableHeaderCell_managed_2" role="columnheader" scope="col" - style="width: 140px;" > (data_stream ? {data_stream} : "-"), }, @@ -82,7 +81,6 @@ const getColumns = (props: IColumnOptions): EuiTableFieldDataColumnType