Skip to content

Commit 887cd20

Browse files
[9.2] [Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix (#241158) (#241447)
# Backport This will backport the following commits from `main` to `9.2`: - [[Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix (#241158)](#241158) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Alex Fernandez","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-10-31T12:03:02Z","message":"[Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix (#241158)\n\nCloses [#576](https://github.com/elastic/streams-program/issues/576)\n\n## Summary\n\nWhen partitioning a wired stream, each partition has restrictions in\ntheir name by which they must keep the name of their parent partition(s)\nkeeping the hierarchy separated by dots in each level.\n\nWhile server-side the restriction is properly enforced, the input we're\nrendering at UI level does not enforce this restriction. Instead it\nallows users to alter and/or remove the required prefix. Even if an\nerror message will be presented should the name not meet the\nrequirements, it still isn't an ideal UX given the input does allow you\nto alter the prefix without clearly stating it shouldn't be modified.\n\nThis PR introduces some fixes to address the issue\n\n## UI\n### Partition Creation\nDuring creation, the stream name input has now been split into 2\nsections. The fixed prefix part of the partition name is now rendered in\na prepend section for the input, making it non editable. The child part\nof the name remains as a regular text input allowing users to name the\nnew partition however they wish to, but keeping the prefix restrictions.\n\n\n\nhttps://github.com/user-attachments/assets/cbf6df0f-47ee-4959-ad6b-a77da78605a4\n\n\n### Partition Update\nDuring update, editing the name is not allowed. However, to keep visual\nconsistency, the prefix will still be separated and rendered as a\nprepend. The input will not be editable, but it has been updated from\nusing `disabled` into using `readOnly`.\n\n<img width=\"1000\" height=\"411\" alt=\"Screenshot 2025-10-29 at 16 20 31\"\nsrc=\"https://github.com/user-attachments/assets/660f22fd-550c-463c-b9a4-d5b219ce361c\"\n/>\n\n## Gotchas\n### Prefix Truncation\nGiven the nesting nature of partitions, the prefix can become too long\nto be fully render while keeping enough space for the partition name\ninput. Generally, we try to render at most 25 prefix characters.\nAnything above that will be truncated. In the case of narrow screens,\nthe prefix prepend will never be larger than half the total size of the\nstream name input, regardless of characters, so anything that doesn't\nfit in this limitation will also be truncated.\n\n<img width=\"993\" height=\"416\" alt=\"Screenshot 2025-10-29 at 16 26 14\"\nsrc=\"https://github.com/user-attachments/assets/2a4d910a-7fd2-4614-ab40-b6ae2e05d56f\"\n/>\n\n### Invalid partition names\nAs we've seen before, each partition is composed of a prefix with the\nname of it's ancestor streams plus the partition name itself. The way we\nhandle nesting in the prefix is by separating each partition name from\nit's predecessors with a dot. These dots are added automatically by the\nsystem when creating partitions and the hierarchy *must* be kept in the\nname, otherwise the backend will reject the partition creation.\n\nAt UI level, we didn't enforce this restriction before which could lead\nusers to attempt to create a partition with dots on the name, only to be\nmet with an error description that can be confusing.\n\n<img width=\"333\" height=\"208\" alt=\"Screenshot 2025-10-30 at 17 02 05\"\nsrc=\"https://github.com/user-attachments/assets/8382b474-ed68-4ca0-b778-79799b2b55af\"\n/>\n\nNotice how the error states that the parent stream and a dot must be\nincluded, which the name `log.child.child` name does satisfy. At no\npoint is it explicit that partition names cannot contain dots\n\nInstead on relaying on the server side error, this PR introduces\nvalidation at UI level in order to display a user friendly error message\nletting them know that dots cannot be used in partition names.\n\n<img width=\"993\" height=\"431\" alt=\"Screenshot 2025-10-30 at 16 54 28\"\nsrc=\"https://github.com/user-attachments/assets/c9ba3144-fc9f-4b6c-b5f1-3ec1aa103b2a\"\n/>","sha":"7a610b1121f517e3fbfa831845d269556500f406","branchLabelMapping":{"^v9.3.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:obs-ux-logs","backport:version","Feature:Streams","v9.2.0","v9.3.0"],"title":"[Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix","number":241158,"url":"https://github.com/elastic/kibana/pull/241158","mergeCommit":{"message":"[Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix (#241158)\n\nCloses [#576](https://github.com/elastic/streams-program/issues/576)\n\n## Summary\n\nWhen partitioning a wired stream, each partition has restrictions in\ntheir name by which they must keep the name of their parent partition(s)\nkeeping the hierarchy separated by dots in each level.\n\nWhile server-side the restriction is properly enforced, the input we're\nrendering at UI level does not enforce this restriction. Instead it\nallows users to alter and/or remove the required prefix. Even if an\nerror message will be presented should the name not meet the\nrequirements, it still isn't an ideal UX given the input does allow you\nto alter the prefix without clearly stating it shouldn't be modified.\n\nThis PR introduces some fixes to address the issue\n\n## UI\n### Partition Creation\nDuring creation, the stream name input has now been split into 2\nsections. The fixed prefix part of the partition name is now rendered in\na prepend section for the input, making it non editable. The child part\nof the name remains as a regular text input allowing users to name the\nnew partition however they wish to, but keeping the prefix restrictions.\n\n\n\nhttps://github.com/user-attachments/assets/cbf6df0f-47ee-4959-ad6b-a77da78605a4\n\n\n### Partition Update\nDuring update, editing the name is not allowed. However, to keep visual\nconsistency, the prefix will still be separated and rendered as a\nprepend. The input will not be editable, but it has been updated from\nusing `disabled` into using `readOnly`.\n\n<img width=\"1000\" height=\"411\" alt=\"Screenshot 2025-10-29 at 16 20 31\"\nsrc=\"https://github.com/user-attachments/assets/660f22fd-550c-463c-b9a4-d5b219ce361c\"\n/>\n\n## Gotchas\n### Prefix Truncation\nGiven the nesting nature of partitions, the prefix can become too long\nto be fully render while keeping enough space for the partition name\ninput. Generally, we try to render at most 25 prefix characters.\nAnything above that will be truncated. In the case of narrow screens,\nthe prefix prepend will never be larger than half the total size of the\nstream name input, regardless of characters, so anything that doesn't\nfit in this limitation will also be truncated.\n\n<img width=\"993\" height=\"416\" alt=\"Screenshot 2025-10-29 at 16 26 14\"\nsrc=\"https://github.com/user-attachments/assets/2a4d910a-7fd2-4614-ab40-b6ae2e05d56f\"\n/>\n\n### Invalid partition names\nAs we've seen before, each partition is composed of a prefix with the\nname of it's ancestor streams plus the partition name itself. The way we\nhandle nesting in the prefix is by separating each partition name from\nit's predecessors with a dot. These dots are added automatically by the\nsystem when creating partitions and the hierarchy *must* be kept in the\nname, otherwise the backend will reject the partition creation.\n\nAt UI level, we didn't enforce this restriction before which could lead\nusers to attempt to create a partition with dots on the name, only to be\nmet with an error description that can be confusing.\n\n<img width=\"333\" height=\"208\" alt=\"Screenshot 2025-10-30 at 17 02 05\"\nsrc=\"https://github.com/user-attachments/assets/8382b474-ed68-4ca0-b778-79799b2b55af\"\n/>\n\nNotice how the error states that the parent stream and a dot must be\nincluded, which the name `log.child.child` name does satisfy. At no\npoint is it explicit that partition names cannot contain dots\n\nInstead on relaying on the server side error, this PR introduces\nvalidation at UI level in order to display a user friendly error message\nletting them know that dots cannot be used in partition names.\n\n<img width=\"993\" height=\"431\" alt=\"Screenshot 2025-10-30 at 16 54 28\"\nsrc=\"https://github.com/user-attachments/assets/c9ba3144-fc9f-4b6c-b5f1-3ec1aa103b2a\"\n/>","sha":"7a610b1121f517e3fbfa831845d269556500f406"}},"sourceBranch":"main","suggestedTargetBranches":["9.2"],"targetPullRequestStates":[{"branch":"9.2","label":"v9.2.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.3.0","branchLabelMappingKey":"^v9.3.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/241158","number":241158,"mergeCommit":{"message":"[Streams 🌊] Prevent stream partition names from messing up required hierarchy prefix (#241158)\n\nCloses [#576](https://github.com/elastic/streams-program/issues/576)\n\n## Summary\n\nWhen partitioning a wired stream, each partition has restrictions in\ntheir name by which they must keep the name of their parent partition(s)\nkeeping the hierarchy separated by dots in each level.\n\nWhile server-side the restriction is properly enforced, the input we're\nrendering at UI level does not enforce this restriction. Instead it\nallows users to alter and/or remove the required prefix. Even if an\nerror message will be presented should the name not meet the\nrequirements, it still isn't an ideal UX given the input does allow you\nto alter the prefix without clearly stating it shouldn't be modified.\n\nThis PR introduces some fixes to address the issue\n\n## UI\n### Partition Creation\nDuring creation, the stream name input has now been split into 2\nsections. The fixed prefix part of the partition name is now rendered in\na prepend section for the input, making it non editable. The child part\nof the name remains as a regular text input allowing users to name the\nnew partition however they wish to, but keeping the prefix restrictions.\n\n\n\nhttps://github.com/user-attachments/assets/cbf6df0f-47ee-4959-ad6b-a77da78605a4\n\n\n### Partition Update\nDuring update, editing the name is not allowed. However, to keep visual\nconsistency, the prefix will still be separated and rendered as a\nprepend. The input will not be editable, but it has been updated from\nusing `disabled` into using `readOnly`.\n\n<img width=\"1000\" height=\"411\" alt=\"Screenshot 2025-10-29 at 16 20 31\"\nsrc=\"https://github.com/user-attachments/assets/660f22fd-550c-463c-b9a4-d5b219ce361c\"\n/>\n\n## Gotchas\n### Prefix Truncation\nGiven the nesting nature of partitions, the prefix can become too long\nto be fully render while keeping enough space for the partition name\ninput. Generally, we try to render at most 25 prefix characters.\nAnything above that will be truncated. In the case of narrow screens,\nthe prefix prepend will never be larger than half the total size of the\nstream name input, regardless of characters, so anything that doesn't\nfit in this limitation will also be truncated.\n\n<img width=\"993\" height=\"416\" alt=\"Screenshot 2025-10-29 at 16 26 14\"\nsrc=\"https://github.com/user-attachments/assets/2a4d910a-7fd2-4614-ab40-b6ae2e05d56f\"\n/>\n\n### Invalid partition names\nAs we've seen before, each partition is composed of a prefix with the\nname of it's ancestor streams plus the partition name itself. The way we\nhandle nesting in the prefix is by separating each partition name from\nit's predecessors with a dot. These dots are added automatically by the\nsystem when creating partitions and the hierarchy *must* be kept in the\nname, otherwise the backend will reject the partition creation.\n\nAt UI level, we didn't enforce this restriction before which could lead\nusers to attempt to create a partition with dots on the name, only to be\nmet with an error description that can be confusing.\n\n<img width=\"333\" height=\"208\" alt=\"Screenshot 2025-10-30 at 17 02 05\"\nsrc=\"https://github.com/user-attachments/assets/8382b474-ed68-4ca0-b778-79799b2b55af\"\n/>\n\nNotice how the error states that the parent stream and a dot must be\nincluded, which the name `log.child.child` name does satisfy. At no\npoint is it explicit that partition names cannot contain dots\n\nInstead on relaying on the server side error, this PR introduces\nvalidation at UI level in order to display a user friendly error message\nletting them know that dots cannot be used in partition names.\n\n<img width=\"993\" height=\"431\" alt=\"Screenshot 2025-10-30 at 16 54 28\"\nsrc=\"https://github.com/user-attachments/assets/c9ba3144-fc9f-4b6c-b5f1-3ec1aa103b2a\"\n/>","sha":"7a610b1121f517e3fbfa831845d269556500f406"}}]}] BACKPORT--> Co-authored-by: Alex Fernandez <[email protected]>
1 parent 88bf493 commit 887cd20

File tree

7 files changed

+99
-26
lines changed

7 files changed

+99
-26
lines changed

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/edit_routing_stream_entry.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function EditRoutingStreamEntry({
3030
data-test-subj={`routingRule-${routingRule.destination}`}
3131
>
3232
<EuiFlexGroup direction="column" gutterSize="m">
33-
<StreamNameFormRow value={routingRule.destination} disabled />
33+
<StreamNameFormRow value={routingRule.destination} readOnly />
3434
<RoutingConditionEditor
3535
condition={routingRule.where}
3636
status={routingRule.status}

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/state_management/stream_routing_state_machine/stream_routing_state_machine.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { MachineImplementationsFrom, ActorRefFrom } from 'xstate5';
88
import { assign, and, enqueueActions, setup, sendTo, assertEvent } from 'xstate5';
99
import { getPlaceholderFor } from '@kbn/xstate-utils';
1010
import type { Streams } from '@kbn/streams-schema';
11-
import { isSchema, routingDefinitionListSchema } from '@kbn/streams-schema';
11+
import { isChildOf, isSchema, routingDefinitionListSchema } from '@kbn/streams-schema';
1212
import { ALWAYS_CONDITION } from '@kbn/streamlang';
1313
import type { RoutingDefinition } from '@kbn/streams-schema';
1414
import type {
@@ -107,7 +107,7 @@ export const streamRoutingMachine = setup({
107107
})),
108108
},
109109
guards: {
110-
canForkStream: and(['hasManagePrivileges', 'isValidRouting']),
110+
canForkStream: and(['hasManagePrivileges', 'isValidRouting', 'isValidChild']),
111111
canReorderRules: and(['hasManagePrivileges', 'hasMultipleRoutingRules']),
112112
canUpdateStream: and(['hasManagePrivileges', 'isValidRouting']),
113113
hasMultipleRoutingRules: ({ context }) => context.routing.length > 1,
@@ -116,6 +116,12 @@ export const streamRoutingMachine = setup({
116116
isAlreadyEditing: ({ context }, params: { id: string }) => context.currentRuleId === params.id,
117117
isValidRouting: ({ context }) =>
118118
isSchema(routingDefinitionListSchema, context.routing.map(routingConverter.toAPIDefinition)),
119+
isValidChild: ({ context }) => {
120+
const currentRule = selectCurrentRule(context);
121+
const currentStream = context.definition.stream;
122+
123+
return isChildOf(currentStream.name, currentRule.destination);
124+
},
119125
},
120126
}).createMachine({
121127
/** @xstate-layout N4IgpgJg5mDOIC5QCcD2BXALgSwHZQGVNkwBDAWwDo9sdSAbbALzygGIBtABgF1FQADqli1sqXPxAAPRACZZATkoA2AIwAWABwL1sgKybZG1bIA0IAJ6IAzKr2V1XLgs3WA7Jq7uFb5QF8-czQsViISCkpwiAs2WGIyKhIAYzBsADdIbj4kECERHHFJGQRVBS5ZSh9y8uU9PWtrPTdzKwRZdWVKTWVZQz0FOx7ZNz0AoIwcfDCEyLJo6gh6MDZgyagAJXQlyiTwzDAsyTzRQpzi9V1KPWUFAe1Pa2UevRbEVVLOrmv39W9DdTGIFWoXiESiFgWSxWE1Ym22kFohxyxwKEjONk09lkQ00vgMei4yk0rxKhK4lDcdzc1gGqlqdkBwKmoMScwh2EWyyZGy2YFmqGQEDAyCRgmEJzRoGKAFpVNYuuo3HYdKpCQplE5iZY3rIuOpKIpugomnpdN0AYEgTDmeFWaR5rsyGsAHJgADucL5SQAFqR8KxoSF8J6dn6UvRRblxaiim8vJouho9HLNJjk8p1CSbhUCXTGj51WprIzrYQWbN7RDHaQXe6Qz6-VAA9z6778AdeEdo2JJdI3o8lNZ1Ap3Lj2t0ta0iRVbCPDO9rJ4TCWg2XbRWHXtWK6Pbydm2m-hA2sQwjMJGUT3YyUMzm9L1hh03PpVCTKaoHNcDI0NNiRyu1mmME2R2Ld8B3VtG2bUsQwAMwFABrC9u1OKU41+FRHBpW5qWUIdM21Uk9QpDV500Ewbn-S1uSAu1Nydbc6z3eDkAQgMIHEPk8DSVAEL5FiENogBBJJMAFZD8ivdEEGpD8enKNxFMcZ8BjfYdKBpJpFEJG4mlUACQXXcFQIY8CmO2ASA2FNBkEoAR6BrFiqAE4TRPEztkRQ3tilVHwHGpLRVBGWx3jMQjtHsJofFqU03EVTQLXGVdaI3CEz1hPcG39I8W0yvYO2yMVJNQvsSkaTolQ8Wd3gSkcSVC-UPF1OpFRcPVi2o0sUuM9Lg0yg9oNXeswzACMPKKiVr0MeVXD1dV3D0lx6tNckOgUXUjC+dp5AMm0Zh6iBRD67YssPdhcpOg8Cq7YrvLeNxnEoPUCWxZQ4sVRd6rehMLmfDQuA8e9Rk65LywOo6eUuqCcpgvczwkybpLsIwHFUWbzX0Wd6pChwCwUh7XCaXa132kDeshr0Bphoa4cO89VEKqNbuvIKOieoldB6X5+n0erfiUfouA0dx3jnNxie6sm6YyqHsvO2HthIchUAyBGYyRoLOkcbp3Cedb1HqPn2hUP8Ae2g2EolsGpYhyC5ePGW+VgUhVfGpnEbQkoPgNXodF6AxdEeerfHJVVuiFgHbhMcWQcA63K0ocmQyVlXHbYDjcC43AeL4yghSWfZXLEkU3cvErikaNwunI-n4rUIKXkIkwLie75hcpf4raMm2Tz3FO0jT6yBTshzMCcvPRrAQuWRE4u1akz2TEUfy6gDnRasnN56i12bCXkWRGkMLvSYTpO93QAQIBrNOM6znO+Qv2BhUwIv3MZsu7oQSuDSCw+LnKRUFxg76EoHYU0Pg6RaHXsfYCp9pbHQfpfa+CC2BD1svZRyAoqCP2fq-Eu78vIs3kAmciGhfB-SGF9IcVxGh6QzN8PUMC6IQhIAKIUyBHawH5IKYUg1e6KzAGw4U89y79h6D7awAMCRaCHAoEkzVyRxV-v0OUdJ9Kx0MifeYrCeEcIQVwnR7C+GO1DLgcMIjP4Eh+joY0NIYqmkbq0XUQUnoAIaLoAGHQmGpW4UY-RvjeHU34U7F211PLMyRl8ckTxhw6BuAlRQyh5ENCrpRcoi40Z4UYRovasDtGCN0ZwygF8r5x1tOnTi1Bs68UQU-ZAL8Z5uXwTdD2pVA5XHXs+A+9Rky-HkUYTopR1ClEeIqL43jjKGMCRTLhJTkEkwoKg5ANkR6YOQNggQdSGm2lnm-Fp6tF6qnJLqDwHclRAMIvIR6AwMkJSVO4AIlpcCoCFPAHINEWT7IXqVWUj07nKmGWqDUr4m4t3oWUDwPhXDWHaN4mgdBGAsHwF80RbQ6RdCeEYIKQVPDDmsMkioSYyg3LRnmYGSUylaNaBNA5Py1AKiVP0QFNxgUkkXFXWcjgfAjHvOMnJCzmGQjACiz++glBqjuG1R4zwSQJQTD0P6DR1TkRjhSzReSqxgSgBBXkIrrxKnJF8LQwx9CeF6CSe8H5yIjluPoYZosJkgWrLWXcsszp6qRv7BwQxKTbVxORWVGgHANB+KmLa6i1W5MFc6xirr+KIVYB6z2IwExGsMM+Aw5RN4IG0PKOoeELiY2-AoR1cDba6vCa04oNwq4G3Ws+fM1I5FN01v5bmdqPH+H5ZLMtwT9zQygEm0qdJ5AaV9QYXwBtxzLRHBpGxdR1QE1VVaUG3de0mP7o7IdxQRgVCqvXOVcU9TY1VJUd6e94wDFLfMM+2w5nBO3YgOK9g60msbTSYOjwVBcExPofQ1JTSaGvSwgpfiZmPoQLpCkcVfC-qFsMfFTd5oGmGcaHl7hhyyGAwEvRMycOJsrbSnyki80UUUC4GkQ4kmXLId6-ouIyjOEkeSldlKNX4f8fe9V5AINxXlEpWDvLo6IacScroZR3AwphZSU0jy-BAA */

‎x-pack/platform/plugins/shared/streams_app/public/components/data_management/stream_detail_routing/stream_name_form_row.tsx‎

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,64 @@
66
*/
77

88
import React from 'react';
9-
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
9+
import {
10+
EuiFormRow,
11+
EuiFieldText,
12+
EuiIcon,
13+
EuiFormLabel,
14+
EuiScreenReaderOnly,
15+
EuiTextTruncate,
16+
useGeneratedHtmlId,
17+
} from '@elastic/eui';
1018
import { i18n } from '@kbn/i18n';
19+
import { css } from '@emotion/react';
20+
import { useStreamsRoutingSelector } from './state_management/stream_routing_state_machine';
1121

1222
interface StreamNameFormRowProps {
1323
value: string;
1424
onChange?: (value: string) => void;
15-
disabled?: boolean;
25+
readOnly?: boolean;
1626
autoFocus?: boolean;
1727
}
1828

1929
const MAX_NAME_LENGTH = 200;
30+
const PREFIX_MAX_VISIBLE_CHARACTERS = 25;
2031

2132
export function StreamNameFormRow({
2233
value,
2334
onChange = () => {},
24-
disabled = false,
35+
readOnly = false,
2536
autoFocus = false,
2637
}: StreamNameFormRowProps) {
38+
const descriptionId = useGeneratedHtmlId();
39+
40+
const parentStreamName = useStreamsRoutingSelector((snapshot) => snapshot.context.definition)
41+
.stream.name;
42+
43+
const prefix = parentStreamName + '.';
44+
const partitionName = value.replace(prefix, '');
45+
2746
const helpText =
28-
value.length >= MAX_NAME_LENGTH && !disabled
47+
value.length >= MAX_NAME_LENGTH && !readOnly
2948
? i18n.translate('xpack.streams.streamDetailRouting.nameHelpText', {
3049
defaultMessage: `Stream name cannot be longer than {maxLength} characters.`,
3150
values: {
3251
maxLength: MAX_NAME_LENGTH,
3352
},
3453
})
3554
: undefined;
55+
const isInvalid = !readOnly && partitionName.includes('.');
56+
const errorMessage = isInvalid
57+
? i18n.translate('xpack.streams.streamDetailRouting.nameContainsDotErrorMessage', {
58+
defaultMessage: `Stream name cannot contain the "." character.`,
59+
})
60+
: undefined;
61+
62+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
63+
const newPartitionName = e.target.value;
64+
65+
onChange(`${prefix}${newPartitionName}`);
66+
};
3667

3768
return (
3869
<EuiFormRow
@@ -41,16 +72,38 @@ export function StreamNameFormRow({
4172
defaultMessage: 'Stream name',
4273
})}
4374
helpText={helpText}
75+
describedByIds={[descriptionId]}
76+
isInvalid={isInvalid}
77+
error={errorMessage}
4478
>
4579
<EuiFieldText
80+
isInvalid={isInvalid}
4681
data-test-subj="streamsAppRoutingStreamEntryNameField"
47-
value={value}
82+
value={partitionName}
4883
fullWidth
4984
compressed
50-
disabled={disabled}
85+
readOnly={readOnly}
5186
autoFocus={autoFocus}
52-
onChange={(e) => onChange(e.target.value)}
53-
maxLength={200}
87+
onChange={handleChange}
88+
maxLength={MAX_NAME_LENGTH - prefix.length}
89+
prepend={[
90+
<EuiIcon type="streamsWired" />,
91+
<EuiFormLabel
92+
css={css`
93+
inline-size: min(${prefix.length}ch, ${PREFIX_MAX_VISIBLE_CHARACTERS}ch);
94+
`}
95+
id={descriptionId}
96+
>
97+
<EuiScreenReaderOnly>
98+
<span>
99+
{i18n.translate('xpack.streams.streamDetailRouting.screenReaderPrefixLabel', {
100+
defaultMessage: 'Stream prefix:',
101+
})}
102+
</span>
103+
</EuiScreenReaderOnly>
104+
<EuiTextTruncate text={prefix} truncation="start" />
105+
</EuiFormLabel>,
106+
]}
54107
/>
55108
</EuiFormRow>
56109
);

‎x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_routing/create_routing_rules.spec.ts‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ test.describe('Stream data routing - creating routing rules', { tag: ['@ess', '@
2828
// Verify we're in the creating new rule state
2929
await expect(page.getByTestId('streamsAppRoutingStreamEntryNameField')).toBeVisible();
3030
await expect(page.getByText('Stream name')).toBeVisible();
31+
await expect(page.getByText('logs.')).toBeVisible();
3132

3233
// Fill in the stream name
33-
await page.getByTestId('streamsAppRoutingStreamEntryNameField').fill('logs.nginx');
34+
await page.getByTestId('streamsAppRoutingStreamEntryNameField').fill('nginx');
3435

3536
// Set up routing condition
3637
await pageObjects.streams.fillConditionEditor({
@@ -53,7 +54,7 @@ test.describe('Stream data routing - creating routing rules', { tag: ['@ess', '@
5354
await pageObjects.streams.clickCreateRoutingRule();
5455

5556
// Fill in some data
56-
await pageObjects.streams.fillRoutingRuleName('logs.test');
57+
await pageObjects.streams.fillRoutingRuleName('test');
5758

5859
// Cancel the operation
5960
await pageObjects.streams.cancelRoutingRule();
@@ -108,4 +109,17 @@ test.describe('Stream data routing - creating routing rules', { tag: ['@ess', '@
108109
const createButton = page.getByTestId('streamsAppStreamDetailRoutingAddRuleButton');
109110
await expect(createButton).toBeHidden();
110111
});
112+
113+
test('should not allow creating a routing rule that is not a child of the current stream', async ({
114+
page,
115+
pageObjects,
116+
}) => {
117+
await pageObjects.streams.clickCreateRoutingRule();
118+
119+
// Input invalid partition name that is not a direct child of the current stream
120+
await pageObjects.streams.fillRoutingRuleName('nginx.access_logs');
121+
122+
const createButton = page.getByTestId('streamsAppStreamDetailRoutingAddRuleButton');
123+
await expect(createButton).toBeDisabled();
124+
});
111125
});

‎x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_routing/edit_routing_rules.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ test.describe('Stream data routing - editing routing rules', { tag: ['@ess', '@s
6464
test('should switch between editing different rules', async ({ page, pageObjects }) => {
6565
// Create another test rule
6666
await pageObjects.streams.clickCreateRoutingRule();
67-
await pageObjects.streams.fillRoutingRuleName('logs.edit-test-2');
67+
await pageObjects.streams.fillRoutingRuleName('edit-test-2');
6868
await pageObjects.streams.fillConditionEditor({
6969
field: 'log.level',
7070
value: 'info',

‎x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_routing/error_handling.spec.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ test.describe(
3333
pageObjects,
3434
}) => {
3535
await pageObjects.streams.clickCreateRoutingRule();
36-
await pageObjects.streams.fillRoutingRuleName('logs.network-test');
36+
await pageObjects.streams.fillRoutingRuleName('network-test');
3737

3838
// Simulate network failure
3939
await context.setOffline(true);
@@ -61,7 +61,7 @@ test.describe(
6161
}) => {
6262
// Create a rule first
6363
await pageObjects.streams.clickCreateRoutingRule();
64-
await pageObjects.streams.fillRoutingRuleName('logs.error-test');
64+
await pageObjects.streams.fillRoutingRuleName('error-test');
6565
await pageObjects.streams.saveRoutingRule();
6666
await pageObjects.toasts.closeAll();
6767

‎x-pack/platform/plugins/shared/streams_app/test/scout/ui/tests/data_management/data_routing/routing_data_preview.spec.ts‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
3434

3535
test('should show preview during rule creation', async ({ pageObjects }) => {
3636
await pageObjects.streams.clickCreateRoutingRule();
37-
await pageObjects.streams.fillRoutingRuleName('logs.preview-test');
37+
await pageObjects.streams.fillRoutingRuleName('preview-test');
3838

3939
// Set condition that should match the test data
4040
await pageObjects.streams.fillConditionEditor({
@@ -57,7 +57,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
5757

5858
test('should update preview when condition changes', async ({ pageObjects }) => {
5959
await pageObjects.streams.clickCreateRoutingRule();
60-
await pageObjects.streams.fillRoutingRuleName('logs.preview-test');
60+
await pageObjects.streams.fillRoutingRuleName('preview-test');
6161

6262
// Set condition that should match the test data
6363
await pageObjects.streams.fillConditionEditor({
@@ -98,7 +98,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
9898

9999
test('should allow updating the condition manually by syntax editor', async ({ pageObjects }) => {
100100
await pageObjects.streams.clickCreateRoutingRule();
101-
await pageObjects.streams.fillRoutingRuleName('logs.preview-test');
101+
await pageObjects.streams.fillRoutingRuleName('preview-test');
102102

103103
// Enable syntax editor
104104
await pageObjects.streams.toggleConditionEditorWithSyntaxSwitch();
@@ -159,7 +159,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
159159

160160
test('should show no matches when condition matches nothing', async ({ page, pageObjects }) => {
161161
await pageObjects.streams.clickCreateRoutingRule();
162-
await pageObjects.streams.fillRoutingRuleName('logs.no-matches');
162+
await pageObjects.streams.fillRoutingRuleName('no-matches');
163163

164164
// Set condition that won't match anything
165165
await pageObjects.streams.fillConditionEditor({
@@ -187,7 +187,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
187187
pageObjects,
188188
}) => {
189189
await pageObjects.streams.clickCreateRoutingRule();
190-
await pageObjects.streams.fillRoutingRuleName('logs.filter-controls-test');
190+
await pageObjects.streams.fillRoutingRuleName('filter-controls-test');
191191

192192
await pageObjects.streams.fillConditionEditor({
193193
field: 'severity_text',
@@ -215,7 +215,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
215215

216216
test('should switch between matched and unmatched documents', async ({ page, pageObjects }) => {
217217
await pageObjects.streams.clickCreateRoutingRule();
218-
await pageObjects.streams.fillRoutingRuleName('logs.filter-switch-test');
218+
await pageObjects.streams.fillRoutingRuleName('filter-switch-test');
219219

220220
await pageObjects.streams.fillConditionEditor({
221221
field: 'severity_text',
@@ -262,7 +262,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
262262

263263
test('should maintain filter state when condition changes', async ({ page, pageObjects }) => {
264264
await pageObjects.streams.clickCreateRoutingRule();
265-
await pageObjects.streams.fillRoutingRuleName('logs.filter-state-test');
265+
await pageObjects.streams.fillRoutingRuleName('filter-state-test');
266266

267267
// Set initial condition
268268
await pageObjects.streams.fillConditionEditor({
@@ -299,7 +299,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
299299

300300
test('should handle filter controls with complex conditions', async ({ page, pageObjects }) => {
301301
await pageObjects.streams.clickCreateRoutingRule();
302-
await pageObjects.streams.fillRoutingRuleName('logs.complex-filter-test');
302+
await pageObjects.streams.fillRoutingRuleName('complex-filter-test');
303303

304304
// Enable syntax editor and set complex condition
305305
await pageObjects.streams.toggleConditionEditorWithSyntaxSwitch();
@@ -328,7 +328,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
328328

329329
test('should disable filter controls when no condition is set', async ({ page, pageObjects }) => {
330330
await pageObjects.streams.clickCreateRoutingRule();
331-
await pageObjects.streams.fillRoutingRuleName('logs.no-condition-test');
331+
await pageObjects.streams.fillRoutingRuleName('no-condition-test');
332332

333333
await expect(page.getByTestId('routingPreviewMatchedFilterButton')).toBeDisabled();
334334
await expect(page.getByTestId('routingPreviewUnmatchedFilterButton')).toBeDisabled();
@@ -343,7 +343,7 @@ test.describe('Stream data routing - previewing data', { tag: ['@ess', '@svlOblt
343343

344344
test('should handle error states', async ({ page, pageObjects }) => {
345345
await pageObjects.streams.clickCreateRoutingRule();
346-
await pageObjects.streams.fillRoutingRuleName('logs.error-test');
346+
await pageObjects.streams.fillRoutingRuleName('error-test');
347347

348348
// Set a condition that might cause issues without field
349349
await pageObjects.streams.fillConditionEditor({

0 commit comments

Comments
 (0)