[Unified Observability] Clean overview page feature toggle (#130650)

* Move old overview page to index

* remove overview page feature toggle

* Remove code for old alerts section

* Fix translations

* remove feature toggle from tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ester Martí Vilaseca 2022-04-29 09:02:46 +02:00 committed by GitHub
parent 5c485d405d
commit b0fff69b2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 287 additions and 731 deletions

View file

@ -182,7 +182,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.maps.showMapsInspectorAdapter (boolean)',
'xpack.observability.unsafe.alertingExperience.enabled (boolean)',
'xpack.observability.unsafe.cases.enabled (boolean)',
'xpack.observability.unsafe.overviewNext.enabled (boolean)',
'xpack.observability.unsafe.rules.enabled (boolean)',
'xpack.osquery.actionEnabled (boolean)',
'xpack.osquery.packs (boolean)',

View file

@ -63,7 +63,6 @@ describe('renderApp', () => {
unsafe: {
alertingExperience: { enabled: true },
cases: { enabled: true },
overviewNext: { enabled: false },
rules: { enabled: true },
},
};

View file

@ -1,195 +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 {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiText,
EuiSpacer,
EuiTitle,
EuiButtonEmpty,
EuiLoadingSpinner,
EuiCallOut,
EuiPanel,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import moment from 'moment';
import React, { useState, useMemo } from 'react';
import { EuiSelect } from '@elastic/eui';
import { uniqBy } from 'lodash';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { usePluginContext } from '../../../../hooks/use_plugin_context';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { getObservabilityAlerts } from '../../../../services/get_observability_alerts';
import { paths } from '../../../../config';
import { ObservabilityAppServices } from '../../../../application/types';
const ALL_TYPES = 'ALL_TYPES';
const allTypes = {
value: ALL_TYPES,
text: i18n.translate('xpack.observability.overview.alert.allTypes', {
defaultMessage: 'All types',
}),
};
export function AlertsSection() {
const { config } = usePluginContext();
const { http } = useKibana<ObservabilityAppServices>().services;
const [filter, setFilter] = useState(ALL_TYPES);
const manageLink = config.unsafe.alertingExperience.enabled
? http.basePath.prepend(paths.observability.alerts)
: http.basePath.prepend(paths.management.rules);
const { data, status } = useFetcher(() => getObservabilityAlerts({ http }), [http]);
const alerts = useMemo(() => data ?? [], [data]);
const filterOptions = useMemo(() => {
if (!alerts) {
return [];
}
return uniqBy(alerts, (alert) => alert.consumer).map(({ consumer }) => ({
value: consumer,
text: consumer,
}));
}, [alerts]);
const isError = status === FETCH_STATUS.FAILURE;
const isLoading = status !== FETCH_STATUS.SUCCESS && !isError;
if (isLoading) {
return (
<EuiFlexGroup alignItems="center" justifyContent="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (isError) {
return (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem>
<EuiCallOut
title={i18n.translate('xpack.observability.overview.alert.errorTitle', {
defaultMessage: "We couldn't load the alert data",
})}
color="danger"
iconType="alert"
>
<p>
<FormattedMessage
id="xpack.observability.overview.alert.errorMessage"
defaultMessage="There was an error loading the alert data. Try again later."
/>
</p>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
);
}
return (
<div>
<EuiFlexGroup
alignItems="center"
justifyContent="spaceBetween"
responsive={false}
gutterSize="s"
>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4>
{i18n.translate('xpack.observability.overview.alerts.title', {
defaultMessage: 'Alerts',
})}
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="sortRight" color="text" size="xs" href={manageLink}>
{i18n.translate('xpack.observability.overview.alert.appLink', {
defaultMessage: 'Show all alerts',
})}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiFlexItem grow={false}>
<EuiSpacer size="s" />
<EuiSelect
compressed
id="filterAlerts"
options={[allTypes, ...filterOptions]}
value={filter}
onChange={(e) => setFilter(e.target.value)}
prepend="Show"
/>
<EuiSpacer size="s" />
</EuiFlexItem>
{alerts
.filter((alert) => filter === ALL_TYPES || alert.consumer === filter)
.map((alert, index) => {
return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<EuiFlexGroup direction="column" gutterSize="s" key={alert.id}>
<EuiFlexItem grow={false}>
<EuiLink
href={http.basePath.prepend(paths.management.alertDetails(alert.id))}
>
<EuiText size="s">{alert.name}</EuiText>
</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiBadge color="hollow">{alert.alertTypeId}</EuiBadge>
</EuiFlexItem>
{alert.tags.map((tag, idx) => {
return (
<EuiFlexItem key={idx} grow={false}>
<EuiBadge color="default">{tag}</EuiBadge>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="s" alignItems="center">
{alert.muteAll && (
<EuiFlexItem grow={false}>
<EuiBadge color="hollow">
{i18n.translate('xpack.observability.overview.alerts.muted', {
defaultMessage: 'Muted',
})}
</EuiBadge>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="xs">
Last updated{' '}
{moment.duration(moment().diff(alert.updatedAt)).humanize()} ago
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
})}
</>
</div>
);
}

View file

@ -48,7 +48,6 @@ describe('APMSection', () => {
unsafe: {
alertingExperience: { enabled: true },
cases: { enabled: true },
overviewNext: { enabled: false },
rules: { enabled: true },
},
},

View file

@ -36,7 +36,6 @@ export interface ConfigSchema {
alertingExperience: { enabled: boolean };
rules: { enabled: boolean };
cases: { enabled: boolean };
overviewNext: { enabled: boolean };
};
}

View file

@ -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 React from 'react';
import { shallow } from 'enzyme';
import * as PluginContext from '../../hooks/use_plugin_context';
import { PluginContextValue } from '../../context/plugin_context';
import { OverviewPage } from '.';
import { OverviewPage as OldOverviewPage } from './old_overview_page';
import { OverviewPage as NewOverviewPage } from './overview_page';
describe('Overview page', () => {
it('should render the old overview page when feature flag is disabled and queryParams are empty', () => {
const pluginContext = {
config: {
unsafe: {
overviewNext: { enabled: false },
},
},
};
jest
.spyOn(PluginContext, 'usePluginContext')
.mockReturnValue(pluginContext as PluginContextValue);
const component = shallow(<OverviewPage routeParams={{ query: {} }} />);
expect(component.find(OldOverviewPage)).toHaveLength(1);
expect(component.find(NewOverviewPage)).toHaveLength(0);
});
it('should render the new overview page when feature flag is enabled and queryParams are empty', () => {
const pluginContext = {
config: {
unsafe: {
overviewNext: { enabled: true },
},
},
};
jest
.spyOn(PluginContext, 'usePluginContext')
.mockReturnValue(pluginContext as PluginContextValue);
const component = shallow(<OverviewPage routeParams={{ query: {} }} />);
expect(component.find(OldOverviewPage)).toHaveLength(0);
expect(component.find(NewOverviewPage)).toHaveLength(1);
});
it('should render the new overview page when feature flag is enabled and alpha param is in the url', () => {
const pluginContext = {
config: {
unsafe: {
overviewNext: { enabled: true },
},
},
};
jest
.spyOn(PluginContext, 'usePluginContext')
.mockReturnValue(pluginContext as PluginContextValue);
const component = shallow(<OverviewPage routeParams={{ query: { alpha: true } }} />);
expect(component.find(OldOverviewPage)).toHaveLength(0);
expect(component.find(NewOverviewPage)).toHaveLength(1);
});
it('should render the new overview page when feature flag is disabled and alpha param is in the url', () => {
const pluginContext = {
config: {
unsafe: {
overviewNext: { enabled: false },
},
},
};
jest
.spyOn(PluginContext, 'usePluginContext')
.mockReturnValue(pluginContext as PluginContextValue);
const component = shallow(<OverviewPage routeParams={{ query: { alpha: true } }} />);
expect(component.find(OldOverviewPage)).toHaveLength(0);
expect(component.find(NewOverviewPage)).toHaveLength(1);
});
});

View file

@ -4,25 +4,300 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { RouteParams } from '../../routes';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiHorizontalRule,
EuiButton,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useMemo, useRef, useCallback, useState, useEffect } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { observabilityFeatureId } from '../../../common';
import { useTrackPageview, useUiTracker } from '../..';
import { EmptySections } from '../../components/app/empty_sections';
import { ObservabilityHeaderMenu } from '../../components/app/header';
import { NewsFeed } from '../../components/app/news_feed';
import { Resources } from '../../components/app/resources';
import { DatePicker } from '../../components/shared/date_picker';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useFetcher } from '../../hooks/use_fetcher';
import { useHasData } from '../../hooks/use_has_data';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { OverviewPage as OldOverviewPage } from './old_overview_page';
import { OverviewPage as NewOverviewPage } from './overview_page';
import { useAlertIndexNames } from '../../hooks/use_alert_index_names';
import { RouteParams } from '../../routes';
import { getNewsFeed } from '../../services/get_news_feed';
import { getBucketSize } from '../../utils/get_bucket_size';
import { getNoDataConfig } from '../../utils/no_data_config';
import { DataSections } from './data_sections';
import { LoadingObservability } from './loading_observability';
import { AlertsTableTGrid } from '../alerts/containers/alerts_table_t_grid/alerts_table_t_grid';
import { SectionContainer } from '../../components/app/section';
import { ObservabilityAppServices } from '../../application/types';
import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions';
import { paths } from '../../config';
import { useDatePickerContext } from '../../hooks/use_date_picker_context';
import { ObservabilityStatusProgress } from '../../components/app/observability_status/observability_status_progress';
import { ObservabilityStatus } from '../../components/app/observability_status';
import { useGuidedSetupProgress } from '../../hooks/use_guided_setup_progress';
export type { BucketSize } from './old_overview_page';
export type BucketSize = ReturnType<typeof calculateBucketSize>;
interface Props {
routeParams: RouteParams<'/overview'>;
}
export function OverviewPage(props: Props) {
const { config } = usePluginContext();
const alpha = props.routeParams.query.alpha;
const CAPABILITIES_KEYS = ['logs', 'infrastructure', 'apm', 'uptime'];
if (config.unsafe.overviewNext.enabled || alpha) {
return <NewOverviewPage {...props} />;
} else {
return <OldOverviewPage {...props} />;
function calculateBucketSize({ start, end }: { start?: number; end?: number }) {
if (start && end) {
return getBucketSize({ start, end, minInterval: '60s' });
}
}
const ALERT_TABLE_STATE_STORAGE_KEY = 'xpack.observability.overview.alert.tableState';
export function OverviewPage({ routeParams }: Props) {
const trackMetric = useUiTracker({ app: 'observability-overview' });
useTrackPageview({ app: 'observability-overview', path: 'overview' });
useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 });
useBreadcrumbs([
{
text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', {
defaultMessage: 'Overview',
}),
},
]);
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const indexNames = useAlertIndexNames();
const {
cases,
docLinks,
http,
application: { capabilities },
} = useKibana<ObservabilityAppServices>().services;
const { ObservabilityPageTemplate, config } = usePluginContext();
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext();
const { data: newsFeed } = useFetcher(() => getNewsFeed({ http }), [http]);
const { hasAnyData, isAllRequestsComplete } = useHasData();
const refetch = useRef<() => void>();
const { isGuidedSetupProgressDismissed } = useGuidedSetupProgress();
const bucketSize = useMemo(
() =>
calculateBucketSize({
start: absoluteStart,
end: absoluteEnd,
}),
[absoluteStart, absoluteEnd]
);
const setRefetch = useCallback((ref) => {
refetch.current = ref;
}, []);
const handleGuidedSetupClick = useCallback(() => {
if (isGuidedSetupProgressDismissed) {
trackMetric({ metric: 'guided_setup_view_details_after_dismiss' });
}
setIsFlyoutVisible(true);
}, [trackMetric, isGuidedSetupProgressDismissed]);
const onTimeRangeRefresh = useCallback(() => {
return refetch.current && refetch.current();
}, []);
const CasesContext = cases.ui.getCasesContext();
const userPermissions = useGetUserCasesPermissions();
useEffect(() => {
if (hasAnyData !== true) {
return;
}
CAPABILITIES_KEYS.forEach((feature) => {
if (capabilities[feature].show === false) {
trackMetric({
metric: `oblt_disabled_feature_${feature === 'infrastructure' ? 'metrics' : feature}`,
});
}
});
}, [capabilities, hasAnyData, trackMetric]);
if (hasAnyData === undefined) {
return <LoadingObservability />;
}
const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false);
const noDataConfig = getNoDataConfig({
hasData,
basePath: http.basePath,
docsLink: docLinks.links.observability.guide,
});
const alertsLink = config.unsafe.alertingExperience.enabled
? paths.observability.alerts
: paths.management.rules;
return (
<ObservabilityPageTemplate
noDataConfig={noDataConfig}
pageHeader={
hasData
? {
children: (
<PageHeader
handleGuidedSetupClick={handleGuidedSetupClick}
onTimeRangeRefresh={onTimeRangeRefresh}
/>
),
}
: undefined
}
>
{hasData && (
<>
<ObservabilityHeaderMenu />
<ObservabilityStatusProgress onViewDetailsClick={() => setIsFlyoutVisible(true)} />
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<SectionContainer
title={i18n.translate('xpack.observability.overview.alerts.title', {
defaultMessage: 'Alerts',
})}
hasError={false}
appLink={{
href: alertsLink,
label: i18n.translate('xpack.observability.overview.alerts.appLink', {
defaultMessage: 'Show alerts',
}),
}}
showExperimentalBadge={true}
>
<CasesContext
owner={[observabilityFeatureId]}
userCanCrud={userPermissions?.crud ?? false}
features={{ alerts: { sync: false } }}
>
<AlertsTableTGrid
setRefetch={setRefetch}
rangeFrom={relativeStart}
rangeTo={relativeEnd}
indexNames={indexNames}
stateStorageKey={ALERT_TABLE_STATE_STORAGE_KEY}
storage={new Storage(window.localStorage)}
/>
</CasesContext>
</SectionContainer>
</EuiFlexItem>
<EuiFlexItem>
{/* Data sections */}
{hasAnyData && <DataSections bucketSize={bucketSize} />}
<EmptySections />
</EuiFlexItem>
<EuiSpacer size="s" />
</EuiFlexGroup>
<EuiHorizontalRule />
<EuiFlexGroup>
<EuiFlexItem>
{/* Resources / What's New sections */}
<EuiFlexGroup direction="row">
<EuiFlexItem grow={4}>
{!!newsFeed?.items?.length && <NewsFeed items={newsFeed.items.slice(0, 3)} />}
</EuiFlexItem>
<EuiFlexItem grow={2}>
<Resources />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</>
)}
{isFlyoutVisible && (
<EuiFlyout
size="s"
ownFocus
onClose={() => setIsFlyoutVisible(false)}
aria-labelledby="statusVisualizationFlyoutTitle"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="statusVisualizationFlyoutTitle">
<FormattedMessage
id="xpack.observability.overview.statusVisualizationFlyoutTitle"
defaultMessage="Guided setup"
/>
</h2>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.observability.overview.statusVisualizationFlyoutDescription"
defaultMessage="Track your progress towards adding observability integrations and features."
/>
</p>
</EuiText>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ObservabilityStatus />
</EuiFlyoutBody>
</EuiFlyout>
)}
</ObservabilityPageTemplate>
);
}
interface PageHeaderProps {
handleGuidedSetupClick: () => void;
onTimeRangeRefresh: () => void;
}
function PageHeader({ handleGuidedSetupClick, onTimeRangeRefresh }: PageHeaderProps) {
const { relativeStart, relativeEnd, refreshInterval, refreshPaused } = useDatePickerContext();
return (
<EuiFlexGroup wrap gutterSize="s" justifyContent="flexEnd">
<EuiFlexItem grow={1}>
<EuiTitle>
<h1 className="eui-textNoWrap">{overviewPageTitle}</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DatePicker
rangeFrom={relativeStart}
rangeTo={relativeEnd}
refreshInterval={refreshInterval}
refreshPaused={refreshPaused}
onTimeRangeRefresh={onTimeRangeRefresh}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ alignItems: 'flex-end' }}>
<EuiButton color="text" iconType="wrench" onClick={handleGuidedSetupClick}>
<FormattedMessage
id="xpack.observability.overview.guidedSetupButton"
defaultMessage="Guided setup"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
}
const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', {
defaultMessage: 'Overview',
});

View file

@ -1,302 +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,
EuiFlexItem,
EuiSpacer,
EuiHorizontalRule,
EuiButton,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useMemo, useRef, useCallback, useState, useEffect } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { observabilityFeatureId } from '../../../common';
import { useTrackPageview, useUiTracker } from '../..';
import { EmptySections } from '../../components/app/empty_sections';
import { ObservabilityHeaderMenu } from '../../components/app/header';
import { NewsFeed } from '../../components/app/news_feed';
import { Resources } from '../../components/app/resources';
import { DatePicker } from '../../components/shared/date_picker';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useFetcher } from '../../hooks/use_fetcher';
import { useHasData } from '../../hooks/use_has_data';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useAlertIndexNames } from '../../hooks/use_alert_index_names';
import { RouteParams } from '../../routes';
import { getNewsFeed } from '../../services/get_news_feed';
import { getBucketSize } from '../../utils/get_bucket_size';
import { getNoDataConfig } from '../../utils/no_data_config';
import { DataSections } from './data_sections';
import { LoadingObservability } from './loading_observability';
import { AlertsTableTGrid } from '../alerts/containers/alerts_table_t_grid/alerts_table_t_grid';
import { SectionContainer } from '../../components/app/section';
import { ObservabilityAppServices } from '../../application/types';
import { useGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions';
import { paths } from '../../config';
import { useDatePickerContext } from '../../hooks/use_date_picker_context';
import { ObservabilityStatusProgress } from '../../components/app/observability_status/observability_status_progress';
import { ObservabilityStatus } from '../../components/app/observability_status';
import { useGuidedSetupProgress } from '../../hooks/use_guided_setup_progress';
interface Props {
routeParams: RouteParams<'/overview'>;
}
export type BucketSize = ReturnType<typeof calculateBucketSize>;
const CAPABILITIES_KEYS = ['logs', 'infrastructure', 'apm', 'uptime'];
function calculateBucketSize({ start, end }: { start?: number; end?: number }) {
if (start && end) {
return getBucketSize({ start, end, minInterval: '60s' });
}
}
const ALERT_TABLE_STATE_STORAGE_KEY = 'xpack.observability.overview.alert.tableState';
export function OverviewPage({ routeParams }: Props) {
const trackMetric = useUiTracker({ app: 'observability-overview' });
useTrackPageview({ app: 'observability-overview', path: 'overview' });
useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 });
useBreadcrumbs([
{
text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', {
defaultMessage: 'Overview',
}),
},
]);
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const indexNames = useAlertIndexNames();
const {
cases,
docLinks,
http,
application: { capabilities },
} = useKibana<ObservabilityAppServices>().services;
const { ObservabilityPageTemplate, config } = usePluginContext();
const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useDatePickerContext();
const { data: newsFeed } = useFetcher(() => getNewsFeed({ http }), [http]);
const { hasAnyData, isAllRequestsComplete } = useHasData();
const refetch = useRef<() => void>();
const { isGuidedSetupProgressDismissed } = useGuidedSetupProgress();
const bucketSize = useMemo(
() =>
calculateBucketSize({
start: absoluteStart,
end: absoluteEnd,
}),
[absoluteStart, absoluteEnd]
);
const setRefetch = useCallback((ref) => {
refetch.current = ref;
}, []);
const handleGuidedSetupClick = useCallback(() => {
if (isGuidedSetupProgressDismissed) {
trackMetric({ metric: 'guided_setup_view_details_after_dismiss' });
}
setIsFlyoutVisible(true);
}, [trackMetric, isGuidedSetupProgressDismissed]);
const onTimeRangeRefresh = useCallback(() => {
return refetch.current && refetch.current();
}, []);
const CasesContext = cases.ui.getCasesContext();
const userPermissions = useGetUserCasesPermissions();
useEffect(() => {
if (hasAnyData !== true) {
return;
}
CAPABILITIES_KEYS.forEach((feature) => {
if (capabilities[feature].show === false) {
trackMetric({
metric: `oblt_disabled_feature_${feature === 'infrastructure' ? 'metrics' : feature}`,
});
}
});
}, [capabilities, hasAnyData, trackMetric]);
if (hasAnyData === undefined) {
return <LoadingObservability />;
}
const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false);
const noDataConfig = getNoDataConfig({
hasData,
basePath: http.basePath,
docsLink: docLinks.links.observability.guide,
});
const alertsLink = config.unsafe.alertingExperience.enabled
? paths.observability.alerts
: paths.management.rules;
return (
<ObservabilityPageTemplate
noDataConfig={noDataConfig}
pageHeader={
hasData
? {
children: (
<PageHeader
handleGuidedSetupClick={handleGuidedSetupClick}
onTimeRangeRefresh={onTimeRangeRefresh}
/>
),
}
: undefined
}
>
{hasData && (
<>
<ObservabilityHeaderMenu />
<ObservabilityStatusProgress onViewDetailsClick={() => setIsFlyoutVisible(true)} />
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<SectionContainer
title={i18n.translate('xpack.observability.overview.alerts.title', {
defaultMessage: 'Alerts',
})}
hasError={false}
appLink={{
href: alertsLink,
label: i18n.translate('xpack.observability.overview.alerts.appLink', {
defaultMessage: 'Show alerts',
}),
}}
showExperimentalBadge={true}
>
<CasesContext
owner={[observabilityFeatureId]}
userCanCrud={userPermissions?.crud ?? false}
features={{ alerts: { sync: false } }}
>
<AlertsTableTGrid
setRefetch={setRefetch}
rangeFrom={relativeStart}
rangeTo={relativeEnd}
indexNames={indexNames}
stateStorageKey={ALERT_TABLE_STATE_STORAGE_KEY}
storage={new Storage(window.localStorage)}
/>
</CasesContext>
</SectionContainer>
</EuiFlexItem>
<EuiFlexItem>
{/* Data sections */}
{hasAnyData && <DataSections bucketSize={bucketSize} />}
<EmptySections />
</EuiFlexItem>
<EuiSpacer size="s" />
</EuiFlexGroup>
<EuiHorizontalRule />
<EuiFlexGroup>
<EuiFlexItem>
{/* Resources / What's New sections */}
<EuiFlexGroup direction="row">
<EuiFlexItem grow={4}>
{!!newsFeed?.items?.length && <NewsFeed items={newsFeed.items.slice(0, 3)} />}
</EuiFlexItem>
<EuiFlexItem grow={2}>
<Resources />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</>
)}
{isFlyoutVisible && (
<EuiFlyout
size="s"
ownFocus
onClose={() => setIsFlyoutVisible(false)}
aria-labelledby="statusVisualizationFlyoutTitle"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="statusVisualizationFlyoutTitle">
<FormattedMessage
id="xpack.observability.overview.statusVisualizationFlyoutTitle"
defaultMessage="Guided setup"
/>
</h2>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.observability.overview.statusVisualizationFlyoutDescription"
defaultMessage="Track your progress towards adding observability integrations and features."
/>
</p>
</EuiText>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ObservabilityStatus />
</EuiFlyoutBody>
</EuiFlyout>
)}
</ObservabilityPageTemplate>
);
}
interface PageHeaderProps {
handleGuidedSetupClick: () => void;
onTimeRangeRefresh: () => void;
}
function PageHeader({ handleGuidedSetupClick, onTimeRangeRefresh }: PageHeaderProps) {
const { relativeStart, relativeEnd, refreshInterval, refreshPaused } = useDatePickerContext();
return (
<EuiFlexGroup wrap gutterSize="s" justifyContent="flexEnd">
<EuiFlexItem grow={1}>
<EuiTitle>
<h1 className="eui-textNoWrap">{overviewPageTitle}</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DatePicker
rangeFrom={relativeStart}
rangeTo={relativeEnd}
refreshInterval={refreshInterval}
refreshPaused={refreshPaused}
onTimeRangeRefresh={onTimeRangeRefresh}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ alignItems: 'flex-end' }}>
<EuiButton color="text" iconType="wrench" onClick={handleGuidedSetupClick}>
<FormattedMessage
id="xpack.observability.overview.guidedSetupButton"
defaultMessage="Guided setup"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
}
const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', {
defaultMessage: 'Overview',
});

