Skip to content

Commit

Permalink
Merge branch 'main' into current-workspace-in-server-side
Browse files Browse the repository at this point in the history
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe authored Apr 12, 2024
2 parents 6f7b4b9 + 7eda01a commit 8eb1683
Show file tree
Hide file tree
Showing 22 changed files with 1,817 additions and 49 deletions.
1 change: 1 addition & 0 deletions .lycheeexclude
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ https://tiles.maps.search-services.aws.a2z.com/
https://telemetry.opensearch.org/
https://telemetry-staging.opensearch.org/
https://api.github.com/repos/opensearch-project/OpenSearch-Dashboards/
https://www.quandl.com/api/v1/datasets/
file:///*
git://*

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Workspace] Add update workspace page ([#6270](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6270))
- [Multiple Datasource] Make sure customer always have a default datasource ([#6237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6237))
- [Workspace] Add workspace list page ([#6182](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6182))
- [Workspace] Add API to duplicate saved objects among workspaces ([#6288](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6288))
- [Workspace] Add workspaces column to saved objects page ([#6225](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6225))
- [Multiple Datasource] Enhanced data source selector with default datasource shows as first choice ([#6293](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6293))
- [Multiple Datasource] Add multi data source support to sample vega visualizations ([#6218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6218))
Expand All @@ -86,6 +87,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303))
- [Workspace] Filter left nav menu items according to the current workspace ([#6234](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6234))
- [Multiple Datasource] Refactor data source selector component to include placeholder and add tests ([#6372](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6372))
- [Dynamic Configurations] Improve dynamic configurations by adding cache and simplifying client fetch ([#6364](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6364))
- [MD] Add OpenSearch cluster group label to top of single selectable dropdown ([#6400](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6400))
- [Workspace] Support workspace in saved objects client in server side. ([#6365](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6365))

Expand Down Expand Up @@ -144,6 +146,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

- Remove unused Sass in `tile_map` plugin ([#4110](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4110))
- [Multiple Datasource] Move data source selectable to its own folder, fix test and a few type errors for data source selectable component ([#6287](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6287))
- Remove KUI usage in `disabled_lab_visualization` ([#5462](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5462))

### 🔩 Tests

Expand Down
5 changes: 5 additions & 0 deletions src/core/public/doc_links/doc_links_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ export class DocLinksService {
// https://opensearch.org/docs/latest/dashboards/visualize/viz-index/
guide: `${OPENSEARCH_WEBSITE_DOCS}visualize/viz-index/`,
},
management: {
// https://opensearch.org/docs/latest/dashboards/management/advanced-settings/
advancedSettings: `${OPENSEARCH_DASHBOARDS_VERSIONED_DOCS}management/advanced-settings/`,
},
},
noDocumentation: {
auditbeat: `${OPENSEARCH_WEBSITE_DOCS}tools/index/#downloads`,
Expand Down Expand Up @@ -819,6 +823,7 @@ export interface DocLinksStart {
readonly guide: string;
};
readonly visualize: Record<string, string>;
readonly management: Record<string, string>;
};
readonly noDocumentation: {
readonly auditbeat: string;
Expand Down
106 changes: 92 additions & 14 deletions src/plugins/application_config/server/opensearch_config_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.getConfig();

Expand Down Expand Up @@ -77,7 +79,10 @@ describe('OpenSearch Configuration Client', () => {
}),
},
};
const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);

const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.getConfig()).rejects.toThrowError(ERROR_MESSAGE);
});
Expand All @@ -99,11 +104,45 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
has: jest.fn().mockReturnValue(false),
set: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.getEntityConfig('config1');

expect(value).toBe('value1');
expect(cache.set).toBeCalledWith('config1', 'value1');
});

it('return configuration value from cache', async () => {
const opensearchClient = {
asInternalUser: {
get: jest.fn().mockImplementation(() => {
return {
body: {
_source: {
value: 'value1',
},
},
};
}),
},
};

const cache = {
has: jest.fn().mockReturnValue(true),
get: jest.fn().mockReturnValue('cachedValue'),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.getEntityConfig('config1');

expect(value).toBe('cachedValue');
expect(cache.get).toBeCalledWith('config1');
});

it('throws error when input is empty', async () => {
Expand All @@ -121,7 +160,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.getEntityConfig(EMPTY_INPUT)).rejects.toThrowError(
ERROR_MESSSAGE_FOR_EMPTY_INPUT
Expand Down Expand Up @@ -151,9 +192,16 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
has: jest.fn().mockReturnValue(false),
set: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.getEntityConfig('config1')).rejects.toThrowError(ERROR_MESSAGE);

expect(cache.set).toBeCalledWith('config1', undefined);
});
});

Expand All @@ -167,11 +215,16 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
del: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.deleteEntityConfig('config1');

expect(value).toBe('config1');
expect(cache.del).toBeCalledWith('config1');
});

it('throws error when input entity is empty', async () => {
Expand All @@ -183,7 +236,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.deleteEntityConfig(EMPTY_INPUT)).rejects.toThrowError(
ERROR_MESSSAGE_FOR_EMPTY_INPUT
Expand Down Expand Up @@ -213,11 +268,16 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
del: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.deleteEntityConfig('config1');

expect(value).toBe('config1');
expect(cache.del).toBeCalledWith('config1');
});

it('return deleted document entity when deletion fails due to document not found', async () => {
Expand All @@ -241,11 +301,16 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
del: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.deleteEntityConfig('config1');

expect(value).toBe('config1');
expect(cache.del).toBeCalledWith('config1');
});

it('throws error when opensearch throws error', async () => {
Expand All @@ -271,7 +336,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.deleteEntityConfig('config1')).rejects.toThrowError(ERROR_MESSAGE);
});
Expand All @@ -287,11 +354,16 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {
set: jest.fn(),
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

const value = await client.updateEntityConfig('config1', 'newValue1');

expect(value).toBe('newValue1');
expect(cache.set).toBeCalledWith('config1', 'newValue1');
});

it('throws error when entity is empty ', async () => {
Expand All @@ -303,7 +375,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.updateEntityConfig(EMPTY_INPUT, 'newValue1')).rejects.toThrowError(
ERROR_MESSSAGE_FOR_EMPTY_INPUT
Expand All @@ -319,7 +393,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.updateEntityConfig('config1', EMPTY_INPUT)).rejects.toThrowError(
ERROR_MESSSAGE_FOR_EMPTY_INPUT
Expand Down Expand Up @@ -349,7 +425,9 @@ describe('OpenSearch Configuration Client', () => {
},
};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger);
const cache = {};

const client = new OpenSearchConfigurationClient(opensearchClient, INDEX_NAME, logger, cache);

await expect(client.updateEntityConfig('config1', 'newValue1')).rejects.toThrowError(
ERROR_MESSAGE
Expand Down
25 changes: 22 additions & 3 deletions src/plugins/application_config/server/opensearch_config_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,54 @@
* SPDX-License-Identifier: Apache-2.0
*/

import LRUCache from 'lru-cache';
import { IScopedClusterClient, Logger } from '../../../../src/core/server';

import { ConfigurationClient } from './types';
import { validate } from './string_utils';

export class OpenSearchConfigurationClient implements ConfigurationClient {
private client: IScopedClusterClient;
private configurationIndexName: string;
private readonly logger: Logger;
private cache: LRUCache<string, string | undefined>;

constructor(
scopedClusterClient: IScopedClusterClient,
configurationIndexName: string,
logger: Logger
logger: Logger,
cache: LRUCache<string, string | undefined>
) {
this.client = scopedClusterClient;
this.configurationIndexName = configurationIndexName;
this.logger = logger;
this.cache = cache;
}

async getEntityConfig(entity: string) {
const entityValidated = validate(entity, this.logger);

if (this.cache.has(entityValidated)) {
return this.cache.get(entityValidated);
}

this.logger.info(`Key ${entityValidated} is not found from cache.`);

try {
const data = await this.client.asInternalUser.get({
index: this.configurationIndexName,
id: entityValidated,
});
const value = data?.body?._source?.value;

return data?.body?._source?.value || '';
this.cache.set(entityValidated, value);

return value;
} catch (e) {
const errorMessage = `Failed to get entity ${entityValidated} due to error ${e}`;

this.logger.error(errorMessage);

this.cache.set(entityValidated, undefined);
throw e;
}
}
Expand All @@ -55,6 +68,8 @@ export class OpenSearchConfigurationClient implements ConfigurationClient {
},
});

this.cache.set(entityValidated, newValueValidated);

return newValueValidated;
} catch (e) {
const errorMessage = `Failed to update entity ${entityValidated} with newValue ${newValueValidated} due to error ${e}`;
Expand All @@ -74,15 +89,19 @@ export class OpenSearchConfigurationClient implements ConfigurationClient {
id: entityValidated,
});

this.cache.del(entityValidated);

return entityValidated;
} catch (e) {
if (e?.body?.error?.type === 'index_not_found_exception') {
this.logger.info('Attemp to delete a not found index.');
this.cache.del(entityValidated);
return entityValidated;
}

if (e?.body?.result === 'not_found') {
this.logger.info('Attemp to delete a not found document.');
this.cache.del(entityValidated);
return entityValidated;
}

Expand Down
Loading

0 comments on commit 8eb1683

Please sign in to comment.