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] Makes caching dataset options optional #8799

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions changelogs/fragments/8799.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fix:
- [Discover] Makes cachign dataset options optional and configurable by the dataset type ([#8799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8799))
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,51 @@ describe('DatasetService', () => {
expect(mockType.fetch).toHaveBeenCalledTimes(2);
});

test('fetchOptions respects cacheOptions', async () => {
const mockDataStructure = {
id: 'root',
title: 'Test Structure',
type: 'test-type',
children: [
{
id: 'child1',
title: 'Child 1',
type: 'test-type',
},
],
};

const fetchMock = jest.fn().mockResolvedValue(mockDataStructure);

const mockTypeWithCache = {
id: 'test-type',
title: 'Test Type',
meta: {
icon: { type: 'test' },
cacheOptions: true,
},
fetch: fetchMock,
toDataset: jest.fn(),
fetchFields: jest.fn(),
supportedLanguages: jest.fn(),
};

service.registerType(mockTypeWithCache);
service.clearCache(); // Ensure clean state

// First call should fetch
const result = await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');
expect(result).toMatchObject(mockDataStructure);

// Clear fetch mock call count
fetchMock.mockClear();

// Second call should use cache
const cachedResult = await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');
expect(cachedResult).toMatchObject(mockDataStructure);
expect(fetchMock).not.toHaveBeenCalled();
});

test('clear cache', async () => {
service.registerType(mockType);

Expand All @@ -99,16 +144,23 @@ describe('DatasetService', () => {
});

test('caching object correctly sets last cache time', async () => {
service.registerType(mockType);

const time = Date.now();

Date.now = jest.fn(() => time);

const mockTypeWithCache = {
...mockType,
meta: {
...mockType.meta,
cacheOptions: true,
},
};

service.registerType(mockTypeWithCache);
await service.fetchOptions(mockDataPluginServices, mockPath, 'test-type');

expect(service.getLastCacheTime()).toEqual(time);
});

test('calling cacheDataset on dataset caches it', async () => {
const mockDataset = {
id: 'test-dataset',
Expand All @@ -117,7 +169,7 @@ describe('DatasetService', () => {
} as Dataset;
service.registerType(mockType);

await service.cacheDataset(mockDataset);
await service.cacheDataset(mockDataset, mockDataPluginServices);
expect(indexPatterns.create).toHaveBeenCalledTimes(1);
expect(indexPatterns.saveToCache).toHaveBeenCalledTimes(1);
});
Expand All @@ -130,7 +182,7 @@ describe('DatasetService', () => {
type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN,
} as Dataset;

await service.cacheDataset(mockDataset);
await service.cacheDataset(mockDataset, mockDataPluginServices);
expect(indexPatterns.create).toHaveBeenCalledTimes(0);
expect(indexPatterns.saveToCache).toHaveBeenCalledTimes(0);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,17 @@
.join('&');
const cacheKey =
`${dataType}.${lastPathItem.id}` + (fetchOptionsKey.length ? `?${fetchOptionsKey}` : '');

const cachedDataStructure = this.sessionStorage.get<CachedDataStructure>(cacheKey);
if (cachedDataStructure?.children?.length > 0) {
return this.cacheToDataStructure(dataType, cachedDataStructure);
if (type.meta.cacheOptions) {
const cachedDataStructure = this.sessionStorage.get<CachedDataStructure>(cacheKey);

Check warning on line 165 in src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts#L165

Added line #L165 was not covered by tests
if (cachedDataStructure?.children?.length > 0) {
return this.cacheToDataStructure(dataType, cachedDataStructure);

Check warning on line 167 in src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts#L167

Added line #L167 was not covered by tests
}
}

const fetchedDataStructure = await type.fetch(services, path, options);
this.cacheDataStructure(dataType, fetchedDataStructure);
if (type.meta.cacheOptions) {
this.cacheDataStructure(dataType, fetchedDataStructure);

Check warning on line 173 in src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts#L173

Added line #L173 was not covered by tests
}
return fetchedDataStructure;
}

Expand Down Expand Up @@ -272,7 +275,7 @@
? {
id: dataSource.id,
title: dataSource.attributes?.title,
type: dataSource.attributes?.dataSourceEngineType,
type: dataSource.attributes?.dataSourceEngineType || '',
}
: undefined,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface DatasetTypeConfig {
supportsTimeFilter?: boolean;
/** Optional isFieldLoadAsync determines if field loads are async */
isFieldLoadAsync?: boolean;
/** Optional cacheOptions determines if the data structure is cacheable. Defaults to false */
cacheOptions?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just name it cacheable since it is a boolean.

};
/**
* Converts a DataStructure to a Dataset.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const s3TypeConfig: DatasetTypeConfig = {
searchOnLoad: true,
supportsTimeFilter: false,
isFieldLoadAsync: true,
cacheOptions: true,
},

toDataset: (path: DataStructure[]): Dataset => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ describe('CreateExtension', () => {
`);
expect(httpMock.get).toBeCalledWith('/api/enhancements/assist/languages', {
query: { dataSourceId: 'mock-data-source-id2' },
signal: new AbortController().signal,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,28 @@ const [getAvailableLanguagesForDataSource, clearCache] = (() => {
const pendingRequests: Map<string | undefined, Promise<string[]>> = new Map();

return [
async (http: HttpSetup, dataSourceId: string | undefined) => {
async (http: HttpSetup, dataSourceId: string | undefined, timeout?: number) => {
const cached = availableLanguagesByDataSource.get(dataSourceId);
if (cached !== undefined) return cached;

const pendingRequest = pendingRequests.get(dataSourceId);
if (pendingRequest !== undefined) return pendingRequest;

const controller = timeout ? new AbortController() : undefined;
const timeoutId = timeout ? setTimeout(() => controller?.abort(), timeout) : undefined;

const languagesPromise = http
.get<{ configuredLanguages: string[] }>(API.QUERY_ASSIST.LANGUAGES, {
query: { dataSourceId },
signal: controller?.signal,
})
.then((response) => response.configuredLanguages)
.catch(() => [])
.finally(() => pendingRequests.delete(dataSourceId));
.finally(() => {
pendingRequests.delete(dataSourceId);
if (timeoutId) clearTimeout(timeoutId);
});

pendingRequests.set(dataSourceId, languagesPromise);

const languages = await languagesPromise;
Expand Down Expand Up @@ -98,9 +106,9 @@ export const createQueryAssistExtension = (
id: 'query-assist',
order: 1000,
getDataStructureMeta: async (dataSourceId) => {
const isEnabled = await getAvailableLanguagesForDataSource(http, dataSourceId).then(
(languages) => languages.length > 0
);
// [TODO] - The timmeout exists because the loading of the Datasource menu is prevented until this request completes. This if a single cluster is down the request holds the whole menu level in a loading state. We should make this call non blocking and load the datasource meta in the background.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// [TODO] - The timmeout exists because the loading of the Datasource menu is prevented until this request completes. This if a single cluster is down the request holds the whole menu level in a loading state. We should make this call non blocking and load the datasource meta in the background.
// [TODO] - The timeout exists because the loading of the Datasource menu is prevented until this request completes. Then if a single cluster is down the request holds the whole menu level in a loading state. We should make this call non blocking and load the datasource meta in the background.

const isEnabled = await getAvailableLanguagesForDataSource(http, dataSourceId, 3000) // 3s timeout for quick check
.then((languages) => languages.length > 0);
if (isEnabled) {
return {
type: DATA_STRUCTURE_META_TYPES.FEATURE,
Expand Down
Loading