[Logs & Metrics] refactor breadcrumbs (#103249) (#104074)

* [Logs & Metrics] refactor breadcrumbs

* [Logs & Metrics] remove Header component, move translations and create readonly badge hook

* add breadcrumb to metric detail page

* fix check_file_casing ci issues

* create separate breadcrumb hook for logs and metrics

* fix metrics translation title

* fix wrong imports and unused variables

* fix translation imports

* fix unused import

* refactor use_breadcrumbs

* remove Header component

* fix linter exhaustive-deps error by wrapping into useMemo

* refactor use_readonly_badge

* remove commented out code

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: mgiota <giota85@gmail.com>
This commit is contained in:
Kibana Machine 2021-07-01 08:59:31 -04:00 committed by GitHub
parent 98bda2d177
commit 92991327a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 222 additions and 117 deletions

View file

@ -9,3 +9,5 @@ export const DEFAULT_SOURCE_ID = 'default';
export const METRICS_INDEX_PATTERN = 'metrics-*,metricbeat-*';
export const LOGS_INDEX_PATTERN = 'logs-*,filebeat-*,kibana_sample_data_logs*';
export const TIMESTAMP_FIELD = '@timestamp';
export const METRICS_APP = 'metrics';
export const LOGS_APP = 'logs';

View file

@ -1,56 +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 { useCallback, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { ChromeBreadcrumb } from 'src/core/public';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
interface HeaderProps {
breadcrumbs?: ChromeBreadcrumb[];
readOnlyBadge?: boolean;
}
export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => {
const chrome = useKibana().services.chrome;
// eslint-disable-next-line react-hooks/exhaustive-deps
const badge = readOnlyBadge
? {
text: i18n.translate('xpack.infra.header.badge.readOnly.text', {
defaultMessage: 'Read only',
}),
tooltip: i18n.translate('xpack.infra.header.badge.readOnly.tooltip', {
defaultMessage: 'Unable to change source configuration',
}),
iconType: 'glasses',
}
: undefined;
const setBreadcrumbs = useCallback(() => {
return chrome?.setBreadcrumbs(breadcrumbs || []);
}, [breadcrumbs, chrome]);
const setBadge = useCallback(() => {
return chrome?.setBadge(badge);
}, [badge, chrome]);
useEffect(() => {
setBreadcrumbs();
setBadge();
}, [setBreadcrumbs, setBadge]);
useEffect(() => {
setBreadcrumbs();
}, [breadcrumbs, setBreadcrumbs]);
useEffect(() => {
setBadge();
}, [badge, setBadge]);
return null;
};

View file

@ -1,8 +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 { Header } from './header';

View file

@ -0,0 +1,37 @@
/*
* 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 { ChromeBreadcrumb } from 'kibana/public';
import { useEffect } from 'react';
import { observabilityTitle } from '../translations';
import { useKibanaContextForPlugin } from './use_kibana';
import { useLinkProps } from './use_link_props';
type AppId = 'logs' | 'metrics';
export const useBreadcrumbs = (app: AppId, appTitle: string, extraCrumbs: ChromeBreadcrumb[]) => {
const {
services: { chrome },
} = useKibanaContextForPlugin();
const observabilityLinkProps = useLinkProps({ app: 'observability-overview' });
const appLinkProps = useLinkProps({ app });
useEffect(() => {
chrome?.setBreadcrumbs?.([
{
...observabilityLinkProps,
text: observabilityTitle,
},
{
...appLinkProps,
text: appTitle,
},
...extraCrumbs,
]);
}, [appLinkProps, appTitle, chrome, extraCrumbs, observabilityLinkProps]);
};

View file

@ -0,0 +1,15 @@
/*
* 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 { ChromeBreadcrumb } from 'kibana/public';
import { useBreadcrumbs } from './use_breadcrumbs';
import { LOGS_APP } from '../../common/constants';
import { logsTitle } from '../translations';
export const useLogsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => {
useBreadcrumbs(LOGS_APP, logsTitle, extraCrumbs);
};

View file

@ -0,0 +1,15 @@
/*
* 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 { ChromeBreadcrumb } from 'kibana/public';
import { useBreadcrumbs } from './use_breadcrumbs';
import { METRICS_APP } from '../../common/constants';
import { metricsTitle } from '../translations';
export const useMetricsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => {
useBreadcrumbs(METRICS_APP, metricsTitle, extraCrumbs);
};

View file

@ -0,0 +1,32 @@
/*
* 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 { useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
export const useReadOnlyBadge = (isReadOnly = false) => {
const chrome = useKibana().services.chrome;
useEffect(() => {
chrome?.setBadge(
isReadOnly
? {
text: i18n.translate('xpack.infra.header.badge.readOnly.text', {
defaultMessage: 'Read only',
}),
tooltip: i18n.translate('xpack.infra.header.badge.readOnly.tooltip', {
defaultMessage: 'Unable to change source configuration',
}),
iconType: 'glasses',
}
: undefined
);
}, [chrome, isReadOnly]);
return null;
};

View file

@ -18,7 +18,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { euiStyled } from '../../../../../src/plugins/kibana_react/common';
import { Header } from '../components/header';
import { ColumnarPage, PageContent } from '../components/page';
const DetailPageContent = euiStyled(PageContent)`
@ -33,7 +32,6 @@ interface Props {
export const Error: React.FC<Props> = ({ message }) => {
return (
<ColumnarPage>
<Header />
<DetailPageContent>
<ErrorPageBody message={message} />
</DetailPageContent>

View file

@ -7,10 +7,18 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { LogEntryCategoriesPageContent } from './page_content';
import { LogEntryCategoriesPageProviders } from './page_providers';
import { logCategoriesTitle } from '../../../translations';
export const LogEntryCategoriesPage = () => {
useLogsBreadcrumbs([
{
text: logCategoriesTitle,
},
]);
return (
<EuiErrorBoundary>
<LogEntryCategoriesPageProviders>

View file

@ -9,8 +9,15 @@ import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { LogEntryRatePageContent } from './page_content';
import { LogEntryRatePageProviders } from './page_providers';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { anomaliesTitle } from '../../../translations';
export const LogEntryRatePage = () => {
useLogsBreadcrumbs([
{
text: anomaliesTitle,
},
]);
return (
<EuiErrorBoundary>
<LogEntryRatePageProviders>

View file

@ -14,7 +14,6 @@ import useMount from 'react-use/lib/useMount';
import { AlertDropdown } from '../../alerting/log_threshold';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { DocumentTitle } from '../../components/document_title';
import { Header } from '../../components/header';
import { HelpCenterContent } from '../../components/help_center_content';
import { useLogSourceContext } from '../../containers/logs/log_source';
import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params';
@ -25,6 +24,7 @@ import { StreamPage } from './stream';
import { HeaderMenuPortal } from '../../../../observability/public';
import { HeaderActionMenuContext } from '../../utils/header_action_menu_provider';
import { useLinkProps } from '../../hooks/use_link_props';
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
export const LogsPageContent: React.FunctionComponent = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
@ -34,6 +34,8 @@ export const LogsPageContent: React.FunctionComponent = () => {
const kibana = useKibana();
useReadOnlyBadge(!uiCapabilities?.logs?.save);
useMount(() => {
initialize();
});
@ -101,14 +103,6 @@ export const LogsPageContent: React.FunctionComponent = () => {
</HeaderMenuPortal>
)}
<Header
breadcrumbs={[
{
text: pageTitle,
},
]}
readOnlyBadge={!uiCapabilities?.logs?.save}
/>
<Switch>
<Route path={streamTab.pathname} component={StreamPage} />
<Route path={anomaliesTab.pathname} component={LogEntryRatePage} />

View file

@ -18,6 +18,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { useCallback, useMemo } from 'react';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { useTrackPageview } from '../../../../../observability/public';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useLogSourceContext } from '../../../containers/logs/log_source';
import { Prompt } from '../../../utils/navigation_warning_prompt';
@ -27,10 +28,7 @@ import { NameConfigurationPanel } from './name_configuration_panel';
import { LogSourceConfigurationFormErrors } from './source_configuration_form_errors';
import { useLogSourceConfigurationFormState } from './source_configuration_form_state';
import { LogsPageTemplate } from '../page_template';
const settingsTitle = i18n.translate('xpack.infra.logs.settingsTitle', {
defaultMessage: 'Settings',
});
import { settingsTitle } from '../../../translations';
export const LogsSettingsPage = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
@ -43,6 +41,12 @@ export const LogsSettingsPage = () => {
delay: 15000,
});
useLogsBreadcrumbs([
{
text: settingsTitle,
},
]);
const {
sourceConfiguration: source,
hasFailedLoadingSource,

View file

@ -8,13 +8,21 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { useTrackPageview } from '../../../../../observability/public';
import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs';
import { StreamPageContent } from './page_content';
import { StreamPageHeader } from './page_header';
import { LogsPageProviders } from './page_providers';
import { streamTitle } from '../../../translations';
export const StreamPage = () => {
useTrackPageview({ app: 'infra_logs', path: 'stream' });
useTrackPageview({ app: 'infra_logs', path: 'stream', delay: 15000 });
useLogsBreadcrumbs([
{
text: streamTitle,
},
]);
return (
<EuiErrorBoundary>
<LogsPageProviders>

View file

@ -15,7 +15,7 @@ import { IIndexPattern } from 'src/plugins/data/common';
import { MetricsSourceConfigurationProperties } from '../../../common/metrics_sources';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
import { Header } from '../../components/header';
import { useReadOnlyBadge } from '../../hooks/use_readonly_badge';
import {
MetricsExplorerOptionsContainer,
DEFAULT_METRICS_EXPLORER_VIEW_STATE,
@ -56,6 +56,8 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
const kibana = useKibana();
useReadOnlyBadge(!uiCapabilities?.infrastructure?.save);
const settingsLinkProps = useLinkProps({
app: 'metrics',
pathname: 'settings',
@ -111,17 +113,6 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
</EuiFlexGroup>
</HeaderMenuPortal>
)}
<Header
breadcrumbs={[
{
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
defaultMessage: 'Metrics',
}),
},
]}
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
/>
<Switch>
<Route path={'/inventory'} component={SnapshotPage} />
<Route

View file

@ -8,7 +8,6 @@
import { EuiButton, EuiErrorBoundary, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { FilterBar } from './components/filter_bar';
import { DocumentTitle } from '../../../components/document_title';
@ -19,6 +18,7 @@ import { SourceLoadingPage } from '../../../components/source_loading_page';
import { ViewSourceConfigurationButton } from '../../../components/source_configuration/view_source_configuration_button';
import { Source } from '../../../containers/metrics_source';
import { useTrackPageview } from '../../../../../observability/public';
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { LayoutView } from './components/layout_view';
import { useLinkProps } from '../../../hooks/use_link_props';
@ -28,10 +28,7 @@ import { useWaffleOptionsContext } from './hooks/use_waffle_options';
import { MetricsPageTemplate } from '../page_template';
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
import { APP_WRAPPER_CLASS } from '../../../../../../../src/core/public';
const inventoryTitle = i18n.translate('xpack.infra.metrics.inventoryPageTitle', {
defaultMessage: 'Inventory',
});
import { inventoryTitle } from '../../../translations';
export const SnapshotPage = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
@ -52,6 +49,12 @@ export const SnapshotPage = () => {
hash: '/tutorial_directory/metrics',
});
useMetricsBreadcrumbs([
{
text: inventoryTitle,
},
]);
return (
<EuiErrorBoundary>
<DocumentTitle

View file

@ -9,19 +9,19 @@ import { i18n } from '@kbn/i18n';
import React, { useContext, useState } from 'react';
import { EuiTheme, withTheme } from '../../../../../../../src/plugins/kibana_react/common';
import { DocumentTitle } from '../../../components/document_title';
import { Header } from '../../../components/header';
import { withMetricPageProviders } from './page_providers';
import { useMetadata } from './hooks/use_metadata';
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
import { Source } from '../../../containers/metrics_source';
import { InfraLoadingPanel } from '../../../components/loading';
import { findInventoryModel } from '../../../../common/inventory_models';
import { NavItem } from './lib/side_nav_context';
import { NodeDetailsPage } from './components/node_details_page';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { InventoryItemType } from '../../../../common/inventory_models/types';
import { useMetricsTimeContext } from './hooks/use_metrics_time';
import { useLinkProps } from '../../../hooks/use_link_props';
import { MetricsPageTemplate } from '../page_template';
import { inventoryTitle } from '../../../translations';
interface Props {
theme: EuiTheme | undefined;
@ -35,7 +35,6 @@ interface Props {
export const MetricDetail = withMetricPageProviders(
withTheme(({ match }: Props) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const nodeId = match.params.node;
const nodeType = match.params.type as InventoryItemType;
const inventoryModel = findInventoryModel(nodeType);
@ -70,20 +69,20 @@ export const MetricDetail = withMetricPageProviders(
[sideNav]
);
const metricsLinkProps = useLinkProps({
const inventoryLinkProps = useLinkProps({
app: 'metrics',
pathname: '/',
pathname: '/inventory',
});
const breadcrumbs = [
useMetricsBreadcrumbs([
{
...metricsLinkProps,
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
defaultMessage: 'Metrics',
}),
...inventoryLinkProps,
text: inventoryTitle,
},
{ text: name },
];
{
text: name,
},
]);
if (metadataLoading && !filteredRequiredMetrics.length) {
return (
@ -101,7 +100,6 @@ export const MetricDetail = withMetricPageProviders(
return (
<>
<Header breadcrumbs={breadcrumbs} readOnlyBadge={!uiCapabilities?.infrastructure?.save} />
<DocumentTitle
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
defaultMessage: 'Infrastructure | Metrics | {name}',

View file

@ -11,6 +11,8 @@ import React, { useEffect } from 'react';
import { IIndexPattern } from 'src/plugins/data/public';
import { MetricsSourceConfigurationProperties } from '../../../../common/metrics_sources';
import { useTrackPageview } from '../../../../../observability/public';
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
import { DocumentTitle } from '../../../components/document_title';
import { NoData } from '../../../components/empty_states';
import { MetricsExplorerCharts } from './components/charts';
@ -18,16 +20,13 @@ import { MetricsExplorerToolbar } from './components/toolbar';
import { useMetricsExplorerState } from './hooks/use_metric_explorer_state';
import { useSavedViewContext } from '../../../containers/saved_view/saved_view';
import { MetricsPageTemplate } from '../page_template';
import { metricsExplorerTitle } from '../../../translations';
interface MetricsExplorerPageProps {
source: MetricsSourceConfigurationProperties;
derivedIndexPattern: IIndexPattern;
}
const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', {
defaultMessage: 'Metrics Explorer',
});
export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => {
const {
loading,
@ -66,6 +65,12 @@ export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExpl
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [loadData, shouldLoadDefault]);
useMetricsBreadcrumbs([
{
text: metricsExplorerTitle,
},
]);
return (
<EuiErrorBoundary>
<DocumentTitle

View file

@ -25,18 +25,23 @@ import { IndicesConfigurationPanel } from './indices_configuration_panel';
import { MLConfigurationPanel } from './ml_configuration_panel';
import { NameConfigurationPanel } from './name_configuration_panel';
import { useSourceConfigurationFormState } from './source_configuration_form_state';
import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs';
import { settingsTitle } from '../../../translations';
import { MetricsPageTemplate } from '../page_template';
interface SourceConfigurationSettingsProps {
shouldAllowEdit: boolean;
}
const settingsTitle = i18n.translate('xpack.infra.metrics.settingsTitle', {
defaultMessage: 'Settings',
});
export const SourceConfigurationSettings = ({
shouldAllowEdit,
}: SourceConfigurationSettingsProps) => {
useMetricsBreadcrumbs([
{
text: settingsTitle,
},
]);
const {
createSourceConfiguration,
source,

View file

@ -0,0 +1,47 @@
/*
* 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';
export const observabilityTitle = i18n.translate('xpack.infra.header.observabilityTitle', {
defaultMessage: 'Observability',
});
export const logsTitle = i18n.translate('xpack.infra.header.logsTitle', {
defaultMessage: 'Logs',
});
export const streamTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', {
defaultMessage: 'Stream',
});
export const anomaliesTitle = i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', {
defaultMessage: 'Anomalies',
});
export const logCategoriesTitle = i18n.translate(
'xpack.infra.logs.index.logCategoriesBetaBadgeTitle',
{
defaultMessage: 'Categories',
}
);
export const settingsTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', {
defaultMessage: 'Settings',
});
export const metricsTitle = i18n.translate('xpack.infra.header.infrastructureTitle', {
defaultMessage: 'Metrics',
});
export const inventoryTitle = i18n.translate('xpack.infra.metrics.inventoryPageTitle', {
defaultMessage: 'Inventory',
});
export const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', {
defaultMessage: 'Metrics Explorer',
});