[Logs / Metrics UI] Client side NP migration (#52867) (#53602)

This commit is contained in:
Kerry Gallagher 2019-12-19 18:09:42 +00:00 committed by GitHub
parent 870379fccb
commit 9d7917ce9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 768 additions and 1115 deletions

View file

@ -4,4 +4,51 @@
* you may not use this file except in compliance with the Elastic License.
*/
import './apps/kibana_app';
// NP_TODO: This app.ts layer is needed until we migrate 100% to the NP.
// This is so other plugins can import from our public/index file without trying to
// actually mount and run our application. Once in the NP this won't be an issue
// as the NP will look for an export named "plugin" and run that from the index file.
import { npStart } from 'ui/new_platform';
import { PluginInitializerContext } from 'kibana/public';
import chrome from 'ui/chrome';
// @ts-ignore
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
// @ts-ignore
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { plugin } from './new_platform_index';
const ROOT_ELEMENT_ID = 'react-infra-root';
export { ROOT_ELEMENT_ID };
const { core, plugins } = npStart;
const __LEGACY = {
uiModules,
uiRoutes,
timezoneProvider,
};
// This will be moved to core.application.register when the new platform
// migration is complete.
// @ts-ignore
chrome.setRootTemplate(`
<main
id="${ROOT_ELEMENT_ID}"
class="infReactRoot"
></main>
`);
const checkForRoot = () => {
return new Promise(resolve => {
const ready = !!document.getElementById(ROOT_ELEMENT_ID);
if (ready) {
resolve();
} else {
setTimeout(() => resolve(checkForRoot()), 10);
}
});
};
checkForRoot().then(() => {
plugin({} as PluginInitializerContext).start(core, plugins, __LEGACY);
});

View file

@ -6,16 +6,15 @@
import { createHashHistory } from 'history';
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { BehaviorSubject } from 'rxjs';
import { pluck } from 'rxjs/operators';
import { CoreStart } from 'kibana/public';
// TODO use theme provided from parentApp when kibana supports it
import { EuiErrorBoundary } from '@elastic/eui';
import { UICapabilitiesProvider } from 'ui/capabilities/react';
import { I18nContext } from 'ui/i18n';
import { npStart } from 'ui/new_platform';
import { EuiThemeProvider } from '../../../../common/eui_styled_components';
import { InfraFrontendLibs } from '../lib/lib';
import { PageRouter } from '../routes';
@ -27,12 +26,10 @@ import {
useUiSetting$,
KibanaContextProvider,
} from '../../../../../../src/plugins/kibana_react/public';
const { uiSettings } = npStart.core;
export async function startApp(libs: InfraFrontendLibs) {
import { ROOT_ELEMENT_ID } from '../app';
// NP_TODO: Type plugins
export async function startApp(libs: InfraFrontendLibs, core: CoreStart, plugins: any) {
const history = createHashHistory();
const libs$ = new BehaviorSubject(libs);
const store = createStore({
apolloClient: libs$.pipe(pluck('apolloClient')),
@ -43,31 +40,35 @@ export async function startApp(libs: InfraFrontendLibs) {
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
return (
<I18nContext>
<UICapabilitiesProvider>
<EuiErrorBoundary>
<ReduxStoreProvider store={store}>
<ReduxStateContextProvider>
<ApolloProvider client={libs.apolloClient}>
<ApolloClientContext.Provider value={libs.apolloClient}>
<EuiThemeProvider darkMode={darkMode}>
<HistoryContext.Provider value={history}>
<PageRouter history={history} />
</HistoryContext.Provider>
</EuiThemeProvider>
</ApolloClientContext.Provider>
</ApolloProvider>
</ReduxStateContextProvider>
</ReduxStoreProvider>
</EuiErrorBoundary>
</UICapabilitiesProvider>
</I18nContext>
<core.i18n.Context>
<EuiErrorBoundary>
<ReduxStoreProvider store={store}>
<ReduxStateContextProvider>
<ApolloProvider client={libs.apolloClient}>
<ApolloClientContext.Provider value={libs.apolloClient}>
<EuiThemeProvider darkMode={darkMode}>
<HistoryContext.Provider value={history}>
<PageRouter history={history} />
</HistoryContext.Provider>
</EuiThemeProvider>
</ApolloClientContext.Provider>
</ApolloProvider>
</ReduxStateContextProvider>
</ReduxStoreProvider>
</EuiErrorBoundary>
</core.i18n.Context>
);
};
libs.framework.render(
<KibanaContextProvider services={{ uiSettings }}>
const node = await document.getElementById(ROOT_ELEMENT_ID);
const App = (
<KibanaContextProvider services={{ ...core, ...plugins }}>
<InfraPluginRoot />
</KibanaContextProvider>
);
if (node) {
ReactDOM.render(App, node);
}
}

View file

@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from '../lib/compose/testing_compose';
import { startApp } from './start_app';
startApp(compose());

View file

@ -1,47 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import isEqual from 'lodash/fp/isEqual';
import React from 'react';
import { Badge } from 'ui/chrome/api/badge';
import { Breadcrumb } from 'ui/chrome/api/breadcrumbs';
interface ExternalHeaderProps {
breadcrumbs?: Breadcrumb[];
setBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void;
badge: Badge | undefined;
setBadge: (badge: Badge | undefined) => void;
}
export class ExternalHeader extends React.Component<ExternalHeaderProps> {
public componentDidMount() {
this.setBreadcrumbs();
this.setBadge();
}
public componentDidUpdate(prevProps: ExternalHeaderProps) {
if (!isEqual(this.props.breadcrumbs, prevProps.breadcrumbs)) {
this.setBreadcrumbs();
}
if (!isEqual(this.props.badge, prevProps.badge)) {
this.setBadge();
}
}
public render() {
return null;
}
private setBadge = () => {
this.props.setBadge(this.props.badge);
};
private setBreadcrumbs = () => {
this.props.setBreadcrumbs(this.props.breadcrumbs || []);
};
}

View file

@ -4,40 +4,51 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { useCallback, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { Breadcrumb } from 'ui/chrome/api/breadcrumbs';
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
import { ExternalHeader } from './external_header';
import { ChromeBreadcrumb } from 'src/core/public';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
interface HeaderProps {
breadcrumbs?: Breadcrumb[];
breadcrumbs?: ChromeBreadcrumb[];
readOnlyBadge?: boolean;
}
export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => (
<WithKibanaChrome>
{({ setBreadcrumbs, setBadge }) => (
<ExternalHeader
breadcrumbs={breadcrumbs}
setBreadcrumbs={setBreadcrumbs}
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
}
setBadge={setBadge}
/>
)}
</WithKibanaChrome>
);
export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => {
const chrome = useKibana().services.chrome;
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

@ -5,7 +5,7 @@
*/
import React, { useEffect } from 'react';
import chrome from 'ui/chrome';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
interface HelpCenterContentProps {
feedbackLink: string;
@ -13,8 +13,10 @@ interface HelpCenterContentProps {
}
export const HelpCenterContent: React.FC<HelpCenterContentProps> = ({ feedbackLink, appName }) => {
const chrome = useKibana().services.chrome;
useEffect(() => {
chrome.helpExtension.set({
return chrome?.setHelpExtension({
appName,
links: [
{
@ -23,7 +25,7 @@ export const HelpCenterContent: React.FC<HelpCenterContentProps> = ({ feedbackLi
},
],
});
}, [feedbackLink, appName]);
}, [feedbackLink, appName, chrome]);
return null;
};

View file

@ -8,10 +8,9 @@ import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { encode } from 'rison-node';
import chrome from 'ui/chrome';
import { QueryString } from 'ui/utils/query_string';
import url from 'url';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimeRange } from '../../../../common/http_api/shared/time_range';
export const AnalyzeInMlButton: React.FunctionComponent<{
@ -19,7 +18,11 @@ export const AnalyzeInMlButton: React.FunctionComponent<{
partition?: string;
timeRange: TimeRange;
}> = ({ jobId, partition, timeRange }) => {
const pathname = chrome.addBasePath('/app/ml');
const prependBasePath = useKibana().services.http?.basePath?.prepend;
if (!prependBasePath) {
return null;
}
const pathname = prependBasePath('/app/ml');
const buttonLabel = (
<FormattedMessage
id="xpack.infra.logs.analysis.analyzeInMlButtonLabel"

View file

@ -8,8 +8,7 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } f
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
import url from 'url';
import chrome from 'ui/chrome';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { InfraLogItem } from '../../../graphql/types';
import { useVisibilityState } from '../../../utils/use_visibility_state';
import { getTraceUrl } from '../../../../../apm/public/components/shared/Links/apm/ExternalLinks';
@ -19,11 +18,18 @@ const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid'];
export const LogEntryActionsMenu: React.FunctionComponent<{
logItem: InfraLogItem;
}> = ({ logItem }) => {
const prependBasePath = useKibana().services.http?.basePath?.prepend;
const { hide, isVisible, show } = useVisibilityState(false);
const uptimeLink = useMemo(() => getUptimeLink(logItem), [logItem]);
const uptimeLink = useMemo(() => {
const link = getUptimeLink(logItem);
return prependBasePath && link ? prependBasePath(link) : link;
}, [logItem, prependBasePath]);
const apmLink = useMemo(() => getAPMLink(logItem), [logItem]);
const apmLink = useMemo(() => {
const link = getAPMLink(logItem);
return prependBasePath && link ? prependBasePath(link) : link;
}, [logItem, prependBasePath]);
const menuItems = useMemo(
() => [
@ -56,7 +62,6 @@ export const LogEntryActionsMenu: React.FunctionComponent<{
);
const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]);
return (
<EuiPopover
anchorPosition="downRight"
@ -94,7 +99,7 @@ const getUptimeLink = (logItem: InfraLogItem) => {
}
return url.format({
pathname: chrome.addBasePath('/app/uptime'),
pathname: '/app/uptime',
hash: `/?search=(${searchExpressions.join(' OR ')})`,
});
};
@ -123,7 +128,7 @@ const getAPMLink = (logItem: InfraLogItem) => {
: { rangeFrom: 'now-1y', rangeTo: 'now' };
return url.format({
pathname: chrome.addBasePath('/app/apm'),
pathname: '/app/apm',
hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }),
});
};

View file

@ -17,8 +17,6 @@ import {
} from '@elastic/charts';
import { first, last } from 'lodash';
import moment from 'moment';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types';
import {
MetricsExplorerOptions,
@ -36,6 +34,7 @@ import { MetricsExplorerNoMetrics } from './no_metrics';
import { getChartTheme } from './helpers/get_chart_theme';
import { useKibanaUiSetting } from '../../utils/use_kibana_ui_setting';
import { calculateDomain } from './helpers/calculate_domain';
import { useKibana, useUiSetting } from '../../../../../../../src/plugins/kibana_react/public';
interface Props {
title?: string | null;
@ -48,132 +47,130 @@ interface Props {
source: SourceQuery.Query['source']['configuration'] | undefined;
timeRange: MetricsExplorerTimeOptions;
onTimeChange: (start: string, end: string) => void;
uiCapabilities: UICapabilities;
}
export const MetricsExplorerChart = injectUICapabilities(
({
source,
options,
chartOptions,
series,
title,
onFilter,
height = 200,
width = '100%',
timeRange,
onTimeChange,
uiCapabilities,
}: Props) => {
const { metrics } = options;
const [dateFormat] = useKibanaUiSetting('dateFormat');
const handleTimeChange = (from: number, to: number) => {
onTimeChange(moment(from).toISOString(), moment(to).toISOString());
};
const dateFormatter = useMemo(
() =>
series.rows.length > 0
? niceTimeFormatter([first(series.rows).timestamp, last(series.rows).timestamp])
: (value: number) => `${value}`,
[series.rows]
);
const tooltipProps = {
headerFormatter: useCallback(
(data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'),
[dateFormat]
),
};
const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]);
const dataDomain = calculateDomain(series, metrics, chartOptions.stack);
const domain =
chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero
? { ...dataDomain, min: 0 }
: dataDomain;
return (
<div style={{ padding: 24 }}>
{options.groupBy ? (
<EuiTitle size="xs">
<EuiFlexGroup alignItems="center">
<ChartTitle>
<EuiToolTip content={title}>
<span>{title}</span>
</EuiToolTip>
</ChartTitle>
<EuiFlexItem grow={false}>
<MetricsExplorerChartContextMenu
timeRange={timeRange}
options={options}
chartOptions={chartOptions}
series={series}
onFilter={onFilter}
source={source}
uiCapabilities={uiCapabilities}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiTitle>
) : (
<EuiFlexGroup justifyContent="flexEnd">
export const MetricsExplorerChart = ({
source,
options,
chartOptions,
series,
title,
onFilter,
height = 200,
width = '100%',
timeRange,
onTimeChange,
}: Props) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const isDarkMode = useUiSetting<boolean>('theme:darkMode');
const { metrics } = options;
const [dateFormat] = useKibanaUiSetting('dateFormat');
const handleTimeChange = (from: number, to: number) => {
onTimeChange(moment(from).toISOString(), moment(to).toISOString());
};
const dateFormatter = useMemo(
() =>
series.rows.length > 0
? niceTimeFormatter([first(series.rows).timestamp, last(series.rows).timestamp])
: (value: number) => `${value}`,
[series.rows]
);
const tooltipProps = {
headerFormatter: useCallback(
(data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'),
[dateFormat]
),
};
const yAxisFormater = useCallback(createFormatterForMetric(first(metrics)), [options]);
const dataDomain = calculateDomain(series, metrics, chartOptions.stack);
const domain =
chartOptions.yAxisMode === MetricsExplorerYAxisMode.fromZero
? { ...dataDomain, min: 0 }
: dataDomain;
return (
<div style={{ padding: 24 }}>
{options.groupBy ? (
<EuiTitle size="xs">
<EuiFlexGroup alignItems="center">
<ChartTitle>
<EuiToolTip content={title}>
<span>{title}</span>
</EuiToolTip>
</ChartTitle>
<EuiFlexItem grow={false}>
<MetricsExplorerChartContextMenu
timeRange={timeRange}
options={options}
chartOptions={chartOptions}
series={series}
onFilter={onFilter}
source={source}
timeRange={timeRange}
uiCapabilities={uiCapabilities}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiTitle>
) : (
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<MetricsExplorerChartContextMenu
options={options}
chartOptions={chartOptions}
series={series}
source={source}
timeRange={timeRange}
uiCapabilities={uiCapabilities}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
<div className="infrastructureChart" style={{ height, width }}>
{series.rows.length > 0 ? (
<Chart>
{metrics.map((metric, id) => (
<MetricExplorerSeriesChart
type={chartOptions.type}
key={id}
metric={metric}
id={id}
series={series}
stack={chartOptions.stack}
/>
))}
<Axis
id={getAxisId('timestamp')}
position={Position.Bottom}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('values')}
position={Position.Left}
tickFormat={yAxisFormater}
domain={domain}
/>
<Settings
tooltip={tooltipProps}
onBrushEnd={handleTimeChange}
theme={getChartTheme(isDarkMode)}
/>
</Chart>
) : options.metrics.length > 0 ? (
<MetricsExplorerEmptyChart />
) : (
<MetricsExplorerNoMetrics />
)}
<div className="infrastructureChart" style={{ height, width }}>
{series.rows.length > 0 ? (
<Chart>
{metrics.map((metric, id) => (
<MetricExplorerSeriesChart
type={chartOptions.type}
key={id}
metric={metric}
id={id}
series={series}
stack={chartOptions.stack}
/>
))}
<Axis
id={getAxisId('timestamp')}
position={Position.Bottom}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('values')}
position={Position.Left}
tickFormat={yAxisFormater}
domain={domain}
/>
<Settings
tooltip={tooltipProps}
onBrushEnd={handleTimeChange}
theme={getChartTheme()}
/>
</Chart>
) : options.metrics.length > 0 ? (
<MetricsExplorerEmptyChart />
) : (
<MetricsExplorerNoMetrics />
)}
</div>
</div>
);
}
);
</div>
);
};
const ChartTitle = euiStyled.div`
width: 100%
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
flex: 1 1 auto;
margin: 12px;
`;
width: 100%
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
flex: 1 1 auto;
margin: 12px;
`;

View file

@ -8,13 +8,13 @@ import React from 'react';
import { MetricsExplorerChartContextMenu, createNodeDetailLink } from './chart_context_menu';
import { mount } from 'enzyme';
import { options, source, timeRange, chartOptions } from '../../utils/fixtures/metrics_explorer';
import { UICapabilities } from 'ui/capabilities';
import { InfraNodeType } from '../../graphql/types';
import DateMath from '@elastic/datemath';
import { ReactWrapper } from 'enzyme';
import { Capabilities } from 'src/core/public';
const series = { id: 'exmaple-01', rows: [], columns: [] };
const uiCapabilities: UICapabilities = {
const uiCapabilities: Capabilities = {
navLinks: { show: false },
management: { fake: { show: false } },
catalogue: { show: false },

View file

@ -12,8 +12,8 @@ import {
EuiContextMenuPanelDescriptor,
EuiPopover,
} from '@elastic/eui';
import { UICapabilities } from 'ui/capabilities';
import DateMath from '@elastic/datemath';
import { Capabilities } from 'src/core/public';
import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types';
import {
MetricsExplorerOptions,
@ -31,7 +31,7 @@ interface Props {
series: MetricsExplorerSeries;
source?: SourceConfiguration;
timeRange: MetricsExplorerTimeOptions;
uiCapabilities: UICapabilities;
uiCapabilities?: Capabilities;
chartOptions: MetricsExplorerChartOptions;
}
@ -118,7 +118,7 @@ export const MetricsExplorerChartContextMenu = ({
]
: [];
const openInVisualize = uiCapabilities.visualize.show
const openInVisualize = uiCapabilities?.visualize?.show
? [
{
name: i18n.translate('xpack.infra.metricsExplorer.openInTSVB', {

View file

@ -8,14 +8,14 @@ import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback } from 'react';
import { FieldType } from 'ui/index_patterns';
import { IFieldType } from 'src/plugins/data/public';
import { MetricsExplorerOptions } from '../../containers/metrics_explorer/use_metrics_explorer_options';
import { isDisplayable } from '../../utils/is_displayable';
interface Props {
options: MetricsExplorerOptions;
onChange: (groupBy: string | null) => void;
fields: FieldType[];
fields: IFieldType[];
}
export const MetricsExplorerGroupBy = ({ options, onChange, fields }: Props) => {

View file

@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
export function getChartTheme(): Theme {
const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode');
export function getChartTheme(isDarkMode: boolean): Theme {
return isDarkMode ? DARK_THEME : LIGHT_THEME;
}

View file

@ -8,7 +8,7 @@ import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useState } from 'react';
import { FieldType } from 'ui/index_patterns';
import { IFieldType } from 'src/plugins/data/public';
import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette';
import { MetricsExplorerMetric } from '../../../server/routes/metrics_explorer/types';
import { MetricsExplorerOptions } from '../../containers/metrics_explorer/use_metrics_explorer_options';
@ -18,7 +18,7 @@ interface Props {
autoFocus?: boolean;
options: MetricsExplorerOptions;
onChange: (metrics: MetricsExplorerMetric[]) => void;
fields: FieldType[];
fields: IFieldType[];
}
interface SelectedOption {

View file

@ -7,11 +7,12 @@
import { EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui';
import React, { useCallback, useState, useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { toastNotifications } from 'ui/notify';
import { i18n } from '@kbn/i18n';
import { useSavedView } from '../../hooks/use_saved_view';
import { SavedViewCreateModal } from './create_modal';
import { SavedViewListFlyout } from './view_list_flyout';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
interface Props<ViewState> {
viewType: string;
viewState: ViewState;
@ -20,6 +21,7 @@ interface Props<ViewState> {
}
export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
const kibana = useKibana();
const {
views,
saveView,
@ -77,11 +79,11 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
useEffect(() => {
if (errorOnCreate) {
toastNotifications.addWarning(getErrorToast('create', errorOnCreate)!);
kibana.notifications.toasts.warning(getErrorToast('create', errorOnCreate)!);
} else if (errorOnFind) {
toastNotifications.addWarning(getErrorToast('find', errorOnFind)!);
kibana.notifications.toasts.warning(getErrorToast('find', errorOnFind)!);
}
}, [errorOnCreate, errorOnFind]);
}, [errorOnCreate, errorOnFind, kibana]);
return (
<>
@ -119,6 +121,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
const getErrorToast = (type: 'create' | 'find', msg?: string) => {
if (type === 'create') {
return {
toastLifeTimeMs: 3000,
title:
msg ||
i18n.translate('xpack.infra.savedView.errorOnCreate.title', {
@ -127,6 +130,7 @@ const getErrorToast = (type: 'create' | 'find', msg?: string) => {
};
} else if (type === 'find') {
return {
toastLifeTimeMs: 3000,
title:
msg ||
i18n.translate('xpack.infra.savedView.findError.title', {

View file

@ -6,12 +6,12 @@
import { EuiButton, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FieldType } from 'ui/index_patterns';
import { IFieldType } from 'src/plugins/data/public';
interface Props {
onSubmit: (field: string) => void;
fields: FieldType[];
fields: IFieldType[];
}
interface SelectedOption {

View file

@ -13,13 +13,12 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { InfraNodeType } from '../../graphql/types';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib';
import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to';
import { createUptimeLink } from './lib/create_uptime_link';
import { findInventoryModel } from '../../../common/inventory_models';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
interface Props {
options: InfraWaffleMapOptions;
@ -29,102 +28,99 @@ interface Props {
nodeType: InfraNodeType;
isPopoverOpen: boolean;
closePopover: () => void;
uiCapabilities: UICapabilities;
popoverPosition: EuiPopoverProps['anchorPosition'];
}
export const NodeContextMenu = injectUICapabilities(
({
options,
currentTime,
children,
node,
isPopoverOpen,
closePopover,
nodeType,
uiCapabilities,
popoverPosition,
}: Props) => {
const inventoryModel = findInventoryModel(nodeType);
// Due to the changing nature of the fields between APM and this UI,
// We need to have some exceptions until 7.0 & ECS is finalized. Reference
// #26620 for the details for these fields.
// TODO: This is tech debt, remove it after 7.0 & ECS migration.
const apmField = nodeType === InfraNodeType.host ? 'host.hostname' : inventoryModel.fields.id;
export const NodeContextMenu = ({
options,
currentTime,
children,
node,
isPopoverOpen,
closePopover,
nodeType,
popoverPosition,
}: Props) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const inventoryModel = findInventoryModel(nodeType);
// Due to the changing nature of the fields between APM and this UI,
// We need to have some exceptions until 7.0 & ECS is finalized. Reference
// #26620 for the details for these fields.
// TODO: This is tech debt, remove it after 7.0 & ECS migration.
const apmField = nodeType === InfraNodeType.host ? 'host.hostname' : inventoryModel.fields.id;
const nodeLogsMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', {
defaultMessage: 'View logs',
}),
href: getNodeLogsUrl({
nodeType,
nodeId: node.id,
time: currentTime,
}),
'data-test-subj': 'viewLogsContextMenuItem',
};
const nodeLogsMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', {
defaultMessage: 'View logs',
}),
href: getNodeLogsUrl({
nodeType,
nodeId: node.id,
time: currentTime,
}),
'data-test-subj': 'viewLogsContextMenuItem',
};
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
const nodeDetailMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', {
defaultMessage: 'View metrics',
}),
href: getNodeDetailUrl({
nodeType,
nodeId: node.id,
from: nodeDetailFrom,
to: currentTime,
}),
};
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
const nodeDetailMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', {
defaultMessage: 'View metrics',
}),
href: getNodeDetailUrl({
nodeType,
nodeId: node.id,
from: nodeDetailFrom,
to: currentTime,
}),
};
const apmTracesMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', {
defaultMessage: 'View APM traces',
}),
href: `../app/apm#/traces?_g=()&kuery=${apmField}:"${node.id}"`,
'data-test-subj': 'viewApmTracesContextMenuItem',
};
const apmTracesMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', {
defaultMessage: 'View APM traces',
}),
href: `../app/apm#/traces?_g=()&kuery=${apmField}:"${node.id}"`,
'data-test-subj': 'viewApmTracesContextMenuItem',
};
const uptimeMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', {
defaultMessage: 'View in Uptime',
}),
href: createUptimeLink(options, nodeType, node),
};
const uptimeMenuItem = {
name: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', {
defaultMessage: 'View in Uptime',
}),
href: createUptimeLink(options, nodeType, node),
};
const showDetail = inventoryModel.crosslinkSupport.details;
const showLogsLink =
inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities.logs.show;
const showAPMTraceLink =
inventoryModel.crosslinkSupport.apm && uiCapabilities.apm && uiCapabilities.apm.show;
const showUptimeLink =
inventoryModel.crosslinkSupport.uptime &&
([InfraNodeType.pod, InfraNodeType.container].includes(nodeType) || node.ip);
const showDetail = inventoryModel.crosslinkSupport.details;
const showLogsLink =
inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities?.logs?.show;
const showAPMTraceLink =
inventoryModel.crosslinkSupport.apm && uiCapabilities?.apm && uiCapabilities?.apm.show;
const showUptimeLink =
inventoryModel.crosslinkSupport.uptime &&
([InfraNodeType.pod, InfraNodeType.container].includes(nodeType) || node.ip);
const items = [
...(showLogsLink ? [nodeLogsMenuItem] : []),
...(showDetail ? [nodeDetailMenuItem] : []),
...(showAPMTraceLink ? [apmTracesMenuItem] : []),
...(showUptimeLink ? [uptimeMenuItem] : []),
];
const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, title: '', items }];
const items = [
...(showLogsLink ? [nodeLogsMenuItem] : []),
...(showDetail ? [nodeDetailMenuItem] : []),
...(showAPMTraceLink ? [apmTracesMenuItem] : []),
...(showUptimeLink ? [uptimeMenuItem] : []),
];
const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, title: '', items }];
// If there is nothing to show then we need to return the child as is
if (items.length === 0) {
return <>{children}</>;
}
return (
<EuiPopover
closePopover={closePopover}
id={`${node.pathId}-popover`}
isOpen={isPopoverOpen}
button={children}
panelPaddingSize="none"
anchorPosition={popoverPosition}
>
<EuiContextMenu initialPanelId={0} panels={panels} data-test-subj="nodeContextMenu" />
</EuiPopover>
);
// If there is nothing to show then we need to return the child as is
if (items.length === 0) {
return <>{children}</>;
}
);
return (
<EuiPopover
closePopover={closePopover}
id={`${node.pathId}-popover`}
isOpen={isPopoverOpen}
button={children}
panelPaddingSize="none"
anchorPosition={popoverPosition}
>
<EuiContextMenu initialPanelId={0} panels={panels} data-test-subj="nodeContextMenu" />
</EuiPopover>
);
};

