mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
🌊 Streams Partitioning design fixes (#206319)
Stacked on * https://github.com/elastic/kibana/pull/206310 * https://github.com/elastic/kibana/pull/206116 Fixes https://github.com/elastic/streams-program/issues/22 Fixes https://github.com/elastic/streams-program/issues/21 Number of descendants as badge in the list of children <img width="336" alt="Screenshot 2025-01-13 at 11 35 04" src="https://github.com/user-attachments/assets/167090d7-f1b0-4f7d-80ba-eda9ea6a21a2" /> Better heading formatting <img width="497" alt="Screenshot 2025-01-13 at 11 35 17" src="https://github.com/user-attachments/assets/007c173e-accd-4e1f-9ff1-2201b3b5bfde" /> Better link to parent stream <img width="478" alt="Screenshot 2025-01-13 at 11 35 35" src="https://github.com/user-attachments/assets/3bbdc6c9-ef80-4af4-8c8c-a51b8dff8d47" /> Show link to child stream in creation modal <img width="381" alt="Screenshot 2025-01-13 at 11 44 45" src="https://github.com/user-attachments/assets/af2d97cc-0b8d-4729-b6db-bbd0b3115f8a" /> Show all sub streams that will be deleted as well <img width="902" alt="Screenshot 2025-01-13 at 11 37 17" src="https://github.com/user-attachments/assets/eeaf386b-dee1-47c4-a7e7-f4ac8d9940db" /> --------- Co-authored-by: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3a95becf63
commit
a0689a2945
3 changed files with 162 additions and 62 deletions
|
@ -8,6 +8,8 @@ import {
|
|||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiListGroup,
|
||||
EuiListGroupItem,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
|
@ -19,18 +21,22 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useAbortController } from '@kbn/observability-utils-browser/hooks/use_abort_controller';
|
||||
import React from 'react';
|
||||
import { isDescendantOf } from '@kbn/streams-schema';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { useStreamsAppRouter } from '../../hooks/use_streams_app_router';
|
||||
|
||||
export function StreamDeleteModal({
|
||||
closeModal,
|
||||
clearChildUnderEdit,
|
||||
refreshDefinition,
|
||||
id,
|
||||
availableStreams,
|
||||
}: {
|
||||
closeModal: () => void;
|
||||
clearChildUnderEdit: () => void;
|
||||
refreshDefinition: () => void;
|
||||
id: string;
|
||||
availableStreams: string[];
|
||||
}) {
|
||||
const {
|
||||
core: { notifications },
|
||||
|
@ -40,9 +46,13 @@ export function StreamDeleteModal({
|
|||
},
|
||||
},
|
||||
} = useKibana();
|
||||
const router = useStreamsAppRouter();
|
||||
const abortController = useAbortController();
|
||||
const [deleteInProgress, setDeleteInProgress] = React.useState(false);
|
||||
const modalTitleId = useGeneratedHtmlId();
|
||||
const streamsToBeDeleted = availableStreams.filter(
|
||||
(stream) => stream === id || isDescendantOf(id, stream)
|
||||
);
|
||||
return (
|
||||
<EuiModal aria-labelledby={modalTitleId} onClose={closeModal}>
|
||||
<EuiModalHeader>
|
||||
|
@ -54,12 +64,40 @@ export function StreamDeleteModal({
|
|||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.deleteModalDescription', {
|
||||
defaultMessage:
|
||||
'Deleting this stream will remove all of its children and the data will no longer be routed. All existing data will be removed as well.',
|
||||
})}
|
||||
</EuiText>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.deleteModalDescription', {
|
||||
defaultMessage:
|
||||
'Deleting this stream will remove all of its children and the data will no longer be routed. All existing data will be removed as well.',
|
||||
})}
|
||||
</EuiText>
|
||||
{streamsToBeDeleted.length > 1 && (
|
||||
<>
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.deleteModalStreams', {
|
||||
defaultMessage: 'The following streams will be deleted:',
|
||||
})}
|
||||
</EuiText>
|
||||
<EuiListGroup flush={true} maxWidth={false}>
|
||||
{streamsToBeDeleted.map((stream) => (
|
||||
<li key={stream}>
|
||||
<EuiListGroupItem
|
||||
target="_blank"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: {
|
||||
key: stream,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
},
|
||||
})}
|
||||
label={stream}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</EuiListGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
EuiBadge,
|
||||
DropResult,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiResizableContainer,
|
||||
|
@ -25,6 +27,8 @@ import {
|
|||
useEuiTheme,
|
||||
euiDragDropReorder,
|
||||
DragStart,
|
||||
EuiBreadcrumbs,
|
||||
EuiBreadcrumb,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -35,10 +39,13 @@ import {
|
|||
StreamChild,
|
||||
ReadStreamDefinition,
|
||||
WiredStreamConfigDefinition,
|
||||
isRoot,
|
||||
isDescendantOf,
|
||||
} from '@kbn/streams-schema';
|
||||
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
|
||||
import { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
|
||||
import { DraggableProvided } from '@hello-pangea/dnd';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch';
|
||||
import { StreamsAppSearchBar } from '../streams_app_search_bar';
|
||||
|
@ -124,6 +131,24 @@ export function StreamDetailRouting({
|
|||
const theme = useEuiTheme().euiTheme;
|
||||
const routingAppState = useRoutingState({ definition });
|
||||
|
||||
const {
|
||||
dependencies: {
|
||||
start: {
|
||||
streams: { streamsRepositoryClient },
|
||||
},
|
||||
},
|
||||
} = useKibana();
|
||||
|
||||
const streamsListFetch = useStreamsAppFetch(
|
||||
({ signal }) => {
|
||||
return streamsRepositoryClient.fetch('GET /api/streams', {
|
||||
signal,
|
||||
});
|
||||
},
|
||||
[streamsRepositoryClient]
|
||||
);
|
||||
|
||||
const availableStreams = streamsListFetch.value?.streams.map((stream) => stream.name) ?? [];
|
||||
useUnsavedChangesPrompt({
|
||||
hasUnsavedChanges:
|
||||
Boolean(routingAppState.childUnderEdit) || routingAppState.hasChildStreamsOrderChanged,
|
||||
|
@ -152,6 +177,7 @@ export function StreamDetailRouting({
|
|||
clearChildUnderEdit={() => routingAppState.setChildUnderEdit(undefined)}
|
||||
refreshDefinition={refreshDefinition}
|
||||
id={routingAppState.childUnderEdit.child.name}
|
||||
availableStreams={availableStreams}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexGroup
|
||||
|
@ -186,7 +212,11 @@ export function StreamDetailRouting({
|
|||
display: flex;
|
||||
`}
|
||||
>
|
||||
<ChildStreamList definition={definition} routingAppState={routingAppState} />
|
||||
<ChildStreamList
|
||||
definition={definition}
|
||||
routingAppState={routingAppState}
|
||||
availableStreams={availableStreams}
|
||||
/>
|
||||
</EuiResizablePanel>
|
||||
|
||||
<EuiResizableButton accountForScrollbars="both" />
|
||||
|
@ -229,7 +259,7 @@ function ControlBar({
|
|||
refreshDefinition: () => void;
|
||||
}) {
|
||||
const {
|
||||
core: { notifications },
|
||||
core,
|
||||
dependencies: {
|
||||
start: {
|
||||
streams: { streamsRepositoryClient },
|
||||
|
@ -237,6 +267,9 @@ function ControlBar({
|
|||
},
|
||||
} = useKibana();
|
||||
|
||||
const { notifications } = core;
|
||||
const router = useStreamsAppRouter();
|
||||
|
||||
const { signal } = useAbortController();
|
||||
|
||||
if (!routingAppState.childUnderEdit && !routingAppState.hasChildStreamsOrderChanged) {
|
||||
|
@ -320,6 +353,28 @@ function ControlBar({
|
|||
title: i18n.translate('xpack.streams.streamDetailRouting.saved', {
|
||||
defaultMessage: 'Stream saved',
|
||||
}),
|
||||
text: toMountPoint(
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
target="_blank"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: {
|
||||
key: routingAppState.childUnderEdit?.child.name!,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
},
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.view', {
|
||||
defaultMessage: 'Open stream in new tab',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
core
|
||||
),
|
||||
});
|
||||
routingAppState.setChildUnderEdit(undefined);
|
||||
refreshDefinition();
|
||||
|
@ -469,7 +524,12 @@ function PreviewPanel({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow>
|
||||
<EuiText size="s">
|
||||
<EuiText
|
||||
size="s"
|
||||
className={css`
|
||||
font-weight: bold;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiIcon type="inspect" />
|
||||
{i18n.translate('xpack.streams.streamDetail.preview.header', {
|
||||
|
@ -577,6 +637,7 @@ function PreviewPanelIllustration({
|
|||
|
||||
function ChildStreamList({
|
||||
definition,
|
||||
availableStreams,
|
||||
routingAppState: {
|
||||
childUnderEdit,
|
||||
setChildUnderEdit,
|
||||
|
@ -588,6 +649,7 @@ function ChildStreamList({
|
|||
}: {
|
||||
definition: ReadStreamDefinition;
|
||||
routingAppState: ReturnType<typeof useRoutingState>;
|
||||
availableStreams: string[];
|
||||
}) {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
@ -603,6 +665,7 @@ function ChildStreamList({
|
|||
className={css`
|
||||
height: 40px;
|
||||
align-content: center;
|
||||
font-weight: bold;
|
||||
`}
|
||||
>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.rules.header', {
|
||||
|
@ -617,7 +680,6 @@ function ChildStreamList({
|
|||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
<PreviousStreamEntry definition={definition} />
|
||||
<CurrentStreamEntry definition={definition} />
|
||||
<EuiDragDropContext onDragEnd={onChildStreamDragEnd} onDragStart={onChildStreamDragStart}>
|
||||
<EuiDroppable droppableId="routing_children_reordering" spacing="none">
|
||||
|
@ -655,6 +717,7 @@ function ChildStreamList({
|
|||
child: newChild,
|
||||
});
|
||||
}}
|
||||
availableStreams={availableStreams}
|
||||
/>
|
||||
</NestedView>
|
||||
)}
|
||||
|
@ -713,49 +776,39 @@ function ChildStreamList({
|
|||
}
|
||||
|
||||
function CurrentStreamEntry({ definition }: { definition: ReadStreamDefinition }) {
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="s">
|
||||
<EuiText size="s">{definition.name}</EuiText>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.currentStream', {
|
||||
defaultMessage: 'Current stream',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviousStreamEntry({ definition }: { definition: ReadStreamDefinition }) {
|
||||
const router = useStreamsAppRouter();
|
||||
const breadcrumbs: EuiBreadcrumb[] = definition.name.split('.').map((_part, pos, parts) => {
|
||||
const parentId = parts.slice(0, pos + 1).join('.');
|
||||
const isBreadcrumbsTail = parentId === definition.name;
|
||||
|
||||
const parentId = definition.name.split('.').slice(0, -1).join('.');
|
||||
if (parentId === '') {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
text: parentId,
|
||||
href: isBreadcrumbsTail
|
||||
? undefined
|
||||
: router.link('/{key}/{tab}/{subtab}', {
|
||||
path: {
|
||||
key: parentId,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="streamsAppPreviousStreamEntryPreviousStreamButton"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: {
|
||||
key: parentId,
|
||||
tab: 'management',
|
||||
subtab: 'route',
|
||||
},
|
||||
<>
|
||||
{!isRoot(definition.name) && <EuiBreadcrumbs breadcrumbs={breadcrumbs} truncate={false} />}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="s">
|
||||
<EuiText size="s">{definition.name}</EuiText>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.currentStream', {
|
||||
defaultMessage: 'Current stream',
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.previousStream', {
|
||||
defaultMessage: '.. (Previous stream)',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -765,13 +818,16 @@ function RoutingStreamEntry({
|
|||
onChildChange,
|
||||
onEditStateChange,
|
||||
edit,
|
||||
availableStreams,
|
||||
}: {
|
||||
draggableProvided: DraggableProvided;
|
||||
child: StreamChild;
|
||||
onChildChange: (child: StreamChild) => void;
|
||||
onEditStateChange: () => void;
|
||||
edit?: boolean;
|
||||
availableStreams: string[];
|
||||
}) {
|
||||
const children = availableStreams.filter((stream) => isDescendantOf(child.name, stream)).length;
|
||||
const router = useStreamsAppRouter();
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="s">
|
||||
|
@ -788,9 +844,24 @@ function RoutingStreamEntry({
|
|||
<EuiIcon type="grab" />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">{child.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="xs" alignItems="center">
|
||||
<EuiLink
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: { key: child.name, tab: 'management', subtab: 'route' },
|
||||
})}
|
||||
data-test-subj="streamsAppRoutingStreamEntryButton"
|
||||
>
|
||||
<EuiText size="s">{child.name}</EuiText>
|
||||
</EuiLink>
|
||||
{children > 0 && (
|
||||
<EuiBadge color="hollow">
|
||||
{i18n.translate('xpack.streams.streamDetailRouting.numberChildren', {
|
||||
defaultMessage: '{children, plural, one {# child} other {# children}}',
|
||||
values: { children },
|
||||
})}
|
||||
</EuiBadge>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiButtonIcon
|
||||
|
@ -803,16 +874,6 @@ function RoutingStreamEntry({
|
|||
defaultMessage: 'Edit',
|
||||
})}
|
||||
/>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="streamsAppRoutingStreamEntryButton"
|
||||
iconType="popout"
|
||||
href={router.link('/{key}/{tab}/{subtab}', {
|
||||
path: { key: child.name, tab: 'management', subtab: 'route' },
|
||||
})}
|
||||
aria-label={i18n.translate('xpack.streams.streamDetailRouting.goto', {
|
||||
defaultMessage: 'Go to stream',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
{child.condition && (
|
||||
<ConditionEditor
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"@kbn/unsaved-changes-prompt",
|
||||
"@kbn/object-utils",
|
||||
"@kbn/deeplinks-analytics",
|
||||
"@kbn/dashboard-plugin"
|
||||
"@kbn/dashboard-plugin",
|
||||
"@kbn/react-kibana-mount"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue