mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Backports the following commits to 7.x: - [Logs UI] Allow for plugins to inject internal source configurations (#36066)
This commit is contained in:
parent
862c67ae0e
commit
4dd925b4d6
28 changed files with 475 additions and 166 deletions
|
@ -18,6 +18,7 @@ export const sharedFragments = {
|
|||
id
|
||||
version
|
||||
updatedAt
|
||||
origin
|
||||
}
|
||||
`,
|
||||
InfraLogEntryFields: gql`
|
||||
|
|
|
@ -22,6 +22,8 @@ export interface InfraSource {
|
|||
version?: string | null;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: number | null;
|
||||
/** The origin of the source (one of 'fallback', 'internal', 'stored') */
|
||||
origin: string;
|
||||
/** The raw configuration of the source */
|
||||
configuration: InfraSourceConfiguration;
|
||||
/** The status of the source */
|
||||
|
@ -1047,6 +1049,8 @@ export namespace InfraSourceFields {
|
|||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
|
||||
origin: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import { InfraFrontendLibs } from '../lib/lib';
|
|||
import { PageRouter } from '../routes';
|
||||
import { createStore } from '../store';
|
||||
import { ApolloClientContext } from '../utils/apollo_context';
|
||||
import { HistoryContext } from '../utils/history_context';
|
||||
import { useKibanaUiSetting } from '../utils/use_kibana_ui_setting';
|
||||
|
||||
export async function startApp(libs: InfraFrontendLibs) {
|
||||
|
@ -44,7 +45,9 @@ export async function startApp(libs: InfraFrontendLibs) {
|
|||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<PageRouter history={history} />
|
||||
<HistoryContext.Provider value={history}>
|
||||
<PageRouter history={history} />
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
|
|
|
@ -84,6 +84,11 @@ export const SourceConfigurationFlyout = injectI18n(
|
|||
]
|
||||
);
|
||||
|
||||
const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [
|
||||
shouldAllowEdit,
|
||||
source,
|
||||
]);
|
||||
|
||||
if (!isVisible || !source || !source.configuration) {
|
||||
return null;
|
||||
}
|
||||
|
@ -101,14 +106,14 @@ export const SourceConfigurationFlyout = injectI18n(
|
|||
<NameConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
nameFieldProps={indicesConfigurationProps.name}
|
||||
readOnly={!shouldAllowEdit}
|
||||
readOnly={!isWriteable}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<IndicesConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
logAliasFieldProps={indicesConfigurationProps.logAlias}
|
||||
metricAliasFieldProps={indicesConfigurationProps.metricAlias}
|
||||
readOnly={!shouldAllowEdit}
|
||||
readOnly={!isWriteable}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<FieldsConfigurationPanel
|
||||
|
@ -116,7 +121,7 @@ export const SourceConfigurationFlyout = injectI18n(
|
|||
hostFieldProps={indicesConfigurationProps.hostField}
|
||||
isLoading={isLoading}
|
||||
podFieldProps={indicesConfigurationProps.podField}
|
||||
readOnly={!shouldAllowEdit}
|
||||
readOnly={!isWriteable}
|
||||
tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField}
|
||||
timestampFieldProps={indicesConfigurationProps.timestampField}
|
||||
/>
|
||||
|
@ -153,7 +158,7 @@ export const SourceConfigurationFlyout = injectI18n(
|
|||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle>
|
||||
<h2 id="sourceConfigurationTitle">
|
||||
{shouldAllowEdit ? (
|
||||
{isWriteable ? (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
|
||||
defaultMessage="Configure source"
|
||||
|
@ -216,7 +221,7 @@ export const SourceConfigurationFlyout = injectI18n(
|
|||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
{shouldAllowEdit && (
|
||||
{isWriteable && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<EuiButton color="primary" isLoading fill>
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
import createContainer from 'constate-latest';
|
||||
import { isString } from 'lodash';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { FlyoutItemQuery, InfraLogItem } from '../../graphql/types';
|
||||
import { useApolloClient } from '../../utils/apollo_context';
|
||||
import { UrlStateContainer } from '../../utils/url_state';
|
||||
import { useTrackedPromise } from '../../utils/use_tracked_promise';
|
||||
import { Source } from '../source';
|
||||
import { flyoutItemQuery } from './flyout_item.gql_query';
|
||||
|
||||
export enum FlyoutVisibility {
|
||||
|
@ -24,7 +26,8 @@ interface FlyoutOptionsUrlState {
|
|||
surroundingLogsId?: string | null;
|
||||
}
|
||||
|
||||
export const useLogFlyout = ({ sourceId }: { sourceId: string }) => {
|
||||
export const useLogFlyout = () => {
|
||||
const { sourceId } = useContext(Source.Context);
|
||||
const [flyoutVisible, setFlyoutVisibility] = useState<boolean>(false);
|
||||
const [flyoutId, setFlyoutId] = useState<string | null>(null);
|
||||
const [flyoutItem, setFlyoutItem] = useState<InfraLogItem | null>(null);
|
||||
|
|
|
@ -9,6 +9,7 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { logFilterSelectors, logPositionSelectors, State } from '../../../store';
|
||||
import { RendererFunction } from '../../../utils/typed_react';
|
||||
import { Source } from '../../source';
|
||||
import { LogViewConfiguration } from '../log_view_configuration';
|
||||
import { LogSummaryBuckets, useLogSummary } from './log_summary';
|
||||
|
||||
|
@ -26,8 +27,9 @@ export const WithSummary = connect((state: State) => ({
|
|||
visibleMidpointTime: number | null;
|
||||
}) => {
|
||||
const { intervalSize } = useContext(LogViewConfiguration.Context);
|
||||
const { sourceId } = useContext(Source.Context);
|
||||
|
||||
const { buckets } = useLogSummary('default', visibleMidpointTime, intervalSize, filterQuery);
|
||||
const { buckets } = useLogSummary(sourceId, visibleMidpointTime, intervalSize, filterQuery);
|
||||
|
||||
return children({ buckets });
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
|
@ -24,6 +25,7 @@ export const withStreamItems = connect(
|
|||
bindPlainActionCreators({
|
||||
loadNewerEntries: logEntriesActions.loadNewerEntries,
|
||||
reloadEntries: logEntriesActions.reloadEntries,
|
||||
setSourceId: logEntriesActions.setSourceId,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -52,3 +54,26 @@ const createLogEntryStreamItem = (logEntry: LogEntry) => ({
|
|||
kind: 'logEntry' as 'logEntry',
|
||||
logEntry,
|
||||
});
|
||||
|
||||
/**
|
||||
* This component serves as connection between the state and side-effects
|
||||
* managed by redux and the state and effects managed by hooks. In particular,
|
||||
* it forwards changes of the source id to redux via the action creator
|
||||
* `setSourceId`.
|
||||
*
|
||||
* It will be mounted beneath the hierachy level where the redux store and the
|
||||
* source state are initialized. Once the log entry state and loading
|
||||
* side-effects have been migrated from redux to hooks it can be removed.
|
||||
*/
|
||||
export const ReduxSourceIdBridge = withStreamItems(
|
||||
({ setSourceId, sourceId }: { setSourceId: (sourceId: string) => void; sourceId: string }) => {
|
||||
useEffect(
|
||||
() => {
|
||||
setSourceId(sourceId);
|
||||
},
|
||||
[setSourceId, sourceId]
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -144,7 +144,7 @@ export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|||
() => {
|
||||
loadSource();
|
||||
},
|
||||
[loadSource]
|
||||
[loadSource, sourceId]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 * from './source_id';
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 * as runtimeTypes from 'io-ts';
|
||||
|
||||
import { useUrlState, replaceStateKeyInQueryString } from '../../utils/use_url_state';
|
||||
|
||||
const SOURCE_ID_URL_STATE_KEY = 'sourceId';
|
||||
|
||||
export const useSourceId = () => {
|
||||
return useUrlState({
|
||||
defaultState: 'default',
|
||||
decodeUrlState: decodeSourceIdUrlState,
|
||||
encodeUrlState: encodeSourceIdUrlState,
|
||||
urlStateKey: SOURCE_ID_URL_STATE_KEY,
|
||||
});
|
||||
};
|
||||
|
||||
export const replaceSourceIdInQueryString = (sourceId: string) =>
|
||||
replaceStateKeyInQueryString(SOURCE_ID_URL_STATE_KEY, sourceId);
|
||||
|
||||
const sourceIdRuntimeType = runtimeTypes.union([runtimeTypes.string, runtimeTypes.undefined]);
|
||||
const encodeSourceIdUrlState = sourceIdRuntimeType.encode;
|
||||
const decodeSourceIdUrlState = (value: unknown) =>
|
||||
sourceIdRuntimeType.decode(value).getOrElse(undefined);
|
|
@ -101,6 +101,18 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "origin",
|
||||
"description": "The origin of the source (one of 'fallback', 'internal', 'stored')",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "configuration",
|
||||
"description": "The raw configuration of the source",
|
||||
|
|
|
@ -22,6 +22,8 @@ export interface InfraSource {
|
|||
version?: string | null;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: number | null;
|
||||
/** The origin of the source (one of 'fallback', 'internal', 'stored') */
|
||||
origin: string;
|
||||
/** The raw configuration of the source */
|
||||
configuration: InfraSourceConfiguration;
|
||||
/** The status of the source */
|
||||
|
@ -1047,6 +1049,8 @@ export namespace InfraSourceFields {
|
|||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
|
||||
origin: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import React from 'react';
|
||||
import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { Source } from '../../containers/source';
|
||||
import { RedirectToLogs } from './redirect_to_logs';
|
||||
import { RedirectToNodeDetail } from './redirect_to_node_detail';
|
||||
import { RedirectToNodeLogs } from './redirect_to_node_logs';
|
||||
|
@ -21,20 +20,18 @@ export class LinkToPage extends React.Component<LinkToPageProps> {
|
|||
const { match } = this.props;
|
||||
|
||||
return (
|
||||
<Source.Provider sourceId="default">
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeId`}
|
||||
component={RedirectToNodeDetail}
|
||||
/>
|
||||
<Route path={`${match.url}/logs`} component={RedirectToLogs} />
|
||||
<Redirect to="/infrastructure" />
|
||||
</Switch>
|
||||
</Source.Provider>
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/:sourceId?/:nodeType(host|container|pod)-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeId`}
|
||||
component={RedirectToNodeDetail}
|
||||
/>
|
||||
<Route path={`${match.url}/:sourceId?/logs`} component={RedirectToLogs} />
|
||||
<Redirect to="/infrastructure" />
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,11 @@ describe('RedirectToLogs component', () => {
|
|||
const component = shallowWithIntl(
|
||||
<RedirectToLogs {...createRouteComponentProps('/logs?time=1550671089404')} />
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))"
|
||||
to="/logs?logFilter=(expression:'',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -32,32 +31,33 @@ describe('RedirectToLogs component', () => {
|
|||
{...createRouteComponentProps('/logs?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE')}
|
||||
/>
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))"
|
||||
to="/logs?logFilter=(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct custom source id', () => {
|
||||
const component = shallowWithIntl(
|
||||
<RedirectToLogs {...createRouteComponentProps('/SOME-OTHER-SOURCE/logs')} />
|
||||
).dive();
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'',kind:kuery)&sourceId=SOME-OTHER-SOURCE"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
const testSourceChildArgs = {
|
||||
configuration: {
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
pod: 'POD_FIELD',
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const createRouteComponentProps = (path: string) => {
|
||||
const location = createLocation(path);
|
||||
return {
|
||||
match: matchPath(location.pathname, { path: '/logs' }) as any,
|
||||
match: matchPath(location.pathname, { path: '/:sourceId?/logs' }) as any,
|
||||
history: null as any,
|
||||
location,
|
||||
};
|
||||
|
|
|
@ -7,44 +7,30 @@
|
|||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import compose from 'lodash/fp/compose';
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter';
|
||||
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
|
||||
type RedirectToLogsType = RouteComponentProps<{}>;
|
||||
|
||||
interface RedirectToLogsProps extends RedirectToLogsType {
|
||||
match: RouteMatch<{
|
||||
sourceId?: string;
|
||||
}>;
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export const RedirectToLogs = injectI18n(({ location, intl }: RedirectToLogsProps) => (
|
||||
<WithSource>
|
||||
{({ configuration, isLoading }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPage
|
||||
message={intl.formatMessage({
|
||||
id: 'xpack.infra.redirectToLogs.loadingLogsMessage',
|
||||
defaultMessage: 'Loading logs',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export const RedirectToLogs = injectI18n(({ location, match }: RedirectToLogsProps) => {
|
||||
const sourceId = match.params.sourceId || 'default';
|
||||
|
||||
if (!configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filter = getFilterFromLocation(location);
|
||||
const searchString = compose(
|
||||
replaceLogFilterInQueryString(filter),
|
||||
replaceLogPositionInQueryString(getTimeFromLocation(location))
|
||||
)('');
|
||||
return <Redirect to={`/logs?${searchString}`} />;
|
||||
}}
|
||||
</WithSource>
|
||||
));
|
||||
const filter = getFilterFromLocation(location);
|
||||
const searchString = compose(
|
||||
replaceLogFilterInQueryString(filter),
|
||||
replaceLogPositionInQueryString(getTimeFromLocation(location)),
|
||||
replaceSourceIdInQueryString(sourceId)
|
||||
)('');
|
||||
return <Redirect to={`/logs?${searchString}`} />;
|
||||
});
|
||||
|
|
|
@ -11,17 +11,32 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
|||
|
||||
import { RedirectToNodeLogs } from './redirect_to_node_logs';
|
||||
|
||||
jest.mock('../../containers/source/source', () => ({
|
||||
useSource: ({ sourceId }: { sourceId: string }) => ({
|
||||
sourceId,
|
||||
source: {
|
||||
configuration: {
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
pod: 'POD_FIELD',
|
||||
},
|
||||
},
|
||||
},
|
||||
isLoading: sourceId === 'perpetuallyLoading',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RedirectToNodeLogs component', () => {
|
||||
it('renders a redirect with the correct host filter', () => {
|
||||
const component = shallowWithIntl(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/host-logs/HOST_NAME')} />
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -30,12 +45,11 @@ describe('RedirectToNodeLogs component', () => {
|
|||
const component = shallowWithIntl(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/container-logs/CONTAINER_ID')} />
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)"
|
||||
to="/logs?logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -44,12 +58,11 @@ describe('RedirectToNodeLogs component', () => {
|
|||
const component = shallowWithIntl(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/pod-logs/POD_ID')} />
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)"
|
||||
to="/logs?logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -60,12 +73,11 @@ describe('RedirectToNodeLogs component', () => {
|
|||
{...createRouteComponentProps('/host-logs/HOST_NAME?time=1550671089404')}
|
||||
/>
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))"
|
||||
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
@ -78,32 +90,35 @@ describe('RedirectToNodeLogs component', () => {
|
|||
)}
|
||||
/>
|
||||
).dive();
|
||||
const withSourceChildFunction = component.prop('children') as any;
|
||||
|
||||
expect(withSourceChildFunction(testSourceChildArgs)).toMatchInlineSnapshot(`
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))"
|
||||
to="/logs?logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404))&sourceId=default"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct custom source id', () => {
|
||||
const component = shallowWithIntl(
|
||||
<RedirectToNodeLogs
|
||||
{...createRouteComponentProps('/SOME-OTHER-SOURCE/host-logs/HOST_NAME')}
|
||||
/>
|
||||
).dive();
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
push={false}
|
||||
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&sourceId=SOME-OTHER-SOURCE"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
const testSourceChildArgs = {
|
||||
configuration: {
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
pod: 'POD_FIELD',
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
const createRouteComponentProps = (path: string) => {
|
||||
const location = createLocation(path);
|
||||
return {
|
||||
match: matchPath(location.pathname, { path: '/:nodeType-logs/:nodeId' }) as any,
|
||||
match: matchPath(location.pathname, { path: '/:sourceId?/:nodeType-logs/:nodeId' }) as any,
|
||||
history: null as any,
|
||||
location,
|
||||
};
|
||||
|
|
|
@ -12,13 +12,15 @@ import { Redirect, RouteComponentProps } from 'react-router-dom';
|
|||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter';
|
||||
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { InfraNodeType } from '../../graphql/types';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
import { useSource } from '../../containers/source/source';
|
||||
|
||||
type RedirectToNodeLogsType = RouteComponentProps<{
|
||||
nodeId: string;
|
||||
nodeType: InfraNodeType;
|
||||
sourceId?: string;
|
||||
}>;
|
||||
|
||||
interface RedirectToNodeLogsProps extends RedirectToNodeLogsType {
|
||||
|
@ -28,46 +30,46 @@ interface RedirectToNodeLogsProps extends RedirectToNodeLogsType {
|
|||
export const RedirectToNodeLogs = injectI18n(
|
||||
({
|
||||
match: {
|
||||
params: { nodeId, nodeType },
|
||||
params: { nodeId, nodeType, sourceId = 'default' },
|
||||
},
|
||||
location,
|
||||
intl,
|
||||
}: RedirectToNodeLogsProps) => (
|
||||
<WithSource>
|
||||
{({ configuration, isLoading }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPage
|
||||
message={intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.redirectToNodeLogs.loadingNodeLogsMessage',
|
||||
defaultMessage: 'Loading {nodeType} logs',
|
||||
},
|
||||
{
|
||||
nodeType,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}: RedirectToNodeLogsProps) => {
|
||||
const { source, isLoading } = useSource({ sourceId });
|
||||
const configuration = source && source.configuration;
|
||||
|
||||
if (!configuration) {
|
||||
return null;
|
||||
}
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPage
|
||||
message={intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.redirectToNodeLogs.loadingNodeLogsMessage',
|
||||
defaultMessage: 'Loading {nodeType} logs',
|
||||
},
|
||||
{
|
||||
nodeType,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const nodeFilter = `${configuration.fields[nodeType]}: ${nodeId}`;
|
||||
const userFilter = getFilterFromLocation(location);
|
||||
const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter;
|
||||
if (!configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const searchString = compose(
|
||||
replaceLogFilterInQueryString(filter),
|
||||
replaceLogPositionInQueryString(getTimeFromLocation(location))
|
||||
)('');
|
||||
const nodeFilter = `${configuration.fields[nodeType]}: ${nodeId}`;
|
||||
const userFilter = getFilterFromLocation(location);
|
||||
const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter;
|
||||
|
||||
return <Redirect to={`/logs?${searchString}`} />;
|
||||
}}
|
||||
</WithSource>
|
||||
)
|
||||
const searchString = compose(
|
||||
replaceLogFilterInQueryString(filter),
|
||||
replaceLogPositionInQueryString(getTimeFromLocation(location)),
|
||||
replaceSourceIdInQueryString(sourceId)
|
||||
)('');
|
||||
|
||||
return <Redirect to={`/logs?${searchString}`} />;
|
||||
}
|
||||
);
|
||||
|
||||
export const getNodeLogsUrl = ({
|
||||
|
|
|
@ -24,7 +24,7 @@ import { WithLogMinimapUrlState } from '../../containers/logs/with_log_minimap';
|
|||
import { WithLogPositionUrlState } from '../../containers/logs/with_log_position';
|
||||
import { WithLogPosition } from '../../containers/logs/with_log_position';
|
||||
import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview';
|
||||
import { WithStreamItems } from '../../containers/logs/with_stream_items';
|
||||
import { ReduxSourceIdBridge, WithStreamItems } from '../../containers/logs/with_stream_items';
|
||||
import { Source } from '../../containers/source';
|
||||
|
||||
import { LogsToolbar } from './page_toolbar';
|
||||
|
@ -44,6 +44,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<ReduxSourceIdBridge sourceId={sourceId} />
|
||||
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithLogPositionUrlState />
|
||||
<WithLogMinimapUrlState />
|
||||
|
|
|
@ -10,13 +10,18 @@ import { SourceConfigurationFlyoutState } from '../../components/source_configur
|
|||
import { LogFlyout } from '../../containers/logs/log_flyout';
|
||||
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
|
||||
import { Source } from '../../containers/source';
|
||||
import { useSourceId } from '../../containers/source_id';
|
||||
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => (
|
||||
<Source.Provider sourceId="default">
|
||||
<SourceConfigurationFlyoutState.Provider>
|
||||
<LogViewConfiguration.Provider>
|
||||
<LogFlyout.Provider sourceId="default">{children}</LogFlyout.Provider>
|
||||
</LogViewConfiguration.Provider>
|
||||
</SourceConfigurationFlyoutState.Provider>
|
||||
</Source.Provider>
|
||||
);
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
|
||||
const [sourceId] = useSourceId();
|
||||
|
||||
return (
|
||||
<Source.Provider sourceId={sourceId}>
|
||||
<SourceConfigurationFlyoutState.Provider>
|
||||
<LogViewConfiguration.Provider>
|
||||
<LogFlyout.Provider>{children}</LogFlyout.Provider>
|
||||
</LogViewConfiguration.Provider>
|
||||
</SourceConfigurationFlyoutState.Provider>
|
||||
</Source.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@ import { loadMoreEntriesActionCreators } from './operations/load_more';
|
|||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/remote/log_entries');
|
||||
|
||||
export const setSourceId = actionCreator<string>('SET_SOURCE_ID');
|
||||
|
||||
export const loadEntries = loadEntriesActionCreators.resolve;
|
||||
export const loadMoreEntries = loadMoreEntriesActionCreators.resolve;
|
||||
|
||||
|
|
|
@ -11,7 +11,13 @@ import { exhaustMap, filter, map, withLatestFrom } from 'rxjs/operators';
|
|||
|
||||
import { logFilterActions, logPositionActions } from '../..';
|
||||
import { pickTimeKey, TimeKey, timeKeyIsBetween } from '../../../../common/time';
|
||||
import { loadEntries, loadMoreEntries, loadNewerEntries, reloadEntries } from './actions';
|
||||
import {
|
||||
loadEntries,
|
||||
loadMoreEntries,
|
||||
loadNewerEntries,
|
||||
reloadEntries,
|
||||
setSourceId,
|
||||
} from './actions';
|
||||
import { loadEntriesEpic } from './operations/load';
|
||||
import { loadMoreEntriesEpic } from './operations/load_more';
|
||||
|
||||
|
@ -62,6 +68,11 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
map(pickTimeKey)
|
||||
);
|
||||
|
||||
const sourceId$ = action$.pipe(
|
||||
filter(setSourceId.match),
|
||||
map(({ payload }) => payload)
|
||||
);
|
||||
|
||||
const shouldLoadAroundNewPosition$ = action$.pipe(
|
||||
filter(logPositionActions.jumpToTargetPosition.match),
|
||||
withLatestFrom(state$),
|
||||
|
@ -81,7 +92,7 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
withLatestFrom(filterQuery$, (filterQuery, filterQueryString) => filterQueryString)
|
||||
);
|
||||
|
||||
const shouldReload$ = action$.pipe(filter(reloadEntries.match));
|
||||
const shouldReload$ = merge(action$.pipe(filter(reloadEntries.match)), sourceId$);
|
||||
|
||||
const shouldLoadMoreBefore$ = action$.pipe(
|
||||
filter(logPositionActions.reportVisiblePositions.match),
|
||||
|
@ -122,10 +133,10 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
|
||||
return merge(
|
||||
shouldLoadAroundNewPosition$.pipe(
|
||||
withLatestFrom(filterQuery$),
|
||||
exhaustMap(([timeKey, filterQuery]) => [
|
||||
withLatestFrom(filterQuery$, sourceId$),
|
||||
exhaustMap(([timeKey, filterQuery, sourceId]) => [
|
||||
loadEntries({
|
||||
sourceId: 'default',
|
||||
sourceId,
|
||||
timeKey,
|
||||
countBefore: LOAD_CHUNK_SIZE,
|
||||
countAfter: LOAD_CHUNK_SIZE,
|
||||
|
@ -134,10 +145,10 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
])
|
||||
),
|
||||
shouldLoadWithNewFilter$.pipe(
|
||||
withLatestFrom(visibleMidpointOrTarget$),
|
||||
exhaustMap(([filterQuery, timeKey]) => [
|
||||
withLatestFrom(visibleMidpointOrTarget$, sourceId$),
|
||||
exhaustMap(([filterQuery, timeKey, sourceId]) => [
|
||||
loadEntries({
|
||||
sourceId: 'default',
|
||||
sourceId,
|
||||
timeKey,
|
||||
countBefore: LOAD_CHUNK_SIZE,
|
||||
countAfter: LOAD_CHUNK_SIZE,
|
||||
|
@ -146,10 +157,10 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
])
|
||||
),
|
||||
shouldReload$.pipe(
|
||||
withLatestFrom(visibleMidpointOrTarget$, filterQuery$),
|
||||
exhaustMap(([_, timeKey, filterQuery]) => [
|
||||
withLatestFrom(visibleMidpointOrTarget$, filterQuery$, sourceId$),
|
||||
exhaustMap(([_, timeKey, filterQuery, sourceId]) => [
|
||||
loadEntries({
|
||||
sourceId: 'default',
|
||||
sourceId,
|
||||
timeKey,
|
||||
countBefore: LOAD_CHUNK_SIZE,
|
||||
countAfter: LOAD_CHUNK_SIZE,
|
||||
|
@ -158,10 +169,10 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
])
|
||||
),
|
||||
shouldLoadMoreAfter$.pipe(
|
||||
withLatestFrom(filterQuery$),
|
||||
exhaustMap(([timeKey, filterQuery]) => [
|
||||
withLatestFrom(filterQuery$, sourceId$),
|
||||
exhaustMap(([timeKey, filterQuery, sourceId]) => [
|
||||
loadMoreEntries({
|
||||
sourceId: 'default',
|
||||
sourceId,
|
||||
timeKey,
|
||||
countBefore: 0,
|
||||
countAfter: LOAD_CHUNK_SIZE,
|
||||
|
@ -170,10 +181,10 @@ export const createEntriesEffectsEpic = <State>(): Epic<
|
|||
])
|
||||
),
|
||||
shouldLoadMoreBefore$.pipe(
|
||||
withLatestFrom(filterQuery$),
|
||||
exhaustMap(([timeKey, filterQuery]) => [
|
||||
withLatestFrom(filterQuery$, sourceId$),
|
||||
exhaustMap(([timeKey, filterQuery, sourceId]) => [
|
||||
loadMoreEntries({
|
||||
sourceId: 'default',
|
||||
sourceId,
|
||||
timeKey,
|
||||
countBefore: LOAD_CHUNK_SIZE,
|
||||
countAfter: 0,
|
||||
|
|
14
x-pack/plugins/infra/public/utils/history_context.ts
Normal file
14
x-pack/plugins/infra/public/utils/history_context.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { createContext, useContext } from 'react';
|
||||
import { History } from 'history';
|
||||
|
||||
export const HistoryContext = createContext<History | undefined>(undefined);
|
||||
|
||||
export const useHistory = () => {
|
||||
return useContext(HistoryContext);
|
||||
};
|
116
x-pack/plugins/infra/public/utils/use_url_state.ts
Normal file
116
x-pack/plugins/infra/public/utils/use_url_state.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { Location } from 'history';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { decode, encode, RisonValue } from 'rison-node';
|
||||
|
||||
import { QueryString } from 'ui/utils/query_string';
|
||||
import { useHistory } from './history_context';
|
||||
|
||||
export const useUrlState = <State>({
|
||||
defaultState,
|
||||
decodeUrlState,
|
||||
encodeUrlState,
|
||||
urlStateKey,
|
||||
}: {
|
||||
defaultState: State;
|
||||
decodeUrlState: (value: RisonValue | undefined) => State | undefined;
|
||||
encodeUrlState: (value: State) => RisonValue | undefined;
|
||||
urlStateKey: string;
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
|
||||
const urlStateString = useMemo(
|
||||
() => {
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getParamFromQueryString(getQueryStringFromLocation(history.location), urlStateKey);
|
||||
},
|
||||
[history && history.location, urlStateKey]
|
||||
);
|
||||
|
||||
const decodedState = useMemo(() => decodeUrlState(decodeRisonUrlState(urlStateString)), [
|
||||
decodeUrlState,
|
||||
urlStateString,
|
||||
]);
|
||||
|
||||
const state = useMemo(() => (typeof decodedState !== 'undefined' ? decodedState : defaultState), [
|
||||
defaultState,
|
||||
decodedState,
|
||||
]);
|
||||
|
||||
const setState = useCallback(
|
||||
(newState: State | undefined) => {
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = history.location;
|
||||
|
||||
const newLocation = replaceQueryStringInLocation(
|
||||
location,
|
||||
replaceStateKeyInQueryString(
|
||||
urlStateKey,
|
||||
typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined
|
||||
)(getQueryStringFromLocation(location))
|
||||
);
|
||||
|
||||
if (newLocation !== location) {
|
||||
history.replace(newLocation);
|
||||
}
|
||||
},
|
||||
[encodeUrlState, history, history && history.location, urlStateKey]
|
||||
);
|
||||
|
||||
return [state, setState] as [typeof state, typeof setState];
|
||||
};
|
||||
|
||||
const decodeRisonUrlState = (value: string | undefined): RisonValue | undefined => {
|
||||
try {
|
||||
return value ? decode(value) : undefined;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith('rison decoder error')) {
|
||||
return {};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const encodeRisonUrlState = (state: any) => encode(state);
|
||||
|
||||
const getQueryStringFromLocation = (location: Location) => location.search.substring(1);
|
||||
|
||||
const getParamFromQueryString = (queryString: string, key: string): string | undefined => {
|
||||
const queryParam = QueryString.decode(queryString)[key];
|
||||
return Array.isArray(queryParam) ? queryParam[0] : queryParam;
|
||||
};
|
||||
|
||||
export const replaceStateKeyInQueryString = <UrlState extends any>(
|
||||
stateKey: string,
|
||||
urlState: UrlState | undefined
|
||||
) => (queryString: string) => {
|
||||
const previousQueryValues = QueryString.decode(queryString);
|
||||
const encodedUrlState =
|
||||
typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined;
|
||||
return QueryString.encode({
|
||||
...previousQueryValues,
|
||||
[stateKey]: encodedUrlState,
|
||||
});
|
||||
};
|
||||
|
||||
const replaceQueryStringInLocation = (location: Location, queryString: string): Location => {
|
||||
if (queryString === getQueryStringFromLocation(location)) {
|
||||
return location;
|
||||
} else {
|
||||
return {
|
||||
...location,
|
||||
search: `?${queryString}`,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -15,6 +15,8 @@ export const sourcesSchema = gql`
|
|||
version: String
|
||||
"The timestamp the source configuration was last persisted at"
|
||||
updatedAt: Float
|
||||
"The origin of the source (one of 'fallback', 'internal', 'stored')"
|
||||
origin: String!
|
||||
"The raw configuration of the source"
|
||||
configuration: InfraSourceConfiguration!
|
||||
"The status of the source"
|
||||
|
|
|
@ -50,6 +50,8 @@ export interface InfraSource {
|
|||
version?: string | null;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: number | null;
|
||||
/** The origin of the source (one of 'fallback', 'internal', 'stored') */
|
||||
origin: string;
|
||||
/** The raw configuration of the source */
|
||||
configuration: InfraSourceConfiguration;
|
||||
/** The status of the source */
|
||||
|
@ -627,6 +629,8 @@ export namespace InfraSourceResolvers {
|
|||
version?: VersionResolver<string | null, TypeParent, Context>;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: UpdatedAtResolver<number | null, TypeParent, Context>;
|
||||
/** The origin of the source (one of 'fallback', 'internal', 'stored') */
|
||||
origin?: OriginResolver<string, TypeParent, Context>;
|
||||
/** The raw configuration of the source */
|
||||
configuration?: ConfigurationResolver<InfraSourceConfiguration, TypeParent, Context>;
|
||||
/** The status of the source */
|
||||
|
@ -662,6 +666,11 @@ export namespace InfraSourceResolvers {
|
|||
Parent = InfraSource,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type OriginResolver<R = string, Parent = InfraSource, Context = InfraContext> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type ConfigurationResolver<
|
||||
R = InfraSourceConfiguration,
|
||||
Parent = InfraSource,
|
||||
|
|
|
@ -19,6 +19,11 @@ export const initServerWithKibana = (kbnServer: KbnServer) => {
|
|||
const libs = compose(kbnServer);
|
||||
initInfraServer(libs);
|
||||
|
||||
kbnServer.expose(
|
||||
'defineInternalSourceConfiguration',
|
||||
libs.sources.defineInternalSourceConfiguration.bind(libs.sources)
|
||||
);
|
||||
|
||||
// Register a function with server to manage the collection of usage stats
|
||||
kbnServer.usage.collectorSet.register(UsageCollector.getUsageCollector(kbnServer));
|
||||
|
||||
|
|
12
x-pack/plugins/infra/server/lib/sources/errors.ts
Normal file
12
x-pack/plugins/infra/server/lib/sources/errors.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 class NotFoundError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import { Pick3 } from '../../../common/utility_types';
|
|||
import { InfraConfigurationAdapter } from '../adapters/configuration';
|
||||
import { InfraFrameworkRequest, internalInfraFrameworkRequest } from '../adapters/framework';
|
||||
import { defaultSourceConfiguration } from './defaults';
|
||||
import { NotFoundError } from './errors';
|
||||
import { infraSourceConfigurationSavedObjectType } from './saved_object_mappings';
|
||||
import {
|
||||
InfraSavedSourceConfiguration,
|
||||
|
@ -23,6 +24,8 @@ import {
|
|||
} from './types';
|
||||
|
||||
export class InfraSources {
|
||||
private internalSourceConfigurations: Map<string, InfraStaticSourceConfiguration> = new Map();
|
||||
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
configuration: InfraConfigurationAdapter;
|
||||
|
@ -34,24 +37,39 @@ export class InfraSources {
|
|||
public async getSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) {
|
||||
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
|
||||
|
||||
const savedSourceConfiguration = await this.getSavedSourceConfiguration(request, sourceId).then(
|
||||
result => ({
|
||||
...result,
|
||||
const savedSourceConfiguration = await this.getInternalSourceConfiguration(sourceId)
|
||||
.then(internalSourceConfiguration => ({
|
||||
id: sourceId,
|
||||
version: undefined,
|
||||
updatedAt: undefined,
|
||||
origin: 'internal' as 'internal',
|
||||
configuration: mergeSourceConfiguration(
|
||||
staticDefaultSourceConfiguration,
|
||||
result.configuration
|
||||
internalSourceConfiguration
|
||||
),
|
||||
}),
|
||||
err =>
|
||||
}))
|
||||
.catch(err =>
|
||||
err instanceof NotFoundError
|
||||
? this.getSavedSourceConfiguration(request, sourceId).then(result => ({
|
||||
...result,
|
||||
configuration: mergeSourceConfiguration(
|
||||
staticDefaultSourceConfiguration,
|
||||
result.configuration
|
||||
),
|
||||
}))
|
||||
: Promise.reject(err)
|
||||
)
|
||||
.catch(err =>
|
||||
this.libs.savedObjects.SavedObjectsClient.errors.isNotFoundError(err)
|
||||
? Promise.resolve({
|
||||
id: sourceId,
|
||||
version: undefined,
|
||||
updatedAt: undefined,
|
||||
origin: 'fallback' as 'fallback',
|
||||
configuration: staticDefaultSourceConfiguration,
|
||||
})
|
||||
: Promise.reject(err)
|
||||
);
|
||||
);
|
||||
|
||||
return savedSourceConfiguration;
|
||||
}
|
||||
|
@ -143,6 +161,25 @@ export class InfraSources {
|
|||
};
|
||||
}
|
||||
|
||||
public async defineInternalSourceConfiguration(
|
||||
sourceId: string,
|
||||
sourceProperties: InfraStaticSourceConfiguration
|
||||
) {
|
||||
this.internalSourceConfigurations.set(sourceId, sourceProperties);
|
||||
}
|
||||
|
||||
public async getInternalSourceConfiguration(sourceId: string) {
|
||||
const internalSourceConfiguration = this.internalSourceConfigurations.get(sourceId);
|
||||
|
||||
if (!internalSourceConfiguration) {
|
||||
throw new NotFoundError(
|
||||
`Failed to load internal source configuration: no configuration "${sourceId}" found.`
|
||||
);
|
||||
}
|
||||
|
||||
return internalSourceConfiguration;
|
||||
}
|
||||
|
||||
private async getStaticDefaultSourceConfiguration() {
|
||||
const staticConfiguration = await this.libs.configuration.get();
|
||||
const staticSourceConfiguration = runtimeTypes
|
||||
|
@ -206,6 +243,7 @@ const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknown) =>
|
|||
id: savedSourceConfiguration.id,
|
||||
version: savedSourceConfiguration.version,
|
||||
updatedAt: savedSourceConfiguration.updated_at,
|
||||
origin: 'stored' as 'stored',
|
||||
configuration: savedSourceConfiguration.attributes,
|
||||
}))
|
||||
.getOrElseL(errors => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue