mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Discover] Add default app state extension and log integration data source profiles (#186347)
## Summary This PR adds a new `getDefaultAppState` extension that allows profiles to set default values for select app state properties, currently `columns` and `rowHeight`. It also adds logs data source sub profiles for the following integrations that consume the `getDefaultAppState` extension (only `columns` are used currently): - System logs - Kubernetes container logs - Windows logs - AWS S3 Logs - Nginx error logs - Nginx access logs - Apache error logs The index patterns and default state for the integrations are hardcoded for the initial implementation, but we should change this later to use an API and state provided by the integrations if we continue this approach. For testing, you can ingest sample data for the integrations using https://github.com/elastic/kibana-demo-data, but you'll need to reindex the data into correctly named data streams for each: ``` log-system_error -> logs-system.system-test log-k8s_container -> logs-kubernetes.container_logs-test log-aws_s3 -> logs-aws.s3access-test log-nginx_error -> logs-nginx.error-test log-nqinx -> logs-nginx.access-test log-apache_error -> logs-apache.error-test POST /_reindex { "source": { "index": "log-k8s_container" }, "dest": { "index": "logs-kubernetes.container_logs-test", "op_type": "create" } } ```  Resolves #186271. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Julia Rechkunova <julia.rechkunova@elastic.co>
This commit is contained in:
parent
fde808999b
commit
ea71c10037
61 changed files with 2141 additions and 282 deletions
|
@ -73,6 +73,10 @@ describe('test fetchAll', () => {
|
|||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
},
|
||||
}),
|
||||
searchSessionId: '123',
|
||||
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
||||
|
@ -261,6 +265,10 @@ describe('test fetchAll', () => {
|
|||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
},
|
||||
}),
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
|
@ -379,6 +387,10 @@ describe('test fetchAll', () => {
|
|||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
},
|
||||
}),
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { Adapters } from '@kbn/inspector-plugin/common';
|
||||
import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public';
|
||||
import { BehaviorSubject, filter, firstValueFrom, map, merge, scan } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, filter, firstValueFrom, switchMap } from 'rxjs';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
|
@ -53,7 +53,8 @@ export interface FetchDeps {
|
|||
export function fetchAll(
|
||||
dataSubjects: SavedSearchData,
|
||||
reset = false,
|
||||
fetchDeps: FetchDeps
|
||||
fetchDeps: FetchDeps,
|
||||
onFetchRecordsComplete?: () => Promise<void>
|
||||
): Promise<void> {
|
||||
const {
|
||||
initialFetchStatus,
|
||||
|
@ -177,10 +178,10 @@ export function fetchAll(
|
|||
|
||||
// Return a promise that will resolve once all the requests have finished or failed
|
||||
return firstValueFrom(
|
||||
merge(
|
||||
fetchStatusByType(dataSubjects.documents$, 'documents'),
|
||||
fetchStatusByType(dataSubjects.totalHits$, 'totalHits')
|
||||
).pipe(scan(toRequestFinishedMap, {}), filter(allRequestsFinished))
|
||||
combineLatest([
|
||||
isComplete(dataSubjects.documents$).pipe(switchMap(async () => onFetchRecordsComplete?.())),
|
||||
isComplete(dataSubjects.totalHits$),
|
||||
])
|
||||
).then(() => {
|
||||
// Send a complete message to main$ once all queries are done and if main$
|
||||
// is not already in an ERROR state, e.g. because the document query has failed.
|
||||
|
@ -250,16 +251,8 @@ export async function fetchMoreDocuments(
|
|||
}
|
||||
}
|
||||
|
||||
const fetchStatusByType = <T extends DataMsg>(subject: BehaviorSubject<T>, type: string) =>
|
||||
subject.pipe(map(({ fetchStatus }) => ({ type, fetchStatus })));
|
||||
|
||||
const toRequestFinishedMap = (
|
||||
currentMap: Record<string, boolean>,
|
||||
{ type, fetchStatus }: { type: string; fetchStatus: FetchStatus }
|
||||
) => ({
|
||||
...currentMap,
|
||||
[type]: [FetchStatus.COMPLETE, FetchStatus.ERROR].includes(fetchStatus),
|
||||
});
|
||||
|
||||
const allRequestsFinished = (requests: Record<string, boolean>) =>
|
||||
Object.values(requests).every((finished) => finished);
|
||||
const isComplete = <T extends DataMsg>(subject: BehaviorSubject<T>) => {
|
||||
return subject.pipe(
|
||||
filter(({ fetchStatus }) => [FetchStatus.COMPLETE, FetchStatus.ERROR].includes(fetchStatus))
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ import { DiscoverAppState } from '../state_management/discover_app_state_contain
|
|||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { VIEW_MODE } from '@kbn/saved-search-plugin/public';
|
||||
import { dataViewAdHoc } from '../../../__mocks__/data_view_complex';
|
||||
import { buildDataTableRecord, EsHitRecord } from '@kbn/discover-utils';
|
||||
|
||||
function getHookProps(
|
||||
query: AggregateQuery | Query | undefined,
|
||||
|
@ -487,4 +488,95 @@ describe('useEsqlMode', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly when index pattern changes', async () => {
|
||||
const { stateContainer } = renderHookWithContext(false);
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
})
|
||||
);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
})
|
||||
);
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern2' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly when columns change', async () => {
|
||||
const { stateContainer } = renderHookWithContext(false);
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const result1 = [buildDataTableRecord({ message: 'foo' } as EsHitRecord)];
|
||||
const result2 = [buildDataTableRecord({ message: 'foo', extension: 'bar' } as EsHitRecord)];
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern' },
|
||||
result: result1,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
})
|
||||
);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern' },
|
||||
result: result1,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
})
|
||||
);
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern' },
|
||||
result: result2,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import { getValidViewMode } from '../utils/get_valid_view_mode';
|
|||
import { FetchStatus } from '../../types';
|
||||
|
||||
const MAX_NUM_OF_COLUMNS = 50;
|
||||
|
||||
/**
|
||||
* Hook to take care of ES|QL state transformations when a new result is returned
|
||||
* If necessary this is setting displayed columns and selected data view
|
||||
|
@ -29,106 +30,122 @@ export function useEsqlMode({
|
|||
stateContainer: DiscoverStateContainer;
|
||||
dataViews: DataViewsContract;
|
||||
}) {
|
||||
const prev = useRef<{
|
||||
query: string;
|
||||
recentlyUpdatedToColumns: string[];
|
||||
}>({
|
||||
recentlyUpdatedToColumns: [],
|
||||
query: '',
|
||||
});
|
||||
const initialFetch = useRef<boolean>(true);
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const prev = useRef<{
|
||||
initialFetch: boolean;
|
||||
query: string;
|
||||
allColumns: string[];
|
||||
defaultColumns: string[];
|
||||
}>({
|
||||
initialFetch: true,
|
||||
query: '',
|
||||
allColumns: [],
|
||||
defaultColumns: [],
|
||||
});
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
if (prev.current.query) {
|
||||
// cleanup when it's not an ES|QL query
|
||||
prev.current = {
|
||||
recentlyUpdatedToColumns: [],
|
||||
query: '',
|
||||
};
|
||||
initialFetch.current = true;
|
||||
if (!prev.current.query) {
|
||||
return;
|
||||
}
|
||||
|
||||
// cleanup when it's not an ES|QL query
|
||||
prev.current = {
|
||||
initialFetch: true,
|
||||
query: '',
|
||||
allColumns: [],
|
||||
defaultColumns: [],
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = stateContainer.dataState.data$.documents$
|
||||
.pipe(
|
||||
switchMap(async (next) => {
|
||||
const { query } = next;
|
||||
if (!query || next.fetchStatus === FetchStatus.ERROR) {
|
||||
const { query: nextQuery } = next;
|
||||
|
||||
if (!nextQuery || next.fetchStatus === FetchStatus.ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sendComplete = () => {
|
||||
stateContainer.dataState.data$.documents$.next({
|
||||
...next,
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
});
|
||||
};
|
||||
|
||||
const { viewMode } = stateContainer.appState.getState();
|
||||
const isEsqlQuery = isOfAggregateQueryType(query);
|
||||
|
||||
if (isEsqlQuery) {
|
||||
const hasResults = Boolean(next.result?.length);
|
||||
|
||||
if (next.fetchStatus !== FetchStatus.PARTIAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextColumns: string[] = prev.current.recentlyUpdatedToColumns;
|
||||
|
||||
if (hasResults) {
|
||||
const firstRow = next.result![0];
|
||||
const firstRowColumns = Object.keys(firstRow.raw);
|
||||
|
||||
if (hasTransformationalCommand(query.esql)) {
|
||||
nextColumns = firstRowColumns.slice(0, MAX_NUM_OF_COLUMNS);
|
||||
} else {
|
||||
nextColumns = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (initialFetch.current) {
|
||||
initialFetch.current = false;
|
||||
prev.current.query = query.esql;
|
||||
prev.current.recentlyUpdatedToColumns = nextColumns;
|
||||
}
|
||||
|
||||
const indexPatternChanged =
|
||||
getIndexPatternFromESQLQuery(query.esql) !==
|
||||
getIndexPatternFromESQLQuery(prev.current.query);
|
||||
|
||||
const addColumnsToState =
|
||||
indexPatternChanged || !isEqual(nextColumns, prev.current.recentlyUpdatedToColumns);
|
||||
|
||||
const changeViewMode = viewMode !== getValidViewMode({ viewMode, isEsqlMode: true });
|
||||
|
||||
if (!indexPatternChanged && !addColumnsToState && !changeViewMode) {
|
||||
sendComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
prev.current.query = query.esql;
|
||||
prev.current.recentlyUpdatedToColumns = nextColumns;
|
||||
|
||||
// just change URL state if necessary
|
||||
if (addColumnsToState || changeViewMode) {
|
||||
const nextState = {
|
||||
...(addColumnsToState && { columns: nextColumns }),
|
||||
...(changeViewMode && { viewMode: undefined }),
|
||||
};
|
||||
await stateContainer.appState.replaceUrlState(nextState);
|
||||
}
|
||||
|
||||
sendComplete();
|
||||
} else {
|
||||
if (!isOfAggregateQueryType(nextQuery)) {
|
||||
// cleanup for a "regular" query
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (next.fetchStatus !== FetchStatus.PARTIAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextAllColumns = prev.current.allColumns;
|
||||
let nextDefaultColumns = prev.current.defaultColumns;
|
||||
|
||||
if (next.result?.length) {
|
||||
nextAllColumns = Object.keys(next.result[0].raw);
|
||||
|
||||
if (hasTransformationalCommand(nextQuery.esql)) {
|
||||
nextDefaultColumns = nextAllColumns.slice(0, MAX_NUM_OF_COLUMNS);
|
||||
} else {
|
||||
nextDefaultColumns = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (prev.current.initialFetch) {
|
||||
prev.current.initialFetch = false;
|
||||
prev.current.query = nextQuery.esql;
|
||||
prev.current.allColumns = nextAllColumns;
|
||||
prev.current.defaultColumns = nextDefaultColumns;
|
||||
}
|
||||
|
||||
const indexPatternChanged =
|
||||
getIndexPatternFromESQLQuery(nextQuery.esql) !==
|
||||
getIndexPatternFromESQLQuery(prev.current.query);
|
||||
|
||||
const allColumnsChanged = !isEqual(nextAllColumns, prev.current.allColumns);
|
||||
|
||||
const changeDefaultColumns =
|
||||
indexPatternChanged || !isEqual(nextDefaultColumns, prev.current.defaultColumns);
|
||||
|
||||
const { viewMode } = stateContainer.appState.getState();
|
||||
const changeViewMode = viewMode !== getValidViewMode({ viewMode, isEsqlMode: true });
|
||||
|
||||
if (indexPatternChanged) {
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
});
|
||||
} else if (allColumnsChanged) {
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
});
|
||||
}
|
||||
|
||||
prev.current.allColumns = nextAllColumns;
|
||||
|
||||
if (indexPatternChanged || changeDefaultColumns || changeViewMode) {
|
||||
prev.current.query = nextQuery.esql;
|
||||
prev.current.defaultColumns = nextDefaultColumns;
|
||||
|
||||
// just change URL state if necessary
|
||||
if (changeDefaultColumns || changeViewMode) {
|
||||
const nextState = {
|
||||
...(changeDefaultColumns && { columns: nextDefaultColumns }),
|
||||
...(changeViewMode && { viewMode: undefined }),
|
||||
};
|
||||
|
||||
await stateContainer.appState.replaceUrlState(nextState);
|
||||
}
|
||||
}
|
||||
|
||||
stateContainer.dataState.data$.documents$.next({
|
||||
...next,
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
});
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
// cleanup for e.g. when savedSearch is switched
|
||||
cleanup();
|
||||
|
|
|
@ -8,39 +8,55 @@
|
|||
|
||||
import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
||||
import {
|
||||
createKbnUrlStateStorage,
|
||||
IKbnUrlStateStorage,
|
||||
withNotifyOnErrors,
|
||||
} from '@kbn/kibana-utils-plugin/public';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { History } from 'history';
|
||||
import { savedSearchMock } from '../../../__mocks__/saved_search';
|
||||
import { discoverServiceMock } from '../../../__mocks__/services';
|
||||
import {
|
||||
DiscoverAppStateContainer,
|
||||
getDiscoverAppStateContainer,
|
||||
isEqualState,
|
||||
} from './discover_app_state_container';
|
||||
import { getDiscoverAppStateContainer, isEqualState } from './discover_app_state_container';
|
||||
import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/common';
|
||||
import { createDataViewDataSource } from '../../../../common/data_sources';
|
||||
import { getInternalStateContainer } from './discover_internal_state_container';
|
||||
import {
|
||||
DiscoverSavedSearchContainer,
|
||||
getSavedSearchContainer,
|
||||
} from './discover_saved_search_container';
|
||||
import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
|
||||
|
||||
let history: History;
|
||||
let state: DiscoverAppStateContainer;
|
||||
let stateStorage: IKbnUrlStateStorage;
|
||||
let internalState: ReturnType<typeof getInternalStateContainer>;
|
||||
let savedSearchState: DiscoverSavedSearchContainer;
|
||||
|
||||
describe('Test discover app state container', () => {
|
||||
beforeEach(async () => {
|
||||
const storeInSessionStorage = discoverServiceMock.uiSettings.get('state:storeInSessionStorage');
|
||||
const toasts = discoverServiceMock.core.notifications.toasts;
|
||||
const stateStorage = createKbnUrlStateStorage({
|
||||
stateStorage = createKbnUrlStateStorage({
|
||||
useHash: storeInSessionStorage,
|
||||
history,
|
||||
...(toasts && withNotifyOnErrors(toasts)),
|
||||
});
|
||||
state = getDiscoverAppStateContainer({
|
||||
stateStorage,
|
||||
savedSearch: savedSearchMock,
|
||||
internalState = getInternalStateContainer();
|
||||
savedSearchState = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
|
||||
});
|
||||
});
|
||||
|
||||
const getStateContainer = () =>
|
||||
getDiscoverAppStateContainer({
|
||||
stateStorage,
|
||||
internalStateContainer: internalState,
|
||||
savedSearchContainer: savedSearchState,
|
||||
services: discoverServiceMock,
|
||||
});
|
||||
|
||||
test('hasChanged returns whether the current state has changed', async () => {
|
||||
const state = getStateContainer();
|
||||
state.set({
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'modified' }),
|
||||
});
|
||||
|
@ -50,6 +66,7 @@ describe('Test discover app state container', () => {
|
|||
});
|
||||
|
||||
test('getPrevious returns the state before the current', async () => {
|
||||
const state = getStateContainer();
|
||||
state.set({
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'first' }),
|
||||
});
|
||||
|
@ -110,6 +127,7 @@ describe('Test discover app state container', () => {
|
|||
} as SavedSearch;
|
||||
|
||||
test('should return correct output', () => {
|
||||
const state = getStateContainer();
|
||||
const appState = state.getAppStateFromSavedSearch(localSavedSearchMock);
|
||||
expect(appState).toMatchObject(
|
||||
expect.objectContaining({
|
||||
|
@ -133,6 +151,7 @@ describe('Test discover app state container', () => {
|
|||
});
|
||||
|
||||
test('should return default query if query is undefined', () => {
|
||||
const state = getStateContainer();
|
||||
discoverServiceMock.data.query.queryString.getDefaultQuery = jest
|
||||
.fn()
|
||||
.mockReturnValue(defaultQuery);
|
||||
|
@ -233,6 +252,7 @@ describe('Test discover app state container', () => {
|
|||
});
|
||||
|
||||
test('should automatically set ES|QL data source when query is ES|QL', () => {
|
||||
const state = getStateContainer();
|
||||
state.update({
|
||||
dataSource: createDataViewDataSource({ dataViewId: 'test' }),
|
||||
});
|
||||
|
@ -244,4 +264,70 @@ describe('Test discover app state container', () => {
|
|||
});
|
||||
expect(state.get().dataSource?.type).toBe('esql');
|
||||
});
|
||||
|
||||
describe('initAndSync', () => {
|
||||
it('should call setResetDefaultProfileState correctly with no initial state', () => {
|
||||
const state = getStateContainer();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly with initial columns', () => {
|
||||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ columns: ['test'] });
|
||||
const state = getStateContainer();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly with initial rowHeight', () => {
|
||||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ rowHeight: 5 });
|
||||
const state = getStateContainer();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly with saved search', () => {
|
||||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ columns: ['test'], rowHeight: 5 });
|
||||
const savedSearchGetSpy = jest.spyOn(savedSearchState, 'getState');
|
||||
savedSearchGetSpy.mockReturnValue({
|
||||
id: 'test',
|
||||
searchSource: createSearchSourceMock(),
|
||||
managed: false,
|
||||
});
|
||||
const state = getStateContainer();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,8 @@ import {
|
|||
DiscoverDataSource,
|
||||
isDataSourceType,
|
||||
} from '../../../../common/data_sources';
|
||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
||||
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
||||
|
||||
export const APP_STATE_URL_KEY = '_a';
|
||||
export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> {
|
||||
|
@ -54,10 +56,9 @@ export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<Disco
|
|||
*/
|
||||
hasChanged: () => boolean;
|
||||
/**
|
||||
* Initializes the state by the given saved search and starts syncing the state with the URL
|
||||
* @param currentSavedSearch
|
||||
* Initializes the app state and starts syncing it with the URL
|
||||
*/
|
||||
initAndSync: (currentSavedSearch: SavedSearch) => () => void;
|
||||
initAndSync: () => () => void;
|
||||
/**
|
||||
* Replaces the current state in URL with the given state
|
||||
* @param newState
|
||||
|
@ -82,11 +83,10 @@ export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<Disco
|
|||
* @param replace
|
||||
*/
|
||||
update: (newPartial: DiscoverAppState, replace?: boolean) => void;
|
||||
|
||||
/*
|
||||
* Get updated AppState when given a saved search
|
||||
*
|
||||
* */
|
||||
*/
|
||||
getAppStateFromSavedSearch: (newSavedSearch: SavedSearch) => DiscoverAppState;
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,17 @@ export interface DiscoverAppState {
|
|||
breakdownField?: string;
|
||||
}
|
||||
|
||||
export interface AppStateUrl extends Omit<DiscoverAppState, 'sort'> {
|
||||
/**
|
||||
* Necessary to take care of legacy links [fieldName,direction]
|
||||
*/
|
||||
sort?: string[][] | [string, string];
|
||||
/**
|
||||
* Legacy data view ID prop
|
||||
*/
|
||||
index?: string;
|
||||
}
|
||||
|
||||
export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelector } =
|
||||
createStateContainerReactHelpers<ReduxLikeStateContainer<DiscoverAppState>>();
|
||||
|
||||
|
@ -168,14 +179,20 @@ export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelec
|
|||
*/
|
||||
export const getDiscoverAppStateContainer = ({
|
||||
stateStorage,
|
||||
savedSearch,
|
||||
internalStateContainer,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
}: {
|
||||
stateStorage: IKbnUrlStateStorage;
|
||||
savedSearch: SavedSearch;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||
services: DiscoverServices;
|
||||
}): DiscoverAppStateContainer => {
|
||||
let initialState = getInitialState(stateStorage, savedSearch, services);
|
||||
let initialState = getInitialState(
|
||||
getCurrentUrlState(stateStorage, services),
|
||||
savedSearchContainer.getState(),
|
||||
services
|
||||
);
|
||||
let previousState = initialState;
|
||||
const appStateContainer = createStateContainer<DiscoverAppState>(initialState);
|
||||
|
||||
|
@ -234,9 +251,20 @@ export const getDiscoverAppStateContainer = ({
|
|||
});
|
||||
};
|
||||
|
||||
const initializeAndSync = (currentSavedSearch: SavedSearch) => {
|
||||
const initializeAndSync = () => {
|
||||
const currentSavedSearch = savedSearchContainer.getState();
|
||||
|
||||
addLog('[appState] initialize state and sync with URL', currentSavedSearch);
|
||||
|
||||
if (!currentSavedSearch.id) {
|
||||
const { columns, rowHeight } = getCurrentUrlState(stateStorage, services);
|
||||
|
||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
||||
columns: columns === undefined,
|
||||
rowHeight: rowHeight === undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = services;
|
||||
const savedSearchDataView = currentSavedSearch.searchSource.getField('index');
|
||||
const appState = enhancedAppContainer.getState();
|
||||
|
@ -314,34 +342,24 @@ export const getDiscoverAppStateContainer = ({
|
|||
};
|
||||
};
|
||||
|
||||
export interface AppStateUrl extends Omit<DiscoverAppState, 'sort'> {
|
||||
/**
|
||||
* Necessary to take care of legacy links [fieldName,direction]
|
||||
*/
|
||||
sort?: string[][] | [string, string];
|
||||
/**
|
||||
* Legacy data view ID prop
|
||||
*/
|
||||
index?: string;
|
||||
function getCurrentUrlState(stateStorage: IKbnUrlStateStorage, services: DiscoverServices) {
|
||||
return cleanupUrlState(
|
||||
stateStorage.get<AppStateUrl>(APP_STATE_URL_KEY) ?? {},
|
||||
services.uiSettings
|
||||
);
|
||||
}
|
||||
|
||||
export function getInitialState(
|
||||
stateStorage: IKbnUrlStateStorage | undefined,
|
||||
initialUrlState: DiscoverAppState | undefined,
|
||||
savedSearch: SavedSearch,
|
||||
services: DiscoverServices
|
||||
) {
|
||||
const appStateFromUrl = stateStorage?.get<AppStateUrl>(APP_STATE_URL_KEY);
|
||||
const defaultAppState = getStateDefaults({
|
||||
savedSearch,
|
||||
services,
|
||||
});
|
||||
return handleSourceColumnState(
|
||||
appStateFromUrl == null
|
||||
? defaultAppState
|
||||
: {
|
||||
...defaultAppState,
|
||||
...cleanupUrlState(appStateFromUrl, services.uiSettings),
|
||||
},
|
||||
initialUrlState === undefined ? defaultAppState : { ...defaultAppState, ...initialUrlState },
|
||||
services.uiSettings
|
||||
);
|
||||
}
|
||||
|
|
|
@ -165,4 +165,62 @@ describe('test getDataStateContainer', () => {
|
|||
|
||||
dataState.refetch$.next('fetch_more');
|
||||
});
|
||||
|
||||
it('should update app state from default profile state', async () => {
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
const dataState = stateContainer.dataState;
|
||||
const dataUnsub = dataState.subscribe();
|
||||
const appUnsub = stateContainer.appState.initAndSync();
|
||||
discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
});
|
||||
dataState.data$.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
result: 0,
|
||||
});
|
||||
dataState.refetch$.next(undefined);
|
||||
await waitFor(() => {
|
||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||
});
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
expect(stateContainer.appState.get().columns).toEqual(['message', 'extension']);
|
||||
expect(stateContainer.appState.get().rowHeight).toEqual(3);
|
||||
dataUnsub();
|
||||
appUnsub();
|
||||
});
|
||||
|
||||
it('should not update app state from default profile state', async () => {
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
const dataState = stateContainer.dataState;
|
||||
const dataUnsub = dataState.subscribe();
|
||||
const appUnsub = stateContainer.appState.initAndSync();
|
||||
discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
dataState.data$.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
result: 0,
|
||||
});
|
||||
dataState.refetch$.next(undefined);
|
||||
await waitFor(() => {
|
||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||
});
|
||||
expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
expect(stateContainer.appState.get().columns).toEqual(['default_column']);
|
||||
expect(stateContainer.appState.get().rowHeight).toBeUndefined();
|
||||
dataUnsub();
|
||||
appUnsub();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,24 +10,29 @@ import { BehaviorSubject, filter, map, mergeMap, Observable, share, Subject, tap
|
|||
import type { AutoRefreshDoneFn } from '@kbn/data-plugin/public';
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { AggregateQuery, isOfAggregateQueryType, Query } from '@kbn/es-query';
|
||||
import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||
import type { SearchResponseWarning } from '@kbn/search-response-warnings';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import { SEARCH_FIELDS_FROM_SOURCE, SEARCH_ON_PAGE_LOAD_SETTING } from '@kbn/discover-utils';
|
||||
import {
|
||||
DEFAULT_COLUMNS_SETTING,
|
||||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
SEARCH_ON_PAGE_LOAD_SETTING,
|
||||
} from '@kbn/discover-utils';
|
||||
import { getEsqlDataView } from './utils/get_esql_data_view';
|
||||
import { DiscoverAppState } from './discover_app_state_container';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { DiscoverSearchSessionManager } from './discover_search_session';
|
||||
import type { DiscoverAppStateContainer } from './discover_app_state_container';
|
||||
import type { DiscoverServices } from '../../../build_services';
|
||||
import type { DiscoverSearchSessionManager } from './discover_search_session';
|
||||
import { FetchStatus } from '../../types';
|
||||
import { validateTimeRange } from './utils/validate_time_range';
|
||||
import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all';
|
||||
import { sendResetMsg } from '../hooks/use_saved_search_messages';
|
||||
import { getFetch$ } from '../data_fetching/get_fetch_observable';
|
||||
import { InternalState } from './discover_internal_state_container';
|
||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
||||
import { getDefaultProfileState } from './utils/get_default_profile_state';
|
||||
|
||||
export interface SavedSearchData {
|
||||
main$: DataMain$;
|
||||
|
@ -138,15 +143,15 @@ export interface DiscoverDataStateContainer {
|
|||
export function getDataStateContainer({
|
||||
services,
|
||||
searchSessionManager,
|
||||
getAppState,
|
||||
getInternalState,
|
||||
appStateContainer,
|
||||
internalStateContainer,
|
||||
getSavedSearch,
|
||||
setDataView,
|
||||
}: {
|
||||
services: DiscoverServices;
|
||||
searchSessionManager: DiscoverSearchSessionManager;
|
||||
getAppState: () => DiscoverAppState;
|
||||
getInternalState: () => InternalState;
|
||||
appStateContainer: DiscoverAppStateContainer;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
getSavedSearch: () => SavedSearch;
|
||||
setDataView: (dataView: DataView) => void;
|
||||
}): DiscoverDataStateContainer {
|
||||
|
@ -221,8 +226,8 @@ export function getDataStateContainer({
|
|||
inspectorAdapters,
|
||||
searchSessionId,
|
||||
services,
|
||||
getAppState,
|
||||
getInternalState,
|
||||
getAppState: appStateContainer.getState,
|
||||
getInternalState: internalStateContainer.getState,
|
||||
savedSearch: getSavedSearch(),
|
||||
useNewFieldsApi: !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE),
|
||||
};
|
||||
|
@ -232,34 +237,65 @@ export function getDataStateContainer({
|
|||
|
||||
if (options.fetchMore) {
|
||||
abortControllerFetchMore = new AbortController();
|
||||
|
||||
const fetchMoreStartTime = window.performance.now();
|
||||
|
||||
await fetchMoreDocuments(dataSubjects, {
|
||||
abortController: abortControllerFetchMore,
|
||||
...commonFetchDeps,
|
||||
});
|
||||
|
||||
const fetchMoreDuration = window.performance.now() - fetchMoreStartTime;
|
||||
reportPerformanceMetricEvent(services.analytics, {
|
||||
eventName: 'discoverFetchMore',
|
||||
duration: fetchMoreDuration,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await profilesManager.resolveDataSourceProfile({
|
||||
dataSource: getAppState().dataSource,
|
||||
dataSource: appStateContainer.getState().dataSource,
|
||||
dataView: getSavedSearch().searchSource.getField('index'),
|
||||
query: getAppState().query,
|
||||
query: appStateContainer.getState().query,
|
||||
});
|
||||
|
||||
abortController = new AbortController();
|
||||
const prevAutoRefreshDone = autoRefreshDone;
|
||||
|
||||
const fetchAllStartTime = window.performance.now();
|
||||
await fetchAll(dataSubjects, options.reset, {
|
||||
abortController,
|
||||
...commonFetchDeps,
|
||||
});
|
||||
|
||||
await fetchAll(
|
||||
dataSubjects,
|
||||
options.reset,
|
||||
{
|
||||
abortController,
|
||||
...commonFetchDeps,
|
||||
},
|
||||
async () => {
|
||||
const { resetDefaultProfileState, dataView } = internalStateContainer.getState();
|
||||
const { esqlQueryColumns } = dataSubjects.documents$.getValue();
|
||||
const defaultColumns = uiSettings.get<string[]>(DEFAULT_COLUMNS_SETTING, []);
|
||||
|
||||
if (dataView) {
|
||||
const stateUpdate = getDefaultProfileState({
|
||||
profilesManager,
|
||||
resetDefaultProfileState,
|
||||
defaultColumns,
|
||||
dataView,
|
||||
esqlQueryColumns,
|
||||
});
|
||||
|
||||
if (stateUpdate) {
|
||||
await appStateContainer.replaceUrlState(stateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const fetchAllDuration = window.performance.now() - fetchAllStartTime;
|
||||
reportPerformanceMetricEvent(services.analytics, {
|
||||
eventName: 'discoverFetchAll',
|
||||
|
@ -286,7 +322,7 @@ export function getDataStateContainer({
|
|||
}
|
||||
|
||||
const fetchQuery = async (resetQuery?: boolean) => {
|
||||
const query = getAppState().query;
|
||||
const query = appStateContainer.getState().query;
|
||||
const currentDataView = getSavedSearch().searchSource.getField('index');
|
||||
|
||||
if (isOfAggregateQueryType(query)) {
|
||||
|
@ -301,6 +337,7 @@ export function getDataStateContainer({
|
|||
} else {
|
||||
refetch$.next(undefined);
|
||||
}
|
||||
|
||||
return refetch$;
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface InternalState {
|
|||
expandedDoc: DataTableRecord | undefined;
|
||||
customFilters: Filter[];
|
||||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving
|
||||
resetDefaultProfileState: { columns: boolean; rowHeight: boolean };
|
||||
}
|
||||
|
||||
export interface InternalStateTransitions {
|
||||
|
@ -48,6 +49,9 @@ export interface InternalStateTransitions {
|
|||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined
|
||||
) => InternalState;
|
||||
resetOnSavedSearchChange: (state: InternalState) => () => InternalState;
|
||||
setResetDefaultProfileState: (
|
||||
state: InternalState
|
||||
) => (resetDefaultProfileState: InternalState['resetDefaultProfileState']) => InternalState;
|
||||
}
|
||||
|
||||
export type DiscoverInternalStateContainer = ReduxLikeStateContainer<
|
||||
|
@ -68,6 +72,7 @@ export function getInternalStateContainer() {
|
|||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: { columns: false, rowHeight: false },
|
||||
},
|
||||
{
|
||||
setDataView: (prevState: InternalState) => (nextDataView: DataView) => ({
|
||||
|
@ -134,6 +139,12 @@ export function getInternalStateContainer() {
|
|||
overriddenVisContextAfterInvalidation: undefined,
|
||||
expandedDoc: undefined,
|
||||
}),
|
||||
setResetDefaultProfileState:
|
||||
(prevState: InternalState) =>
|
||||
(resetDefaultProfileState: InternalState['resetDefaultProfileState']) => ({
|
||||
...prevState,
|
||||
resetDefaultProfileState,
|
||||
}),
|
||||
},
|
||||
{},
|
||||
{ freeze: (state) => state }
|
||||
|
|
|
@ -256,20 +256,21 @@ export function getDiscoverStateContainer({
|
|||
globalStateContainer,
|
||||
});
|
||||
|
||||
/**
|
||||
* Internal State Container, state that's not persisted and not part of the URL
|
||||
*/
|
||||
const internalStateContainer = getInternalStateContainer();
|
||||
|
||||
/**
|
||||
* App State Container, synced with the _a part URL
|
||||
*/
|
||||
const appStateContainer = getDiscoverAppStateContainer({
|
||||
stateStorage,
|
||||
savedSearch: savedSearchContainer.getState(),
|
||||
internalStateContainer,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
});
|
||||
|
||||
/**
|
||||
* Internal State Container, state that's not persisted and not part of the URL
|
||||
*/
|
||||
const internalStateContainer = getInternalStateContainer();
|
||||
|
||||
const pauseAutoRefreshInterval = async (dataView: DataView) => {
|
||||
if (dataView && (!dataView.isTimeBased() || dataView.type === DataViewType.ROLLUP)) {
|
||||
const state = globalStateContainer.get();
|
||||
|
@ -281,6 +282,7 @@ export function getDiscoverStateContainer({
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setDataView = (dataView: DataView) => {
|
||||
internalStateContainer.transitions.setDataView(dataView);
|
||||
pauseAutoRefreshInterval(dataView);
|
||||
|
@ -290,8 +292,8 @@ export function getDiscoverStateContainer({
|
|||
const dataStateContainer = getDataStateContainer({
|
||||
services,
|
||||
searchSessionManager,
|
||||
getAppState: appStateContainer.getState,
|
||||
getInternalState: internalStateContainer.getState,
|
||||
appStateContainer,
|
||||
internalStateContainer,
|
||||
getSavedSearch: savedSearchContainer.getState,
|
||||
setDataView,
|
||||
});
|
||||
|
@ -403,9 +405,8 @@ export function getDiscoverStateContainer({
|
|||
});
|
||||
|
||||
// initialize app state container, syncing with _g and _a part of the URL
|
||||
const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync(
|
||||
savedSearchContainer.getState()
|
||||
);
|
||||
const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync();
|
||||
|
||||
// subscribing to state changes of appStateContainer, triggering data fetching
|
||||
const appStateUnsubscribe = appStateContainer.subscribe(
|
||||
buildStateSubscribe({
|
||||
|
@ -417,6 +418,7 @@ export function getDiscoverStateContainer({
|
|||
setDataView,
|
||||
})
|
||||
);
|
||||
|
||||
// start subscribing to dataStateContainer, triggering data fetching
|
||||
const unsubscribeData = dataStateContainer.subscribe();
|
||||
|
||||
|
@ -467,6 +469,7 @@ export function getDiscoverStateContainer({
|
|||
await onChangeDataView(newDataView);
|
||||
return newDataView;
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggered when a user submits a query in the search bar
|
||||
*/
|
||||
|
@ -492,6 +495,7 @@ export function getDiscoverStateContainer({
|
|||
appState: appStateContainer,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo all changes to the current saved search
|
||||
*/
|
||||
|
@ -518,6 +522,7 @@ export function getDiscoverStateContainer({
|
|||
await appStateContainer.replaceUrlState(newAppState);
|
||||
return nextSavedSearch;
|
||||
};
|
||||
|
||||
const fetchData = (initial: boolean = false) => {
|
||||
addLog('fetchData', { initial });
|
||||
if (!initial || dataStateContainer.getInitialFetchStatus() === FetchStatus.LOADING) {
|
||||
|
|
|
@ -31,6 +31,7 @@ const setupTestParams = (dataView: DataView | undefined) => {
|
|||
discoverState.appState.update = jest.fn();
|
||||
discoverState.internalState.transitions = {
|
||||
setIsDataViewLoading: jest.fn(),
|
||||
setResetDefaultProfileState: jest.fn(),
|
||||
} as unknown as Readonly<PureTransitionsToTransitions<InternalStateTransitions>>;
|
||||
return {
|
||||
services,
|
||||
|
@ -71,4 +72,14 @@ describe('changeDataView', () => {
|
|||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly when switching data view', async () => {
|
||||
const params = setupTestParams(dataViewComplexMock);
|
||||
expect(params.internalState.transitions.setResetDefaultProfileState).not.toHaveBeenCalled();
|
||||
await changeDataView(dataViewComplexMock.id!, params);
|
||||
expect(params.internalState.transitions.setResetDefaultProfileState).toHaveBeenCalledWith({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,10 +35,12 @@ export async function changeDataView(
|
|||
}
|
||||
) {
|
||||
addLog('[ui] changeDataView', { id });
|
||||
|
||||
const { dataViews, uiSettings } = services;
|
||||
const dataView = internalState.getState().dataView;
|
||||
const state = appState.getState();
|
||||
let nextDataView: DataView | null = null;
|
||||
|
||||
internalState.transitions.setIsDataViewLoading(true);
|
||||
|
||||
try {
|
||||
|
@ -60,9 +62,12 @@ export async function changeDataView(
|
|||
);
|
||||
|
||||
appState.update(nextAppState);
|
||||
|
||||
if (internalState.getState().expandedDoc) {
|
||||
internalState.transitions.setExpandedDoc(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
internalState.transitions.setIsDataViewLoading(false);
|
||||
internalState.transitions.setResetDefaultProfileState({ columns: true, rowHeight: true });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fieldList } from '@kbn/data-views-plugin/common';
|
||||
import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { createContextAwarenessMocks } from '../../../../context_awareness/__mocks__';
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { getDefaultProfileState } from './get_default_profile_state';
|
||||
|
||||
const emptyDataView = buildDataViewMock({
|
||||
name: 'emptyDataView',
|
||||
fields: fieldList(),
|
||||
});
|
||||
const { profilesManagerMock } = createContextAwarenessMocks();
|
||||
|
||||
profilesManagerMock.resolveDataSourceProfile({});
|
||||
|
||||
describe('getDefaultProfileState', () => {
|
||||
it('should return expected columns', () => {
|
||||
let appState = getDefaultProfileState({
|
||||
profilesManager: profilesManagerMock,
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
},
|
||||
defaultColumns: ['messsage', 'bytes'],
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
esqlQueryColumns: undefined,
|
||||
});
|
||||
expect(appState).toEqual({
|
||||
columns: ['message', 'extension', 'bytes'],
|
||||
grid: {
|
||||
columns: {
|
||||
extension: {
|
||||
width: 200,
|
||||
},
|
||||
message: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
appState = getDefaultProfileState({
|
||||
profilesManager: profilesManagerMock,
|
||||
resetDefaultProfileState: {
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
},
|
||||
defaultColumns: ['messsage', 'bytes'],
|
||||
dataView: emptyDataView,
|
||||
esqlQueryColumns: [{ id: '1', name: 'foo', meta: { type: 'string' } }],
|
||||
});
|
||||
expect(appState).toEqual({
|
||||
columns: ['foo'],
|
||||
grid: {
|
||||
columns: {
|
||||
foo: {
|
||||
width: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return expected rowHeight', () => {
|
||||
const appState = getDefaultProfileState({
|
||||
profilesManager: profilesManagerMock,
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: true,
|
||||
},
|
||||
defaultColumns: [],
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
esqlQueryColumns: undefined,
|
||||
});
|
||||
expect(appState).toEqual({
|
||||
rowHeight: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined', () => {
|
||||
const appState = getDefaultProfileState({
|
||||
profilesManager: profilesManagerMock,
|
||||
resetDefaultProfileState: {
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
},
|
||||
defaultColumns: [],
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
esqlQueryColumns: undefined,
|
||||
});
|
||||
expect(appState).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { uniqBy } from 'lodash';
|
||||
import type { DiscoverAppState } from '../discover_app_state_container';
|
||||
import {
|
||||
DefaultAppStateColumn,
|
||||
getMergedAccessor,
|
||||
ProfilesManager,
|
||||
} from '../../../../context_awareness';
|
||||
import type { InternalState } from '../discover_internal_state_container';
|
||||
import type { DataDocumentsMsg } from '../discover_data_state_container';
|
||||
|
||||
export const getDefaultProfileState = ({
|
||||
profilesManager,
|
||||
resetDefaultProfileState,
|
||||
defaultColumns,
|
||||
dataView,
|
||||
esqlQueryColumns,
|
||||
}: {
|
||||
profilesManager: ProfilesManager;
|
||||
resetDefaultProfileState: InternalState['resetDefaultProfileState'];
|
||||
defaultColumns: string[];
|
||||
dataView: DataView;
|
||||
esqlQueryColumns: DataDocumentsMsg['esqlQueryColumns'];
|
||||
}) => {
|
||||
const stateUpdate: DiscoverAppState = {};
|
||||
const defaultState = getDefaultState(profilesManager, dataView);
|
||||
|
||||
if (resetDefaultProfileState.columns) {
|
||||
const mappedDefaultColumns = defaultColumns.map((name) => ({ name }));
|
||||
const isValidColumn = getIsValidColumn(dataView, esqlQueryColumns);
|
||||
const validColumns = uniqBy(
|
||||
defaultState.columns?.concat(mappedDefaultColumns).filter(isValidColumn),
|
||||
'name'
|
||||
);
|
||||
|
||||
if (validColumns?.length) {
|
||||
const columns = validColumns.reduce<DiscoverGridSettings['columns']>(
|
||||
(acc, { name, width }) => (width ? { ...acc, [name]: { width } } : acc),
|
||||
undefined
|
||||
);
|
||||
|
||||
stateUpdate.grid = columns ? { columns } : undefined;
|
||||
stateUpdate.columns = validColumns.map(({ name }) => name);
|
||||
}
|
||||
}
|
||||
|
||||
if (resetDefaultProfileState.rowHeight && defaultState.rowHeight !== undefined) {
|
||||
stateUpdate.rowHeight = defaultState.rowHeight;
|
||||
}
|
||||
|
||||
return Object.keys(stateUpdate).length ? stateUpdate : undefined;
|
||||
};
|
||||
|
||||
const getDefaultState = (profilesManager: ProfilesManager, dataView: DataView) => {
|
||||
const getDefaultAppState = getMergedAccessor(
|
||||
profilesManager.getProfiles(),
|
||||
'getDefaultAppState',
|
||||
() => ({})
|
||||
);
|
||||
|
||||
return getDefaultAppState({ dataView });
|
||||
};
|
||||
|
||||
const getIsValidColumn =
|
||||
(dataView: DataView, esqlQueryColumns: DataDocumentsMsg['esqlQueryColumns']) =>
|
||||
(column: DefaultAppStateColumn) => {
|
||||
const isValid = esqlQueryColumns
|
||||
? esqlQueryColumns.some((esqlColumn) => esqlColumn.name === column.name)
|
||||
: dataView.fields.getByName(column.name);
|
||||
|
||||
return Boolean(isValid);
|
||||
};
|
|
@ -49,6 +49,23 @@ export const createContextAwarenessMocks = ({
|
|||
...prev(),
|
||||
rootProfile: () => <>data-source-profile</>,
|
||||
})),
|
||||
getDefaultAppState: jest.fn(() => () => ({
|
||||
columns: [
|
||||
{
|
||||
name: 'message',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
name: 'extension',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
name: 'foo',
|
||||
width: 300,
|
||||
},
|
||||
],
|
||||
rowHeight: 3,
|
||||
})),
|
||||
},
|
||||
resolve: jest.fn(() => ({
|
||||
isMatch: true,
|
||||
|
|
|
@ -71,6 +71,22 @@ export const exampleDataSourceProfileProvider: DataSourceProfileProvider = {
|
|||
},
|
||||
};
|
||||
},
|
||||
getDefaultAppState: () => () => ({
|
||||
columns: [
|
||||
{
|
||||
name: '@timestamp',
|
||||
width: 212,
|
||||
},
|
||||
{
|
||||
name: 'log.level',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
},
|
||||
],
|
||||
rowHeight: 5,
|
||||
}),
|
||||
},
|
||||
resolve: (params) => {
|
||||
let indexPattern: string | undefined;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createContextAwarenessMocks } from '../__mocks__';
|
||||
import { extendProfileProvider } from './extend_profile_provider';
|
||||
|
||||
const { dataSourceProfileProviderMock } = createContextAwarenessMocks();
|
||||
|
||||
describe('extendProfileProvider', () => {
|
||||
it('should merge profiles and overwrite other properties', () => {
|
||||
const resolve = jest.fn();
|
||||
const getDefaultAppState = jest.fn();
|
||||
const extendedProfileProvider = extendProfileProvider(dataSourceProfileProviderMock, {
|
||||
profileId: 'extended-profile',
|
||||
profile: { getDefaultAppState },
|
||||
resolve,
|
||||
});
|
||||
|
||||
expect(extendedProfileProvider).toEqual({
|
||||
...dataSourceProfileProviderMock,
|
||||
profileId: 'extended-profile',
|
||||
profile: {
|
||||
...dataSourceProfileProviderMock.profile,
|
||||
getDefaultAppState,
|
||||
},
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { BaseProfileProvider } from '../profile_service';
|
||||
|
||||
export const extendProfileProvider = <TProvider extends BaseProfileProvider<{}>>(
|
||||
baseProvider: TProvider,
|
||||
extension: Partial<TProvider> & Pick<TProvider, 'profileId'>
|
||||
): TProvider => ({
|
||||
...baseProvider,
|
||||
...extension,
|
||||
profile: {
|
||||
...baseProvider.profile,
|
||||
...extension.profile,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createDataViewDataSource, createEsqlDataSource } from '../../../common/data_sources';
|
||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||
import { extractIndexPatternFrom } from './extract_index_pattern_from';
|
||||
|
||||
describe('extractIndexPatternFrom', () => {
|
||||
it('should return index pattern from data view', () => {
|
||||
const indexPattern = extractIndexPatternFrom({
|
||||
dataSource: createDataViewDataSource({ dataViewId: dataViewWithTimefieldMock.id! }),
|
||||
dataView: dataViewWithTimefieldMock,
|
||||
});
|
||||
expect(indexPattern).toBe(dataViewWithTimefieldMock.getIndexPattern());
|
||||
});
|
||||
|
||||
it('should return index pattern from ES|QL query', () => {
|
||||
const indexPattern = extractIndexPatternFrom({
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM index-pattern' },
|
||||
});
|
||||
expect(indexPattern).toBe('index-pattern');
|
||||
});
|
||||
|
||||
it('should return null if no data view or ES|QL query', () => {
|
||||
let indexPattern = extractIndexPatternFrom({
|
||||
dataSource: createDataViewDataSource({ dataViewId: dataViewWithTimefieldMock.id! }),
|
||||
});
|
||||
expect(indexPattern).toBeNull();
|
||||
indexPattern = extractIndexPatternFrom({
|
||||
dataSource: createEsqlDataSource(),
|
||||
});
|
||||
expect(indexPattern).toBeNull();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils';
|
||||
import { isDataViewSource, isEsqlSource } from '../../../common/data_sources';
|
||||
import type { DataSourceProfileProviderParams } from '../profiles';
|
||||
|
||||
export const extractIndexPatternFrom = ({
|
||||
dataSource,
|
||||
dataView,
|
||||
query,
|
||||
}: Pick<DataSourceProfileProviderParams, 'dataSource' | 'dataView' | 'query'>) => {
|
||||
if (isEsqlSource(dataSource) && isOfAggregateQueryType(query)) {
|
||||
return getIndexPatternFromESQLQuery(query.esql);
|
||||
} else if (isDataViewSource(dataSource) && dataView) {
|
||||
return dataView.getIndexPattern();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { DefaultAppStateColumn } from '../../../types';
|
||||
|
||||
export const createGetDefaultAppState = ({
|
||||
defaultColumns,
|
||||
}: {
|
||||
defaultColumns?: DefaultAppStateColumn[];
|
||||
}): DataSourceProfileProvider['profile']['getDefaultAppState'] => {
|
||||
return (prev) => (params) => {
|
||||
const appState = { ...prev(params) };
|
||||
|
||||
if (defaultColumns) {
|
||||
appState.columns = [];
|
||||
|
||||
if (params.dataView.isTimeBased()) {
|
||||
appState.columns.push({ name: params.dataView.timeFieldName, width: 212 });
|
||||
}
|
||||
|
||||
appState.columns.push(...defaultColumns);
|
||||
}
|
||||
|
||||
return appState;
|
||||
};
|
||||
};
|
|
@ -7,4 +7,5 @@
|
|||
*/
|
||||
|
||||
export { getRowIndicatorProvider } from './get_row_indicator_provider';
|
||||
export { createGetDefaultAppState } from './get_default_app_state';
|
||||
export { getCellRenderers } from './get_cell_renderers';
|
||||
|
|
|
@ -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
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { DefaultAppStateColumn } from '../../types';
|
||||
|
||||
export const LOG_LEVEL_COLUMN: DefaultAppStateColumn = { name: 'log.level', width: 150 };
|
||||
export const MESSAGE_COLUMN: DefaultAppStateColumn = { name: 'message' };
|
||||
export const CLIENT_IP_COLUMN: DefaultAppStateColumn = { name: 'client.ip', width: 150 };
|
||||
export const HOST_NAME_COLUMN: DefaultAppStateColumn = { name: 'host.name', width: 250 };
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { ProfileProviderServices } from '../profile_provider_services';
|
||||
import { createLogsDataSourceProfileProvider } from './profile';
|
||||
import {
|
||||
createApacheErrorLogsDataSourceProfileProvider,
|
||||
createAwsS3accessLogsDataSourceProfileProvider,
|
||||
createKubernetesContainerLogsDataSourceProfileProvider,
|
||||
createNginxAccessLogsDataSourceProfileProvider,
|
||||
createNginxErrorLogsDataSourceProfileProvider,
|
||||
createSystemLogsDataSourceProfileProvider,
|
||||
createWindowsLogsDataSourceProfileProvider,
|
||||
} from './sub_profiles';
|
||||
|
||||
export const createLogsDataSourceProfileProviders = (providerServices: ProfileProviderServices) => {
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(providerServices);
|
||||
|
||||
return [
|
||||
createSystemLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createKubernetesContainerLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createWindowsLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createAwsS3accessLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createNginxErrorLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createNginxAccessLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
createApacheErrorLogsDataSourceProfileProvider(logsDataSourceProfileProvider),
|
||||
logsDataSourceProfileProvider,
|
||||
];
|
||||
};
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { createLogsDataSourceProfileProvider } from './profile';
|
||||
export { createLogsDataSourceProfileProviders } from './create_profile_providers';
|
||||
|
|
|
@ -6,16 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils';
|
||||
import { isDataViewSource, isEsqlSource } from '../../../../common/data_sources';
|
||||
import {
|
||||
DataSourceCategory,
|
||||
DataSourceProfileProvider,
|
||||
DataSourceProfileProviderParams,
|
||||
} from '../../profiles';
|
||||
import { DataSourceCategory, DataSourceProfileProvider } from '../../profiles';
|
||||
import { ProfileProviderServices } from '../profile_provider_services';
|
||||
import { getRowIndicatorProvider } from './accessors';
|
||||
import { extractIndexPatternFrom } from '../extract_index_pattern_from';
|
||||
import { getCellRenderers } from './accessors';
|
||||
|
||||
export const createLogsDataSourceProfileProvider = (
|
||||
|
@ -39,17 +33,3 @@ export const createLogsDataSourceProfileProvider = (
|
|||
};
|
||||
},
|
||||
});
|
||||
|
||||
const extractIndexPatternFrom = ({
|
||||
dataSource,
|
||||
dataView,
|
||||
query,
|
||||
}: DataSourceProfileProviderParams) => {
|
||||
if (isEsqlSource(dataSource) && isOfAggregateQueryType(query)) {
|
||||
return getIndexPatternFromESQLQuery(query.esql);
|
||||
} else if (isDataViewSource(dataSource) && dataView) {
|
||||
return dataView.getIndexPattern();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createApacheErrorLogsDataSourceProfileProvider } from './apache_error_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createApacheErrorLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createApacheErrorLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-apache.error-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-apache.access-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'log.level', width: 150 },
|
||||
{ name: 'client.ip', width: 150 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { CLIENT_IP_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createApacheErrorLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'apache-error-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [LOG_LEVEL_COLUMN, CLIENT_IP_COLUMN, MESSAGE_COLUMN],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-apache.error'),
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createAwsS3accessLogsDataSourceProfileProvider } from './aws_s3access_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createAwsS3accessLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createAwsS3accessLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-aws.s3access-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-aws.s3noaccess-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'aws.s3.bucket.name', width: 200 },
|
||||
{ name: 'aws.s3.object.key', width: 200 },
|
||||
{ name: 'aws.s3access.operation', width: 200 },
|
||||
{ name: 'client.ip', width: 150 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { CLIENT_IP_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createAwsS3accessLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'aws-s3access-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [
|
||||
{ name: 'aws.s3.bucket.name', width: 200 },
|
||||
{ name: 'aws.s3.object.key', width: 200 },
|
||||
{ name: 'aws.s3access.operation', width: 200 },
|
||||
CLIENT_IP_COLUMN,
|
||||
MESSAGE_COLUMN,
|
||||
],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-aws.s3access'),
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/data-view-utils';
|
||||
import { DataSourceCategory, DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extractIndexPatternFrom } from '../../extract_index_pattern_from';
|
||||
|
||||
export const createResolve = (baseIndexPattern: string): DataSourceProfileProvider['resolve'] => {
|
||||
const testIndexPattern = testPatternAgainstAllowedList([
|
||||
createRegExpPatternFrom(baseIndexPattern),
|
||||
]);
|
||||
|
||||
return (params) => {
|
||||
const indexPattern = extractIndexPatternFrom(params);
|
||||
|
||||
if (!indexPattern || !testIndexPattern(indexPattern)) {
|
||||
return { isMatch: false };
|
||||
}
|
||||
|
||||
return {
|
||||
isMatch: true,
|
||||
context: { category: DataSourceCategory.Logs },
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { createApacheErrorLogsDataSourceProfileProvider } from './apache_error_logs';
|
||||
export { createAwsS3accessLogsDataSourceProfileProvider } from './aws_s3access_logs';
|
||||
export { createKubernetesContainerLogsDataSourceProfileProvider } from './kubernetes_container_logs';
|
||||
export { createNginxAccessLogsDataSourceProfileProvider } from './nginx_access_logs';
|
||||
export { createNginxErrorLogsDataSourceProfileProvider } from './nginx_error_logs';
|
||||
export { createSystemLogsDataSourceProfileProvider } from './system_logs';
|
||||
export { createWindowsLogsDataSourceProfileProvider } from './windows_logs';
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createKubernetesContainerLogsDataSourceProfileProvider } from './kubernetes_container_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createKubernetesContainerLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createKubernetesContainerLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-kubernetes.container_logs-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-kubernetes.access_logs-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'log.level', width: 150 },
|
||||
{ name: 'kubernetes.pod.name', width: 200 },
|
||||
{ name: 'kubernetes.namespace', width: 200 },
|
||||
{ name: 'orchestrator.cluster.name', width: 200 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createKubernetesContainerLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'kubernetes-container-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [
|
||||
LOG_LEVEL_COLUMN,
|
||||
{ name: 'kubernetes.pod.name', width: 200 },
|
||||
{ name: 'kubernetes.namespace', width: 200 },
|
||||
{ name: 'orchestrator.cluster.name', width: 200 },
|
||||
MESSAGE_COLUMN,
|
||||
],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-kubernetes.container_logs'),
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createNginxAccessLogsDataSourceProfileProvider } from './nginx_access_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createNginxAccessLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createNginxAccessLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-nginx.access-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-nginx.error-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'url.path', width: 150 },
|
||||
{ name: 'http.response.status_code', width: 200 },
|
||||
{ name: 'client.ip', width: 150 },
|
||||
{ name: 'host.name', width: 250 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { CLIENT_IP_COLUMN, HOST_NAME_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createNginxAccessLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'nginx-access-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [
|
||||
{ name: 'url.path', width: 150 },
|
||||
{ name: 'http.response.status_code', width: 200 },
|
||||
CLIENT_IP_COLUMN,
|
||||
HOST_NAME_COLUMN,
|
||||
MESSAGE_COLUMN,
|
||||
],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-nginx.access'),
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createNginxErrorLogsDataSourceProfileProvider } from './nginx_error_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createNginxErrorLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createNginxErrorLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-nginx.error-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-nginx.access-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'log.level', width: 150 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createNginxErrorLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'nginx-error-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [LOG_LEVEL_COLUMN, MESSAGE_COLUMN],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-nginx.error'),
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createSystemLogsDataSourceProfileProvider } from './system_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createSystemLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createSystemLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-system.syslog-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-notsystem.syslog-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'log.level', width: 150 },
|
||||
{ name: 'process.name', width: 150 },
|
||||
{ name: 'host.name', width: 250 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { HOST_NAME_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createSystemLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'system-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [
|
||||
LOG_LEVEL_COLUMN,
|
||||
{ name: 'process.name', width: 150 },
|
||||
HOST_NAME_COLUMN,
|
||||
MESSAGE_COLUMN,
|
||||
],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-system'),
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield';
|
||||
import { createEsqlDataSource } from '../../../../../common/data_sources';
|
||||
import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles';
|
||||
import { createContextAwarenessMocks } from '../../../__mocks__';
|
||||
import { createLogsDataSourceProfileProvider } from '../profile';
|
||||
import { createWindowsLogsDataSourceProfileProvider } from './windows_logs';
|
||||
|
||||
const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default };
|
||||
const { profileProviderServices } = createContextAwarenessMocks();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices);
|
||||
const dataSourceProfileProvider = createWindowsLogsDataSourceProfileProvider(
|
||||
logsDataSourceProfileProvider
|
||||
);
|
||||
|
||||
describe('createWindowsLogsDataSourceProfileProvider', () => {
|
||||
it('should match a valid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-windows.powershell-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } });
|
||||
});
|
||||
|
||||
it('should not match an invalid index pattern', async () => {
|
||||
const result = await dataSourceProfileProvider.resolve({
|
||||
rootContext: ROOT_CONTEXT,
|
||||
dataSource: createEsqlDataSource(),
|
||||
query: { esql: 'FROM logs-notwindows.powershell-*' },
|
||||
});
|
||||
expect(result).toEqual({ isMatch: false });
|
||||
});
|
||||
|
||||
it('should return default app state', () => {
|
||||
const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}));
|
||||
expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({
|
||||
columns: [
|
||||
{ name: 'timestamp', width: 212 },
|
||||
{ name: 'log.level', width: 150 },
|
||||
{ name: 'host.name', width: 250 },
|
||||
{ name: 'message' },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataSourceProfileProvider } from '../../../profiles';
|
||||
import { extendProfileProvider } from '../../extend_profile_provider';
|
||||
import { createGetDefaultAppState } from '../accessors';
|
||||
import { HOST_NAME_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts';
|
||||
import { createResolve } from './create_resolve';
|
||||
|
||||
export const createWindowsLogsDataSourceProfileProvider = (
|
||||
logsDataSourceProfileProvider: DataSourceProfileProvider
|
||||
): DataSourceProfileProvider =>
|
||||
extendProfileProvider(logsDataSourceProfileProvider, {
|
||||
profileId: 'windows-logs-data-source',
|
||||
profile: {
|
||||
getDefaultAppState: createGetDefaultAppState({
|
||||
defaultColumns: [LOG_LEVEL_COLUMN, HOST_NAME_COLUMN, MESSAGE_COLUMN],
|
||||
}),
|
||||
},
|
||||
resolve: createResolve('logs-windows'),
|
||||
});
|
|
@ -10,15 +10,19 @@ import { uniq } from 'lodash';
|
|||
import type {
|
||||
DataSourceProfileService,
|
||||
DocumentProfileService,
|
||||
RootProfileProvider,
|
||||
RootProfileService,
|
||||
} from '../profiles';
|
||||
import type { BaseProfileProvider, BaseProfileService } from '../profile_service';
|
||||
import { exampleDataSourceProfileProvider } from './example_data_source_profile';
|
||||
import { exampleDocumentProfileProvider } from './example_document_profile';
|
||||
import { exampleRootProfileProvider } from './example_root_pofile';
|
||||
import { createLogsDataSourceProfileProvider } from './logs_data_source_profile';
|
||||
import { createLogsDataSourceProfileProviders } from './logs_data_source_profile';
|
||||
import { createLogDocumentProfileProvider } from './log_document_profile';
|
||||
import { createProfileProviderServices } from './profile_provider_services';
|
||||
import {
|
||||
createProfileProviderServices,
|
||||
ProfileProviderServices,
|
||||
} from './profile_provider_services';
|
||||
|
||||
export const registerProfileProviders = ({
|
||||
rootProfileService,
|
||||
|
@ -32,35 +36,31 @@ export const registerProfileProviders = ({
|
|||
experimentalProfileIds: string[];
|
||||
}) => {
|
||||
const providerServices = createProfileProviderServices();
|
||||
const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(providerServices);
|
||||
const logsDocumentProfileProvider = createLogDocumentProfileProvider(providerServices);
|
||||
const rootProfileProviders = [exampleRootProfileProvider];
|
||||
const dataSourceProfileProviders = [
|
||||
exampleDataSourceProfileProvider,
|
||||
logsDataSourceProfileProvider,
|
||||
];
|
||||
const documentProfileProviders = [exampleDocumentProfileProvider, logsDocumentProfileProvider];
|
||||
const rootProfileProviders = createRootProfileProviders(providerServices);
|
||||
const dataSourceProfileProviders = createDataSourceProfileProviders(providerServices);
|
||||
const documentProfileProviders = createDocumentProfileProviders(providerServices);
|
||||
const enabledProfileIds = uniq([
|
||||
logsDataSourceProfileProvider.profileId,
|
||||
logsDocumentProfileProvider.profileId,
|
||||
...extractProfileIds(rootProfileProviders),
|
||||
...extractProfileIds(dataSourceProfileProviders),
|
||||
...extractProfileIds(documentProfileProviders),
|
||||
...experimentalProfileIds,
|
||||
]);
|
||||
|
||||
registerEnabledProfileProviders({
|
||||
profileService: rootProfileService,
|
||||
availableProviders: rootProfileProviders,
|
||||
availableProviders: [exampleRootProfileProvider, ...rootProfileProviders],
|
||||
enabledProfileIds,
|
||||
});
|
||||
|
||||
registerEnabledProfileProviders({
|
||||
profileService: dataSourceProfileService,
|
||||
availableProviders: dataSourceProfileProviders,
|
||||
availableProviders: [exampleDataSourceProfileProvider, ...dataSourceProfileProviders],
|
||||
enabledProfileIds,
|
||||
});
|
||||
|
||||
registerEnabledProfileProviders({
|
||||
profileService: documentProfileService,
|
||||
availableProviders: documentProfileProviders,
|
||||
availableProviders: [exampleDocumentProfileProvider, ...documentProfileProviders],
|
||||
enabledProfileIds,
|
||||
});
|
||||
};
|
||||
|
@ -77,9 +77,23 @@ export const registerEnabledProfileProviders = <
|
|||
availableProviders: TProvider[];
|
||||
enabledProfileIds: string[];
|
||||
}) => {
|
||||
for (const profile of availableProviders) {
|
||||
if (enabledProfileIds.includes(profile.profileId)) {
|
||||
profileService.registerProvider(profile);
|
||||
for (const provider of availableProviders) {
|
||||
if (enabledProfileIds.includes(provider.profileId)) {
|
||||
profileService.registerProvider(provider);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const extractProfileIds = (providers: Array<BaseProfileProvider<{}>>) =>
|
||||
providers.map(({ profileId }) => profileId);
|
||||
|
||||
const createRootProfileProviders = (_providerServices: ProfileProviderServices) =>
|
||||
[] as RootProfileProvider[];
|
||||
|
||||
const createDataSourceProfileProviders = (providerServices: ProfileProviderServices) => [
|
||||
...createLogsDataSourceProfileProviders(providerServices),
|
||||
];
|
||||
|
||||
const createDocumentProfileProviders = (providerServices: ProfileProviderServices) => [
|
||||
createLogDocumentProfileProvider(providerServices),
|
||||
];
|
||||
|
|
|
@ -17,7 +17,7 @@ export enum DocumentType {
|
|||
Default = 'default',
|
||||
}
|
||||
|
||||
export type DocumentProfile = Omit<Profile, 'getCellRenderers'>;
|
||||
export type DocumentProfile = Pick<Profile, 'getDocViewer'>;
|
||||
|
||||
export interface DocumentProfileProviderParams {
|
||||
rootContext: RootContext;
|
||||
|
|
|
@ -24,9 +24,24 @@ export interface RowIndicatorExtensionParams {
|
|||
dataView: DataView;
|
||||
}
|
||||
|
||||
export interface DefaultAppStateColumn {
|
||||
name: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export interface DefaultAppStateExtensionParams {
|
||||
dataView: DataView;
|
||||
}
|
||||
|
||||
export interface DefaultAppStateExtension {
|
||||
columns?: DefaultAppStateColumn[];
|
||||
rowHeight?: number;
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
getCellRenderers: () => CustomCellRenderer;
|
||||
getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension;
|
||||
getDefaultAppState: (params: DefaultAppStateExtensionParams) => DefaultAppStateExtension;
|
||||
getRowIndicatorProvider: (
|
||||
params: RowIndicatorExtensionParams
|
||||
) => UnifiedDataTableProps['getRowIndicator'] | undefined;
|
||||
|
|
|
@ -24,8 +24,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -43,8 +43,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -66,8 +66,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -82,8 +82,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -98,7 +98,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('data view mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should render custom @timestamp but not custom log.level', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -112,7 +114,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should render custom @timestamp and custom log.level', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -130,7 +134,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('doc viewer extension', () => {
|
||||
it('should not render custom doc viewer view', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -141,7 +147,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should render custom doc viewer view', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
|
|
@ -23,8 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp');
|
||||
|
@ -38,7 +38,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('data view mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should render custom @timestamp', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp');
|
||||
|
|
|
@ -35,8 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level');
|
||||
|
@ -55,8 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level');
|
||||
|
@ -68,7 +68,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('data view mode', () => {
|
||||
it('should render log.level badge cell', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs,logstash*');
|
||||
await queryBar.setQuery('log.level:*');
|
||||
await queryBar.submitQuery();
|
||||
|
@ -83,7 +85,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it("should not render log.level badge cell if it's not a logs data source", async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await queryBar.setQuery('log.level:*');
|
||||
await queryBar.submitQuery();
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import kbnRison from '@kbn/rison';
|
||||
import type { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList']);
|
||||
const dataViews = getService('dataViews');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const queryBar = getService('queryBar');
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
describe('extension getDefaultAppState', () => {
|
||||
afterEach(async () => {
|
||||
await kibanaServer.uiSettings.unset('defaultColumns');
|
||||
});
|
||||
|
||||
describe('ES|QL mode', () => {
|
||||
it('should render default columns and row height', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
const rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should render default columns and row height when switching index patterns', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-*',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
let rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(3);
|
||||
await monacoEditor.setCodeEditorValue('from my-example-logs');
|
||||
await queryBar.clickQuerySubmitButton();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should reset default columns and row height when clicking "New"', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level');
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('message');
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
await dataGrid.changeRowHeightValue('Single');
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Single');
|
||||
await testSubjects.click('discoverNewButton');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should merge and dedup configured default columns with default profile columns', async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
defaultColumns: ['bad_column', 'data_stream.type', 'message'],
|
||||
});
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data view mode', () => {
|
||||
it('should render default columns and row height', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
const rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should render default columns and row height when switching data views', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
let rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(3);
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should reset default columns and row height when clicking "New"', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level');
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('message');
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
await dataGrid.changeRowHeightValue('Single');
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Single');
|
||||
await testSubjects.click('discoverNewButton');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should merge and dedup configured default columns with default profile columns', async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
defaultColumns: ['bad_column', 'data_stream.type', 'message'],
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -22,8 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -38,8 +38,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-metrics | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -50,7 +50,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('data view mode', () => {
|
||||
it('should render logs overview tab for logs data source', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -61,7 +63,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should not render logs overview tab for non-logs data source', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-metrics');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
|
|
@ -31,8 +31,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from logstash* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||
|
@ -51,8 +51,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
// my-example* has a log.level field, but it's not matching the logs profile, so the color indicator should not be rendered
|
||||
|
@ -67,8 +67,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
// in this case it's matching the logs data source profile and has a log.level field, so the color indicator should be rendered
|
||||
|
|
|
@ -38,5 +38,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
|
|||
loadTestFile(require.resolve('./extensions/_get_row_indicator_provider'));
|
||||
loadTestFile(require.resolve('./extensions/_get_doc_viewer'));
|
||||
loadTestFile(require.resolve('./extensions/_get_cell_renderers'));
|
||||
loadTestFile(require.resolve('./extensions/_get_default_app_state'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -469,6 +469,13 @@ export class DataGridService extends FtrService {
|
|||
return value;
|
||||
}
|
||||
|
||||
public async getCustomRowHeightNumber(scope: 'row' | 'header' = 'row') {
|
||||
const input = await this.testSubjects.find(
|
||||
`unifiedDataTable${scope === 'header' ? 'Header' : ''}RowHeightSettings_lineCountNumber`
|
||||
);
|
||||
return Number(await input.getAttribute('value'));
|
||||
}
|
||||
|
||||
public async changeRowHeightValue(newValue: string) {
|
||||
const buttonGroup = await this.testSubjects.find(
|
||||
'unifiedDataTableRowHeightSettings_rowHeightButtonGroup'
|
||||
|
|
|
@ -125,7 +125,7 @@ export const useDiscoverInTimelineActions = (
|
|||
newSavedSearchId
|
||||
);
|
||||
const savedSearchState = savedSearch ? getAppStateFromSavedSearch(savedSearch) : null;
|
||||
discoverStateContainer.current?.appState.initAndSync(savedSearch);
|
||||
discoverStateContainer.current?.appState.initAndSync();
|
||||
await discoverStateContainer.current?.appState.replaceUrlState(
|
||||
savedSearchState?.appState ?? {}
|
||||
);
|
||||
|
|
|
@ -33,8 +33,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -50,8 +50,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -71,8 +71,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -87,8 +87,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -103,7 +103,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('data view mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should not render custom @timestamp or log.level', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -115,7 +117,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should not render custom @timestamp but should render custom log.level', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp');
|
||||
|
@ -131,7 +135,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('doc viewer extension', () => {
|
||||
it('should not render custom doc viewer view', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
@ -142,7 +148,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should render custom doc viewer view', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
|
|
|
@ -16,8 +16,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('root profile', () => {
|
||||
before(async () => {
|
||||
await PageObjects.svlCommonPage.loginAsViewer();
|
||||
await PageObjects.svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
describe('ES|QL mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should not render custom @timestamp', async () => {
|
||||
|
@ -25,8 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500);
|
||||
|
@ -38,7 +39,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('data view mode', () => {
|
||||
describe('cell renderers', () => {
|
||||
it('should not render custom @timestamp', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500);
|
||||
|
|
|
@ -10,7 +10,7 @@ import expect from '@kbn/expect';
|
|||
import type { FtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList']);
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList', 'svlCommonPage']);
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const dataGrid = getService('dataGrid');
|
||||
|
@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('extension getCellRenderers', () => {
|
||||
before(async () => {
|
||||
await PageObjects.svlCommonPage.loginAsAdmin();
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
});
|
||||
|
||||
|
@ -34,8 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level');
|
||||
|
@ -54,8 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level');
|
||||
|
@ -67,7 +68,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('data view mode', () => {
|
||||
it('should render log.level badge cell', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs,logstash*');
|
||||
await queryBar.setQuery('log.level:*');
|
||||
await queryBar.submitQuery();
|
||||
|
@ -82,7 +85,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it("should not render log.level badge cell if it's not a logs data source", async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await queryBar.setQuery('log.level:*');
|
||||
await queryBar.submitQuery();
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import kbnRison from '@kbn/rison';
|
||||
import type { FtrProviderContext } from '../../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage', 'unifiedFieldList']);
|
||||
const dataViews = getService('dataViews');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const queryBar = getService('queryBar');
|
||||
const monacoEditor = getService('monacoEditor');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
describe('extension getDefaultAppState', () => {
|
||||
before(async () => {
|
||||
await PageObjects.svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await kibanaServer.uiSettings.unset('defaultColumns');
|
||||
});
|
||||
|
||||
describe('ES|QL mode', () => {
|
||||
it('should render default columns and row height', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
const rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should render default columns and row height when switching index patterns', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-*',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
let rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(3);
|
||||
await monacoEditor.setCodeEditorValue('from my-example-logs');
|
||||
await queryBar.clickQuerySubmitButton();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should reset default columns and row height when clicking "New"', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level');
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('message');
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
await dataGrid.changeRowHeightValue('Single');
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Single');
|
||||
await testSubjects.click('discoverNewButton');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should merge and dedup configured default columns with default profile columns', async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
defaultColumns: ['bad_column', 'data_stream.type', 'message'],
|
||||
});
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: {
|
||||
esql: 'from my-example-logs',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('data view mode', () => {
|
||||
it('should render default columns and row height', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
const rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should render default columns and row height when switching data views', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-*');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
let rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(3);
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should reset default columns and row height when clicking "New"', async () => {
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level');
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove('message');
|
||||
let columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'Document']);
|
||||
await dataGrid.clickGridSettings();
|
||||
await dataGrid.changeRowHeightValue('Single');
|
||||
let rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Single');
|
||||
await testSubjects.click('discoverNewButton');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message']);
|
||||
await dataGrid.clickGridSettings();
|
||||
rowHeightValue = await dataGrid.getCurrentRowHeightValue();
|
||||
expect(rowHeightValue).to.be('Custom');
|
||||
const rowHeightNumber = await dataGrid.getCustomRowHeightNumber();
|
||||
expect(rowHeightNumber).to.be(5);
|
||||
});
|
||||
|
||||
it('should merge and dedup configured default columns with default profile columns', async () => {
|
||||
await kibanaServer.uiSettings.update({
|
||||
defaultColumns: ['bad_column', 'data_stream.type', 'message'],
|
||||
});
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
const columns = await PageObjects.discover.getColumnHeaders();
|
||||
expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -18,14 +18,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
before(async () => {
|
||||
await PageObjects.svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
describe('ES|QL mode', () => {
|
||||
it('should render logs overview tab for logs data source', async () => {
|
||||
const state = kbnRison.encode({
|
||||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-logs | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -40,8 +41,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example-metrics | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -52,7 +53,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('data view mode', () => {
|
||||
it('should render logs overview tab for logs data source', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-logs');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
@ -63,7 +66,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should not render logs overview tab for non-logs data source', async () => {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.common.navigateToActualUrl('discover', undefined, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await dataViews.switchTo('my-example-metrics');
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await dataGrid.clickRowToggle();
|
||||
|
|
|
@ -37,8 +37,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from logstash* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRange();
|
||||
|
@ -57,8 +57,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
dataSource: { type: 'esql' },
|
||||
query: { esql: 'from my-example* | sort @timestamp desc' },
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
// my-example* has a log.level field, but it's not matching the logs profile, so the color indicator should not be rendered
|
||||
|
@ -73,8 +73,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null',
|
||||
},
|
||||
});
|
||||
await PageObjects.common.navigateToApp('discover', {
|
||||
hash: `/?_a=${state}`,
|
||||
await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, {
|
||||
ensureCurrentUrl: false,
|
||||
});
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
// in this case it's matching the logs data source profile and has a log.level field, so the color indicator should be rendered
|
||||
|
|
|
@ -40,5 +40,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
|
|||
loadTestFile(require.resolve('./extensions/_get_row_indicator_provider'));
|
||||
loadTestFile(require.resolve('./extensions/_get_doc_viewer'));
|
||||
loadTestFile(require.resolve('./extensions/_get_cell_renderers'));
|
||||
loadTestFile(require.resolve('./extensions/_get_default_app_state'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue