Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discover] Add a default "All logs" temporary data view in the Observability Solution view #205991

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8cd7089
Initial getDefaultAdHocDataViews extension
davismcphee Jan 9, 2025
c146fac
Finish initial implementation
davismcphee Jan 10, 2025
79f0ec0
Fix types
davismcphee Jan 10, 2025
13db01c
Add useDefaultAdHocDataViews hook
davismcphee Jan 10, 2025
deae60b
Move default profile ad hoc data views to internal state
davismcphee Jan 10, 2025
0e8f21c
Fix types and tests
davismcphee Jan 13, 2025
3f9bdba
Fix broken test
davismcphee Jan 17, 2025
255fff2
Fix broken test
davismcphee Jan 18, 2025
7770cc0
Update default o11y logs data view
davismcphee Jan 18, 2025
f79ef92
Fix Jest test
davismcphee Jan 20, 2025
f3d14f0
Remove CCS special handling
davismcphee Jan 20, 2025
dfdbf94
Update implementation and add tests
davismcphee Jan 20, 2025
e45a53a
Fix profile data view fields loading, and clean up resolve_data_view
davismcphee Jan 21, 2025
a411d60
Duplicate default profile data view on saved search change
davismcphee Jan 22, 2025
c3d1bd6
Add Jest test
davismcphee Jan 22, 2025
5ac5cfc
Add more tests
davismcphee Jan 22, 2025
1f372b2
Fix missing fields when changing data view
davismcphee Jan 22, 2025
c430307
Add functional tests
davismcphee Jan 22, 2025
3db6ffb
Fix test skips
davismcphee Jan 22, 2025
c665e5c
Updating comments
davismcphee Jan 22, 2025
76e11f4
Merge branch 'main' into discover-ad-hoc-data-views-extension
davismcphee Jan 23, 2025
c2328cd
Merge branch 'main' into discover-ad-hoc-data-views-extension
davismcphee Jan 23, 2025
0d803a7
Use Discover session title instead of 'copy' for cloned default profi…
davismcphee Jan 23, 2025
ad21e3d
Clean up logic around no data page check
davismcphee Jan 23, 2025
0b7d4ee
Allow overwriting default profile data views with spec in location state
davismcphee Jan 24, 2025
ebd3cb6
Update filter references when saving session with default profiel dat…
davismcphee Jan 24, 2025
c4d8168
Fix type
davismcphee Jan 24, 2025
751f366
Add support for showing managed data views in the data view picker
davismcphee Jan 25, 2025
af6171f
Merge branch 'main' into discover-ad-hoc-data-views-extension
davismcphee Jan 25, 2025
641c013
Fix Jest tests
davismcphee Jan 25, 2025
ed79553
Merge branch 'main' into discover-ad-hoc-data-views-extension
davismcphee Jan 27, 2025
991548e
Allow sidebar URL tracking for default profile data views
davismcphee Jan 27, 2025
7306fc1
Add test for getDefaultAdHocDataViews
davismcphee Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
import { DEFAULT_ALLOWED_LOGS_BASE_PATTERNS_REGEXP, getLogsContextService } from '../data_types';

