Skip to content

Commit

Permalink
[Lens] Disable the global timepicker for index pattern without primar…
Browse files Browse the repository at this point in the history
…y timefield and visualizations without timefield (#108052)
  • Loading branch information
mbondyra authored Aug 13, 2021
1 parent dd85150 commit e33dacc
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 8 deletions.
48 changes: 47 additions & 1 deletion x-pack/plugins/lens/public/app_plugin/app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,52 @@ describe('Lens App', () => {
});
});

describe('TopNavMenu#showDatePicker', () => {
it('shows date picker if any used index pattern isTimeBased', async () => {
const customServices = makeDefaultServices(sessionIdSubject);
customServices.data.indexPatterns.get = jest
.fn()
.mockImplementation((id) =>
Promise.resolve({ id, isTimeBased: () => true } as IndexPattern)
);
const { services } = await mountWith({ services: customServices });
expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith(
expect.objectContaining({ showDatePicker: true }),
{}
);
});
it('shows date picker if active datasource isTimeBased', async () => {
const customServices = makeDefaultServices(sessionIdSubject);
customServices.data.indexPatterns.get = jest
.fn()
.mockImplementation((id) =>
Promise.resolve({ id, isTimeBased: () => true } as IndexPattern)
);
const customProps = makeDefaultProps();
customProps.datasourceMap.testDatasource.isTimeBased = () => true;
const { services } = await mountWith({ props: customProps, services: customServices });
expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith(
expect.objectContaining({ showDatePicker: true }),
{}
);
});
it('does not show date picker if index pattern nor active datasource is not time based', async () => {
const customServices = makeDefaultServices(sessionIdSubject);
customServices.data.indexPatterns.get = jest
.fn()
.mockImplementation((id) =>
Promise.resolve({ id, isTimeBased: () => true } as IndexPattern)
);
const customProps = makeDefaultProps();
customProps.datasourceMap.testDatasource.isTimeBased = () => false;
const { services } = await mountWith({ props: customProps, services: customServices });
expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith(
expect.objectContaining({ showDatePicker: false }),
{}
);
});
});

describe('persistence', () => {
it('passes query and indexPatterns to TopNavMenu', async () => {
const { instance, lensStore, services } = await mountWith({ preloadedState: {} });
Expand All @@ -294,7 +340,7 @@ describe('Lens App', () => {
expect(services.navigation.ui.TopNavMenu).toHaveBeenCalledWith(
expect.objectContaining({
query: 'fake query',
indexPatterns: [{ id: 'mockip' }],
indexPatterns: [{ id: 'mockip', isTimeBased: expect.any(Function) }],
}),
{}
);
Expand Down
12 changes: 11 additions & 1 deletion x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export const LensTopNavMenu = ({
activeDatasourceId,
datasourceStates,
} = useLensSelector((state) => state.lens);
const allLoaded = Object.values(datasourceStates).every(({ isLoading }) => isLoading === false);

useEffect(() => {
const activeDatasource =
Expand Down Expand Up @@ -390,7 +391,16 @@ export const LensTopNavMenu = ({
dateRangeTo={to}
indicateNoData={indicateNoData}
showSearchBar={true}
showDatePicker={true}
showDatePicker={
indexPatterns.some((ip) => ip.isTimeBased()) ||
Boolean(
allLoaded &&
activeDatasourceId &&
datasourceMap[activeDatasourceId].isTimeBased(
datasourceStates[activeDatasourceId].state
)
)
}
showQueryBar={true}
showFilterBar={true}
data-test-subj="lnsApp_topNav"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1544,4 +1544,83 @@ describe('IndexPattern Data Source', () => {
});
});
});
describe('#isTimeBased', () => {
it('should return true if date histogram exists in any layer', () => {
const state = enrichBaseState({
currentIndexPatternId: '1',
layers: {
first: {
indexPatternId: '1',
columnOrder: ['metric'],
columns: {
metric: {
label: 'Count of records2',
dataType: 'number',
isBucketed: false,
sourceField: 'Records',
operationType: 'count',
},
},
},
second: {
indexPatternId: '1',
columnOrder: ['bucket1', 'bucket2', 'metric2'],
columns: {
metric2: {
label: 'Count of records',
dataType: 'number',
isBucketed: false,
sourceField: 'Records',
operationType: 'count',
},
bucket1: {
label: 'Date',
dataType: 'date',
isBucketed: true,
operationType: 'date_histogram',
sourceField: 'timestamp',
params: {
interval: '1d',
},
},
bucket2: {
label: 'Terms',
dataType: 'string',
isBucketed: true,
operationType: 'terms',
sourceField: 'geo.src',
params: {
orderBy: { type: 'alphabetical' },
orderDirection: 'asc',
size: 10,
},
},
},
},
},
});
expect(indexPatternDatasource.isTimeBased(state)).toEqual(true);
});
it('should return false if date histogram does not exist in any layer', () => {
const state = enrichBaseState({
currentIndexPatternId: '1',
layers: {
first: {
indexPatternId: '1',
columnOrder: ['metric'],
columns: {
metric: {
label: 'Count of records',
dataType: 'number',
isBucketed: false,
sourceField: 'Records',
operationType: 'count',
},
},
},
},
});
expect(indexPatternDatasource.isTimeBased(state)).toEqual(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,16 @@ export function getIndexPatternDatasource({
const ids = Object.values(state.layers || {}).map(({ indexPatternId }) => indexPatternId);
return ids.filter((id) => !state.indexPatterns[id]);
},
isTimeBased: (state) => {
const { layers } = state;
return (
Boolean(layers) &&
Object.values(layers).some((layer) => {
const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed);
return buckets.some((colId) => layer.columns[colId].operationType === 'date_histogram');
})
);
},
};

return indexPatternDatasource;
Expand Down
5 changes: 2 additions & 3 deletions x-pack/plugins/lens/public/mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export function createMockDatasource(id: string): DatasourceMock {
publicAPIMock,
getErrorMessages: jest.fn((_state) => undefined),
checkIntegrity: jest.fn((_state) => []),
isTimeBased: jest.fn(),
};
}

Expand Down Expand Up @@ -309,9 +310,7 @@ export function mockDataPlugin(sessionIdSubject = new Subject<string>()) {
state$: new Observable(),
},
indexPatterns: {
get: jest.fn((id) => {
return new Promise((resolve) => resolve({ id }));
}),
get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })),
},
search: createMockSearchService(),
nowProvider: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,7 @@ function makeDefaultData(): jest.Mocked<DataPublicPluginStart> {
state$: new Observable(),
},
indexPatterns: {
get: jest.fn((id) => {
return new Promise((resolve) => resolve({ id }));
}),
get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })),
},
search: createMockSearchService(),
nowProvider: {
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/lens/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ export interface Datasource<T = unknown, P = unknown> {
* The frame calls this function to display warnings about visualization
*/
getWarningMessages?: (state: T, frame: FramePublicAPI) => React.ReactNode[] | undefined;
/**
* Checks if the visualization created is time based, for example date histogram
*/
isTimeBased: (state: T) => boolean;
}

export interface DatasourceFixAction<T> {
Expand Down

0 comments on commit e33dacc

Please sign in to comment.