View file

@ -85,7 +85,6 @@ const withCore = makeDecorator({
unsafe: {
alertingExperience: { enabled: true },
cases: { enabled: true },
overviewNext: { enabled: false },
rules: { enabled: true },
},
},

View file

@ -1,110 +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 { i18n } from '@kbn/i18n';
import React, { useState } from 'react';
import { EuiButton, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useTrackPageview } from '../..';
import { DatePicker } from '../../components/shared/date_picker';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useHasData } from '../../hooks/use_has_data';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useDatePickerContext } from '../../hooks/use_date_picker_context';
import { RouteParams } from '../../routes';
import { getNoDataConfig } from '../../utils/no_data_config';
import { LoadingObservability } from './loading_observability';
import { ObservabilityStatus } from '../../components/app/observability_status';
import { ObservabilityAppServices } from '../../application/types';
interface Props {
routeParams: RouteParams<'/overview'>;
}
export function OverviewPage({ routeParams }: Props) {
useTrackPageview({ app: 'observability-overview', path: 'overview' });
useTrackPageview({ app: 'observability-overview', path: 'overview', delay: 15000 });
useBreadcrumbs([
{
text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', {
defaultMessage: 'Overview',
}),
},
]);
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
const { docLinks, http } = useKibana<ObservabilityAppServices>().services;
const { ObservabilityPageTemplate } = usePluginContext();
const { relativeStart, relativeEnd } = useDatePickerContext();
const relativeTime = { start: relativeStart, end: relativeEnd };
const { hasAnyData, isAllRequestsComplete } = useHasData();
if (hasAnyData === undefined) {
return <LoadingObservability />;
}
const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false);
const noDataConfig = getNoDataConfig({
hasData,
basePath: http.basePath,
docsLink: docLinks.links.observability.guide,
});
const { refreshInterval = 10000, refreshPaused = true } = routeParams.query;
return (
<ObservabilityPageTemplate
noDataConfig={noDataConfig}
pageHeader={
hasData
? {
pageTitle: overviewPageTitle,
rightSideItems: [
<DatePicker
rangeFrom={relativeTime.start}
rangeTo={relativeTime.end}
refreshInterval={refreshInterval}
refreshPaused={refreshPaused}
/>,
],
}
: undefined
}
>
{hasData && (
<>
<EuiButton onClick={() => setIsFlyoutVisible(true)}>Show observability status</EuiButton>
{isFlyoutVisible && (
<EuiFlyout
size="s"
ownFocus
onClose={() => setIsFlyoutVisible(false)}
aria-labelledby="flyout-id"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="flyout-id">Status</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ObservabilityStatus />
</EuiFlyoutBody>
</EuiFlyout>
)}
</>
)}
</ObservabilityPageTemplate>
);
}
const overviewPageTitle = i18n.translate('xpack.observability.overview.pageTitle', {
defaultMessage: 'Overview',
});

View file

@ -26,7 +26,6 @@ const config = {
unsafe: {
alertingExperience: { enabled: true },
cases: { enabled: true },
overviewNext: { enabled: false },
rules: { enabled: true },
},
};

View file

@ -35,7 +35,6 @@ export const config: PluginConfigDescriptor = {
alertingExperience: schema.object({ enabled: schema.boolean({ defaultValue: true }) }),
rules: schema.object({ enabled: schema.boolean({ defaultValue: true }) }),
cases: schema.object({ enabled: schema.boolean({ defaultValue: true }) }),
overviewNext: schema.object({ enabled: schema.boolean({ defaultValue: false }) }),
}),
}),
};

View file

@ -21658,12 +21658,6 @@
"xpack.observability.noDataConfig.beatsCard.title": "Ajouter des intégrations",
"xpack.observability.noDataConfig.solutionName": "Observabilité",
"xpack.observability.notAvailable": "S. O.",
"xpack.observability.overview.alert.allTypes": "Tous les types",
"xpack.observability.overview.alert.appLink": "Afficher toutes les alertes",
"xpack.observability.overview.alert.errorMessage": "Une erreur sest produite lors du chargement des données dalerte. Réessayez plus tard.",
"xpack.observability.overview.alert.errorTitle": "Impossible de charger les données dalerte.",
"xpack.observability.overview.alerts.appLink": "Afficher les alertes",
"xpack.observability.overview.alerts.muted": "Muet",
"xpack.observability.overview.alerts.title": "Alertes",
"xpack.observability.overview.apm.appLink": "Afficher linventaire des services",
"xpack.observability.overview.apm.services": "Services",

