From 298968af2e2cc7c1d7953c094ac6e8422e23a6f4 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Sat, 5 Oct 2024 19:26:45 +0800 Subject: [PATCH 1/4] feat: introducing workspace level ui settings and hide non-global ui settings from advance settings page + make defaultIndex pattern a workspace ui setting when workspace is on and make it a global setting when workspace is off Signed-off-by: Yulong Ruan --- src/core/server/index.ts | 5 ++ src/core/server/internal_types.ts | 3 ++ src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 2 + src/core/server/mocks.ts | 5 ++ src/core/server/plugins/plugin_context.ts | 2 + src/core/server/server.ts | 7 +++ src/core/server/workspace/index.ts | 19 +++++++ src/core/server/workspace/mocks.ts | 43 +++++++++++++++ .../server/workspace/workspace_service.ts | 53 +++++++++++++++++++ src/core/types/ui_settings.ts | 1 + .../management_app/advanced_settings.tsx | 18 ++++++- src/plugins/data/server/plugin.ts | 2 +- src/plugins/data/server/ui_settings.ts | 9 ++-- 14 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 src/core/server/workspace/index.ts create mode 100644 src/core/server/workspace/mocks.ts create mode 100644 src/core/server/workspace/workspace_service.ts diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 26dd30b8c6c4..984318556b4e 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -93,6 +93,7 @@ import { CoreEnvironmentUsageData, CoreServicesUsageData, } from './core_usage_data'; +import { WorkspaceSetup, WorkspaceStart } from './workspace'; export { CoreUsageData, CoreConfigUsageData, CoreEnvironmentUsageData, CoreServicesUsageData }; @@ -480,6 +481,8 @@ export interface CoreSetup = OsdServer as any; @@ -114,6 +115,7 @@ beforeEach(() => { metrics: metricsServiceMock.createInternalSetupContract(), security: securityServiceMock.createSetupContract(), dynamicConfig: dynamicConfigServiceMock.createInternalSetupContract(), + workspace: workspaceServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, uiPlugins: { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 1dd9be84a663..60c3e628b4ed 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -239,6 +239,7 @@ export class LegacyService implements CoreService { getAsyncLocalStore: startDeps.core.dynamicConfig.getAsyncLocalStore, createStoreFromRequest: startDeps.core.dynamicConfig.createStoreFromRequest, }, + workspace: startDeps.core.workspace, }; const router = setupDeps.core.http.createRouter('', this.legacyId); @@ -308,6 +309,7 @@ export class LegacyService implements CoreService { auditTrail: setupDeps.core.auditTrail, getStartServices: () => Promise.resolve([coreStart, startDeps.plugins, {}]), security: setupDeps.core.security, + workspace: setupDeps.core.workspace, }; // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 823e778b469e..37d90bcfa93f 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -53,6 +53,7 @@ import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_serv import { securityServiceMock } from './security/security_service.mock'; import { crossCompatibilityServiceMock } from './cross_compatibility/cross_compatibility.mock'; import { dynamicConfigServiceMock } from './config/dynamic_config_service.mock'; +import { workspaceServiceMock } from './workspace/mocks'; export { configServiceMock } from './config/mocks'; export { dynamicConfigServiceMock } from './config/mocks'; @@ -170,6 +171,7 @@ function createCoreSetupMock({ .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), security: securityServiceMock.createSetupContract(), dynamicConfigService: dynamicConfigServiceMock.createSetupContract(), + workspace: workspaceServiceMock.createSetupContract(), }; return mock; @@ -187,6 +189,7 @@ function createCoreStartMock() { coreUsageData: coreUsageDataServiceMock.createStartContract(), crossCompatibility: crossCompatibilityServiceMock.createStartContract(), dynamicConfig: dynamicConfigServiceMock.createStartContract(), + workspace: workspaceServiceMock.createStartContract(), }; return mock; @@ -209,6 +212,7 @@ function createInternalCoreSetupMock() { metrics: metricsServiceMock.createInternalSetupContract(), security: securityServiceMock.createSetupContract(), dynamicConfig: dynamicConfigServiceMock.createInternalSetupContract(), + workspace: workspaceServiceMock.createInternalSetupContract(), }; return setupDeps; } @@ -225,6 +229,7 @@ function createInternalCoreStartMock() { coreUsageData: coreUsageDataServiceMock.createStartContract(), crossCompatibility: crossCompatibilityServiceMock.createStartContract(), dynamicConfig: dynamicConfigServiceMock.createInternalStartContract(), + workspace: workspaceServiceMock.createInternalStartContract(), }; return startDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index d9e79ec51a2b..28b7ec6337cf 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -226,6 +226,7 @@ export function createPluginSetupContext( registerAsyncLocalStoreRequestHeader: deps.dynamicConfig.registerAsyncLocalStoreRequestHeader, getStartService: deps.dynamicConfig.getStartService, }, + workspace: deps.workspace, }; } @@ -282,5 +283,6 @@ export function createPluginStartContext( getClient: deps.dynamicConfig.getClient, createStoreFromRequest: deps.dynamicConfig.createStoreFromRequest, }, + workspace: deps.workspace, }; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 1b2e5f05679e..ca8d7a7775fc 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -71,6 +71,7 @@ import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from '. import { CoreUsageDataService } from './core_usage_data'; import { CoreRouteHandlerContext } from './core_route_handler_context'; import { DynamicConfigService } from './config/dynamic_config_service'; +import { WorkspaceService } from './workspace/workspace_service'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -88,6 +89,7 @@ export class Server { private readonly plugins: PluginsService; private readonly savedObjects: SavedObjectsService; private readonly uiSettings: UiSettingsService; + private readonly workspace: WorkspaceService; private readonly environment: EnvironmentService; private readonly metrics: MetricsService; private readonly httpResources: HttpResourcesService; @@ -130,6 +132,7 @@ export class Server { this.opensearch = new OpenSearchService(core); this.savedObjects = new SavedObjectsService(core); this.uiSettings = new UiSettingsService(core); + this.workspace = new WorkspaceService(core); this.capabilities = new CapabilitiesService(core); this.environment = new EnvironmentService(core); this.metrics = new MetricsService(core); @@ -199,6 +202,7 @@ export class Server { http: httpSetup, savedObjects: savedObjectsSetup, }); + const workspaceSetup = await this.workspace.setup(); const metricsSetup = await this.metrics.setup({ http: httpSetup }); @@ -247,6 +251,7 @@ export class Server { metrics: metricsSetup, security: securitySetup, dynamicConfig: dynamicConfigServiceSetup, + workspace: workspaceSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -285,6 +290,7 @@ export class Server { soStartSpan?.end(); const capabilitiesStart = this.capabilities.start(); const uiSettingsStart = await this.uiSettings.start(); + const workspaceStart = await this.workspace.start(); const metricsStart = await this.metrics.start(); const httpStart = this.http.getStartContract(); const coreUsageDataStart = this.coreUsageData.start({ @@ -308,6 +314,7 @@ export class Server { coreUsageData: coreUsageDataStart, crossCompatibility: crossCompatibilityServiceStart, dynamicConfig: dynamicConfigServiceStart, + workspace: workspaceStart, }; const pluginsStart = await this.plugins.start(this.coreStart); diff --git a/src/core/server/workspace/index.ts b/src/core/server/workspace/index.ts new file mode 100644 index 000000000000..5fec378f0722 --- /dev/null +++ b/src/core/server/workspace/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PublicMethodsOf } from '@osd/utility-types'; + +import { + InternalWorkspaceServiceSetup, + InternalWorkspaceServiceStart, + WorkspaceService, +} from './workspace_service'; + +export { InternalWorkspaceServiceSetup, InternalWorkspaceServiceStart } from './workspace_service'; + +export type WorkspaceSetup = InternalWorkspaceServiceSetup; +export type WorkspaceStart = InternalWorkspaceServiceStart; + +export type IWorkspaceService = PublicMethodsOf; diff --git a/src/core/server/workspace/mocks.ts b/src/core/server/workspace/mocks.ts new file mode 100644 index 000000000000..d708885edc92 --- /dev/null +++ b/src/core/server/workspace/mocks.ts @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IWorkspaceService } from '.'; +import { InternalWorkspaceServiceSetup, InternalWorkspaceServiceStart } from './workspace_service'; + +const createWorkspaceServiceMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createInternalSetupContractMock()), + start: jest.fn().mockReturnValue({}), + stop: jest.fn(), + }; + + return mocked; +}; + +const createInternalSetupContractMock = () => { + const mocked: jest.Mocked = { + isWorkspaceEnabled: jest.fn(), + }; + + return mocked; +}; +const createSetupContractMock = createInternalSetupContractMock; + +const createInternalStartContractMock = () => { + const mocked: jest.Mocked = { + isWorkspaceEnabled: jest.fn(), + }; + + return mocked; +}; +const createStartContractMock = createInternalStartContractMock; + +export const workspaceServiceMock = { + create: createWorkspaceServiceMock, + createInternalSetupContract: createInternalSetupContractMock, + createInternalStartContract: createInternalStartContractMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, +}; diff --git a/src/core/server/workspace/workspace_service.ts b/src/core/server/workspace/workspace_service.ts new file mode 100644 index 000000000000..9adeb130be96 --- /dev/null +++ b/src/core/server/workspace/workspace_service.ts @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; + +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; + +export interface InternalWorkspaceServiceSetup { + isWorkspaceEnabled: () => boolean; +} + +export interface InternalWorkspaceServiceStart { + isWorkspaceEnabled: () => boolean; +} + +/** @internal */ +export class WorkspaceService + implements CoreService { + private readonly log: Logger; + private readonly config$: Observable<{ enabled: boolean }>; + + constructor(private readonly coreContext: CoreContext) { + this.log = this.coreContext.logger.get('workspace-service'); + this.config$ = this.coreContext.configService.atPath<{ enabled: boolean }>('workspace'); + } + + public async setup(): Promise { + this.log.debug('Setting up workspace service'); + + const workspaceConfig = await this.config$.pipe(first()).toPromise(); + + return { + isWorkspaceEnabled: () => workspaceConfig.enabled, + }; + } + + public async start(): Promise { + this.log.debug('Starting workspace service'); + + const workspaceConfig = await this.config$.pipe(first()).toPromise(); + + return { + isWorkspaceEnabled: () => workspaceConfig.enabled, + }; + } + + public async stop() {} +} diff --git a/src/core/types/ui_settings.ts b/src/core/types/ui_settings.ts index 061c31c1d73a..6c0b90566d5f 100644 --- a/src/core/types/ui_settings.ts +++ b/src/core/types/ui_settings.ts @@ -63,6 +63,7 @@ export interface DeprecationSettings { export enum UiSettingScope { GLOBAL = 'global', USER = 'user', + WORKSPACE = 'workspace', } /** diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx index 0ecca876e94d..bb5e107dc066 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.tsx @@ -173,7 +173,23 @@ export class AdvancedSettingsComponent extends Component< const all = config.getAll(); const userSettingsEnabled = config.get('theme:enableUserControl'); return Object.entries(all) - .filter(([, setting]) => setting.scope !== UiSettingScope.USER) + .filter(([, setting]) => { + const scope = setting.scope; + // if scope is not defined, then it's a global ui setting + if (!scope) { + return true; + } + + if (typeof scope === 'string') { + return scope === UiSettingScope.GLOBAL; + } + + if (Array.isArray(scope)) { + return scope.includes(UiSettingScope.GLOBAL); + } + + return false; + }) .map((setting) => { return toEditableConfig({ def: setting[1], diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index a1e52fbb66a1..c9ca35a18cf6 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -106,7 +106,7 @@ export class DataServerPlugin this.autocompleteService.setup(core); this.dqlTelemetryService.setup(core, { usageCollection }); - core.uiSettings.register(getUiSettings()); + core.uiSettings.register(getUiSettings(core.workspace.isWorkspaceEnabled())); const searchSetup = await this.searchService.setup(core, { registerFunction: expressions.registerFunction, diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 4cee0a9894dd..1fbdd5973d13 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -30,10 +30,10 @@ import { i18n } from '@osd/i18n'; import { schema } from '@osd/config-schema'; -import { UiSettingsParams } from 'opensearch-dashboards/server'; // @ts-ignore untyped module import numeralLanguages from '@elastic/numeral/languages'; import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; +import { UiSettingsParams, UiSettingScope } from '../../../core/server'; const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', { defaultMessage: 'Lucene', @@ -79,7 +79,9 @@ const numeralLanguageIds = [ }), ]; -export function getUiSettings(): Record> { +export function getUiSettings( + workspaceEnabled: boolean +): Record> { return { [UI_SETTINGS.META_FIELDS]: { name: i18n.translate('data.advancedSettings.metaFieldsTitle', { @@ -200,6 +202,7 @@ export function getUiSettings(): Record> { defaultMessage: 'The index to access if no index is set', }), schema: schema.nullable(schema.string()), + scope: workspaceEnabled ? UiSettingScope.WORKSPACE : UiSettingScope.GLOBAL, }, [UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX]: { name: i18n.translate('data.advancedSettings.courier.ignoreFilterTitle', { @@ -757,7 +760,7 @@ export function getUiSettings(): Record> { }), value: ['none'], description: i18n.translate('data.advancedSettings.searchQueryLanguageBlocklistText', { - defaultMessage: `Additional languages that are blocked from being used in the query editor. + defaultMessage: `Additional languages that are blocked from being used in the query editor. Note: DQL and Lucene will not be blocked even if set.`, }), schema: schema.arrayOf(schema.string()), From a10dc9cb1bfb3bd335b4b705badc3046b0af8f80 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 02:44:56 +0000 Subject: [PATCH 2/4] Changeset file for PR #8500 created/updated --- changelogs/fragments/8500.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/8500.yml diff --git a/changelogs/fragments/8500.yml b/changelogs/fragments/8500.yml new file mode 100644 index 000000000000..63e3a509aed0 --- /dev/null +++ b/changelogs/fragments/8500.yml @@ -0,0 +1,2 @@ +feat: +- Introducing workspace level ui settings and hide non-global ui settings from advance settings page ([#8500](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8500)) \ No newline at end of file From dd15fe67390bbc2c592bdbe7c3409ffc39428f39 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Mon, 7 Oct 2024 17:33:43 +0800 Subject: [PATCH 3/4] fix: failed tests Signed-off-by: Yulong Ruan --- src/plugins/data/server/ui_settings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 1fbdd5973d13..cd613b848a9a 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -30,10 +30,11 @@ import { i18n } from '@osd/i18n'; import { schema } from '@osd/config-schema'; +import { UiSettingsParams } from 'opensearch-dashboards/server'; // @ts-ignore untyped module import numeralLanguages from '@elastic/numeral/languages'; import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; -import { UiSettingsParams, UiSettingScope } from '../../../core/server'; +import { UiSettingScope } from '../../../core/server/ui_settings/types'; const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', { defaultMessage: 'Lucene', From 5c0f79f25ebf4743246d85418435f9f9c22af653 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Mon, 7 Oct 2024 18:36:09 +0800 Subject: [PATCH 4/4] fix: lint Signed-off-by: Yulong Ruan --- src/plugins/data/server/ui_settings.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index cd613b848a9a..153361959e63 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -34,6 +34,9 @@ import { UiSettingsParams } from 'opensearch-dashboards/server'; // @ts-ignore untyped module import numeralLanguages from '@elastic/numeral/languages'; import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; +// cannot import from core/server due to src/core/server/saved_objects/opensearch_query.js which +// export { opensearchKuery } from '../../../plugins/data/server'; +// eslint-disable-next-line @osd/eslint/no-restricted-paths import { UiSettingScope } from '../../../core/server/ui_settings/types'; const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', {