[Logs + Metrics UI] Clean up async plugin initialization (#67654)
This refactors the browser-side plugin bootstrap code such that the eagerly loaded bundle `infra.plugin.js` is minimal and the rest of the logs and metrics app bundles are loaded only when the apps are visited.
This commit is contained in:
parent
2e3578602f
commit
938771a537
|
@ -384,3 +384,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// required for dynamic import
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default Expressions;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isNumber } from 'lodash';
|
||||
import {
|
||||
MetricExpressionParams,
|
||||
Comparator,
|
||||
|
@ -106,3 +105,5 @@ export function validateMetricThreshold({
|
|||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const isNumber = (value: unknown): value is number => typeof value === 'number';
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { Expressions } from './components/expression';
|
||||
import { validateMetricThreshold } from './components/validation';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/metric_threshold/types';
|
||||
|
@ -18,7 +18,7 @@ export function createMetricThresholdAlertType(): AlertTypeModel {
|
|||
defaultMessage: 'Metric threshold',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: Expressions,
|
||||
alertParamsExpression: React.lazy(() => import('./components/expression')),
|
||||
validate: validateMetricThreshold,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.infra.metrics.alerting.threshold.defaultActionMessage',
|
||||
|
|
47
x-pack/plugins/infra/public/apps/common_providers.tsx
Normal file
47
x-pack/plugins/infra/public/apps/common_providers.tsx
Normal 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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import {
|
||||
useUiSetting$,
|
||||
KibanaContextProvider,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { TriggersActionsProvider } from '../utils/triggers_actions_context';
|
||||
import { ClientPluginDeps } from '../types';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
|
||||
import { ApolloClientContext } from '../utils/apollo_context';
|
||||
import { EuiThemeProvider } from '../../../observability/public';
|
||||
import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt';
|
||||
|
||||
export const CommonInfraProviders: React.FC<{
|
||||
apolloClient: ApolloClient<{}>;
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginStart;
|
||||
}> = ({ apolloClient, children, triggersActionsUI }) => {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
return (
|
||||
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
||||
<ApolloClientContext.Provider value={apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<NavigationWarningPromptProvider>{children}</NavigationWarningPromptProvider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</TriggersActionsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CoreProviders: React.FC<{
|
||||
core: CoreStart;
|
||||
plugins: ClientPluginDeps;
|
||||
}> = ({ children, core, plugins }) => {
|
||||
return (
|
||||
<KibanaContextProvider services={{ ...core, ...plugins }}>
|
||||
<core.i18n.Context>{children}</core.i18n.Context>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
13
x-pack/plugins/infra/public/apps/common_styles.ts
Normal file
13
x-pack/plugins/infra/public/apps/common_styles.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const CONTAINER_CLASSNAME = 'infra-container-element';
|
||||
|
||||
export const prepareMountElement = (element: HTMLElement) => {
|
||||
// Ensure the element we're handed from application mounting is assigned a class
|
||||
// for our index.scss styles to apply to.
|
||||
element.classList.add(CONTAINER_CLASSNAME);
|
||||
};
|
98
x-pack/plugins/infra/public/apps/legacy_app.tsx
Normal file
98
x-pack/plugins/infra/public/apps/legacy_app.tsx
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 { EuiErrorBoundary } from '@elastic/eui';
|
||||
import { createBrowserHistory, History } from 'history';
|
||||
import { AppMountParameters } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route, RouteProps, Router, Switch } from 'react-router-dom';
|
||||
import url from 'url';
|
||||
|
||||
// This exists purely to facilitate legacy app/infra URL redirects.
|
||||
// It will be removed in 8.0.0.
|
||||
export async function renderApp({ element }: AppMountParameters) {
|
||||
const history = createBrowserHistory();
|
||||
|
||||
ReactDOM.render(<LegacyApp history={history} />, element);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
}
|
||||
|
||||
const LegacyApp: React.FunctionComponent<{ history: History<unknown> }> = ({ history }) => {
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route
|
||||
path={'/'}
|
||||
render={({ location }: RouteProps) => {
|
||||
if (!location) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nextPath = '';
|
||||
let nextBasePath = '';
|
||||
let nextSearch;
|
||||
|
||||
if (
|
||||
location.hash.indexOf('#infrastructure') > -1 ||
|
||||
location.hash.indexOf('#/infrastructure') > -1
|
||||
) {
|
||||
nextPath = location.hash.replace(
|
||||
new RegExp(
|
||||
'#infrastructure/|#/infrastructure/|#/infrastructure|#infrastructure',
|
||||
'g'
|
||||
),
|
||||
''
|
||||
);
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/metrics');
|
||||
} else if (
|
||||
location.hash.indexOf('#logs') > -1 ||
|
||||
location.hash.indexOf('#/logs') > -1
|
||||
) {
|
||||
nextPath = location.hash.replace(
|
||||
new RegExp('#logs/|#/logs/|#/logs|#logs', 'g'),
|
||||
''
|
||||
);
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/logs');
|
||||
} else {
|
||||
// This covers /app/infra and /app/infra/home (both of which used to render
|
||||
// the metrics inventory page)
|
||||
nextPath = 'inventory';
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/metrics');
|
||||
nextSearch = undefined;
|
||||
}
|
||||
|
||||
// app/infra#infrastructure/metrics/:type/:node was changed to app/metrics/detail/:type/:node, this
|
||||
// accounts for that edge case
|
||||
nextPath = nextPath.replace('metrics/', 'detail/');
|
||||
|
||||
// Query parameters (location.search) will arrive as part of location.hash and not location.search
|
||||
const nextPathParts = nextPath.split('?');
|
||||
nextPath = nextPathParts[0];
|
||||
nextSearch = nextPathParts[1] ? nextPathParts[1] : undefined;
|
||||
|
||||
let nextUrl = url.format({
|
||||
pathname: `${nextBasePath}/${nextPath}`,
|
||||
hash: undefined,
|
||||
search: nextSearch,
|
||||
});
|
||||
|
||||
nextUrl = nextUrl.replace('//', '/');
|
||||
|
||||
window.location.href = nextUrl;
|
||||
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
};
|
66
x-pack/plugins/infra/public/apps/logs_app.tsx
Normal file
66
x-pack/plugins/infra/public/apps/logs_app.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { ApolloClient } from 'apollo-client';
|
||||
import { History } from 'history';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { AppMountParameters } from '../../../../../src/core/public';
|
||||
import '../index.scss';
|
||||
import { NotFoundPage } from '../pages/404';
|
||||
import { LinkToLogsPage } from '../pages/link_to/link_to_logs';
|
||||
import { LogsPage } from '../pages/logs';
|
||||
import { ClientPluginDeps } from '../types';
|
||||
import { createApolloClient } from '../utils/apollo_client';
|
||||
import { CommonInfraProviders, CoreProviders } from './common_providers';
|
||||
import { prepareMountElement } from './common_styles';
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
plugins: ClientPluginDeps,
|
||||
{ element, history }: AppMountParameters
|
||||
) => {
|
||||
const apolloClient = createApolloClient(core.http.fetch);
|
||||
|
||||
prepareMountElement(element);
|
||||
|
||||
ReactDOM.render(
|
||||
<LogsApp apolloClient={apolloClient} core={core} history={history} plugins={plugins} />,
|
||||
element
|
||||
);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
||||
|
||||
const LogsApp: React.FC<{
|
||||
apolloClient: ApolloClient<{}>;
|
||||
core: CoreStart;
|
||||
history: History<unknown>;
|
||||
plugins: ClientPluginDeps;
|
||||
}> = ({ apolloClient, core, history, plugins }) => {
|
||||
const uiCapabilities = core.application.capabilities;
|
||||
|
||||
return (
|
||||
<CoreProviders core={core} plugins={plugins}>
|
||||
<CommonInfraProviders
|
||||
apolloClient={apolloClient}
|
||||
triggersActionsUI={plugins.triggers_actions_ui}
|
||||
>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
{uiCapabilities?.logs?.show && <Route path="/" component={LogsPage} />}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
</CommonInfraProviders>
|
||||
</CoreProviders>
|
||||
);
|
||||
};
|
82
x-pack/plugins/infra/public/apps/metrics_app.tsx
Normal file
82
x-pack/plugins/infra/public/apps/metrics_app.tsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { ApolloClient } from 'apollo-client';
|
||||
import { History } from 'history';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { AppMountParameters } from '../../../../../src/core/public';
|
||||
import '../index.scss';
|
||||
import { NotFoundPage } from '../pages/404';
|
||||
import { LinkToMetricsPage } from '../pages/link_to/link_to_metrics';
|
||||
import { InfrastructurePage } from '../pages/metrics';
|
||||
import { MetricDetail } from '../pages/metrics/metric_detail';
|
||||
import { ClientPluginDeps } from '../types';
|
||||
import { createApolloClient } from '../utils/apollo_client';
|
||||
import { RedirectWithQueryParams } from '../utils/redirect_with_query_params';
|
||||
import { CommonInfraProviders, CoreProviders } from './common_providers';
|
||||
import { prepareMountElement } from './common_styles';
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
plugins: ClientPluginDeps,
|
||||
{ element, history }: AppMountParameters
|
||||
) => {
|
||||
const apolloClient = createApolloClient(core.http.fetch);
|
||||
|
||||
prepareMountElement(element);
|
||||
|
||||
ReactDOM.render(
|
||||
<MetricsApp apolloClient={apolloClient} core={core} history={history} plugins={plugins} />,
|
||||
element
|
||||
);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
||||
|
||||
const MetricsApp: React.FC<{
|
||||
apolloClient: ApolloClient<{}>;
|
||||
core: CoreStart;
|
||||
history: History<unknown>;
|
||||
plugins: ClientPluginDeps;
|
||||
}> = ({ apolloClient, core, history, plugins }) => {
|
||||
const uiCapabilities = core.application.capabilities;
|
||||
|
||||
return (
|
||||
<CoreProviders core={core} plugins={plugins}>
|
||||
<CommonInfraProviders
|
||||
apolloClient={apolloClient}
|
||||
triggersActionsUI={plugins.triggers_actions_ui}
|
||||
>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToMetricsPage} />
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/" exact={true} to="/inventory" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/snapshot" exact={true} to="/inventory" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/metrics-explorer" exact={true} to="/explorer" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<Route path="/detail/:type/:node" component={MetricDetail} />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<Route path="/" component={InfrastructurePage} />
|
||||
)}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
</CommonInfraProviders>
|
||||
</CoreProviders>
|
||||
);
|
||||
};
|
|
@ -1,80 +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 ReactDOM from 'react-dom';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
import { CoreStart, AppMountParameters } from 'kibana/public';
|
||||
|
||||
// TODO use theme provided from parentApp when kibana supports it
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components';
|
||||
import { InfraFrontendLibs } from '../lib/lib';
|
||||
import { ApolloClientContext } from '../utils/apollo_context';
|
||||
import { HistoryContext } from '../utils/history_context';
|
||||
import {
|
||||
useUiSetting$,
|
||||
KibanaContextProvider,
|
||||
} from '../../../../../src/plugins/kibana_react/public';
|
||||
import { AppRouter } from '../routers';
|
||||
import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public';
|
||||
import { TriggersActionsProvider } from '../utils/triggers_actions_context';
|
||||
import '../index.scss';
|
||||
import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt';
|
||||
|
||||
export const CONTAINER_CLASSNAME = 'infra-container-element';
|
||||
|
||||
export async function startApp(
|
||||
libs: InfraFrontendLibs,
|
||||
core: CoreStart,
|
||||
plugins: object,
|
||||
params: AppMountParameters,
|
||||
Router: AppRouter,
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginSetup
|
||||
) {
|
||||
const { element, history } = params;
|
||||
|
||||
const InfraPluginRoot: React.FunctionComponent = () => {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
return (
|
||||
<core.i18n.Context>
|
||||
<EuiErrorBoundary>
|
||||
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<HistoryContext.Provider value={history}>
|
||||
<NavigationWarningPromptProvider>
|
||||
<Router history={history} />
|
||||
</NavigationWarningPromptProvider>
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
</TriggersActionsProvider>
|
||||
</EuiErrorBoundary>
|
||||
</core.i18n.Context>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FunctionComponent = () => (
|
||||
<KibanaContextProvider services={{ ...core, ...plugins }}>
|
||||
<InfraPluginRoot />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
// Ensure the element we're handed from application mounting is assigned a class
|
||||
// for our index.scss styles to apply to.
|
||||
element.className += ` ${CONTAINER_CLASSNAME}`;
|
||||
|
||||
ReactDOM.render(<App />, element);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
}
|
|
@ -1,100 +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 { createBrowserHistory } from 'history';
|
||||
import React from 'react';
|
||||
import url from 'url';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters } from 'kibana/public';
|
||||
import { Route, Router, Switch, RouteProps } from 'react-router-dom';
|
||||
// TODO use theme provided from parentApp when kibana supports it
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
|
||||
// This exists purely to facilitate legacy app/infra URL redirects.
|
||||
// It will be removed in 8.0.0.
|
||||
export async function startLegacyApp(params: AppMountParameters) {
|
||||
const { element } = params;
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const App: React.FunctionComponent = () => {
|
||||
return (
|
||||
<EuiErrorBoundary>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route
|
||||
path={'/'}
|
||||
render={({ location }: RouteProps) => {
|
||||
if (!location) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let nextPath = '';
|
||||
let nextBasePath = '';
|
||||
let nextSearch;
|
||||
|
||||
if (
|
||||
location.hash.indexOf('#infrastructure') > -1 ||
|
||||
location.hash.indexOf('#/infrastructure') > -1
|
||||
) {
|
||||
nextPath = location.hash.replace(
|
||||
new RegExp(
|
||||
'#infrastructure/|#/infrastructure/|#/infrastructure|#infrastructure',
|
||||
'g'
|
||||
),
|
||||
''
|
||||
);
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/metrics');
|
||||
} else if (
|
||||
location.hash.indexOf('#logs') > -1 ||
|
||||
location.hash.indexOf('#/logs') > -1
|
||||
) {
|
||||
nextPath = location.hash.replace(
|
||||
new RegExp('#logs/|#/logs/|#/logs|#logs', 'g'),
|
||||
''
|
||||
);
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/logs');
|
||||
} else {
|
||||
// This covers /app/infra and /app/infra/home (both of which used to render
|
||||
// the metrics inventory page)
|
||||
nextPath = 'inventory';
|
||||
nextBasePath = location.pathname.replace('app/infra', 'app/metrics');
|
||||
nextSearch = undefined;
|
||||
}
|
||||
|
||||
// app/inra#infrastructure/metrics/:type/:node was changed to app/metrics/detail/:type/:node, this
|
||||
// accounts for that edge case
|
||||
nextPath = nextPath.replace('metrics/', 'detail/');
|
||||
|
||||
// Query parameters (location.search) will arrive as part of location.hash and not location.search
|
||||
const nextPathParts = nextPath.split('?');
|
||||
nextPath = nextPathParts[0];
|
||||
nextSearch = nextPathParts[1] ? nextPathParts[1] : undefined;
|
||||
|
||||
let nextUrl = url.format({
|
||||
pathname: `${nextBasePath}/${nextPath}`,
|
||||
hash: undefined,
|
||||
search: nextSearch,
|
||||
});
|
||||
|
||||
nextUrl = nextUrl.replace('//', '/');
|
||||
|
||||
window.location.href = nextUrl;
|
||||
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</EuiErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, element);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
}
|
|
@ -336,6 +336,10 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
// required for dynamic import
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default Expressions;
|
||||
|
||||
interface ExpressionRowProps {
|
||||
nodeType: InventoryItemType;
|
||||
expressionId: number;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
|
||||
import { Expressions } from './expression';
|
||||
import { validateMetricThreshold } from './validation';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
|
||||
|
@ -18,7 +18,7 @@ export function getInventoryMetricAlertType(): AlertTypeModel {
|
|||
defaultMessage: 'Inventory',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: Expressions,
|
||||
alertParamsExpression: React.lazy(() => import('./expression')),
|
||||
validate: validateMetricThreshold,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.infra.metrics.alerting.inventory.threshold.defaultActionMessage',
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { isNumber } from 'lodash';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { MetricExpressionParams } from '../../../../server/lib/alerting/metric_threshold/types';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ValidationResult } from '../../../../../triggers_actions_ui/public/types';
|
||||
|
@ -95,3 +93,5 @@ export function validateMetricThreshold({
|
|||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const isNumber = (value: unknown): value is number => typeof value === 'number';
|
||||
|
|
|
@ -236,3 +236,7 @@ export const Editor: React.FC<Props> = (props) => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// required for dynamic import
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default Editor;
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
|
||||
import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../../common/alerting/logs/types';
|
||||
import { ExpressionEditor } from './expression_editor';
|
||||
import { validateExpression } from './validation';
|
||||
|
||||
export function getAlertType(): AlertTypeModel {
|
||||
|
@ -17,7 +17,7 @@ export function getAlertType(): AlertTypeModel {
|
|||
defaultMessage: 'Log threshold',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: ExpressionEditor,
|
||||
alertParamsExpression: React.lazy(() => import('./expression_editor/editor')),
|
||||
validate: validateExpression,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.infra.logs.alerting.threshold.defaultActionMessage',
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { ApolloLink } from 'apollo-link';
|
||||
import { createHttpLink } from 'apollo-link-http';
|
||||
import { withClientState } from 'apollo-link-state';
|
||||
import { CoreStart, HttpFetchOptions } from 'src/core/public';
|
||||
import { InfraFrontendLibs } from './lib/lib';
|
||||
import introspectionQueryResultData from './graphql/introspection.json';
|
||||
import { InfraKibanaObservableApiAdapter } from './lib/adapters/observable_api/kibana_observable_api';
|
||||
|
||||
export function composeLibs(core: CoreStart) {
|
||||
const cache = new InMemoryCache({
|
||||
addTypename: false,
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
});
|
||||
|
||||
const observableApi = new InfraKibanaObservableApiAdapter({
|
||||
basePath: core.http.basePath.get(),
|
||||
});
|
||||
|
||||
const wrappedFetch = (path: string, options: HttpFetchOptions) => {
|
||||
return new Promise<Response>(async (resolve, reject) => {
|
||||
// core.http.fetch isn't 100% compatible with the Fetch API and will
|
||||
// throw Errors on 401s. This top level try / catch handles those scenarios.
|
||||
try {
|
||||
core.http
|
||||
.fetch(path, {
|
||||
...options,
|
||||
// Set headers to undefined due to this bug: https://github.com/apollographql/apollo-link/issues/249,
|
||||
// Apollo will try to set a "content-type" header which will conflict with the "Content-Type" header that
|
||||
// core.http.fetch correctly sets.
|
||||
headers: undefined,
|
||||
asResponse: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.response) {
|
||||
return reject();
|
||||
}
|
||||
// core.http.fetch will parse the Response and set a body before handing it back. As such .text() / .json()
|
||||
// will have already been called on the Response instance. However, Apollo will also want to call
|
||||
// .text() / .json() on the instance, as it expects the raw Response instance, rather than core's wrapper.
|
||||
// .text() / .json() can only be called once, and an Error will be thrown if those methods are accessed again.
|
||||
// This hacks around that by setting up a new .text() method that will restringify the JSON response we already have.
|
||||
// This does result in an extra stringify / parse cycle, which isn't ideal, but as we only have a few endpoints left using
|
||||
// GraphQL this shouldn't create excessive overhead.
|
||||
// Ref: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http/src/httpLink.ts#L134
|
||||
// and
|
||||
// https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L125
|
||||
return resolve({
|
||||
...res.response,
|
||||
text: () => {
|
||||
return new Promise(async (resolveText, rejectText) => {
|
||||
if (res.body) {
|
||||
return resolveText(JSON.stringify(res.body));
|
||||
} else {
|
||||
return rejectText();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const HttpLink = createHttpLink({
|
||||
fetch: wrappedFetch,
|
||||
uri: `/api/infra/graphql`,
|
||||
});
|
||||
|
||||
const graphQLOptions = {
|
||||
cache,
|
||||
link: ApolloLink.from([
|
||||
withClientState({
|
||||
cache,
|
||||
resolvers: {},
|
||||
}),
|
||||
HttpLink,
|
||||
]),
|
||||
};
|
||||
|
||||
const apolloClient = new ApolloClient(graphQLOptions);
|
||||
|
||||
const libs: InfraFrontendLibs = {
|
||||
apolloClient,
|
||||
observableApi,
|
||||
};
|
||||
return libs;
|
||||
}
|
|
@ -1,131 +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 { parse, stringify } from 'query-string';
|
||||
import { Location } from 'history';
|
||||
import { omit } from 'lodash';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
import { decode_object, encode_object } from 'rison-node';
|
||||
import { Omit } from '../lib/lib';
|
||||
|
||||
interface AnyObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface WithStateFromLocationOptions<StateInLocation> {
|
||||
mapLocationToState: (location: Location) => StateInLocation;
|
||||
mapStateToLocation: (state: StateInLocation, location: Location) => Location;
|
||||
}
|
||||
|
||||
type InjectedPropsFromLocation<StateInLocation> = Partial<StateInLocation> & {
|
||||
pushStateInLocation?: (state: StateInLocation) => void;
|
||||
replaceStateInLocation?: (state: StateInLocation) => void;
|
||||
};
|
||||
|
||||
export const withStateFromLocation = <StateInLocation extends {}>({
|
||||
mapLocationToState,
|
||||
mapStateToLocation,
|
||||
}: WithStateFromLocationOptions<StateInLocation>) => <
|
||||
WrappedComponentProps extends InjectedPropsFromLocation<StateInLocation>
|
||||
>(
|
||||
WrappedComponent: React.ComponentType<WrappedComponentProps>
|
||||
) => {
|
||||
const wrappedName = WrappedComponent.displayName || WrappedComponent.name;
|
||||
|
||||
return withRouter(
|
||||
class WithStateFromLocation extends React.PureComponent<
|
||||
RouteComponentProps<{}> &
|
||||
Omit<WrappedComponentProps, InjectedPropsFromLocation<StateInLocation>>
|
||||
> {
|
||||
public static displayName = `WithStateFromLocation(${wrappedName})`;
|
||||
|
||||
public render() {
|
||||
const { location } = this.props;
|
||||
const otherProps = omit(this.props, ['location', 'history', 'match', 'staticContext']);
|
||||
|
||||
const stateFromLocation = mapLocationToState(location);
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<WrappedComponent
|
||||
{...otherProps}
|
||||
{...stateFromLocation}
|
||||
pushStateInLocation={this.pushStateInLocation}
|
||||
replaceStateInLocation={this.replaceStateInLocation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private pushStateInLocation = (state: StateInLocation) => {
|
||||
const { history, location } = this.props;
|
||||
|
||||
const newLocation = mapStateToLocation(state, this.props.location);
|
||||
|
||||
if (newLocation !== location) {
|
||||
history.push(newLocation);
|
||||
}
|
||||
};
|
||||
|
||||
private replaceStateInLocation = (state: StateInLocation) => {
|
||||
const { history, location } = this.props;
|
||||
|
||||
const newLocation = mapStateToLocation(state, this.props.location);
|
||||
|
||||
if (newLocation !== location) {
|
||||
history.replace(newLocation);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const decodeRisonAppState = (queryValues: { _a?: string }): AnyObject => {
|
||||
try {
|
||||
return queryValues && queryValues._a ? decode_object(queryValues._a) : {};
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('rison decoder error')) {
|
||||
return {};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const encodeRisonAppState = (state: AnyObject) => ({
|
||||
_a: encode_object(state),
|
||||
});
|
||||
|
||||
export const mapRisonAppLocationToState = <State extends {}>(
|
||||
mapState: (risonAppState: AnyObject) => State = (state: AnyObject) => state as State
|
||||
) => (location: Location): State => {
|
||||
const queryValues = parse(location.search.substring(1), { sort: false });
|
||||
const decodedState = decodeRisonAppState(queryValues);
|
||||
return mapState(decodedState);
|
||||
};
|
||||
|
||||
export const mapStateToRisonAppLocation = <State extends {}>(
|
||||
mapState: (state: State) => AnyObject = (state: State) => state
|
||||
) => (state: State, location: Location): Location => {
|
||||
const previousQueryValues = parse(location.search.substring(1), { sort: false });
|
||||
const previousState = decodeRisonAppState(previousQueryValues);
|
||||
|
||||
const encodedState = encodeRisonAppState({
|
||||
...previousState,
|
||||
...mapState(state),
|
||||
});
|
||||
const newQueryValues = stringify(
|
||||
{
|
||||
...previousQueryValues,
|
||||
...encodedState,
|
||||
},
|
||||
{ sort: false }
|
||||
);
|
||||
return {
|
||||
...location,
|
||||
search: `?${newQueryValues}`,
|
||||
};
|
||||
};
|
|
@ -1,44 +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 gql from 'graphql-tag';
|
||||
|
||||
import { sharedFragments } from '../../common/graphql/shared';
|
||||
|
||||
export const logEntriesQuery = gql`
|
||||
query LogEntries(
|
||||
$sourceId: ID = "default"
|
||||
$timeKey: InfraTimeKeyInput!
|
||||
$countBefore: Int = 0
|
||||
$countAfter: Int = 0
|
||||
$filterQuery: String
|
||||
) {
|
||||
source(id: $sourceId) {
|
||||
id
|
||||
logEntriesAround(
|
||||
key: $timeKey
|
||||
countBefore: $countBefore
|
||||
countAfter: $countAfter
|
||||
filterQuery: $filterQuery
|
||||
) {
|
||||
start {
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
end {
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
|
@ -4,15 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { encode } from 'rison-node';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { HistoryContext } from '../utils/history_context';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { encode } from 'rison-node';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { useLinkProps, LinkDescriptor } from './use_link_props';
|
||||
import { ScopedHistory } from '../../../../../src/core/public';
|
||||
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { LinkDescriptor, useLinkProps } from './use_link_props';
|
||||
|
||||
const PREFIX = '/test-basepath/s/test-space/app/';
|
||||
|
||||
|
@ -30,9 +30,9 @@ const scopedHistory = new ScopedHistory(history, `${PREFIX}${INTERNAL_APP}`);
|
|||
|
||||
const ProviderWrapper: React.FC = ({ children }) => {
|
||||
return (
|
||||
<HistoryContext.Provider value={scopedHistory}>
|
||||
<Router history={scopedHistory}>
|
||||
<KibanaContextProvider services={{ ...coreStartMock }}>{children}</KibanaContextProvider>;
|
||||
</HistoryContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext, PluginInitializer } from 'kibana/public';
|
||||
import { Plugin, ClientSetup, ClientStart, ClientPluginsSetup, ClientPluginsStart } from './plugin';
|
||||
import { PluginInitializer, PluginInitializerContext } from 'kibana/public';
|
||||
import { ClientSetup, ClientStart, Plugin } from './plugin';
|
||||
import { ClientPluginsSetup, ClientPluginsStart } from './types';
|
||||
|
||||
export const plugin: PluginInitializer<
|
||||
ClientSetup,
|
||||
|
|
|
@ -1,45 +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 { ajax } from 'rxjs/ajax';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
InfraObservableApi,
|
||||
InfraObservableApiPostParams,
|
||||
InfraObservableApiResponse,
|
||||
} from '../../lib';
|
||||
|
||||
export class InfraKibanaObservableApiAdapter implements InfraObservableApi {
|
||||
private basePath: string;
|
||||
private defaultHeaders: {
|
||||
[headerName: string]: boolean | string;
|
||||
};
|
||||
|
||||
constructor({ basePath }: { basePath: string }) {
|
||||
this.basePath = basePath;
|
||||
this.defaultHeaders = {
|
||||
'kbn-xsrf': true,
|
||||
};
|
||||
}
|
||||
|
||||
public post = <RequestBody extends {} = {}, ResponseBody extends {} = {}>({
|
||||
url,
|
||||
body,
|
||||
}: InfraObservableApiPostParams<RequestBody>): InfraObservableApiResponse<ResponseBody> =>
|
||||
ajax({
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers: {
|
||||
...this.defaultHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
timeout: 30000,
|
||||
url: `${this.basePath}/api/${url}`,
|
||||
withCredentials: true,
|
||||
}).pipe(map(({ response, status }) => ({ response, status })));
|
||||
}
|
|
@ -4,102 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IModule, IScope } from 'angular';
|
||||
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import React from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as rt from 'io-ts';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SourceQuery } from '../graphql/types';
|
||||
import * as rt from 'io-ts';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
InfraTimerangeInput,
|
||||
SnapshotGroupBy,
|
||||
SnapshotMetricInput,
|
||||
SnapshotNodeMetric,
|
||||
SnapshotNodePath,
|
||||
} from '../../common/http_api/snapshot_api';
|
||||
import { SourceQuery } from '../graphql/types';
|
||||
import { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options';
|
||||
|
||||
export interface InfraFrontendLibs {
|
||||
apolloClient: InfraApolloClient;
|
||||
observableApi: InfraObservableApi;
|
||||
}
|
||||
|
||||
export type InfraTimezoneProvider = () => string;
|
||||
|
||||
export type InfraApolloClient = ApolloClient<NormalizedCacheObject>;
|
||||
|
||||
export interface InfraFrameworkAdapter {
|
||||
// Insstance vars
|
||||
appState?: object;
|
||||
kbnVersion?: string;
|
||||
timezone?: string;
|
||||
|
||||
// Methods
|
||||
setUISettings(key: string, value: any): void;
|
||||
render(component: React.ReactElement<any>): void;
|
||||
renderBreadcrumbs(component: React.ReactElement<any>): void;
|
||||
}
|
||||
|
||||
export type InfraFramworkAdapterConstructable = new (
|
||||
uiModule: IModule,
|
||||
timezoneProvider: InfraTimezoneProvider
|
||||
) => InfraFrameworkAdapter;
|
||||
|
||||
// TODO: replace AxiosRequestConfig with something more defined
|
||||
export type InfraRequestConfig = AxiosRequestConfig;
|
||||
|
||||
export interface InfraApiAdapter {
|
||||
get<T>(url: string, config?: InfraRequestConfig | undefined): Promise<T>;
|
||||
post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise<object>;
|
||||
delete(url: string, config?: InfraRequestConfig | undefined): Promise<object>;
|
||||
put(url: string, data?: any, config?: InfraRequestConfig | undefined): Promise<object>;
|
||||
}
|
||||
|
||||
export interface InfraObservableApiPostParams<RequestBody extends {} = {}> {
|
||||
url: string;
|
||||
body?: RequestBody;
|
||||
}
|
||||
|
||||
export type InfraObservableApiResponse<BodyType extends {} = {}> = Observable<{
|
||||
status: number;
|
||||
response: BodyType;
|
||||
}>;
|
||||
|
||||
export interface InfraObservableApi {
|
||||
post<RequestBody extends {} = {}, ResponseBody extends {} = {}>(
|
||||
params: InfraObservableApiPostParams<RequestBody>
|
||||
): InfraObservableApiResponse<ResponseBody>;
|
||||
}
|
||||
|
||||
export interface InfraUiKibanaAdapterScope extends IScope {
|
||||
breadcrumbs: any[];
|
||||
topNavMenu: any[];
|
||||
}
|
||||
|
||||
export interface InfraKibanaUIConfig {
|
||||
get(key: string): any;
|
||||
set(key: string, value: any): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface InfraKibanaAdapterServiceRefs {
|
||||
config: InfraKibanaUIConfig;
|
||||
rootScope: IScope;
|
||||
}
|
||||
|
||||
export type InfraBufferedKibanaServiceCall<ServiceRefs> = (serviceRefs: ServiceRefs) => void;
|
||||
|
||||
export interface InfraField {
|
||||
name: string;
|
||||
type: string;
|
||||
searchable: boolean;
|
||||
aggregatable: boolean;
|
||||
}
|
||||
|
||||
export type InfraWaffleData = InfraWaffleMapGroup[];
|
||||
|
||||
export interface InfraWaffleMapNode {
|
||||
pathId: string;
|
||||
id: string;
|
||||
|
@ -221,8 +137,6 @@ export interface InfraOptions {
|
|||
wafflemap: InfraWaffleMapOptions;
|
||||
}
|
||||
|
||||
export type Omit<T1, T2> = Pick<T1, Exclude<keyof T1, keyof T2>>;
|
||||
|
||||
export interface InfraWaffleMapBounds {
|
||||
min: number;
|
||||
max: number;
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
to="/stream?sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -47,7 +47,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?sourceId=default&logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)"
|
||||
to="/stream?sourceId=default&logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -59,7 +59,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?sourceId=default&logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)"
|
||||
to="/stream?sourceId=default&logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
to="/stream?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -89,7 +89,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)"
|
||||
to="/stream?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -103,7 +103,7 @@ describe('RedirectToNodeLogs component', () => {
|
|||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/?sourceId=SOME-OTHER-SOURCE&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
to="/stream?sourceId=SOME-OTHER-SOURCE&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -71,7 +71,7 @@ export const RedirectToNodeLogs = ({
|
|||
replaceSourceIdInQueryString(sourceId)
|
||||
)('');
|
||||
|
||||
return <Redirect to={`/?${searchString}`} />;
|
||||
return <Redirect to={`/stream?${searchString}`} />;
|
||||
};
|
||||
|
||||
export const getNodeLogsUrl = ({
|
||||
|
|
|
@ -96,6 +96,7 @@ export const LogsPageContent: React.FunctionComponent = () => {
|
|||
<Route path={logCategoriesTab.pathname} component={LogEntryCategoriesPage} />
|
||||
<Route path={settingsTab.pathname} component={LogsSettingsPage} />
|
||||
<RedirectWithQueryParams from={'/analysis'} to={logRateTab.pathname} exact />
|
||||
<RedirectWithQueryParams from={'/'} to={streamTab.pathname} exact />
|
||||
</Switch>
|
||||
</ColumnarPage>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ import { fieldToName } from '../lib/field_to_display_name';
|
|||
import { NodeContextMenu } from './waffle/node_context_menu';
|
||||
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||
import { SnapshotNode, SnapshotNodePath } from '../../../../../common/http_api/snapshot_api';
|
||||
import { CONTAINER_CLASSNAME } from '../../../../apps/start_app';
|
||||
import { CONTAINER_CLASSNAME } from '../../../../apps/common_styles';
|
||||
|
||||
interface Props {
|
||||
nodes: SnapshotNode[];
|
||||
|
|
|
@ -26,7 +26,9 @@ export const useWaffleTime = () => {
|
|||
|
||||
const [state, setState] = useState<WaffleTimeState>(urlState);
|
||||
|
||||
useEffect(() => setUrlState(state), [setUrlState, state]);
|
||||
useEffect(() => {
|
||||
setUrlState(state);
|
||||
}, [setUrlState, state]);
|
||||
|
||||
const { currentTime, isAutoReloading } = urlState;
|
||||
|
||||
|
|
|
@ -4,14 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { mountHook } from 'test_utils/enzyme_helpers';
|
||||
|
||||
import { ScopedHistory } from '../../../../../../../../src/core/public';
|
||||
import { useMetricsTime } from './use_metrics_time';
|
||||
|
||||
describe('useMetricsTime hook', () => {
|
||||
describe('timeRange state', () => {
|
||||
it('has a default value', () => {
|
||||
const { getLastHookValue } = mountHook(() => useMetricsTime().timeRange);
|
||||
const { getLastHookValue } = mountHook(
|
||||
() => useMetricsTime().timeRange,
|
||||
createProviderWrapper()
|
||||
);
|
||||
const hookValue = getLastHookValue();
|
||||
expect(hookValue).toHaveProperty('from');
|
||||
expect(hookValue).toHaveProperty('to');
|
||||
|
@ -19,7 +25,7 @@ describe('useMetricsTime hook', () => {
|
|||
});
|
||||
|
||||
it('can be updated', () => {
|
||||
const { act, getLastHookValue } = mountHook(() => useMetricsTime());
|
||||
const { act, getLastHookValue } = mountHook(() => useMetricsTime(), createProviderWrapper());
|
||||
|
||||
const timeRange = {
|
||||
from: 'now-15m',
|
||||
|
@ -37,12 +43,15 @@ describe('useMetricsTime hook', () => {
|
|||
|
||||
describe('AutoReloading state', () => {
|
||||
it('has a default value', () => {
|
||||
const { getLastHookValue } = mountHook(() => useMetricsTime().isAutoReloading);
|
||||
const { getLastHookValue } = mountHook(
|
||||
() => useMetricsTime().isAutoReloading,
|
||||
createProviderWrapper()
|
||||
);
|
||||
expect(getLastHookValue()).toBe(false);
|
||||
});
|
||||
|
||||
it('can be updated', () => {
|
||||
const { act, getLastHookValue } = mountHook(() => useMetricsTime());
|
||||
const { act, getLastHookValue } = mountHook(() => useMetricsTime(), createProviderWrapper());
|
||||
|
||||
act(({ setAutoReload }) => {
|
||||
setAutoReload(true);
|
||||
|
@ -52,3 +61,17 @@ describe('useMetricsTime hook', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
const createProviderWrapper = () => {
|
||||
const INITIAL_URL = '/test-basepath/s/test-space/app/metrics';
|
||||
const history = createMemoryHistory();
|
||||
|
||||
history.push(INITIAL_URL);
|
||||
const scopedHistory = new ScopedHistory(history, INITIAL_URL);
|
||||
|
||||
const ProviderWrapper: React.FC = ({ children }) => {
|
||||
return <Router history={scopedHistory}>{children}</Router>;
|
||||
};
|
||||
|
||||
return ProviderWrapper;
|
||||
};
|
||||
|
|
|
@ -4,54 +4,29 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { merge } from 'lodash';
|
||||
import {
|
||||
Plugin as PluginClass,
|
||||
AppMountParameters,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin as PluginClass,
|
||||
PluginInitializerContext,
|
||||
AppMountParameters,
|
||||
} from 'kibana/public';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
|
||||
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
|
||||
import { getInventoryMetricAlertType } from './components/alerting/inventory/metric_inventory_threshold_alert_type';
|
||||
import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type';
|
||||
import { registerStartSingleton } from './legacy_singletons';
|
||||
import { registerFeatures } from './register_feature';
|
||||
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
|
||||
import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public';
|
||||
|
||||
import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public';
|
||||
import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type';
|
||||
import { getInventoryMetricAlertType } from './components/alerting/inventory/metric_inventory_threshold_alert_type';
|
||||
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
|
||||
import { ClientPluginsSetup, ClientPluginsStart } from './types';
|
||||
|
||||
export type ClientSetup = void;
|
||||
export type ClientStart = void;
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
home: HomePublicPluginSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
dataEnhanced: DataEnhancedSetup;
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
}
|
||||
|
||||
export interface ClientPluginsStart {
|
||||
data: DataPublicPluginStart;
|
||||
dataEnhanced: DataEnhancedStart;
|
||||
}
|
||||
|
||||
export type InfraPlugins = ClientPluginsSetup & ClientPluginsStart;
|
||||
|
||||
const getMergedPlugins = (setup: ClientPluginsSetup, start: ClientPluginsStart): InfraPlugins => {
|
||||
return merge({}, setup, start);
|
||||
};
|
||||
|
||||
export class Plugin
|
||||
implements PluginClass<ClientSetup, ClientStart, ClientPluginsSetup, ClientPluginsStart> {
|
||||
constructor(context: PluginInitializerContext) {}
|
||||
constructor(_context: PluginInitializerContext) {}
|
||||
|
||||
setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) {
|
||||
setup(core: CoreSetup<ClientPluginsStart, ClientStart>, pluginsSetup: ClientPluginsSetup) {
|
||||
registerFeatures(pluginsSetup.home);
|
||||
|
||||
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getInventoryMetricAlertType());
|
||||
|
@ -69,16 +44,18 @@ export class Plugin
|
|||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
const plugins = getMergedPlugins(pluginsSetup, pluginsStart as ClientPluginsStart);
|
||||
const { startApp, composeLibs, LogsRouter } = await this.downloadAssets();
|
||||
const { renderApp } = await import('./apps/logs_app');
|
||||
|
||||
return startApp(
|
||||
composeLibs(coreStart),
|
||||
return renderApp(
|
||||
coreStart,
|
||||
plugins,
|
||||
params,
|
||||
LogsRouter,
|
||||
pluginsSetup.triggers_actions_ui
|
||||
{
|
||||
data: pluginsStart.data,
|
||||
dataEnhanced: pluginsSetup.dataEnhanced,
|
||||
home: pluginsSetup.home,
|
||||
triggers_actions_ui: pluginsStart.triggers_actions_ui,
|
||||
usageCollection: pluginsSetup.usageCollection,
|
||||
},
|
||||
params
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -94,16 +71,18 @@ export class Plugin
|
|||
category: DEFAULT_APP_CATEGORIES.observability,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
const [coreStart, pluginsStart] = await core.getStartServices();
|
||||
const plugins = getMergedPlugins(pluginsSetup, pluginsStart as ClientPluginsStart);
|
||||
const { startApp, composeLibs, MetricsRouter } = await this.downloadAssets();
|
||||
const { renderApp } = await import('./apps/metrics_app');
|
||||
|
||||
return startApp(
|
||||
composeLibs(coreStart),
|
||||
return renderApp(
|
||||
coreStart,
|
||||
plugins,
|
||||
params,
|
||||
MetricsRouter,
|
||||
pluginsSetup.triggers_actions_ui
|
||||
{
|
||||
data: pluginsStart.data,
|
||||
dataEnhanced: pluginsSetup.dataEnhanced,
|
||||
home: pluginsSetup.home,
|
||||
triggers_actions_ui: pluginsStart.triggers_actions_ui,
|
||||
usageCollection: pluginsSetup.usageCollection,
|
||||
},
|
||||
params
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -116,28 +95,14 @@ export class Plugin
|
|||
title: 'infra',
|
||||
navLinkStatus: 3,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
const { startLegacyApp } = await import('./apps/start_legacy_app');
|
||||
return startLegacyApp(params);
|
||||
const { renderApp } = await import('./apps/legacy_app');
|
||||
|
||||
return renderApp(params);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
start(core: CoreStart, plugins: ClientPluginsStart) {
|
||||
start(core: CoreStart, _plugins: ClientPluginsStart) {
|
||||
registerStartSingleton(core);
|
||||
}
|
||||
|
||||
private async downloadAssets() {
|
||||
const [{ startApp }, { composeLibs }, { LogsRouter, MetricsRouter }] = await Promise.all([
|
||||
import('./apps/start_app'),
|
||||
import('./compose_libs'),
|
||||
import('./routers'),
|
||||
]);
|
||||
|
||||
return {
|
||||
startApp,
|
||||
composeLibs,
|
||||
LogsRouter,
|
||||
MetricsRouter,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +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 { History } from 'history';
|
||||
|
||||
export * from './logs_router';
|
||||
export * from './metrics_router';
|
||||
|
||||
interface RouterProps {
|
||||
history: History;
|
||||
}
|
||||
|
||||
export type AppRouter = React.FC<RouterProps>;
|
|
@ -1,31 +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 { Route, Router, Switch } from 'react-router-dom';
|
||||
|
||||
import { NotFoundPage } from '../pages/404';
|
||||
import { LinkToLogsPage } from '../pages/link_to';
|
||||
import { LogsPage } from '../pages/logs';
|
||||
import { RedirectWithQueryParams } from '../utils/redirect_with_query_params';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { AppRouter } from './index';
|
||||
|
||||
export const LogsRouter: AppRouter = ({ history }) => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
{uiCapabilities?.logs?.show && (
|
||||
<RedirectWithQueryParams from="/" exact={true} to="/stream" />
|
||||
)}
|
||||
{uiCapabilities?.logs?.show && <Route path="/" component={LogsPage} />}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
};
|
|
@ -1,41 +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 { Route, Router, Switch } from 'react-router-dom';
|
||||
|
||||
import { NotFoundPage } from '../pages/404';
|
||||
import { InfrastructurePage } from '../pages/metrics';
|
||||
import { MetricDetail } from '../pages/metrics/metric_detail';
|
||||
import { RedirectWithQueryParams } from '../utils/redirect_with_query_params';
|
||||
import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
||||
import { AppRouter } from './index';
|
||||
import { LinkToMetricsPage } from '../pages/link_to';
|
||||
|
||||
export const MetricsRouter: AppRouter = ({ history }) => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToMetricsPage} />
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/" exact={true} to="/inventory" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/snapshot" exact={true} to="/inventory" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<RedirectWithQueryParams from="/metrics-explorer" exact={true} to="/explorer" />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && (
|
||||
<Route path="/detail/:type/:node" component={MetricDetail} />
|
||||
)}
|
||||
{uiCapabilities?.infrastructure?.show && <Route path="/" component={InfrastructurePage} />}
|
||||
<Route component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
};
|
25
x-pack/plugins/infra/public/types.ts
Normal file
25
x-pack/plugins/infra/public/types.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
|
||||
import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public';
|
||||
import { DataEnhancedSetup } from '../../data_enhanced/public';
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
dataEnhanced: DataEnhancedSetup;
|
||||
home: HomePublicPluginSetup;
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
export interface ClientPluginsStart {
|
||||
data: DataPublicPluginStart;
|
||||
triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup;
|
||||
}
|
||||
|
||||
export type ClientPluginDeps = ClientPluginsSetup & ClientPluginsStart;
|
85
x-pack/plugins/infra/public/utils/apollo_client.ts
Normal file
85
x-pack/plugins/infra/public/utils/apollo_client.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { ApolloLink } from 'apollo-link';
|
||||
import { createHttpLink } from 'apollo-link-http';
|
||||
import { withClientState } from 'apollo-link-state';
|
||||
import { HttpFetchOptions, HttpHandler } from 'src/core/public';
|
||||
import introspectionQueryResultData from '../graphql/introspection.json';
|
||||
|
||||
export const createApolloClient = (fetch: HttpHandler) => {
|
||||
const cache = new InMemoryCache({
|
||||
addTypename: false,
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
});
|
||||
|
||||
const wrappedFetch = (path: string, options: HttpFetchOptions) => {
|
||||
return new Promise<Response>(async (resolve, reject) => {
|
||||
// core.http.fetch isn't 100% compatible with the Fetch API and will
|
||||
// throw Errors on 401s. This top level try / catch handles those scenarios.
|
||||
try {
|
||||
fetch(path, {
|
||||
...options,
|
||||
// Set headers to undefined due to this bug: https://github.com/apollographql/apollo-link/issues/249,
|
||||
// Apollo will try to set a "content-type" header which will conflict with the "Content-Type" header that
|
||||
// core.http.fetch correctly sets.
|
||||
headers: undefined,
|
||||
asResponse: true,
|
||||
}).then((res) => {
|
||||
if (!res.response) {
|
||||
return reject();
|
||||
}
|
||||
// core.http.fetch will parse the Response and set a body before handing it back. As such .text() / .json()
|
||||
// will have already been called on the Response instance. However, Apollo will also want to call
|
||||
// .text() / .json() on the instance, as it expects the raw Response instance, rather than core's wrapper.
|
||||
// .text() / .json() can only be called once, and an Error will be thrown if those methods are accessed again.
|
||||
// This hacks around that by setting up a new .text() method that will restringify the JSON response we already have.
|
||||
// This does result in an extra stringify / parse cycle, which isn't ideal, but as we only have a few endpoints left using
|
||||
// GraphQL this shouldn't create excessive overhead.
|
||||
// Ref: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http/src/httpLink.ts#L134
|
||||
// and
|
||||
// https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L125
|
||||
return resolve({
|
||||
...res.response,
|
||||
text: () => {
|
||||
return new Promise(async (resolveText, rejectText) => {
|
||||
if (res.body) {
|
||||
return resolveText(JSON.stringify(res.body));
|
||||
} else {
|
||||
return rejectText();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const HttpLink = createHttpLink({
|
||||
fetch: wrappedFetch,
|
||||
uri: `/api/infra/graphql`,
|
||||
});
|
||||
|
||||
const graphQLOptions = {
|
||||
cache,
|
||||
link: ApolloLink.from([
|
||||
withClientState({
|
||||
cache,
|
||||
resolvers: {},
|
||||
}),
|
||||
HttpLink,
|
||||
]),
|
||||
};
|
||||
|
||||
return new ApolloClient(graphQLOptions);
|
||||
};
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
|
||||
|
||||
interface ContextProps {
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginSetup | null;
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginStart | null;
|
||||
}
|
||||
|
||||
export const TriggerActionsContext = React.createContext<ContextProps>({
|
||||
|
@ -16,7 +16,7 @@ export const TriggerActionsContext = React.createContext<ContextProps>({
|
|||
});
|
||||
|
||||
interface Props {
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginSetup;
|
||||
triggersActionsUI: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export const TriggersActionsProvider: React.FC<Props> = (props) => {
|
||||
|
|
|
@ -8,10 +8,9 @@ import { parse, stringify } from 'query-string';
|
|||
import { Location } from 'history';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { decode, encode, RisonValue } from 'rison-node';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { url } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { useHistory } from './history_context';
|
||||
|
||||
export const useUrlState = <State>({
|
||||
defaultState,
|
||||
decodeUrlState,
|
||||
|
|
Loading…
Reference in a new issue