diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f49d2d674330f..9c69e4fa612f5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26137,13 +26137,7 @@ "xpack.uptime.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS設定", "xpack.uptime.durationChart.emptyPrompt.description": "このモニターは選択された時間範囲で一度も{emphasizedText}していません。", "xpack.uptime.durationChart.emptyPrompt.title": "利用可能な期間データがありません", - "xpack.uptime.emptyState.configureHeartbeatIndexSettings": "Heartbeatがすでに設定されている場合は、データがElasticsearchに送信されていることを確認してから、Heartbeat構成に合わせてインデックスパターン設定を更新します。", - "xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage": "サービスの監視を開始するには、Heartbeatを設定します。", "xpack.uptime.emptyState.loadingMessage": "読み込み中…", - "xpack.uptime.emptyState.noDataMessage": "インデックス{indexName}にはアップタイムデータが見つかりません", - "xpack.uptime.emptyState.noIndexTitle": "パターン{indexName}のインデックスが見つかりません", - "xpack.uptime.emptyState.updateIndexPattern": "インデックスパターン設定を更新", - "xpack.uptime.emptyState.viewSetupInstructions": "セットアップの手順を表示", "xpack.uptime.emptyStateError.notAuthorized": "アップタイムデータの表示が承認されていません。システム管理者にお問い合わせください。", "xpack.uptime.emptyStateError.notFoundPage": "ページが見つかりません", "xpack.uptime.emptyStateError.title": "エラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 85e11905e5eb7..1c91700a74e81 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -26572,13 +26572,7 @@ "xpack.uptime.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS 设置", "xpack.uptime.durationChart.emptyPrompt.description": "在选定时间范围内此监测从未{emphasizedText}。", "xpack.uptime.durationChart.emptyPrompt.title": "没有持续时间数据", - "xpack.uptime.emptyState.configureHeartbeatIndexSettings": "如果已设置 Heartbeat,请确认其正向 Elasticsearch 发送数据,然后更新索引模式设置以匹配 Heartbeat 配置。", - "xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage": "设置 Heartbeat 以开始监测您的服务。", "xpack.uptime.emptyState.loadingMessage": "正在加载……", - "xpack.uptime.emptyState.noDataMessage": "在索引 {indexName} 中找不到运行时间数据", - "xpack.uptime.emptyState.noIndexTitle": "找不到模式 {indexName} 的索引", - "xpack.uptime.emptyState.updateIndexPattern": "更新索引模式设置", - "xpack.uptime.emptyState.viewSetupInstructions": "查看设置说明", "xpack.uptime.emptyStateError.notAuthorized": "您无权查看 Uptime 数据,请联系系统管理员。", "xpack.uptime.emptyStateError.notFoundPage": "未找到页面", "xpack.uptime.emptyStateError.title": "错误", diff --git a/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx new file mode 100644 index 0000000000000..829f587e248e7 --- /dev/null +++ b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx @@ -0,0 +1,64 @@ +/* + * 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 React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { EuiPageHeaderProps } from '@elastic/eui'; +import { OVERVIEW_ROUTE } from '../../common/constants'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from './plugin'; +import { useNoDataConfig } from './use_no_data_config'; +import { EmptyStateLoading } from '../components/overview/empty_state/empty_state_loading'; +import { EmptyStateError } from '../components/overview/empty_state/empty_state_error'; +import { useHasData } from '../components/overview/empty_state/use_has_data'; + +interface Props { + path: string; + pageHeader?: EuiPageHeaderProps; +} + +export const UptimePageTemplateComponent: React.FC = ({ path, pageHeader, children }) => { + const { + services: { observability }, + } = useKibana(); + + const PageTemplateComponent = observability.navigation.PageTemplate; + + const StyledPageTemplateComponent = useMemo(() => { + return styled(PageTemplateComponent)` + .euiPageHeaderContent > .euiFlexGroup { + flex-wrap: wrap; + } + `; + }, [PageTemplateComponent]); + + const noDataConfig = useNoDataConfig(); + + const { loading, error } = useHasData(); + + if (error) { + return ; + } + + return ( + <> +
+ + {loading && path === OVERVIEW_ROUTE && } +
+ {children} +
+
+ + ); +}; diff --git a/x-pack/plugins/uptime/public/apps/use_no_data_config.ts b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts new file mode 100644 index 0000000000000..dc00a25e3a111 --- /dev/null +++ b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts @@ -0,0 +1,46 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import { KibanaPageTemplateProps, useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { UptimeSettingsContext } from '../contexts'; +import { ClientPluginsStart } from './plugin'; +import { indexStatusSelector } from '../state/selectors'; + +export function useNoDataConfig(): KibanaPageTemplateProps['noDataConfig'] { + const { basePath } = useContext(UptimeSettingsContext); + + const { + services: { docLinks }, + } = useKibana(); + + const { data } = useSelector(indexStatusSelector); + + // Returns no data config when there is no historical data + if (data && !data.indexExists) { + return { + solution: i18n.translate('xpack.uptime.noDataConfig.solutionName', { + defaultMessage: 'Observability', + }), + actions: { + beats: { + title: i18n.translate('xpack.uptime.noDataConfig.beatsCard.title', { + defaultMessage: 'Add monitors with Heartbeat', + }), + description: i18n.translate('xpack.uptime.noDataConfig.beatsCard.description', { + defaultMessage: + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users experience.', + }), + href: basePath + `/app/home#/tutorial/uptimeMonitors`, + }, + }, + docsLink: docLinks!.links.observability.guide, + }; + } +} diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx deleted file mode 100644 index caff055ce987c..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx +++ /dev/null @@ -1,26 +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 React from 'react'; -import { screen } from '@testing-library/react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { DataOrIndexMissing } from './data_or_index_missing'; - -describe('DataOrIndexMissing component', () => { - it('renders headingMessage', () => { - const headingMessage = ( - heartbeat-* }} - /> - ); - render(); - expect(screen.getByText(/heartbeat-*/)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx deleted file mode 100644 index 44e55de990bbf..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx +++ /dev/null @@ -1,87 +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 { - EuiFlexGroup, - EuiEmptyPrompt, - EuiFlexItem, - EuiSpacer, - EuiPanel, - EuiTitle, - EuiButton, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useContext } from 'react'; -import { UptimeSettingsContext } from '../../../contexts'; -import { DynamicSettings } from '../../../../common/runtime_types'; - -interface DataMissingProps { - headingMessage: JSX.Element; - settings?: DynamicSettings; -} - -export const DataOrIndexMissing = ({ headingMessage, settings }: DataMissingProps) => { - const { basePath } = useContext(UptimeSettingsContext); - return ( - - - - - -

{headingMessage}

- - } - body={ - <> -

- -

-

- -

- - } - actions={ - - - - - - - - - - - - - } - /> -
-
-
- ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx deleted file mode 100644 index 45b107928d79a..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx +++ /dev/null @@ -1,99 +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 React from 'react'; -import { screen } from '@testing-library/react'; -import { EmptyStateComponent } from './empty_state'; -import { StatesIndexStatus } from '../../../../common/runtime_types'; -import { HttpFetchError, IHttpFetchError } from 'src/core/public'; -import { render } from '../../../lib/helper/rtl_helpers'; - -describe('EmptyState component', () => { - let statesIndexStatus: StatesIndexStatus; - - beforeEach(() => { - statesIndexStatus = { - indexExists: true, - docCount: 1, - indices: 'heartbeat-*,synthetics-*', - }; - }); - - it('renders child components when count is truthy', () => { - render( - -
Foo
-
Bar
-
Baz
-
- ); - - expect(screen.getByText('Foo')).toBeInTheDocument(); - expect(screen.getByText('Bar')).toBeInTheDocument(); - expect(screen.getByText('Baz')).toBeInTheDocument(); - }); - - it(`doesn't render child components when count is falsy`, () => { - render( - -
Should not be rendered
-
- ); - expect(screen.queryByText('Should not be rendered')).toBeNull(); - }); - - it(`renders error message when an error occurs`, () => { - const errors: IHttpFetchError[] = [ - new HttpFetchError('There was an error fetching your data.', 'error', {} as any, {} as any, { - body: { message: 'There was an error fetching your data.' }, - }), - ]; - render( - -
Should not appear...
-
- ); - expect(screen.queryByText('Should not appear...')).toBeNull(); - }); - - it('renders loading state if no errors or doc count', () => { - render( - -
Should appear even while loading...
-
- ); - expect(screen.queryByText('Should appear even while loading...')).toBeInTheDocument(); - }); - - it('does not render empty state with appropriate base path and no docs', () => { - statesIndexStatus = { - docCount: 0, - indexExists: true, - indices: 'heartbeat-*,synthetics-*', - }; - const text = 'If this is in the snapshot the test should fail'; - render( - -
{text}
-
- ); - expect(screen.queryByText(text)).toBeNull(); - }); - - it('notifies when index does not exist', () => { - statesIndexStatus.indexExists = false; - - const text = 'This text should not render'; - - render( - -
{text}
-
- ); - expect(screen.queryByText(text)).toBeNull(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx deleted file mode 100644 index a6fd6579c49fa..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx +++ /dev/null @@ -1,73 +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 React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IHttpFetchError } from 'src/core/public'; -import { EmptyStateError } from './empty_state_error'; -import { EmptyStateLoading } from './empty_state_loading'; -import { DataOrIndexMissing } from './data_or_index_missing'; -import { DynamicSettings, StatesIndexStatus } from '../../../../common/runtime_types'; - -interface EmptyStateProps { - children: JSX.Element[] | JSX.Element; - statesIndexStatus: StatesIndexStatus | null; - loading: boolean; - errors?: IHttpFetchError[]; - settings?: DynamicSettings; -} - -export const EmptyStateComponent = ({ - children, - statesIndexStatus, - loading, - errors, - settings, -}: EmptyStateProps) => { - if (errors?.length) { - return ; - } - const { indexExists, docCount } = statesIndexStatus ?? {}; - - const isLoading = loading && (!indexExists || docCount === 0 || !statesIndexStatus); - - const noIndicesMessage = ( - {settings?.heartbeatIndices} }} - /> - ); - - const noUptimeDataMessage = ( - {settings?.heartbeatIndices} }} - /> - ); - - if (!indexExists && !isLoading) { - return ; - } else if (indexExists && docCount === 0 && !isLoading) { - return ; - } - /** - * We choose to render the children any time the count > 0, even if - * the component is loading. If we render the loading state for this component, - * it will blow away the state of child components and trigger an ugly - * jittery UX any time the components refresh. This way we'll keep the stale - * state displayed during the fetching process. - */ - return ( - - {isLoading && } -
{children}
-
- ); - // } -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx deleted file mode 100644 index 562e45727dda7..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx +++ /dev/null @@ -1,54 +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 React, { useContext, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { indexStatusAction } from '../../../state/actions'; -import { indexStatusSelector, selectDynamicSettings } from '../../../state/selectors'; -import { EmptyStateComponent } from './index'; -import { UptimeRefreshContext } from '../../../contexts'; -import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; - -export const EmptyState: React.FC = ({ children }) => { - const { data, loading, error } = useSelector(indexStatusSelector); - const { lastRefresh } = useContext(UptimeRefreshContext); - - const { settings } = useSelector(selectDynamicSettings); - - const heartbeatIndices = settings?.heartbeatIndices || ''; - - const dispatch = useDispatch(); - - const noDataInfo = !data || data?.docCount === 0 || data?.indexExists === false; - - useEffect(() => { - if (noDataInfo) { - // only call when we haven't fetched it already - dispatch(indexStatusAction.get()); - } - }, [dispatch, lastRefresh, noDataInfo]); - - useEffect(() => { - // using separate side effect, we want to call index status, - // every statue indices setting changes - dispatch(indexStatusAction.get()); - }, [dispatch, heartbeatIndices]); - - useEffect(() => { - dispatch(getDynamicSettings()); - }, [dispatch]); - - return ( - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts b/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts deleted file mode 100644 index 5ffcc15ed404b..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts +++ /dev/null @@ -1,9 +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. - */ - -export { EmptyStateComponent } from './empty_state'; -export { EmptyState } from './empty_state_container'; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx new file mode 100644 index 0000000000000..66c68834f285f --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx @@ -0,0 +1,36 @@ +/* + * 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 { useContext, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { indexStatusAction } from '../../../state/actions'; +import { indexStatusSelector, selectDynamicSettings } from '../../../state/selectors'; +import { UptimeRefreshContext } from '../../../contexts'; +import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; + +export const useHasData = () => { + const { loading, error } = useSelector(indexStatusSelector); + const { lastRefresh } = useContext(UptimeRefreshContext); + + const { settings } = useSelector(selectDynamicSettings); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(indexStatusAction.get()); + }, [dispatch, lastRefresh]); + + useEffect(() => { + dispatch(getDynamicSettings()); + }, [dispatch]); + + return { + error, + loading, + settings, + }; +}; diff --git a/x-pack/plugins/uptime/public/components/overview/index.ts b/x-pack/plugins/uptime/public/components/overview/index.ts index d647c38cee1ca..6ff4524df5277 100644 --- a/x-pack/plugins/uptime/public/components/overview/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/index.ts @@ -6,6 +6,5 @@ */ export * from './monitor_list'; -export * from './empty_state'; export * from './alerts'; export * from './snapshot'; diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index c350c8e1c962f..5199eab00f09f 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -12,7 +12,6 @@ import styled from 'styled-components'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { useTrackPageview } from '../../../observability/public'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; -import { EmptyState } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; import { QueryBar } from '../components/overview/query_bar/query_bar'; import { MONITORING_OVERVIEW_LABEL } from '../routes'; @@ -37,7 +36,7 @@ export const OverviewPageComponent = () => { useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview return ( - + <> @@ -48,6 +47,6 @@ export const OverviewPageComponent = () => { - + ); }; diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index d111d44f08c2d..e151e19180dd4 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -6,7 +6,6 @@ */ import React, { FC, useEffect } from 'react'; -import styled from 'styled-components'; import { Route, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -27,10 +26,8 @@ import { SyntheticsCheckStepsPageHeader, SyntheticsCheckStepsPageRightSideItem, } from './pages/synthetics/synthetics_checks'; -import { ClientPluginsStart } from './apps/plugin'; import { MonitorPageTitle, MonitorPageTitleContent } from './components/monitor/monitor_title'; import { UptimeDatePicker } from './components/common/uptime_date_picker'; -import { useKibana } from '../../../../src/plugins/kibana_react/public'; import { CertRefreshBtn } from './components/certificates/cert_refresh_btn'; import { CertificateTitle } from './components/certificates/certificate_title'; import { SyntheticsCallout } from './components/overview/synthetics_callout'; @@ -40,6 +37,7 @@ import { StepDetailPageHeader, StepDetailPageRightSideItem, } from './pages/synthetics/step_detail_page'; +import { UptimePageTemplateComponent } from './apps/uptime_page_template'; interface RouteProps { path: string; @@ -159,17 +157,6 @@ const RouteInit: React.FC> = }; export const PageRouter: FC = () => { - const { - services: { observability }, - } = useKibana(); - const PageTemplateComponent = observability.navigation.PageTemplate; - - const StyledPageTemplateComponent = styled(PageTemplateComponent)` - .euiPageHeaderContent > .euiFlexGroup { - flex-wrap: wrap; - } - `; - return ( {Routes.map( @@ -178,9 +165,9 @@ export const PageRouter: FC = () => {
- + - +
)