From 2809ce717990c97bd6f09574d6fc3ba6f772592a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 12 Dec 2024 12:24:38 +0100 Subject: [PATCH 1/2] New listing page --- .../stream_detail_overview/index.tsx | 15 +- .../components/stream_list_view/index.tsx | 4 +- .../public/components/streams_list/index.tsx | 275 ++++++++++++++++++ .../public/components/streams_table/index.tsx | 78 ----- .../public/util/hierarchy_helpers.ts | 22 ++ 5 files changed, 302 insertions(+), 92 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/streams_app/public/components/streams_list/index.tsx delete mode 100644 x-pack/solutions/observability/plugins/streams_app/public/components/streams_table/index.tsx create mode 100644 x-pack/solutions/observability/plugins/streams_app/public/util/hierarchy_helpers.ts diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_overview/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_overview/index.tsx index c051d114317ea..24e05ba2be6c1 100644 --- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_overview/index.tsx +++ b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_detail_overview/index.tsx @@ -15,6 +15,7 @@ import { useKibana } from '../../hooks/use_kibana'; import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; import { ControlledEsqlChart } from '../esql_chart/controlled_esql_chart'; import { StreamsAppSearchBar } from '../streams_app_search_bar'; +import { getIndexPatterns } from '../../util/hierarchy_helpers'; export function StreamDetailOverview({ definition }: { definition?: StreamDefinition }) { const { @@ -35,18 +36,8 @@ export function StreamDetailOverview({ definition }: { definition?: StreamDefini } = useDateRange({ data }); const indexPatterns = useMemo(() => { - if (!definition?.id) { - return undefined; - } - - const isRoot = definition.id.indexOf('.') === -1; - - const dataStreamOfDefinition = definition.id; - - return isRoot - ? [dataStreamOfDefinition, `${dataStreamOfDefinition}.*`] - : [`${dataStreamOfDefinition}*`]; - }, [definition?.id]); + return getIndexPatterns(definition); + }, [definition]); const discoverLocator = useMemo( () => share.url.locators.get('DISCOVER_APP_LOCATOR'), diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_list_view/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_list_view/index.tsx index 7ffda5f40295a..926b9d746f924 100644 --- a/x-pack/solutions/observability/plugins/streams_app/public/components/stream_list_view/index.tsx +++ b/x-pack/solutions/observability/plugins/streams_app/public/components/stream_list_view/index.tsx @@ -12,7 +12,7 @@ import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; import { StreamsAppPageHeader } from '../streams_app_page_header'; import { StreamsAppPageHeaderTitle } from '../streams_app_page_header/streams_app_page_header_title'; import { StreamsAppPageBody } from '../streams_app_page_body'; -import { StreamsTable } from '../streams_table'; +import { StreamsList } from '../streams_list'; export function StreamListView() { const { @@ -61,7 +61,7 @@ export function StreamListView() { /> - + diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/streams_list/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/streams_list/index.tsx new file mode 100644 index 0000000000000..6a376e01df554 --- /dev/null +++ b/x-pack/solutions/observability/plugins/streams_app/public/components/streams_list/index.tsx @@ -0,0 +1,275 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiBadge, + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSwitch, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import { StreamDefinition } from '@kbn/streams-plugin/common'; +import React, { useMemo } from 'react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/css'; +import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; +import { NestedView } from '../nested_view'; +import { useKibana } from '../../hooks/use_kibana'; +import { getIndexPatterns } from '../../util/hierarchy_helpers'; + +export interface StreamTree { + id: string; + type: 'wired' | 'root' | 'classic'; + definition: StreamDefinition; + children: StreamTree[]; +} + +function asTrees(definitions: StreamDefinition[]) { + const trees: StreamTree[] = []; + const wiredDefinitions = definitions.filter((definition) => definition.managed); + wiredDefinitions.sort((a, b) => a.id.split('.').length - b.id.split('.').length); + + wiredDefinitions.forEach((definition) => { + let currentTree = trees; + let existingNode: StreamTree | undefined; + // traverse the tree following the prefix of the current id. + // once we reach the leaf, the current id is added as child - this works because the ids are sorted by depth + while ((existingNode = currentTree.find((node) => definition.id.startsWith(node.id)))) { + currentTree = existingNode.children; + } + if (!existingNode) { + const newNode: StreamTree = { + id: definition.id, + children: [], + definition, + type: definition.id.split('.').length === 1 ? 'root' : 'wired', + }; + currentTree.push(newNode); + } + }); + + return trees; +} + +export function StreamsList({ + listFetch, + query, +}: { + listFetch: AbortableAsyncState<{ definitions: StreamDefinition[] }>; + query: string; +}) { + const [collapsed, setCollapsed] = React.useState>({}); + const [showClassic, setShowClassic] = React.useState(true); + const items = useMemo(() => { + return listFetch.value?.definitions ?? []; + }, [listFetch.value?.definitions]); + + const filteredItems = useMemo(() => { + return items + .filter((item) => showClassic || item.managed) + .filter((item) => !query || item.id.toLowerCase().includes(query.toLowerCase())); + }, [query, items, showClassic]); + + const classicStreams = useMemo(() => { + return filteredItems.filter((item) => !item.managed); + }, [filteredItems]); + + const treeView = useMemo(() => { + const trees = asTrees(filteredItems); + const classicList = classicStreams.map((definition) => ({ + id: definition.id, + type: 'classic' as const, + definition, + children: [], + })); + return [...trees, ...classicList]; + }, [filteredItems, classicStreams]); + + return ( + + +

+ {i18n.translate('xpack.streams.streamsTable.tableTitle', { + defaultMessage: 'Streams', + })} +

+
+ + + {Object.keys(collapsed).length === 0 ? ( + setCollapsed(Object.fromEntries(items.map((item) => [item.id, true])))} + > + {i18n.translate('xpack.streams.streamsTable.collapseAll', { + defaultMessage: 'Collapse all', + })} + + ) : ( + setCollapsed({})} size="s"> + {i18n.translate('xpack.streams.streamsTable.expandAll', { + defaultMessage: 'Expand all', + })} + + )} + setShowClassic(e.target.checked)} + /> + + + + {treeView.map((tree) => ( + + ))} + +
+ ); +} + +function StreamNode({ + node, + collapsed, + setCollapsed, +}: { + node: StreamTree; + collapsed: Record; + setCollapsed: (collapsed: Record) => void; +}) { + const router = useStreamsAppRouter(); + const { + dependencies: { + start: { share }, + }, + } = useKibana(); + const discoverLocator = useMemo( + () => share.url.locators.get('DISCOVER_APP_LOCATOR'), + [share.url.locators] + ); + + const discoverUrl = useMemo(() => { + const indexPatterns = getIndexPatterns(node.definition); + + if (!discoverLocator || !indexPatterns) { + return undefined; + } + + return discoverLocator.getRedirectUrl({ + query: { + esql: `FROM ${indexPatterns.join(', ')}`, + }, + }); + }, [discoverLocator, node.definition]); + + return ( + + + {node.children.length > 0 && ( + // Using a regular button here instead of the EUI one to control styling + + )} + + {node.id} + + {node.type === 'root' && ( + + + + )} + {node.type === 'classic' && ( + + + + )} + + + + + + + {node.children.length > 0 && !collapsed?.[node.id] && ( + + + {node.children.map((child, index) => ( + + + + ))} + + + )} + + ); +} diff --git a/x-pack/solutions/observability/plugins/streams_app/public/components/streams_table/index.tsx b/x-pack/solutions/observability/plugins/streams_app/public/components/streams_table/index.tsx deleted file mode 100644 index 39814ed904555..0000000000000 --- a/x-pack/solutions/observability/plugins/streams_app/public/components/streams_table/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { - EuiBasicTable, - EuiBasicTableColumn, - EuiFlexGroup, - EuiIcon, - EuiLink, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import type { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; -import { StreamDefinition } from '@kbn/streams-plugin/common'; -import React, { useMemo } from 'react'; -import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; - -export function StreamsTable({ - listFetch, - query, -}: { - listFetch: AbortableAsyncState<{ definitions: StreamDefinition[] }>; - query: string; -}) { - const router = useStreamsAppRouter(); - - const items = useMemo(() => { - return listFetch.value?.definitions ?? []; - }, [listFetch.value?.definitions]); - - const filteredItems = useMemo(() => { - if (!query) { - return items; - } - - return items.filter((item) => item.id.toLowerCase().includes(query.toLowerCase())); - }, [query, items]); - - const columns = useMemo>>(() => { - return [ - { - field: 'id', - name: i18n.translate('xpack.streams.streamsTable.nameColumnTitle', { - defaultMessage: 'Name', - }), - render: (_, { id, managed }) => { - return ( - - - - {id} - - - ); - }, - }, - ]; - }, [router]); - - return ( - - -

- {i18n.translate('xpack.streams.streamsTable.tableTitle', { - defaultMessage: 'Streams', - })} -

-
- -
- ); -} diff --git a/x-pack/solutions/observability/plugins/streams_app/public/util/hierarchy_helpers.ts b/x-pack/solutions/observability/plugins/streams_app/public/util/hierarchy_helpers.ts new file mode 100644 index 0000000000000..6a2d2e2b9708c --- /dev/null +++ b/x-pack/solutions/observability/plugins/streams_app/public/util/hierarchy_helpers.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StreamDefinition } from '@kbn/streams-plugin/common'; + +export function getIndexPatterns(definition: StreamDefinition | undefined) { + if (!definition) { + return undefined; + } + if (!definition.managed) { + return [definition.id]; + } + const isRoot = definition?.id?.indexOf('.') === -1; + const dataStreamOfDefinition = definition?.id; + return isRoot + ? [dataStreamOfDefinition, `${dataStreamOfDefinition}.*`] + : [`${dataStreamOfDefinition}*`]; +} From 5b470e91de5fdde0a090dbe2d3ba5a2ff99f7812 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Sun, 29 Dec 2024 20:24:11 +0000 Subject: [PATCH 2/2] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/solutions/observability/plugins/streams_app/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/observability/plugins/streams_app/tsconfig.json b/x-pack/solutions/observability/plugins/streams_app/tsconfig.json index 7824c84d6ea6b..49e5680f68a2c 100644 --- a/x-pack/solutions/observability/plugins/streams_app/tsconfig.json +++ b/x-pack/solutions/observability/plugins/streams_app/tsconfig.json @@ -38,5 +38,6 @@ "@kbn/navigation-plugin", "@kbn/core-notifications-browser", "@kbn/streams-schema", + "@kbn/observability-ai-assistant-plugin", ] }