|
4 | 4 | * 2.0; you may not use this file except in compliance with the Elastic License
|
5 | 5 | * 2.0.
|
6 | 6 | */
|
7 |
| -import { |
8 |
| - EuiButton, |
9 |
| - EuiFlexGroup, |
10 |
| - EuiFlexItem, |
11 |
| - EuiLoadingSpinner, |
12 |
| - EuiPanel, |
13 |
| - EuiTab, |
14 |
| - EuiTabs, |
15 |
| - EuiText, |
16 |
| -} from '@elastic/eui'; |
17 |
| -import { calculateAuto } from '@kbn/calculate-auto'; |
18 |
| -import { i18n } from '@kbn/i18n'; |
19 |
| -import moment from 'moment'; |
20 |
| -import React, { useMemo } from 'react'; |
21 |
| -import { css } from '@emotion/css'; |
22 |
| -import { |
23 |
| - IngestStreamGetResponse, |
24 |
| - isDescendantOf, |
25 |
| - isUnwiredStreamGetResponse, |
26 |
| - isWiredStreamDefinition, |
27 |
| -} from '@kbn/streams-schema'; |
28 |
| -import type { SanitizedDashboardAsset } from '@kbn/streams-plugin/server/routes/dashboards/route'; |
29 |
| -import { useKibana } from '../../hooks/use_kibana'; |
30 |
| -import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; |
31 |
| -import { ControlledEsqlChart } from '../esql_chart/controlled_esql_chart'; |
32 |
| -import { StreamsAppSearchBar } from '../streams_app_search_bar'; |
33 |
| -import { getIndexPatterns } from '../../util/hierarchy_helpers'; |
34 |
| -import { StreamsList } from '../streams_list'; |
35 |
| -import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; |
36 |
| -import { useDashboardsFetch } from '../../hooks/use_dashboards_fetch'; |
37 |
| -import { DashboardsTable } from '../stream_detail_dashboards_view/dashboard_table'; |
38 |
| -import { AssetImage } from '../asset_image'; |
39 |
| -import { useWiredStreams } from '../../hooks/use_wired_streams'; |
40 | 7 |
|
41 |
| -const formatNumber = (val: number) => { |
42 |
| - return Number(val).toLocaleString('en', { |
43 |
| - maximumFractionDigits: 1, |
44 |
| - }); |
45 |
| -}; |
46 |
| - |
47 |
| -export function StreamDetailOverview({ definition }: { definition?: IngestStreamGetResponse }) { |
48 |
| - const { |
49 |
| - dependencies: { |
50 |
| - start: { |
51 |
| - data, |
52 |
| - dataViews, |
53 |
| - streams: { streamsRepositoryClient }, |
54 |
| - share, |
55 |
| - }, |
56 |
| - }, |
57 |
| - } = useKibana(); |
58 |
| - |
59 |
| - const { |
60 |
| - timeRange, |
61 |
| - setTimeRange, |
62 |
| - absoluteTimeRange: { start, end }, |
63 |
| - } = data.query.timefilter.timefilter.useTimefilter(); |
64 |
| - |
65 |
| - const indexPatterns = useMemo(() => { |
66 |
| - return getIndexPatterns(definition?.stream); |
67 |
| - }, [definition]); |
68 |
| - |
69 |
| - const discoverLocator = useMemo( |
70 |
| - () => share.url.locators.get('DISCOVER_APP_LOCATOR'), |
71 |
| - [share.url.locators] |
72 |
| - ); |
73 |
| - |
74 |
| - const queries = useMemo(() => { |
75 |
| - if (!indexPatterns) { |
76 |
| - return undefined; |
77 |
| - } |
78 |
| - |
79 |
| - const baseQuery = `FROM ${indexPatterns.join(', ')}`; |
80 |
| - |
81 |
| - const bucketSize = Math.round( |
82 |
| - calculateAuto.atLeast(50, moment.duration(1, 'minute'))!.asSeconds() |
83 |
| - ); |
84 |
| - |
85 |
| - const histogramQuery = `${baseQuery} | STATS metric = COUNT(*) BY @timestamp = BUCKET(@timestamp, ${bucketSize} seconds)`; |
86 |
| - |
87 |
| - return { |
88 |
| - baseQuery, |
89 |
| - histogramQuery, |
90 |
| - }; |
91 |
| - }, [indexPatterns]); |
92 |
| - |
93 |
| - const discoverLink = useMemo(() => { |
94 |
| - if (!discoverLocator || !queries?.baseQuery) { |
95 |
| - return undefined; |
96 |
| - } |
97 |
| - |
98 |
| - return discoverLocator.getRedirectUrl({ |
99 |
| - query: { |
100 |
| - esql: queries.baseQuery, |
101 |
| - }, |
102 |
| - }); |
103 |
| - }, [queries?.baseQuery, discoverLocator]); |
104 |
| - |
105 |
| - const histogramQueryFetch = useStreamsAppFetch( |
106 |
| - async ({ signal }) => { |
107 |
| - if (!queries?.histogramQuery || !indexPatterns) { |
108 |
| - return undefined; |
109 |
| - } |
110 |
| - |
111 |
| - const existingIndices = await dataViews.getExistingIndices(indexPatterns); |
112 |
| - |
113 |
| - if (existingIndices.length === 0) { |
114 |
| - return undefined; |
115 |
| - } |
116 |
| - |
117 |
| - return streamsRepositoryClient.fetch('POST /internal/streams/esql', { |
118 |
| - params: { |
119 |
| - body: { |
120 |
| - operationName: 'get_histogram_for_stream', |
121 |
| - query: queries.histogramQuery, |
122 |
| - start, |
123 |
| - end, |
124 |
| - }, |
125 |
| - }, |
126 |
| - signal, |
127 |
| - }); |
128 |
| - }, |
129 |
| - [indexPatterns, dataViews, streamsRepositoryClient, queries?.histogramQuery, start, end] |
130 |
| - ); |
131 |
| - |
132 |
| - const docCountFetch = useStreamsAppFetch( |
133 |
| - async ({ signal }) => { |
134 |
| - if ( |
135 |
| - !definition || |
136 |
| - (isUnwiredStreamGetResponse(definition) && !definition.data_stream_exists) |
137 |
| - ) { |
138 |
| - return undefined; |
139 |
| - } |
140 |
| - return streamsRepositoryClient.fetch('GET /internal/streams/{name}/_details', { |
141 |
| - signal, |
142 |
| - params: { |
143 |
| - path: { |
144 |
| - name: definition.stream.name, |
145 |
| - }, |
146 |
| - query: { |
147 |
| - start: String(start), |
148 |
| - end: String(end), |
149 |
| - }, |
150 |
| - }, |
151 |
| - }); |
152 |
| - }, |
153 |
| - |
154 |
| - [definition, dataViews, streamsRepositoryClient, start, end] |
155 |
| - ); |
156 |
| - |
157 |
| - const [selectedTab, setSelectedTab] = React.useState<string | undefined>(undefined); |
158 |
| - |
159 |
| - const tabs = [ |
160 |
| - ...(definition && isWiredStreamDefinition(definition.stream) |
161 |
| - ? [ |
162 |
| - { |
163 |
| - id: 'streams', |
164 |
| - name: i18n.translate('xpack.streams.entityDetailOverview.tabs.streams', { |
165 |
| - defaultMessage: 'Streams', |
166 |
| - }), |
167 |
| - content: <ChildStreamList definition={definition} />, |
168 |
| - }, |
169 |
| - ] |
170 |
| - : []), |
171 |
| - { |
172 |
| - id: 'quicklinks', |
173 |
| - name: i18n.translate('xpack.streams.entityDetailOverview.tabs.quicklinks', { |
174 |
| - defaultMessage: 'Quick Links', |
175 |
| - }), |
176 |
| - content: <QuickLinks definition={definition} />, |
177 |
| - }, |
178 |
| - ]; |
179 |
| - |
180 |
| - return ( |
181 |
| - <> |
182 |
| - <EuiFlexGroup direction="column"> |
183 |
| - <EuiFlexItem grow={false}> |
184 |
| - <EuiFlexGroup direction="row" gutterSize="s" alignItems="center"> |
185 |
| - <EuiFlexItem> |
186 |
| - {docCountFetch.loading ? ( |
187 |
| - <EuiLoadingSpinner size="m" /> |
188 |
| - ) : ( |
189 |
| - docCountFetch.value && ( |
190 |
| - <EuiText> |
191 |
| - {i18n.translate('xpack.streams.entityDetailOverview.docCount', { |
192 |
| - defaultMessage: '{docCount} documents', |
193 |
| - values: { docCount: formatNumber(docCountFetch.value.details.count) }, |
194 |
| - })} |
195 |
| - </EuiText> |
196 |
| - ) |
197 |
| - )} |
198 |
| - </EuiFlexItem> |
199 |
| - <EuiFlexItem grow> |
200 |
| - <StreamsAppSearchBar |
201 |
| - onQuerySubmit={({ dateRange }, isUpdate) => { |
202 |
| - if (!isUpdate) { |
203 |
| - histogramQueryFetch.refresh(); |
204 |
| - docCountFetch.refresh(); |
205 |
| - return; |
206 |
| - } |
207 |
| - |
208 |
| - if (dateRange) { |
209 |
| - setTimeRange({ from: dateRange.from, to: dateRange?.to, mode: dateRange.mode }); |
210 |
| - } |
211 |
| - }} |
212 |
| - onRefresh={() => { |
213 |
| - histogramQueryFetch.refresh(); |
214 |
| - }} |
215 |
| - placeholder={i18n.translate( |
216 |
| - 'xpack.streams.entityDetailOverview.searchBarPlaceholder', |
217 |
| - { |
218 |
| - defaultMessage: 'Filter data by using KQL', |
219 |
| - } |
220 |
| - )} |
221 |
| - dateRangeFrom={timeRange.from} |
222 |
| - dateRangeTo={timeRange.to} |
223 |
| - /> |
224 |
| - </EuiFlexItem> |
225 |
| - <EuiButton |
226 |
| - data-test-subj="streamsDetailOverviewOpenInDiscoverButton" |
227 |
| - iconType="discoverApp" |
228 |
| - href={discoverLink} |
229 |
| - color="text" |
230 |
| - > |
231 |
| - {i18n.translate('xpack.streams.streamDetailOverview.openInDiscoverButtonLabel', { |
232 |
| - defaultMessage: 'Open in Discover', |
233 |
| - })} |
234 |
| - </EuiButton> |
235 |
| - </EuiFlexGroup> |
236 |
| - </EuiFlexItem> |
237 |
| - <EuiFlexItem grow={false}> |
238 |
| - <EuiPanel hasShadow={false} hasBorder> |
239 |
| - <EuiFlexGroup direction="column"> |
240 |
| - <ControlledEsqlChart |
241 |
| - result={histogramQueryFetch} |
242 |
| - id="entity_log_rate" |
243 |
| - metricNames={['metric']} |
244 |
| - height={200} |
245 |
| - chartType={'bar'} |
246 |
| - /> |
247 |
| - </EuiFlexGroup> |
248 |
| - </EuiPanel> |
249 |
| - </EuiFlexItem> |
250 |
| - <EuiFlexItem grow> |
251 |
| - <EuiFlexGroup direction="column" gutterSize="s"> |
252 |
| - {definition && ( |
253 |
| - <> |
254 |
| - <EuiTabs> |
255 |
| - {tabs.map((tab, index) => ( |
256 |
| - <EuiTab |
257 |
| - isSelected={(!selectedTab && index === 0) || selectedTab === tab.id} |
258 |
| - onClick={() => setSelectedTab(tab.id)} |
259 |
| - key={tab.id} |
260 |
| - > |
261 |
| - {tab.name} |
262 |
| - </EuiTab> |
263 |
| - ))} |
264 |
| - </EuiTabs> |
265 |
| - { |
266 |
| - tabs.find((tab, index) => (!selectedTab && index === 0) || selectedTab === tab.id) |
267 |
| - ?.content |
268 |
| - } |
269 |
| - </> |
270 |
| - )} |
271 |
| - </EuiFlexGroup> |
272 |
| - </EuiFlexItem> |
273 |
| - </EuiFlexGroup> |
274 |
| - </> |
275 |
| - ); |
276 |
| -} |
277 |
| - |
278 |
| -const EMPTY_DASHBOARD_LIST: SanitizedDashboardAsset[] = []; |
279 |
| - |
280 |
| -function QuickLinks({ definition }: { definition?: IngestStreamGetResponse }) { |
281 |
| - const dashboardsFetch = useDashboardsFetch(definition?.stream.name); |
282 |
| - |
283 |
| - return ( |
284 |
| - <DashboardsTable |
285 |
| - entityId={definition?.stream.name} |
286 |
| - dashboards={dashboardsFetch.value?.dashboards ?? EMPTY_DASHBOARD_LIST} |
287 |
| - loading={dashboardsFetch.loading} |
288 |
| - /> |
289 |
| - ); |
290 |
| -} |
291 |
| - |
292 |
| -function ChildStreamList({ definition }: { definition?: IngestStreamGetResponse }) { |
293 |
| - const router = useStreamsAppRouter(); |
294 |
| - |
295 |
| - const { wiredStreams } = useWiredStreams(); |
296 |
| - |
297 |
| - const childrenStreams = useMemo(() => { |
298 |
| - if (!definition) { |
299 |
| - return []; |
300 |
| - } |
301 |
| - return wiredStreams?.filter((d) => isDescendantOf(definition.stream.name, d.name)); |
302 |
| - }, [definition, wiredStreams]); |
303 |
| - |
304 |
| - if (definition && childrenStreams?.length === 0) { |
305 |
| - return ( |
306 |
| - <EuiFlexItem grow> |
307 |
| - <EuiFlexGroup alignItems="center" justifyContent="center"> |
308 |
| - <EuiFlexItem |
309 |
| - grow={false} |
310 |
| - className={css` |
311 |
| - max-width: 350px; |
312 |
| - `} |
313 |
| - > |
314 |
| - <EuiFlexGroup direction="column" gutterSize="s"> |
315 |
| - <AssetImage type="welcome" /> |
316 |
| - <EuiText size="m" textAlign="center"> |
317 |
| - {i18n.translate('xpack.streams.entityDetailOverview.noChildStreams', { |
318 |
| - defaultMessage: 'Create streams for your logs', |
319 |
| - })} |
320 |
| - </EuiText> |
321 |
| - <EuiText size="xs" textAlign="center"> |
322 |
| - {i18n.translate('xpack.streams.entityDetailOverview.noChildStreams', { |
323 |
| - defaultMessage: |
324 |
| - 'Create sub streams to split out data with different retention policies, schemas, and more.', |
325 |
| - })} |
326 |
| - </EuiText> |
327 |
| - <EuiFlexGroup justifyContent="center"> |
328 |
| - <EuiButton |
329 |
| - data-test-subj="streamsAppChildStreamListCreateChildStreamButton" |
330 |
| - iconType="plusInCircle" |
331 |
| - href={router.link('/{key}/management/{subtab}', { |
332 |
| - path: { |
333 |
| - key: definition?.stream.name, |
334 |
| - subtab: 'route', |
335 |
| - }, |
336 |
| - })} |
337 |
| - > |
338 |
| - {i18n.translate('xpack.streams.entityDetailOverview.createChildStream', { |
339 |
| - defaultMessage: 'Create child stream', |
340 |
| - })} |
341 |
| - </EuiButton> |
342 |
| - </EuiFlexGroup> |
343 |
| - </EuiFlexGroup> |
344 |
| - </EuiFlexItem> |
345 |
| - </EuiFlexGroup> |
346 |
| - </EuiFlexItem> |
347 |
| - ); |
348 |
| - } |
349 |
| - |
350 |
| - return <StreamsList streams={childrenStreams} showControls={false} />; |
351 |
| -} |
| 8 | +export { StreamDetailOverview } from './stream_detail_overview'; |
0 commit comments