View file

@ -21815,12 +21815,6 @@
"xpack.observability.noDataConfig.beatsCard.title": "統合の追加",
"xpack.observability.noDataConfig.solutionName": "Observability",
"xpack.observability.notAvailable": "N/A",
"xpack.observability.overview.alert.allTypes": "すべてのタイプ",
"xpack.observability.overview.alert.appLink": "すべてのアラートを表示",
"xpack.observability.overview.alert.errorMessage": "アラートデータの読み込みエラーが発生しました。しばらくたってから再試行してください。",
"xpack.observability.overview.alert.errorTitle": "アラートデータを読み込めませんでした",
"xpack.observability.overview.alerts.appLink": "アラートを表示",
"xpack.observability.overview.alerts.muted": "ミュート",
"xpack.observability.overview.alerts.title": "アラート",
"xpack.observability.overview.apm.appLink": "サービスインベントリを表示",
"xpack.observability.overview.apm.services": "サービス",

View file

@ -21847,12 +21847,6 @@
"xpack.observability.noDataConfig.beatsCard.title": "添加集成",
"xpack.observability.noDataConfig.solutionName": "Observability",
"xpack.observability.notAvailable": "不可用",
"xpack.observability.overview.alert.allTypes": "所有类型",
"xpack.observability.overview.alert.appLink": "显示所有告警",
"xpack.observability.overview.alert.errorMessage": "加载告警数据时出现错误。请稍后重试。",
"xpack.observability.overview.alert.errorTitle": "我们无法加载告警数据",
"xpack.observability.overview.alerts.appLink": "显示告警",
"xpack.observability.overview.alerts.muted": "已静音",
"xpack.observability.overview.alerts.title": "告警",
"xpack.observability.overview.apm.appLink": "显示服务库存",
"xpack.observability.overview.apm.services": "服务",