View file

@ -16,7 +16,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { FieldType } from 'ui/index_patterns';
import { IFieldType } from 'src/plugins/data/public';
import { InfraNodeType, InfraSnapshotGroupbyInput } from '../../graphql/types';
import { InfraGroupByOptions } from '../../lib/lib';
import { CustomFieldPanel } from './custom_field_panel';
@ -28,7 +28,7 @@ interface Props {
groupBy: InfraSnapshotGroupbyInput[];
onChange: (groupBy: InfraSnapshotGroupbyInput[]) => void;
onChangeCustomOptions: (options: InfraGroupByOptions[]) => void;
fields: FieldType[];
fields: IFieldType[];
customOptions: InfraGroupByOptions[];
}

View file

@ -8,7 +8,7 @@ import * as rt from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import { getDatafeedId, getJobId } from '../../../../../common/log_analysis';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
@ -19,9 +19,8 @@ export const callDeleteJobs = async <JobType extends string>(
jobTypes: JobType[]
) => {
// NOTE: Deleting the jobs via this API will delete the datafeeds at the same time
const deleteJobsResponse = await kfetch({
const deleteJobsResponse = await npStart.core.http.fetch('/api/ml/jobs/delete_jobs', {
method: 'POST',
pathname: '/api/ml/jobs/delete_jobs',
body: JSON.stringify(
deleteJobsRequestPayloadRT.encode({
jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)),
@ -36,10 +35,9 @@ export const callDeleteJobs = async <JobType extends string>(
};
export const callGetJobDeletionTasks = async () => {
const jobDeletionTasksResponse = await kfetch({
method: 'GET',
pathname: '/api/ml/jobs/deleting_jobs_tasks',
});
const jobDeletionTasksResponse = await npStart.core.http.fetch(
'/api/ml/jobs/deleting_jobs_tasks'
);
return pipe(
getJobDeletionTasksResponsePayloadRT.decode(jobDeletionTasksResponse),
@ -53,9 +51,8 @@ export const callStopDatafeeds = async <JobType extends string>(
jobTypes: JobType[]
) => {
// Stop datafeed due to https://github.com/elastic/kibana/issues/44652
const stopDatafeedResponse = await kfetch({
const stopDatafeedResponse = await npStart.core.http.fetch('/api/ml/jobs/stop_datafeeds', {
method: 'POST',
pathname: '/api/ml/jobs/stop_datafeeds',
body: JSON.stringify(
stopDatafeedsRequestPayloadRT.encode({
datafeedIds: jobTypes.map(jobType => getDatafeedId(spaceId, sourceId, jobType)),

View file

@ -8,8 +8,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import * as rt from 'io-ts';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import { jobCustomSettingsRT } from './ml_api_types';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import { getJobId } from '../../../../../common/log_analysis';
@ -19,9 +18,8 @@ export const callJobsSummaryAPI = async <JobType extends string>(
sourceId: string,
jobTypes: JobType[]
) => {
const response = await kfetch({
const response = await npStart.core.http.fetch('/api/ml/jobs/jobs_summary', {
method: 'POST',
pathname: '/api/ml/jobs/jobs_summary',
body: JSON.stringify(
fetchJobStatusRequestPayloadRT.encode({
jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)),

View file

@ -8,15 +8,13 @@ import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import * as rt from 'io-ts';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import { jobCustomSettingsRT } from './ml_api_types';
export const callGetMlModuleAPI = async (moduleId: string) => {
const response = await kfetch({
const response = await npStart.core.http.fetch(`/api/ml/modules/get_module/${moduleId}`, {
method: 'GET',
pathname: `/api/ml/modules/get_module/${moduleId}`,
});
return pipe(

View file

@ -8,8 +8,7 @@ import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import * as rt from 'io-ts';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import { getJobIdPrefix } from '../../../../../common/log_analysis';
@ -23,9 +22,8 @@ export const callSetupMlModuleAPI = async (
jobOverrides: SetupMlModuleJobOverrides[] = [],
datafeedOverrides: SetupMlModuleDatafeedOverrides[] = []
) => {
const response = await kfetch({
const response = await npStart.core.http.fetch(`/api/ml/modules/setup/${moduleId}`, {
method: 'POST',
pathname: `/api/ml/modules/setup/${moduleId}`,
body: JSON.stringify(
setupMlModuleRequestPayloadRT.encode({
start,

View file

@ -7,8 +7,7 @@
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import {
LOG_ANALYSIS_VALIDATE_INDICES_PATH,
ValidationIndicesFieldSpecification,
@ -22,9 +21,8 @@ export const callValidateIndicesAPI = async (
indices: string[],
fields: ValidationIndicesFieldSpecification[]
) => {
const response = await kfetch({
const response = await npStart.core.http.fetch(LOG_ANALYSIS_VALIDATE_INDICES_PATH, {
method: 'POST',
pathname: LOG_ANALYSIS_VALIDATE_INDICES_PATH,
body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })),
});

View file

@ -6,8 +6,7 @@
import createContainer from 'constate';
import { useMemo, useState, useEffect } from 'react';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
@ -27,10 +26,7 @@ export const useLogAnalysisCapabilities = () => {
{
cancelPreviousOn: 'resolution',
createPromise: async () => {
const rawResponse = await kfetch({
method: 'GET',
pathname: '/api/ml/ml_capabilities',
});
const rawResponse = await npStart.core.http.fetch('/api/ml/ml_capabilities');
return pipe(
getMlCapabilitiesResponsePayloadRT.decode(rawResponse),

View file

@ -1,42 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import chrome from 'ui/chrome';
import { Badge } from 'ui/chrome/api/badge';
import { Breadcrumb } from 'ui/chrome/api/breadcrumbs';
import { RendererFunction } from '../utils/typed_react';
interface WithKibanaChromeProps {
children: RendererFunction<
{
setBreadcrumbs: (newBreadcrumbs: Breadcrumb[]) => void;
setBadge: (badge: Badge | undefined) => void;
} & WithKibanaChromeState
>;
}
interface WithKibanaChromeState {
basePath: string;
}
export class WithKibanaChrome extends React.Component<
WithKibanaChromeProps,
WithKibanaChromeState
> {
public state: WithKibanaChromeState = {
basePath: chrome.getBasePath(),
};
public render() {
return this.props.children({
...this.state,
setBreadcrumbs: chrome.breadcrumbs.set,
setBadge: chrome.badge.set,
});
}
}

View file

@ -5,57 +5,69 @@
*/
import React, { useMemo, useState } from 'react';
import { kfetch } from 'ui/kfetch';
import { toastNotifications } from 'ui/notify';
import { IHttpFetchError } from 'src/core/public';
import { i18n } from '@kbn/i18n';
import { KFetchError } from 'ui/kfetch/kfetch_error';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
import { useTrackedPromise } from '../utils/use_tracked_promise';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
export function useHTTPRequest<Response>(
pathname: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD',
body?: string,
decode: (response: any) => Response = response => response
) {
const kibana = useKibana();
const fetch = kibana.services.http?.fetch;
const toasts = kibana.notifications.toasts;
const [response, setResponse] = useState<Response | null>(null);
const [error, setError] = useState<KFetchError | null>(null);
const [error, setError] = useState<IHttpFetchError | null>(null);
const [request, makeRequest] = useTrackedPromise(
{
cancelPreviousOn: 'resolution',
createPromise: () =>
kfetch({
createPromise: () => {
if (!fetch) {
throw new Error('HTTP service is unavailable');
}
return fetch(pathname, {
method,
pathname,
body,
}),
});
},
onResolve: resp => setResponse(decode(resp)),
onReject: (e: unknown) => {
const err = e as KFetchError;
const err = e as IHttpFetchError;
setError(err);
toastNotifications.addWarning({
toasts.warning({
toastLifeTimeMs: 3000,
title: i18n.translate('xpack.infra.useHTTPRequest.error.title', {
defaultMessage: `Error while fetching resource`,
}),
text: toMountPoint(
body: (
<div>
<h5>
{i18n.translate('xpack.infra.useHTTPRequest.error.status', {
defaultMessage: `Error`,
})}
</h5>
{err.res?.statusText} ({err.res?.status})
<h5>
{i18n.translate('xpack.infra.useHTTPRequest.error.url', {
defaultMessage: `URL`,
})}
</h5>
{err.res?.url}
{err.response ? (
<>
<h5>
{i18n.translate('xpack.infra.useHTTPRequest.error.status', {
defaultMessage: `Error`,
})}
</h5>
{err.response?.statusText} ({err.response?.status})
<h5>
{i18n.translate('xpack.infra.useHTTPRequest.error.url', {
defaultMessage: `URL`,
})}
</h5>
{err.response?.url}
</>
) : (
<h5>{err.message}</h5>
)}
</div>
),
});
},
},
[pathname, body, method]
[pathname, body, method, fetch, toasts]
);
const loading = useMemo(() => {

View file

@ -4,4 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
// NP_NOTE: Whilst we are in the transition period of the NP migration, this index file
// is exclusively for our static code exports that other plugins (e.g. APM) use.
// When we switch over to the real NP, and an export of "plugin" is expected and called,
// we can do away with the middle "app.ts" layer. The "app.ts" layer is needed for now,
// and needs to be situated differently to this index file, so that our code for setting the root template
// and attempting to start the app doesn't try to run just because another plugin is importing from this file.
export { useTrackPageview } from './hooks/use_track_metric';

View file

@ -1,187 +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;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable max-classes-per-file */
import { IModule, IScope } from 'angular';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIRoutes as KibanaUIRoutes } from 'ui/routes';
import {
InfraBufferedKibanaServiceCall,
InfraFrameworkAdapter,
InfraKibanaAdapterServiceRefs,
InfraKibanaUIConfig,
InfraTimezoneProvider,
InfraUiKibanaAdapterScope,
} from '../../lib';
const ROOT_ELEMENT_ID = 'react-infra-root';
const BREADCRUMBS_ELEMENT_ID = 'react-infra-breadcrumbs';
export class KibanaFramework implements InfraFrameworkAdapter {
public appState: object;
public kbnVersion?: string;
public timezone?: string;
private adapterService: KibanaAdapterServiceProvider;
private timezoneProvider: InfraTimezoneProvider;
private rootComponent: React.ReactElement<any> | null = null;
private breadcrumbsComponent: React.ReactElement<any> | null = null;
constructor(
uiModule: IModule,
uiRoutes: KibanaUIRoutes,
timezoneProvider: InfraTimezoneProvider
) {
this.adapterService = new KibanaAdapterServiceProvider();
this.timezoneProvider = timezoneProvider;
this.appState = {};
this.register(uiModule, uiRoutes);
}
public setUISettings = (key: string, value: any) => {
this.adapterService.callOrBuffer(({ config }) => {
config.set(key, value);
});
};
public render = (component: React.ReactElement<any>) => {
this.adapterService.callOrBuffer(() => (this.rootComponent = component));
};
public renderBreadcrumbs = (component: React.ReactElement<any>) => {
this.adapterService.callOrBuffer(() => (this.breadcrumbsComponent = component));
};
private register = (adapterModule: IModule, uiRoutes: KibanaUIRoutes) => {
adapterModule.provider('kibanaAdapter', this.adapterService);
adapterModule.directive('infraUiKibanaAdapter', () => ({
controller: ($scope: InfraUiKibanaAdapterScope, $element: JQLite) => ({
$onDestroy: () => {
const targetRootElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`);
const targetBreadcrumbsElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`);
if (targetRootElement) {
ReactDOM.unmountComponentAtNode(targetRootElement);
}
if (targetBreadcrumbsElement) {
ReactDOM.unmountComponentAtNode(targetBreadcrumbsElement);
}
},
$onInit: () => {
$scope.topNavMenu = [];
},
$postLink: () => {
$scope.$watchGroup(
[
() => this.breadcrumbsComponent,
() => $element[0].querySelector(`#${BREADCRUMBS_ELEMENT_ID}`),
],
([breadcrumbsComponent, targetElement]) => {
if (!targetElement) {
return;
}
if (breadcrumbsComponent) {
ReactDOM.render(breadcrumbsComponent, targetElement);
} else {
ReactDOM.unmountComponentAtNode(targetElement);
}
}
);
$scope.$watchGroup(
[() => this.rootComponent, () => $element[0].querySelector(`#${ROOT_ELEMENT_ID}`)],
([rootComponent, targetElement]) => {
if (!targetElement) {
return;
}
if (rootComponent) {
ReactDOM.render(rootComponent, targetElement);
} else {
ReactDOM.unmountComponentAtNode(targetElement);
}
}
);
},
}),
scope: true,
template: `
<main
id="${ROOT_ELEMENT_ID}"
class="infReactRoot"
></main>
`,
}));
adapterModule.run(
(
config: InfraKibanaUIConfig,
kbnVersion: string,
Private: <Provider>(provider: Provider) => Provider,
// @ts-ignore: inject kibanaAdapter to force eager instatiation
kibanaAdapter: any
) => {
this.timezone = Private(this.timezoneProvider)();
this.kbnVersion = kbnVersion;
}
);
uiRoutes.enable();
uiRoutes.otherwise({
reloadOnSearch: false,
template:
'<infra-ui-kibana-adapter style="display: flex; align-items: stretch; flex: 1 0 0%;"></infra-ui-kibana-adapter>',
});
};
}
class KibanaAdapterServiceProvider {
public serviceRefs: InfraKibanaAdapterServiceRefs | null = null;
public bufferedCalls: Array<InfraBufferedKibanaServiceCall<InfraKibanaAdapterServiceRefs>> = [];
public $get($rootScope: IScope, config: InfraKibanaUIConfig) {
this.serviceRefs = {
config,
rootScope: $rootScope,
};
this.applyBufferedCalls(this.bufferedCalls);
return this;
}
public callOrBuffer(serviceCall: (serviceRefs: InfraKibanaAdapterServiceRefs) => void) {
if (this.serviceRefs !== null) {
this.applyBufferedCalls([serviceCall]);
} else {
this.bufferedCalls.push(serviceCall);
}
}
public applyBufferedCalls(
bufferedCalls: Array<InfraBufferedKibanaServiceCall<InfraKibanaAdapterServiceRefs>>
) {
if (!this.serviceRefs) {
return;
}
this.serviceRefs.rootScope.$apply(() => {
bufferedCalls.forEach(serviceCall => {
if (!this.serviceRefs) {
return;
}
return serviceCall(this.serviceRefs);
});
});
}
}

View file

@ -1,27 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import { InfraFrameworkAdapter } from '../../lib';
export class InfraTestingFrameworkAdapter implements InfraFrameworkAdapter {
public appState?: object;
public kbnVersion?: string;
public timezone?: string;
constructor() {
this.appState = {};
}
public render() {
return;
}
public renderBreadcrumbs() {
return;
}
public setUISettings() {
return;
}
}

View file

@ -16,13 +16,13 @@ import {
export class InfraKibanaObservableApiAdapter implements InfraObservableApi {
private basePath: string;
private defaultHeaders: {
[headerName: string]: string;
[headerName: string]: boolean | string;
};
constructor({ basePath, xsrfToken }: { basePath: string; xsrfToken: string }) {
constructor({ basePath }: { basePath: string }) {
this.basePath = basePath;
this.defaultHeaders = {
'kbn-version': xsrfToken,
'kbn-xsrf': true,
};
}

View file

@ -1,68 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import 'ui/autoload/all';
// @ts-ignore: path dynamic for kibana
import chrome from 'ui/chrome';
// @ts-ignore: path dynamic for kibana
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { withClientState } from 'apollo-link-state';
import { InfraFrontendLibs } from '../lib';
import introspectionQueryResultData from '../../graphql/introspection.json';
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
export function compose(): InfraFrontendLibs {
const cache = new InMemoryCache({
addTypename: false,
fragmentMatcher: new IntrospectionFragmentMatcher({
introspectionQueryResultData,
}),
});
const observableApi = new InfraKibanaObservableApiAdapter({
basePath: chrome.getBasePath(),
xsrfToken: chrome.getXsrfToken(),
});
const graphQLOptions = {
cache,
link: ApolloLink.from([
withClientState({
cache,
resolvers: {},
}),
new HttpLink({
credentials: 'same-origin',
headers: {
'kbn-xsrf': chrome.getXsrfToken(),
},
uri: `${chrome.getBasePath()}/api/infra/graphql`,
}),
]),
};
const apolloClient = new ApolloClient(graphQLOptions);
const infraModule = uiModules.get('app/infa');
const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider);
const libs: InfraFrontendLibs = {
apolloClient,
framework,
observableApi,
};
return libs;
}

View file

@ -1,59 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import 'ui/autoload/all';
// @ts-ignore: path dynamic for kibana
import chrome from 'ui/chrome';
// @ts-ignore: path dynamic for kibana
import { uiModules } from 'ui/modules';
import uiRoutes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { InMemoryCache } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { SchemaLink } from 'apollo-link-schema';
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
import { InfraFrontendLibs } from '../lib';
export function compose(): InfraFrontendLibs {
const infraModule = uiModules.get('app/infa');
const observableApi = new InfraKibanaObservableApiAdapter({
basePath: chrome.getBasePath(),
xsrfToken: chrome.getXsrfToken(),
});
const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider);
const typeDefs = `
Query {}
`;
const mocks = {
Mutation: () => undefined,
Query: () => undefined,
};
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({
mocks,
schema,
});
const cache = new InMemoryCache((window as any).__APOLLO_CLIENT__);
const apolloClient = new ApolloClient({
cache,
link: new SchemaLink({ schema }),
});
const libs: InfraFrontendLibs = {
apolloClient,
framework,
observableApi,
};
return libs;
}

View file

@ -20,7 +20,6 @@ import {
} from '../graphql/types';
export interface InfraFrontendLibs {
framework: InfraFrameworkAdapter;
apolloClient: InfraApolloClient;
observableApi: InfraObservableApi;
}

View file

@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { compose } from '../lib/compose/kibana_compose';
import { startApp } from './start_app';
startApp(compose());
import { PluginInitializerContext } from 'kibana/public';
import { Plugin } from './new_platform_plugin';
export function plugin(context: PluginInitializerContext) {
return new Plugin(context);
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { CoreStart, PluginInitializerContext } from 'kibana/public';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { withClientState } from 'apollo-link-state';
import { startApp } from './apps/start_app';
import { InfraFrontendLibs } from './lib/lib';
import introspectionQueryResultData from './graphql/introspection.json';
import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api';
type ClientPlugins = any;
type LegacyDeps = any;
export class Plugin {
constructor(context: PluginInitializerContext) {}
start(core: CoreStart, plugins: ClientPlugins, __LEGACY: LegacyDeps) {
startApp(this.composeLibs(core, plugins, __LEGACY), core, plugins);
}
composeLibs(core: CoreStart, plugins: ClientPlugins, legacy: LegacyDeps) {
const cache = new InMemoryCache({
addTypename: false,
fragmentMatcher: new IntrospectionFragmentMatcher({
introspectionQueryResultData,
}),
});
const observableApi = new InfraKibanaObservableApiAdapter({
basePath: core.http.basePath.get(),
});
const graphQLOptions = {
cache,
link: ApolloLink.from([
withClientState({
cache,
resolvers: {},
}),
new HttpLink({
credentials: 'same-origin',
headers: {
'kbn-xsrf': true,
},
uri: `${core.http.basePath.get()}/api/infra/graphql`,
}),
]),
};
const apolloClient = new ApolloClient(graphQLOptions);
const libs: InfraFrontendLibs = {
apolloClient,
observableApi,
};
return libs;
}
}

View file

@ -8,8 +8,6 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
@ -25,13 +23,11 @@ import { SnapshotPage } from './snapshot';
import { SettingsPage } from '../shared/settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
interface InfrastructurePageProps extends RouteComponentProps {
uiCapabilities: UICapabilities;
}
export const InfrastructurePage = injectUICapabilities(
({ match, uiCapabilities }: InfrastructurePageProps) => (
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
return (
<Source.Provider sourceId="default">
<ColumnarPage>
<DocumentTitle
@ -55,7 +51,7 @@ export const InfrastructurePage = injectUICapabilities(
}),
},
]}
readOnlyBadge={!uiCapabilities.infrastructure.save}
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
/>
<AppNavigation
@ -114,5 +110,5 @@ export const InfrastructurePage = injectUICapabilities(
</Switch>
</ColumnarPage>
</Source.Provider>
)
);
);
};

View file

@ -8,8 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useContext } from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { SnapshotPageContent } from './page_content';
import { SnapshotToolbar } from './toolbar';
@ -27,15 +25,11 @@ import { Source } from '../../../containers/source';
import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters';
import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options';
import { WithWaffleTimeUrlState } from '../../../containers/waffle/with_waffle_time';
import { WithKibanaChrome } from '../../../containers/with_kibana_chrome';
import { useTrackPageview } from '../../../hooks/use_track_metric';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
interface SnapshotPageProps {
uiCapabilities: UICapabilities;
}
export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => {
const { uiCapabilities } = props;
export const SnapshotPage = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
const {
createDerivedIndexPattern,
hasFailedLoadingSource,
@ -44,7 +38,7 @@ export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => {
loadSource,
metricIndicesExist,
} = useContext(Source.Context);
const basePath = useKibana().services.http?.basePath || '';
useTrackPageview({ app: 'infra_metrics', path: 'inventory' });
useTrackPageview({ app: 'infra_metrics', path: 'inventory', delay: 15000 });
@ -73,49 +67,44 @@ export const SnapshotPage = injectUICapabilities((props: SnapshotPageProps) => {
) : hasFailedLoadingSource ? (
<SourceErrorPage errorMessage={loadSourceFailureMessage || ''} retry={loadSource} />
) : (
<WithKibanaChrome>
{({ basePath }) => (
<NoIndices
title={i18n.translate('xpack.infra.homePage.noMetricsIndicesTitle', {
defaultMessage: "Looks like you don't have any metrics indices.",
})}
message={i18n.translate('xpack.infra.homePage.noMetricsIndicesDescription', {
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
color="primary"
fill
data-test-subj="infrastructureViewSetupInstructionsButton"
>
{i18n.translate(
'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel',
{ defaultMessage: 'View setup instructions' }
)}
</EuiButton>
</EuiFlexItem>
{uiCapabilities.infrastructure.configureSource ? (
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
{i18n.translate('xpack.infra.configureSourceActionLabel', {
defaultMessage: 'Change source configuration',
})}
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
}
data-test-subj="noMetricsIndicesPrompt"
/>
)}
</WithKibanaChrome>
<NoIndices
title={i18n.translate('xpack.infra.homePage.noMetricsIndicesTitle', {
defaultMessage: "Looks like you don't have any metrics indices.",
})}
message={i18n.translate('xpack.infra.homePage.noMetricsIndicesDescription', {
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
color="primary"
fill
data-test-subj="infrastructureViewSetupInstructionsButton"
>
{i18n.translate('xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel', {
defaultMessage: 'View setup instructions',
})}
</EuiButton>
</EuiFlexItem>
{uiCapabilities?.infrastructure?.configureSource ? (
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
{i18n.translate('xpack.infra.configureSourceActionLabel', {
defaultMessage: 'Change source configuration',
})}
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
}
data-test-subj="noMetricsIndicesPrompt"
/>
)}
</ColumnarPage>
);
});
};

View file

@ -7,8 +7,6 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
@ -27,14 +25,12 @@ import {
} from '../../containers/logs/log_analysis';
import { useSourceId } from '../../containers/source_id';
import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params';
import { useKibana } from '../../../../../../..//src/plugins/kibana_react/public';
import { LogEntryCategoriesPage } from './log_entry_categories';
import { LogEntryRatePage } from './log_entry_rate';
interface LogsPageProps extends RouteComponentProps {
uiCapabilities: UICapabilities;
}
export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPageProps) => {
export const LogsPage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const [sourceId] = useSourceId();
const source = useSource({ sourceId });
const logAnalysisCapabilities = useLogAnalysisCapabilities();
@ -83,7 +79,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag
text: pageTitle,
},
]}
readOnlyBadge={!uiCapabilities.logs.save}
readOnlyBadge={!uiCapabilities?.logs?.save}
/>
{source.isLoadingSource ||
(!source.isLoadingSource &&
@ -124,7 +120,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag
</LogAnalysisCapabilities.Context.Provider>
</Source.Context.Provider>
);
});
};
const pageTitle = i18n.translate('xpack.infra.header.logsTitle', {
defaultMessage: 'Logs',

View file

@ -7,8 +7,7 @@
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { kfetch } from 'ui/kfetch';
import { npStart } from 'ui/new_platform';
import {
getLogEntryRateRequestPayloadRT,
getLogEntryRateSuccessReponsePayloadRT,
@ -22,9 +21,8 @@ export const callGetLogEntryRateAPI = async (
endTime: number,
bucketDuration: number
) => {
const response = await kfetch({
const response = await npStart.core.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH, {
method: 'POST',
pathname: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
body: JSON.stringify(
getLogEntryRateRequestPayloadRT.encode({
data: {

View file

@ -9,66 +9,53 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { NoIndices } from '../../../components/empty_states/no_indices';
import { WithKibanaChrome } from '../../../containers/with_kibana_chrome';
import {
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from '../../../components/source_configuration';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
interface LogsPageNoIndicesContentProps {
uiCapabilities: UICapabilities;
}
export const LogsPageNoIndicesContent = injectUICapabilities(
(props: LogsPageNoIndicesContentProps) => {
const { uiCapabilities } = props;
return (
<WithKibanaChrome>
{({ basePath }) => (
<NoIndices
data-test-subj="noLogsIndicesPrompt"
title={i18n.translate('xpack.infra.logsPage.noLoggingIndicesTitle', {
defaultMessage: "Looks like you don't have any logging indices.",
})}
message={i18n.translate('xpack.infra.logsPage.noLoggingIndicesDescription', {
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
color="primary"
fill
data-test-subj="logsViewSetupInstructionsButton"
>
{i18n.translate(
'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel',
{ defaultMessage: 'View setup instructions' }
)}
</EuiButton>
</EuiFlexItem>
{uiCapabilities.logs.configureSource ? (
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.logs}
>
{i18n.translate('xpack.infra.configureSourceActionLabel', {
defaultMessage: 'Change source configuration',
})}
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
}
/>
)}
</WithKibanaChrome>
);
}
);
export const LogsPageNoIndicesContent = () => {
const basePath = useKibana().services.http?.basePath || '';
const uiCapabilities = useKibana().services.application?.capabilities;
return (
<NoIndices
data-test-subj="noLogsIndicesPrompt"
title={i18n.translate('xpack.infra.logsPage.noLoggingIndicesTitle', {
defaultMessage: "Looks like you don't have any logging indices.",
})}
message={i18n.translate('xpack.infra.logsPage.noLoggingIndicesDescription', {
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
color="primary"
fill
data-test-subj="logsViewSetupInstructionsButton"
>
{i18n.translate('xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel', {
defaultMessage: 'View setup instructions',
})}
</EuiButton>
</EuiFlexItem>
{uiCapabilities?.logs?.configureSource ? (
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.logs}
>
{i18n.translate('xpack.infra.configureSourceActionLabel', {
defaultMessage: 'Change source configuration',
})}
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
}
/>
);
};

View file

@ -28,6 +28,7 @@ import {
} from './helpers';
import { ErrorMessage } from './error_message';
import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting';
import { useUiSetting } from '../../../../../../../../src/plugins/kibana_react/public';
import { VisSectionProps } from '../types';
export const ChartSectionVis = ({
@ -42,6 +43,7 @@ export const ChartSectionVis = ({
seriesOverrides,
type,
}: VisSectionProps) => {
const isDarkMode = useUiSetting<boolean>('theme:darkMode');
const [dateFormat] = useKibanaUiSetting('dateFormat');
const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [
formatter,
@ -125,7 +127,7 @@ export const ChartSectionVis = ({
<Settings
tooltip={tooltipProps}
onBrushEnd={handleTimeChange}
theme={getChartTheme()}
theme={getChartTheme(isDarkMode)}
showLegend={true}
legendPosition="right"
/>

View file

@ -9,70 +9,67 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import euiStyled from '../../../../../../common/eui_styled_components';
import { WithKibanaChrome } from '../../../containers/with_kibana_chrome';
import {
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from '../../../components/source_configuration';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
interface InvalidNodeErrorProps {
nodeName: string;
}
export const InvalidNodeError: React.FunctionComponent<InvalidNodeErrorProps> = ({ nodeName }) => {
const basePath = useKibana().services.http?.basePath || '';
return (
<WithKibanaChrome>
{({ basePath }) => (
<CenteredEmptyPrompt
title={
<h2>
<CenteredEmptyPrompt
title={
<h2>
<FormattedMessage
id="xpack.infra.metrics.invalidNodeErrorTitle"
defaultMessage="Looks like {nodeName} isn't collecting any metrics data"
values={{
nodeName,
}}
/>
</h2>
}
body={
<p>
<FormattedMessage
id="xpack.infra.metrics.invalidNodeErrorDescription"
defaultMessage="Double check your configuration"
/>
</p>
}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
color="primary"
fill
>
<FormattedMessage
id="xpack.infra.metrics.invalidNodeErrorTitle"
defaultMessage="Looks like {nodeName} isn't collecting any metrics data"
values={{
nodeName,
}}
id="xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel"
defaultMessage="View setup instructions"
/>
</h2>
}
body={
<p>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
<FormattedMessage
id="xpack.infra.metrics.invalidNodeErrorDescription"
defaultMessage="Double check your configuration"
id="xpack.infra.configureSourceActionLabel"
defaultMessage="Change source configuration"
/>
</p>
}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
color="primary"
fill
>
<FormattedMessage
id="xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel"
defaultMessage="View setup instructions"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
<FormattedMessage
id="xpack.infra.configureSourceActionLabel"
defaultMessage="Change source configuration"
/>
</ViewSourceConfigurationButton>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
)}
</WithKibanaChrome>
</ViewSourceConfigurationButton>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
);
};

View file

@ -7,7 +7,7 @@
// import { GraphQLFormattedError } from 'graphql';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { KFetchError } from 'ui/kfetch/kfetch_error';
import { IHttpFetchError } from 'src/core/public';
import { InvalidNodeError } from './invalid_node';
// import { InfraMetricsErrorCodes } from '../../../../common/errors';
import { DocumentTitle } from '../../../components/document_title';
@ -15,7 +15,7 @@ import { ErrorPageBody } from '../../error';
interface Props {
name: string;
error: KFetchError;
error: IHttpFetchError;
}
export const PageError = ({ error, name }: Props) => {

View file

@ -5,8 +5,6 @@
*/
import { i18n } from '@kbn/i18n';
import React, { useContext, useState } from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import euiStyled, { EuiTheme, withTheme } from '../../../../../common/eui_styled_components';
import { DocumentTitle } from '../../components/document_title';
import { Header } from '../../components/header';
@ -20,6 +18,7 @@ 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';
const DetailPageContent = euiStyled(PageContent)`
overflow: auto;
@ -34,111 +33,109 @@ interface Props {
node: string;
};
};
uiCapabilities: UICapabilities;
}
export const MetricDetail = withMetricPageProviders(
injectUICapabilities(
withTheme(({ uiCapabilities, match, theme }: Props) => {
const nodeId = match.params.node;
const nodeType = match.params.type as InfraNodeType;
const inventoryModel = findInventoryModel(nodeType);
const { sourceId } = useContext(Source.Context);
const {
name,
filteredRequiredMetrics,
loading: metadataLoading,
cloudId,
metadata,
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId);
withTheme(({ match, theme }: Props) => {
const uiCapabilities = useKibana().services.application?.capabilities;
const nodeId = match.params.node;
const nodeType = match.params.type as InfraNodeType;
const inventoryModel = findInventoryModel(nodeType);
const { sourceId } = useContext(Source.Context);
const {
name,
filteredRequiredMetrics,
loading: metadataLoading,
cloudId,
metadata,
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId);
const [sideNav, setSideNav] = useState<NavItem[]>([]);
const [sideNav, setSideNav] = useState<NavItem[]>([]);
const addNavItem = React.useCallback(
(item: NavItem) => {
if (!sideNav.some(n => n.id === item.id)) {
setSideNav([item, ...sideNav]);
}
},
[sideNav]
);
const addNavItem = React.useCallback(
(item: NavItem) => {
if (!sideNav.some(n => n.id === item.id)) {
setSideNav([item, ...sideNav]);
}
},
[sideNav]
);
const breadcrumbs = [
{
href: '#/',
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
defaultMessage: 'Metrics',
}),
},
{ text: name },
];
if (metadataLoading && !filteredRequiredMetrics.length) {
return (
<InfraLoadingPanel
height="100vh"
width="100%"
text={i18n.translate('xpack.infra.metrics.loadingNodeDataText', {
defaultMessage: 'Loading data',
})}
/>
);
}
const breadcrumbs = [
{
href: '#/',
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
defaultMessage: 'Metrics',
}),
},
{ text: name },
];
if (metadataLoading && !filteredRequiredMetrics.length) {
return (
<WithMetricsTime>
{({
timeRange,
parsedTimeRange,
setTimeRange,
refreshInterval,
setRefreshInterval,
isAutoReloading,
setAutoReload,
triggerRefresh,
}) => (
<ColumnarPage>
<Header
breadcrumbs={breadcrumbs}
readOnlyBadge={!uiCapabilities.infrastructure.save}
/>
<WithMetricsTimeUrlState />
<DocumentTitle
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
defaultMessage: 'Infrastructure | Metrics | {name}',
values: {
name,
},
})}
/>
<DetailPageContent data-test-subj="infraMetricsPage">
{metadata ? (
<NodeDetailsPage
name={name}
requiredMetrics={filteredRequiredMetrics}
sourceId={sourceId}
timeRange={timeRange}
parsedTimeRange={parsedTimeRange}
nodeType={nodeType}
nodeId={nodeId}
cloudId={cloudId}
metadataLoading={metadataLoading}
isAutoReloading={isAutoReloading}
refreshInterval={refreshInterval}
sideNav={sideNav}
metadata={metadata}
addNavItem={addNavItem}
setRefreshInterval={setRefreshInterval}
setAutoReload={setAutoReload}
triggerRefresh={triggerRefresh}
setTimeRange={setTimeRange}
/>
) : null}
</DetailPageContent>
</ColumnarPage>
)}
</WithMetricsTime>
<InfraLoadingPanel
height="100vh"
width="100%"
text={i18n.translate('xpack.infra.metrics.loadingNodeDataText', {
defaultMessage: 'Loading data',
})}
/>
);
})
)
}
return (
<WithMetricsTime>
{({
timeRange,
parsedTimeRange,
setTimeRange,
refreshInterval,
setRefreshInterval,
isAutoReloading,
setAutoReload,
triggerRefresh,
}) => (
<ColumnarPage>
<Header
breadcrumbs={breadcrumbs}
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
/>
<WithMetricsTimeUrlState />
<DocumentTitle
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
defaultMessage: 'Infrastructure | Metrics | {name}',
values: {
name,
},
})}
/>
<DetailPageContent data-test-subj="infraMetricsPage">
{metadata ? (
<NodeDetailsPage
name={name}
requiredMetrics={filteredRequiredMetrics}
sourceId={sourceId}
timeRange={timeRange}
parsedTimeRange={parsedTimeRange}
nodeType={nodeType}
nodeId={nodeId}
cloudId={cloudId}
metadataLoading={metadataLoading}
isAutoReloading={isAutoReloading}
refreshInterval={refreshInterval}
sideNav={sideNav}
metadata={metadata}
addNavItem={addNavItem}
setRefreshInterval={setRefreshInterval}
setAutoReload={setAutoReload}
triggerRefresh={triggerRefresh}
setTimeRange={setTimeRange}
/>
) : null}
</DetailPageContent>
</ColumnarPage>
)}
</WithMetricsTime>
);
})
);

View file

@ -5,14 +5,14 @@
*/
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { SourceConfigurationSettings } from '../../../components/source_configuration/source_configuration_settings';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
interface SettingsPageProps {
uiCapabilities: UICapabilities;
}
export const SettingsPage = injectUICapabilities(({ uiCapabilities }: SettingsPageProps) => (
<SourceConfigurationSettings shouldAllowEdit={uiCapabilities.logs.configureSource as boolean} />
));
export const SettingsPage = () => {
const uiCapabilities = useKibana().services.application?.capabilities;
return (
<SourceConfigurationSettings
shouldAllowEdit={uiCapabilities?.logs?.configureSource as boolean}
/>
);
};

View file

@ -8,55 +8,54 @@ import { History } from 'history';
import React from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { NotFoundPage } from './pages/404';
import { InfrastructurePage } from './pages/infrastructure';
import { LinkToPage } from './pages/link_to';
import { LogsPage } from './pages/logs';
import { MetricDetail } from './pages/metrics';
import { RedirectWithQueryParams } from './utils/redirect_with_query_params';
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
interface RouterProps {
history: History;
uiCapabilities: UICapabilities;
}
const PageRouterComponent: React.FC<RouterProps> = ({ history, uiCapabilities }) => {
export const PageRouter: React.FC<RouterProps> = ({ history }) => {
const uiCapabilities = useKibana().services.application?.capabilities;
return (
<Router history={history}>
<Switch>
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/" exact={true} to="/infrastructure/inventory" />
)}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams
from="/infrastructure"
exact={true}
to="/infrastructure/inventory"
/>
)}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams
from="/infrastructure/snapshot"
exact={true}
to="/infrastructure/inventory"
/>
)}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/home" exact={true} to="/infrastructure/inventory" />
)}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<Route path="/infrastructure/metrics/:type/:node" component={MetricDetail} />
)}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.infrastructure?.show && (
<RedirectWithQueryParams from="/metrics" to="/infrastructure/metrics" />
)}
{uiCapabilities.logs.show && (
{uiCapabilities?.logs?.show && (
<RedirectWithQueryParams from="/logs" exact={true} to="/logs/stream" />
)}
{uiCapabilities.logs.show && <Route path="/logs" component={LogsPage} />}
{uiCapabilities.infrastructure.show && (
{uiCapabilities?.logs?.show && <Route path="/logs" component={LogsPage} />}
{uiCapabilities?.infrastructure?.show && (
<Route path="/infrastructure" component={InfrastructurePage} />
)}
<Route path="/link-to" component={LinkToPage} />
@ -65,5 +64,3 @@ const PageRouterComponent: React.FC<RouterProps> = ({ history, uiCapabilities })
</Router>
);
};
export const PageRouter = injectUICapabilities(PageRouterComponent);

View file

@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { FieldType } from 'ui/index_patterns';
import { IFieldType } from 'src/plugins/data/public';
import { startsWith, uniq } from 'lodash';
import { getAllowedListForPrefix } from '../../common/ecs_allowed_list';
interface DisplayableFieldType extends FieldType {
interface DisplayableFieldType extends IFieldType {
displayable?: boolean;
}

View file

@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
state: undefined,
};
const expectedSearchString =
"logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default&_g=()";
"logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default";
const expectedRedirect = `/logs/stream?${expectedSearchString}`;
await pageObjects.common.navigateToActualUrl(