diff --git a/.github/workflows/cypress_workflow.yml b/.github/workflows/cypress_workflow.yml index c66b2f750567..ef985cfa9065 100644 --- a/.github/workflows/cypress_workflow.yml +++ b/.github/workflows/cypress_workflow.yml @@ -32,7 +32,7 @@ env: TEST_REPO: ${{ inputs.test_repo != '' && inputs.test_repo || 'opensearch-project/opensearch-dashboards-functional-test' }} TEST_BRANCH: "${{ inputs.test_branch != '' && inputs.test_branch || github.base_ref }}" FTR_PATH: 'ftr' - START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --savedObjects.maxImportPayloadBytes=10485760 --server.maxPayloadBytes=1759977 --logging.json=false --data.search.aggs.shardDelay.enabled=true --csp.warnLegacyBrowsers=false' + START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --savedObjects.maxImportPayloadBytes=10485760 --server.maxPayloadBytes=1759977 --logging.json=false --data.search.aggs.shardDelay.enabled=true --csp.warnLegacyBrowsers=false --uiSettings.overrides["query:enhancements:enabled"]=false' OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot -E cluster.routing.allocation.disk.threshold_enabled=false' CYPRESS_BROWSER: 'chromium' CYPRESS_VISBUILDER_ENABLED: true diff --git a/changelogs/fragments/7314.yml b/changelogs/fragments/7314.yml new file mode 100644 index 000000000000..a3ff4b409d54 --- /dev/null +++ b/changelogs/fragments/7314.yml @@ -0,0 +1,2 @@ +fix: +- Unused config setting and remove data sources as a required plugin. ([#7314](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7314)) \ No newline at end of file diff --git a/changelogs/fragments/7318.yml b/changelogs/fragments/7318.yml new file mode 100644 index 000000000000..4f7d9abe3553 --- /dev/null +++ b/changelogs/fragments/7318.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Add "All use case" option to workspace form ([#7318](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7318)) \ No newline at end of file diff --git a/package.json b/package.json index 3e684a46e280..f30b130865ab 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "start": "scripts/use_node scripts/opensearch_dashboards --dev", "start:docker": "scripts/use_node scripts/opensearch_dashboards --dev --opensearch.hosts=$OPENSEARCH_HOSTS --opensearch.ignoreVersionMismatch=true --server.host=$SERVER_HOST", "start:security": "scripts/use_node scripts/opensearch_dashboards --dev --security", - "start:enhancements": "scripts/use_node scripts/opensearch_dashboards --dev --data_source.enabled=true --uiSettings.overrides['query:enhancements:enabled']=true", + "start:enhancements": "scripts/use_node scripts/opensearch_dashboards --dev --uiSettings.overrides['query:enhancements:enabled']=true", "debug": "scripts/use_node --nolazy --inspect scripts/opensearch_dashboards --dev", "debug-break": "scripts/use_node --nolazy --inspect-brk scripts/opensearch_dashboards --dev", "lint": "yarn run lint:es && yarn run lint:style", diff --git a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx index 2f6f47659cdc..a7c8e0af3e54 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx @@ -191,16 +191,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { } function isValidQuery(query: Query | undefined) { - if (!query || !query.query) return false; - return ( - !Array.isArray(props.indexPatterns!) || - compact(props.indexPatterns!).length === 0 || - fromUser(query!.query).includes( - typeof props.indexPatterns[0] === 'string' - ? props.indexPatterns[0] - : props.indexPatterns[0].title - ) - ); + if (query && query.query) return true; } function getQueryStringInitialValue(language: string) { diff --git a/src/plugins/data/public/ui/settings/settings.ts b/src/plugins/data/public/ui/settings/settings.ts index f90bee5cf47e..df04d3dc6e7d 100644 --- a/src/plugins/data/public/ui/settings/settings.ts +++ b/src/plugins/data/public/ui/settings/settings.ts @@ -35,7 +35,7 @@ export class Settings { private readonly queryEnhancements: Map, private readonly queryEditorExtensionMap: Record ) { - this.isEnabled = false; + this.isEnabled = true; this.setUserQueryEnhancementsEnabled(this.isEnabled); this.enhancedAppNames = this.isEnabled ? this.config.supportedAppNames : []; } diff --git a/src/plugins/data/public/ui/ui_service.ts b/src/plugins/data/public/ui/ui_service.ts index 243490dc8201..1e0e6be8b78c 100644 --- a/src/plugins/data/public/ui/ui_service.ts +++ b/src/plugins/data/public/ui/ui_service.ts @@ -42,7 +42,6 @@ export class UiService implements Plugin { return { __enhance: (enhancements?: UiEnhancements) => { if (!enhancements) return; - if (!this.enhancementsConfig.enabled) return; if (enhancements.query && enhancements.query.language) { this.queryEnhancements.set(enhancements.query.language, enhancements.query); } diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index dfe27d16c252..95439335e18e 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -709,7 +709,7 @@ export function getUiSettings(): Record> { name: i18n.translate('data.advancedSettings.query.enhancements.enableTitle', { defaultMessage: 'Enable query enhancements', }), - value: false, + value: true, description: i18n.translate('data.advancedSettings.query.enhancements.enableText', { defaultMessage: ` Experimental: diff --git a/src/plugins/query_enhancements/opensearch_dashboards.json b/src/plugins/query_enhancements/opensearch_dashboards.json index e6ed7e2e0a17..b09494aab0ca 100644 --- a/src/plugins/query_enhancements/opensearch_dashboards.json +++ b/src/plugins/query_enhancements/opensearch_dashboards.json @@ -3,7 +3,7 @@ "version": "opensearchDashboards", "server": true, "ui": true, - "requiredPlugins": ["data", "opensearchDashboardsReact", "opensearchDashboardsUtils", "dataSource", "dataSourceManagement", "savedObjects", "uiActions"], - "optionalPlugins": [] + "requiredPlugins": ["data", "opensearchDashboardsReact", "opensearchDashboardsUtils", "dataSourceManagement", "savedObjects", "uiActions"], + "optionalPlugins": ["dataSource"] } diff --git a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts index 2d5a5e00ce6f..ae9ba5d6a4fa 100644 --- a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts +++ b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts @@ -127,4 +127,17 @@ describe('useWorkspaceForm', () => { }) ); }); + it('should update selected use case', () => { + const { renderResult } = setup({ + id: 'foo', + name: 'test-workspace-name', + features: ['use-case-observability'], + }); + + expect(renderResult.result.current.formData.useCase).toBe('observability'); + act(() => { + renderResult.result.current.handleUseCaseChange('search'); + }); + expect(renderResult.result.current.formData.useCase).toBe('search'); + }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts index b15a6e6a670d..e0b01454fa0f 100644 --- a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts +++ b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts @@ -13,8 +13,8 @@ import { import { useApplications } from '../../hooks'; import { + getFirstUseCaseOfFeatureConfigs, getUseCaseFeatureConfig, - getUseCaseFromFeatureConfig, isUseCaseFeatureConfig, } from '../../utils'; import { DataSource } from '../../../common/types'; @@ -30,8 +30,6 @@ import { WorkspacePermissionItemType } from './constants'; const workspaceHtmlIdGenerator = htmlIdGenerator(); -const isNotNull = (value: T | null): value is T => !!value; - export const useWorkspaceForm = ({ application, defaultValues, @@ -51,10 +49,9 @@ export const useWorkspaceForm = ({ const [featureConfigs, setFeatureConfigs] = useState( appendDefaultFeatureIds(defaultValues?.features ?? []) ); - const selectedUseCases = useMemo( - () => featureConfigs.map(getUseCaseFromFeatureConfig).filter(isNotNull), - [featureConfigs] - ); + const selectedUseCase = useMemo(() => getFirstUseCaseOfFeatureConfigs(featureConfigs), [ + featureConfigs, + ]); const [permissionSettings, setPermissionSettings] = useState< Array & Partial> >(initialPermissionSettingsRef.current); @@ -72,7 +69,7 @@ export const useWorkspaceForm = ({ name, description, features: featureConfigs, - useCases: selectedUseCases, + useCase: selectedUseCase, color, permissionSettings, selectedDataSources, @@ -92,14 +89,14 @@ export const useWorkspaceForm = ({ formIdRef.current = workspaceHtmlIdGenerator(); } - const handleUseCasesChange = useCallback( - (newUseCases: string[]) => { + const handleUseCaseChange = useCallback( + (newUseCase: string) => { setFeatureConfigs((previousFeatureConfigs) => { return [ ...previousFeatureConfigs.filter( (featureConfig) => !isUseCaseFeatureConfig(featureConfig) ), - ...newUseCases.map((useCaseItem) => getUseCaseFeatureConfig(useCaseItem)), + getUseCaseFeatureConfig(newUseCase), ]; }); }, @@ -157,7 +154,7 @@ export const useWorkspaceForm = ({ numberOfChanges, handleFormSubmit, handleColorChange, - handleUseCasesChange, + handleUseCaseChange, handleNameInputChange, setPermissionSettings, setSelectedDataSources, diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx index 2e8b7cb32415..3834e7a23628 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx @@ -59,7 +59,7 @@ export const WorkspaceDetailForm = (props: WorkspaceFormProps) => { numberOfChanges, handleFormSubmit, handleColorChange, - handleUseCasesChange, + handleUseCaseChange, setPermissionSettings, handleNameInputChange, setSelectedDataSources, @@ -109,8 +109,8 @@ export const WorkspaceDetailForm = (props: WorkspaceFormProps) => { diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_form.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_form.tsx index 07c86ef15ab0..c7e319b48435 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_form.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_form.tsx @@ -41,7 +41,7 @@ export const WorkspaceForm = (props: WorkspaceFormProps) => { numberOfChanges, handleFormSubmit, handleColorChange, - handleUseCasesChange, + handleUseCaseChange, handleNameInputChange, setPermissionSettings, setSelectedDataSources, @@ -85,8 +85,8 @@ export const WorkspaceForm = (props: WorkspaceFormProps) => { diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_use_case.test.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_use_case.test.tsx index 7aa04a547c2c..9825cf07c209 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_use_case.test.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_use_case.test.tsx @@ -23,11 +23,10 @@ const setup = (options?: Partial) => { id: 'system-use-case', title: 'System use case', description: 'System use case description', - features: [], systematic: true, }, ]} - value={[]} + value="" onChange={onChangeMock} formErrors={formErrors} {...options} @@ -49,19 +48,19 @@ describe('WorkspaceUseCase', () => { expect(renderResult.getByText('Search')).toBeInTheDocument(); }); - it('should call onChange with new added use case', () => { + it('should call onChange with new checked use case', () => { const { renderResult, onChangeMock } = setup(); expect(onChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getByText('Observability')); - expect(onChangeMock).toHaveBeenLastCalledWith(['observability']); + expect(onChangeMock).toHaveBeenLastCalledWith('observability'); }); - it('should call onChange without removed use case', () => { - const { renderResult, onChangeMock } = setup({ value: ['observability'] }); + it('should not call onChange after checked use case clicked', () => { + const { renderResult, onChangeMock } = setup({ value: 'observability' }); expect(onChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getByText('Observability')); - expect(onChangeMock).toHaveBeenLastCalledWith([]); + expect(onChangeMock).not.toHaveBeenCalled(); }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_use_case.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_use_case.tsx index 06a00cefa39b..e77f5b03c709 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_use_case.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_use_case.tsx @@ -6,6 +6,8 @@ import React, { useCallback } from 'react'; import { i18n } from '@osd/i18n'; import { EuiCheckableCard, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui'; + +import { DEFAULT_NAV_GROUPS } from '../../../../../core/public'; import { WorkspaceUseCase as WorkspaceUseCaseObject } from '../../types'; import { WorkspaceFormErrors } from './types'; import './workspace_use_case.scss'; @@ -31,7 +33,7 @@ const WorkspaceUseCaseCard = ({ return ( void; + value: string | undefined; + onChange: (newValue: string) => void; formErrors: WorkspaceFormErrors; - availableUseCases: WorkspaceUseCaseObject[]; + availableUseCases: Array< + Pick + >; } export const WorkspaceUseCase = ({ @@ -59,17 +63,6 @@ export const WorkspaceUseCase = ({ formErrors, availableUseCases, }: WorkspaceUseCaseProps) => { - const handleCardChange = useCallback( - (id: string) => { - if (!value.includes(id)) { - onChange([...value, id]); - return; - } - onChange(value.filter((item) => item !== id)); - }, - [value, onChange] - ); - return ( {availableUseCases .filter((item) => !item.systematic) + .concat(DEFAULT_NAV_GROUPS.all) .map(({ id, title, description }) => ( ))} diff --git a/src/plugins/workspace/public/components/workspace_list/index.test.tsx b/src/plugins/workspace/public/components/workspace_list/index.test.tsx index 5e55205c196e..d939b8f84ea4 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.test.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.test.tsx @@ -25,7 +25,7 @@ jest.mock('../delete_workspace_modal', () => ({ function getWrapWorkspaceListInContext( workspaceList = [ - { id: 'id1', name: 'name1', features: [] }, + { id: 'id1', name: 'name1', features: ['use-case-all'] }, { id: 'id2', name: 'name2' }, { id: 'id3', name: 'name3', features: ['use-case-observability'] }, ] @@ -69,6 +69,7 @@ describe('WorkspaceList', () => { expect(getByText('name2')).toBeInTheDocument(); // should display use case + expect(getByText('All use case')).toBeInTheDocument(); expect(getByText('Observability')).toBeInTheDocument(); }); it('should be able to apply debounce search after input', async () => { diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 0d2e3c79082d..3a9342cf68f6 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -17,7 +17,7 @@ import { import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject, of } from 'rxjs'; import { i18n } from '@osd/i18n'; -import { debounce } from '../../../../../core/public'; +import { debounce, DEFAULT_NAV_GROUPS } from '../../../../../core/public'; import { WorkspaceAttribute } from '../../../../../core/public'; import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; import { navigateToWorkspaceDetail } from '../utils/workspace'; @@ -26,7 +26,7 @@ import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; import { cleanWorkspaceId } from '../../../../../core/public'; import { DeleteWorkspaceModal } from '../delete_workspace_modal'; -import { getUseCaseFromFeatureConfig } from '../../utils'; +import { getFirstUseCaseOfFeatureConfigs, getUseCaseFromFeatureConfig } from '../../utils'; import { WorkspaceUseCase } from '../../types'; const WORKSPACE_LIST_PAGE_DESCRIPTION = i18n.translate('workspace.list.description', { @@ -108,17 +108,14 @@ export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { if (!features || features.length === 0) { return ''; } - const results: string[] = []; - features.forEach((featureConfig) => { - const useCaseId = getUseCaseFromFeatureConfig(featureConfig); - if (useCaseId) { - const useCase = registeredUseCases?.find(({ id }) => id === useCaseId); - if (useCase) { - results.push(useCase.title); - } - } - }); - return results.join(', '); + const useCaseId = getFirstUseCaseOfFeatureConfigs(features); + const useCase = + useCaseId === DEFAULT_NAV_GROUPS.all.id + ? DEFAULT_NAV_GROUPS.all + : registeredUseCases?.find(({ id }) => id === useCaseId); + if (useCase) { + return useCase.title; + } }, }, { diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index db496633128c..a2c84554205a 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -21,6 +21,7 @@ import { NavGroupStatus, DEFAULT_NAV_GROUPS, NavGroupType, + ALL_USE_CASE_ID, } from '../../../core/public'; import { WORKSPACE_FATAL_ERROR_APP_ID, @@ -38,6 +39,7 @@ import { getWorkspaceColumn } from './components/workspace_column'; import { DataSourceManagementPluginSetup } from '../../../plugins/data_source_management/public'; import { filterWorkspaceConfigurableApps, + getFirstUseCaseOfFeatureConfigs, isAppAccessibleInWorkspace, isNavGroupInFeatureConfigs, } from './utils'; @@ -119,9 +121,20 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> this.currentWorkspaceSubscription = currentWorkspace$.subscribe((currentWorkspace) => { if (currentWorkspace) { this.navGroupUpdater$.next((navGroup) => { + /** + * The following logic determines whether a navigation group should be hidden or not based on the workspace's feature configurations. + * It checks the following conditions: + * 1. The navigation group is not a system-level group (system groups are always visible). + * 2. The current workspace has feature configurations set up. + * 3. The current workspace's use case it not "All use case". + * 4. The current navigation group is not included in the feature configurations of the workspace. + * + * If all these conditions are true, it means that the navigation group should be hidden. + */ if ( navGroup.type !== NavGroupType.SYSTEM && currentWorkspace.features && + getFirstUseCaseOfFeatureConfigs(currentWorkspace.features) !== ALL_USE_CASE_ID && !isNavGroupInFeatureConfigs(navGroup.id, currentWorkspace.features) ) { return { diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 852324025100..4763a4455746 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -125,12 +125,18 @@ describe('workspace utils: featureMatchesConfig', () => { it('should match features include by any use cases', () => { const match = featureMatchesConfig( - ['use-case-observability', 'use-case-analytics'], + ['use-case-observability', 'use-case-search'], STATIC_USE_CASES ); expect(match({ id: 'dashboards' })).toBe(true); expect(match({ id: 'observability-traces' })).toBe(true); - expect(match({ id: 'alerting' })).toBe(true); + + /** + * The searchRelevance is a feature under search use case. Since each workspace only can be a specific use case, + * the feature matches will use first use case to check if features exists. The observability doesn't have + * searchRelevance feature, it will return false. + */ + expect(match({ id: 'searchRelevance' })).toBe(false); expect(match({ id: 'not-in-any-use-case' })).toBe(false); }); }); @@ -240,6 +246,15 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { ) ).toBe(true); }); + it('any app is accessible when workspace is all use case', () => { + expect( + isAppAccessibleInWorkspace( + { id: 'any_app', title: 'Any app', mount: jest.fn() }, + { id: 'workspace_id', name: 'workspace name', features: ['use-case-all'] }, + STATIC_USE_CASES + ) + ).toBe(true); + }); }); describe('workspace utils: filterWorkspaceConfigurableApps', () => { @@ -309,11 +324,11 @@ describe('workspace utils: filterWorkspaceConfigurableApps', () => { describe('workspace utils: isFeatureIdInsideUseCase', () => { it('should return false for invalid use case', () => { - expect(isFeatureIdInsideUseCase('discover', 'use-case-invalid', [])).toBe(false); + expect(isFeatureIdInsideUseCase('discover', 'invalid', [])).toBe(false); }); it('should return false if feature not in use case', () => { expect( - isFeatureIdInsideUseCase('discover', 'use-case-foo', [ + isFeatureIdInsideUseCase('discover', 'foo', [ { id: 'foo', title: 'Foo', @@ -325,7 +340,7 @@ describe('workspace utils: isFeatureIdInsideUseCase', () => { }); it('should return true if feature id exists in use case', () => { expect( - isFeatureIdInsideUseCase('discover', 'use-case-foo', [ + isFeatureIdInsideUseCase('discover', 'foo', [ { id: 'foo', title: 'Foo', diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index aeb46993b6c6..589f1d8159d2 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -3,7 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { NavGroupType, SavedObjectsStart, NavGroupItemInMap } from '../../../core/public'; +import { + NavGroupType, + SavedObjectsStart, + NavGroupItemInMap, + ALL_USE_CASE_ID, +} from '../../../core/public'; import { App, AppCategory, @@ -32,14 +37,11 @@ export const getUseCaseFromFeatureConfig = (featureConfig: string) => { export const isFeatureIdInsideUseCase = ( featureId: string, - featureConfig: string, + useCaseId: string, useCases: WorkspaceUseCase[] ) => { - const useCase = useCases.find(({ id }) => id === getUseCaseFromFeatureConfig(featureConfig)); - if (useCase) { - return useCase.features.includes(featureId); - } - return false; + const availableFeatures = useCases.find(({ id }) => id === useCaseId)?.features ?? []; + return availableFeatures.includes(featureId); }; export const isNavGroupInFeatureConfigs = (navGroupId: string, featureConfigs: string[]) => @@ -66,6 +68,7 @@ export const featureMatchesConfig = (featureConfigs: string[], useCases: Workspa category?: AppCategory; }) => { let matched = false; + let firstUseCaseId: string | undefined; /** * Iterate through each feature configuration to determine if the given feature matches any of them. @@ -79,8 +82,14 @@ export const featureMatchesConfig = (featureConfigs: string[], useCases: Workspa } // matches any feature inside use cases - if (isFeatureIdInsideUseCase(id, featureConfig, useCases)) { - matched = true; + if (!firstUseCaseId) { + const useCaseId = getUseCaseFromFeatureConfig(featureConfig); + if (useCaseId) { + firstUseCaseId = useCaseId; + if (isFeatureIdInsideUseCase(id, firstUseCaseId, useCases)) { + matched = true; + } + } } // The config starts with `@` matches a category @@ -130,6 +139,13 @@ export function isAppAccessibleInWorkspace( return true; } + /** + * When workspace is all use case, all apps are accessible + */ + if (getFirstUseCaseOfFeatureConfigs(workspace.features) === ALL_USE_CASE_ID) { + return true; + } + /** * The app is configured into a workspace, it is accessible after entering the workspace */ @@ -244,3 +260,8 @@ export const isEqualWorkspaceUseCase = (a: WorkspaceUseCase, b: WorkspaceUseCase } return true; }; + +const isNotNull = (value: T | null): value is T => !!value; + +export const getFirstUseCaseOfFeatureConfigs = (featureConfigs: string[]): string | undefined => + featureConfigs.map(getUseCaseFromFeatureConfig).filter(isNotNull)[0]; diff --git a/test/common/config.js b/test/common/config.js index d95b46ae3b11..96bed2091f73 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -78,6 +78,7 @@ export default function () { `--opensearchDashboards.branding.mark.defaultUrl=https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_default.svg`, `--opensearchDashboards.branding.mark.darkModeUrl=https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_darkmode.svg`, `--opensearchDashboards.branding.applicationTitle=OpenSearch`, + `--uiSettings.overrides['query:enhancements:enabled']=false`, ], }, services,