export const createLogsContextServiceMock = () => {
return getLogsContextService([DEFAULT_ALLOWED_LOGS_BASE_PATTERNS_REGEXP]);
return getLogsContextService({
allLogsIndexPattern: 'logs-*',
allowedDataSources: [DEFAULT_ALLOWED_LOGS_BASE_PATTERNS_REGEXP],
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/dat
import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';

export interface LogsContextService {
getAllLogsIndexPattern(): string | undefined;
isLogsIndexPattern(indexPattern: unknown): boolean;
}

Expand All @@ -32,34 +33,45 @@ export const DEFAULT_ALLOWED_LOGS_BASE_PATTERNS_REGEXP = createRegExpPatternFrom
'data'
);

export const createLogsContextService = async ({ logsDataAccess }: LogsContextServiceDeps) => {
export const createLogsContextService = async ({
logsDataAccess,
}: LogsContextServiceDeps): Promise<LogsContextService> => {
let allLogsIndexPattern: string | undefined;
let logSources: string[] | undefined;

if (logsDataAccess) {
const logSourcesService = logsDataAccess.services.logSourcesService;
logSources = (await logSourcesService.getLogSources())
allLogsIndexPattern = (await logSourcesService.getLogSources())
.map((logSource) => logSource.indexPattern)
.join(',') // TODO: Will be replaced by helper in: https://github.com/elastic/kibana/pull/192003
.split(',');
.join(','); // TODO: Will be replaced by helper in: https://github.com/elastic/kibana/pull/192003
logSources = allLogsIndexPattern.split(',');
}

const ALLOWED_LOGS_DATA_SOURCES = [
DEFAULT_ALLOWED_LOGS_BASE_PATTERNS_REGEXP,
...(logSources ? logSources : []),
];

return getLogsContextService(ALLOWED_LOGS_DATA_SOURCES);
return getLogsContextService({
allLogsIndexPattern,
allowedDataSources: ALLOWED_LOGS_DATA_SOURCES,
});
};

export const getLogsContextService = (allowedDataSources: Array<string | RegExp>) => {
export const getLogsContextService = ({
allLogsIndexPattern,
allowedDataSources,
}: {
allLogsIndexPattern: string | undefined;
allowedDataSources: Array<string | RegExp>;
}): LogsContextService => {
const getAllLogsIndexPattern = () => allLogsIndexPattern;
const isLogsIndexPattern = (indexPattern: unknown) => {
return (
typeof indexPattern === 'string' &&
testPatternAgainstAllowedList(allowedDataSources)(indexPattern)
);
};

return {
isLogsIndexPattern,
};
return { getAllLogsIndexPattern, isLogsIndexPattern };
};
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('test fetchAll', () => {
isDataViewLoading: false,
savedDataViews: [],
adHocDataViews: [],
defaultProfileAdHocDataViewIds: [],
expandedDoc: undefined,
customFilters: [],
overriddenVisContextAfterInvalidation: undefined,
Expand Down Expand Up @@ -265,6 +266,7 @@ describe('test fetchAll', () => {
isDataViewLoading: false,
savedDataViews: [],
adHocDataViews: [],
defaultProfileAdHocDataViewIds: [],
expandedDoc: undefined,
customFilters: [],
overriddenVisContextAfterInvalidation: undefined,
Expand Down Expand Up @@ -389,6 +391,7 @@ describe('test fetchAll', () => {
isDataViewLoading: false,
savedDataViews: [],
adHocDataViews: [],
defaultProfileAdHocDataViewIds: [],
expandedDoc: undefined,
customFilters: [],
overriddenVisContextAfterInvalidation: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../../customizations/customization_service';
import { DiscoverTopNavInline } from './components/top_nav/discover_topnav_inline';
import { mockCustomizationContext } from '../../customizations/__mocks__/customization_context';
import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';

let mockCustomizationService: DiscoverCustomizationService | undefined;

Expand All @@ -43,6 +44,7 @@ jest.mock('./discover_main_app', () => {
});

let mockRootProfileLoading = false;
let mockDefaultAdHocDataViews: DataViewSpec[] = [];

jest.mock('../../context_awareness', () => {
const originalModule = jest.requireActual('../../context_awareness');
Expand All @@ -51,6 +53,7 @@ jest.mock('../../context_awareness', () => {
useRootProfile: () => ({
rootProfileLoading: mockRootProfileLoading,
AppWrapper: ({ children }: { children: ReactNode }) => <>{children}</>,
getDefaultAdHocDataViews: () => mockDefaultAdHocDataViews,
}),
};
});
Expand All @@ -59,6 +62,7 @@ describe('DiscoverMainRoute', () => {
beforeEach(() => {
mockCustomizationService = createCustomizationService();
mockRootProfileLoading = false;
mockDefaultAdHocDataViews = [];
});

test('renders the main app when hasESData=true & hasUserDataView=true ', async () => {
Expand All @@ -70,6 +74,16 @@ describe('DiscoverMainRoute', () => {
});
});

test('renders the main app when ad hoc data views exist', async () => {
mockDefaultAdHocDataViews = [{ id: 'test', title: 'test' }];
const component = mountComponent(true, false);

await waitFor(() => {
component.update();
expect(component.find(DiscoverMainApp).exists()).toBe(true);
});
});

test('renders no data page when hasESData=false & hasUserDataView=false', async () => {
const component = mountComponent(false, false);

Expand Down Expand Up @@ -158,6 +172,7 @@ function getServicesMock(hasESData = true, hasUserDataView = true) {
hasUserDataView: jest.fn(() => Promise.resolve(hasUserDataView)),
hasDataView: jest.fn(() => Promise.resolve(true)),
};
dataViewsMock.create = jest.fn((spec) => Promise.resolve(spec as unknown as DataView));
discoverServiceMock.core.http.get = jest.fn().mockResolvedValue({});
return discoverServiceMock;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
import { DiscoverTopNavInline } from './components/top_nav/discover_topnav_inline';
import { DiscoverStateContainer, LoadParams } from './state_management/discover_state';
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
import { useRootProfile } from '../../context_awareness';
import { useDefaultAdHocDataViews, useRootProfile } from '../../context_awareness';

const DiscoverMainAppMemoized = memo(DiscoverMainApp);

Expand Down Expand Up @@ -134,14 +134,21 @@ export function DiscoverMainRoute({
stateContainer.actions.loadDataViewList(),
]);

if (!hasUserDataViewValue || !defaultDataViewExists) {
const hasAdHocDataViews = stateContainer.internalState.getState().adHocDataViews.length > 0;

if (
(!hasUserDataViewValue || !defaultDataViewExists) &&
(!hasAdHocDataViews || !hasESDataValue)
jughosta marked this conversation as resolved.
Show resolved Hide resolved
) {
setNoDataState({
showNoDataPage: true,
hasESData: hasESDataValue,
hasUserDataView: hasUserDataViewValue,
});

return false;
}

return true;
} catch (e) {
setError(e);
Expand Down Expand Up @@ -228,22 +235,45 @@ export function DiscoverMainRoute({
]
);

const rootProfileState = useRootProfile();
const { initializeProfileDataViews } = useDefaultAdHocDataViews({
stateContainer,
rootProfileState,
});

useEffect(() => {
if (!isCustomizationServiceInitialized) return;
setLoading(true);
setNoDataState({
hasESData: false,
hasUserDataView: false,
showNoDataPage: false,
});
setError(undefined);
if (savedSearchId) {
loadSavedSearch();
} else {
// restore the previously selected data view for a new state (when a saved search was open)
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
if (!isCustomizationServiceInitialized || rootProfileState.rootProfileLoading) {
return;
}
}, [isCustomizationServiceInitialized, loadSavedSearch, savedSearchId, stateContainer]);

const load = async () => {
setLoading(true);
setNoDataState({
hasESData: false,
hasUserDataView: false,
showNoDataPage: false,
});
setError(undefined);

await initializeProfileDataViews();

if (savedSearchId) {
await loadSavedSearch();
} else {
// restore the previously selected data view for a new state (when a saved search was open)
await loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
}
};

load();
}, [
initializeProfileDataViews,
isCustomizationServiceInitialized,
loadSavedSearch,
rootProfileState.rootProfileLoading,
savedSearchId,
stateContainer,
]);

// secondary fetch: in case URL is set to `/`, used to reset to 'new' state, keeping the current data view
useUrl({
Expand Down Expand Up @@ -340,8 +370,6 @@ export function DiscoverMainRoute({
stateContainer,
]);

const rootProfileState = useRootProfile();

if (error) {
return <DiscoverError error={error} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe('Test discover app state container', () => {
savedSearchState = getSavedSearchContainer({
services: discoverServiceMock,
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
internalStateContainer: internalState,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface InternalState {
isDataViewLoading: boolean;
savedDataViews: DataViewListItem[];
adHocDataViews: DataView[];
defaultProfileAdHocDataViewIds: string[];
expandedDoc: DataTableRecord | undefined;
customFilters: Filter[];
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving
Expand All @@ -46,10 +47,12 @@ export interface InternalStateTransitions {
setIsDataViewLoading: (state: InternalState) => (isLoading: boolean) => InternalState;
setSavedDataViews: (state: InternalState) => (dataView: DataViewListItem[]) => InternalState;
setAdHocDataViews: (state: InternalState) => (dataViews: DataView[]) => InternalState;
setDefaultProfileAdHocDataViews: (
state: InternalState
) => (dataViews: DataView[]) => InternalState;
appendAdHocDataViews: (
state: InternalState
) => (dataViews: DataView | DataView[]) => InternalState;
removeAdHocDataViewById: (state: InternalState) => (id: string) => InternalState;
replaceAdHocDataViewWithId: (
state: InternalState
) => (id: string, dataView: DataView) => InternalState;
Expand Down Expand Up @@ -90,6 +93,7 @@ export function getInternalStateContainer() {
dataView: undefined,
isDataViewLoading: false,
adHocDataViews: [],
defaultProfileAdHocDataViewIds: [],
savedDataViews: [],
expandedDoc: undefined,
customFilters: [],
Expand Down Expand Up @@ -126,6 +130,22 @@ export function getInternalStateContainer() {
...prevState,
adHocDataViews: newAdHocDataViewList,
}),
setDefaultProfileAdHocDataViews:
(prevState: InternalState) => (defaultProfileAdHocDataViews: DataView[]) => {
const adHocDataViews = prevState.adHocDataViews
.filter((dataView) => !prevState.defaultProfileAdHocDataViewIds.includes(dataView.id!))
.concat(defaultProfileAdHocDataViews);

const defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViews.map(
(dataView) => dataView.id!
);

return {
...prevState,
adHocDataViews,
defaultProfileAdHocDataViewIds,
};
},
appendAdHocDataViews:
(prevState: InternalState) => (dataViewsAdHoc: DataView | DataView[]) => {
// check for already existing data views
Expand All @@ -142,17 +162,24 @@ export function getInternalStateContainer() {
adHocDataViews: prevState.adHocDataViews.concat(dataViewsAdHoc),
};
},
removeAdHocDataViewById: (prevState: InternalState) => (id: string) => ({
...prevState,
adHocDataViews: prevState.adHocDataViews.filter((dataView) => dataView.id !== id),
}),
replaceAdHocDataViewWithId:
(prevState: InternalState) => (prevId: string, newDataView: DataView) => ({
...prevState,
adHocDataViews: prevState.adHocDataViews.map((dataView) =>
dataView.id === prevId ? newDataView : dataView
),
}),
(prevState: InternalState) => (prevId: string, newDataView: DataView) => {
let defaultProfileAdHocDataViewIds = prevState.defaultProfileAdHocDataViewIds;

if (defaultProfileAdHocDataViewIds.includes(prevId)) {
defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViewIds.map((id) =>
id === prevId ? newDataView.id! : id
);
}

return {
...prevState,
adHocDataViews: prevState.adHocDataViews.map((dataView) =>
dataView.id === prevId ? newDataView : dataView
),
defaultProfileAdHocDataViewIds,
};
},
setExpandedDoc: (prevState: InternalState) => (expandedDoc: DataTableRecord | undefined) => ({
...prevState,
expandedDoc,
Expand Down
Loading