diff --git a/changelogs/fragments/6674.yml b/changelogs/fragments/6674.yml new file mode 100644 index 000000000000..7a559d02fa55 --- /dev/null +++ b/changelogs/fragments/6674.yml @@ -0,0 +1,2 @@ +fix: +- [VisBuilder][BUG] Flat render structure in Metric and Table Vis ([#6674](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6674)) \ No newline at end of file diff --git a/changelogs/fragments/7401.yml b/changelogs/fragments/7401.yml new file mode 100644 index 000000000000..5aebdb156692 --- /dev/null +++ b/changelogs/fragments/7401.yml @@ -0,0 +1,2 @@ +fix: +- Make breadcrumb of 4 new added applications comply with BrowserRouter. ([#7401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7401)) \ No newline at end of file diff --git a/changelogs/fragments/7405.yml b/changelogs/fragments/7405.yml new file mode 100644 index 000000000000..81fa1b1780c2 --- /dev/null +++ b/changelogs/fragments/7405.yml @@ -0,0 +1,2 @@ +fix: +- [Bug][Workspace] Navigate to detail page when clicking all use case workspace ([#7405](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7405)) \ No newline at end of file diff --git a/changelogs/fragments/7420.yml b/changelogs/fragments/7420.yml new file mode 100644 index 000000000000..7426906bfd47 --- /dev/null +++ b/changelogs/fragments/7420.yml @@ -0,0 +1,2 @@ +fix: +- [Version Decoupling] Add data source version and installed plugins in data source viewer returns ([#7420](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7420)) \ No newline at end of file diff --git a/changelogs/fragments/7440.yml b/changelogs/fragments/7440.yml new file mode 100644 index 000000000000..9c314cc7263a --- /dev/null +++ b/changelogs/fragments/7440.yml @@ -0,0 +1,2 @@ +fix: +- [Bug][Data Source] Move data source manageable feature flag to DSM plugin ([#7440](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7440)) \ No newline at end of file diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 4833c3f96e30..7ba83c9248b6 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -325,7 +325,7 @@ # "none": The data source is readonly for all users. # "dashboard_admin": The data source can only be managed by dashboard admin. # "all": The data source can be managed by all users. Default to "all". -# data_source.manageableBy: "all" +# data_source_management.manageableBy: "all" # Set the value of this setting to false to hide the help menu link to the OpenSearch Dashboards user survey # opensearchDashboards.survey.url: "https://survey.opensearch.org" diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap index 1bd0458ef1b5..4201e5146669 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap @@ -1,34 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should hide left navigation when in home page when workspace is enabled 1`] = ` -
-
-
-
-
-
-
-
-
-
-
-`; - exports[` should render correctly 1`] = `
should show all use case by default and
`; +exports[` should show all use case when current nav group is \`all\` 1`] = ` +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+`; + exports[` should render correctly 1`] = `
', () => { function mockProps( props?: Partial & { navGroupsMap?: Record; + currentNavGroupId?: string; + navLinks?: ChromeNavLink[]; } ): CollapsibleNavGroupEnabledProps { - const currentNavGroup$ = new BehaviorSubject(undefined); const navGroupsMap$ = new BehaviorSubject>({ [ALL_USE_CASE_ID]: { ...DEFAULT_NAV_GROUPS[ALL_USE_CASE_ID], @@ -121,6 +122,9 @@ describe('', () => { }, ...props?.navGroupsMap, }); + const currentNavGroup$ = new BehaviorSubject( + props?.currentNavGroupId ? navGroupsMap$.getValue()[props.currentNavGroupId] : undefined + ); return { appId$: new BehaviorSubject('test'), basePath: mockBasePath, @@ -146,6 +150,7 @@ describe('', () => { baseUrl: '', href: '', }, + ...(props?.navLinks || []), ]), storage: new StubBrowserStorage(), onIsLockedUpdate: () => {}, @@ -226,8 +231,9 @@ describe('', () => { expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(2); }); - it('should hide left navigation when in home page when workspace is enabled', async () => { + it('should show all use case when current nav group is `all`', async () => { const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, navGroupsMap: { [DEFAULT_NAV_GROUPS.analytics.id]: { ...DEFAULT_NAV_GROUPS.analytics, @@ -241,12 +247,45 @@ describe('', () => { }, }, }); - props.appId$ = new BehaviorSubject('home'); - if (props.capabilities.workspaces) { - (props.capabilities.workspaces as Record) = {}; - (props.capabilities.workspaces as Record).enabled = true; - } - const { container } = render(); + const { container, getAllByTestId, getByTestId } = render( + + ); + fireEvent.click(getAllByTestId('collapsibleNavAppLink-link-in-analytics')[1]); + expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(1); expect(container).toMatchSnapshot(); + fireEvent.click(getByTestId('back')); + expect(getAllByTestId('collapsibleNavAppLink-link-in-analytics').length).toEqual(2); + }); + + it('should not show group if the nav link is hidden', async () => { + const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, + navGroupsMap: { + [DEFAULT_NAV_GROUPS.analytics.id]: { + ...DEFAULT_NAV_GROUPS.analytics, + navLinks: [ + { + id: 'link-in-analytics-but-hidden', + title: 'link-in-analytics-but-hidden', + showInAllNavGroup: true, + }, + ], + }, + }, + navLinks: [ + { + id: 'link-in-analytics-but-hidden', + hidden: true, + title: 'link-in-analytics-but-hidden', + baseUrl: '', + href: '', + }, + ], + }); + const { queryAllByTestId } = render(); + expect(queryAllByTestId('collapsibleNavAppLink-link-in-analytics-but-hidden').length).toEqual( + 0 + ); + expect(queryAllByTestId('collapsibleNavAppLink-link-in-all').length).toEqual(1); }); }); diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx index c6669a6b99f8..ea5510ad5994 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx @@ -177,7 +177,7 @@ export function CollapsibleNavGroupEnabled({ basePath, id, isLocked, - isNavOpen: isNavOpenProps, + isNavOpen, storage = window.localStorage, onIsLockedUpdate, closeNav, @@ -194,7 +194,7 @@ export function CollapsibleNavGroupEnabled({ const currentNavGroup = useObservable(observables.currentNavGroup$, undefined); const navLinksForRender: ChromeNavLink[] = useMemo(() => { - if (currentNavGroup) { + if (currentNavGroup && currentNavGroup.id !== ALL_USE_CASE_ID) { return fulfillRegistrationLinksToChromeNavLinks( navGroupsMap[currentNavGroup.id].navLinks || [], navLinks @@ -241,7 +241,10 @@ export function CollapsibleNavGroupEnabled({ label: group.title, order: group.order, }; - const linksForAllUseCaseWithinNavGroup = group.navLinks + const linksForAllUseCaseWithinNavGroup = fulfillRegistrationLinksToChromeNavLinks( + group.navLinks, + navLinks + ) .filter((navLink) => navLink.showInAllNavGroup) .map((navLink) => ({ ...navLink, @@ -263,18 +266,6 @@ export function CollapsibleNavGroupEnabled({ return fulfillRegistrationLinksToChromeNavLinks(navLinksForAll, navLinks); }, [navLinks, navGroupsMap, currentNavGroup]); - const isNavOpen = useMemo(() => { - // For now, only home page need to always collapse left navigation - // when workspace is enabled. - // If there are more pages need to collapse left navigation in the future - // need to come up with a mechanism to register. - if (capabilities.workspaces.enabled && appId === 'home') { - return false; - } - - return isNavOpenProps; - }, [isNavOpenProps, capabilities.workspaces.enabled, appId]); - const width = useMemo(() => { if (!isNavOpen) { return 50; diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx index 9e89155a8e4e..568290da727b 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx @@ -18,6 +18,7 @@ import { i18n } from '@osd/i18n'; import { createEuiListItem } from './nav_link'; import { NavGroupItemInMap } from '../../nav_group'; import { ChromeNavLink } from '../../nav_links'; +import { ALL_USE_CASE_ID } from '../../../../../core/utils'; export interface CollapsibleNavTopProps { navLinks: ChromeNavLink[]; @@ -44,6 +45,7 @@ export const CollapsibleNavTop = ({ const shouldShowBackButton = useMemo( () => + currentNavGroup?.id !== ALL_USE_CASE_ID && !shouldShrinkNavigation && Object.values(navGroupsMap).filter((item) => !item.type).length > 1 && currentNavGroup, diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 4e3539f2e53b..bef0f152c6a4 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -187,4 +187,22 @@ describe('Header', () => { expect(component.find('CollapsibleNavGroupEnabled').exists()).toBeTruthy(); }); + + it('show hide expand icon in top left navigation when workspace enabled + homepage + new navigation enabled', () => { + const branding = { + useExpandedHeader: false, + }; + const props = { + ...mockProps(), + branding, + }; + props.application.currentAppId$ = new BehaviorSubject('home'); + props.application.capabilities = { ...props.application.capabilities }; + (props.application.capabilities.workspaces as Record) = {}; + (props.application.capabilities.workspaces as Record).enabled = true; + + const component = mountWithIntl(
); + + expect(component.find('.header__toggleNavButtonSection').exists()).toBeFalsy(); + }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index dc161456ba84..adcbba00fe8c 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -131,9 +131,17 @@ export function Header({ }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); const isLocked = useObservable(observables.isLocked$, false); + const appId = useObservable(application.currentAppId$, ''); const [isNavOpen, setIsNavOpen] = useState(false); const sidecarConfig = useObservable(observables.sidecarConfig$, undefined); + /** + * This is a workaround on 2.16 to hide the navigation items within left navigation + * when user is in homepage with workspace enabled + new navigation enabled + */ + const shouldHideExpandIcon = + navGroupEnabled && appId === 'home' && application.capabilities.workspaces.enabled; + const sidecarPaddingStyle = useMemo(() => { return getOsdSidecarPaddingStyle(sidecarConfig); }, [sidecarConfig]); @@ -198,29 +206,31 @@ export function Header({ - - - + setIsNavOpen(!isNavOpen)} - aria-expanded={isNavOpen} - aria-pressed={isNavOpen} - aria-controls={navId} - ref={toggleCollapsibleNavRef} + delay="long" + position="bottom" > - - - - + setIsNavOpen(!isNavOpen)} + aria-expanded={isNavOpen} + aria-pressed={isNavOpen} + aria-controls={navId} + ref={toggleCollapsibleNavRef} + > + + + + + )} @@ -293,7 +303,7 @@ export function Header({ id={navId} isLocked={isLocked} navLinks$={observables.navLinks$} - isNavOpen={isNavOpen} + isNavOpen={shouldHideExpandIcon ? false : isNavOpen} basePath={basePath} navigateToApp={application.navigateToApp} navigateToUrl={application.navigateToUrl} diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index f2bf05da4114..17cb7508c440 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -35,6 +35,7 @@ import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; import { setupTopNavThemeButton } from './register_nav_control'; import { DEFAULT_NAV_GROUPS, AppNavLinkStatus, WorkspaceAvailability } from '../../../core/public'; +import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public'; const component = new ComponentRegistry(); @@ -81,7 +82,8 @@ export class AdvancedSettingsPlugin { ...params, basePath: core.http.basePath.get(), - setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + setBreadcrumbs: (breadCrumbs) => + coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)), wrapInPage: true, }, component.start diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index bd147ac00c04..cde21f648c61 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -60,9 +60,3 @@ export enum DataSourceEngineType { Elasticsearch = 'Elasticsearch', NA = 'No Engine Type Available', } - -export enum ManageableBy { - All = 'all', - DashboardAdmin = 'dashboard_admin', - None = 'none', -} diff --git a/src/plugins/data_source/config.ts b/src/plugins/data_source/config.ts index 36c298cde119..30824b486257 100644 --- a/src/plugins/data_source/config.ts +++ b/src/plugins/data_source/config.ts @@ -59,10 +59,6 @@ export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), }), }), - manageableBy: schema.oneOf( - [schema.literal('all'), schema.literal('dashboard_admin'), schema.literal('none')], - { defaultValue: 'all' } - ), }); export type DataSourcePluginConfigType = TypeOf; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index fa3085a63935..bbf5a89d1b53 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -33,8 +33,6 @@ import { registerTestConnectionRoute } from './routes/test_connection'; import { registerFetchDataSourceMetaDataRoute } from './routes/fetch_data_source_metadata'; import { AuthenticationMethodRegistry, IAuthenticationMethodRegistry } from './auth_registry'; import { CustomApiSchemaRegistry } from './schema_registry'; -import { ManageableBy } from '../common/data_sources'; -import { getWorkspaceState } from '../../../../src/core/server/utils'; export class DataSourcePlugin implements Plugin { private readonly logger: Logger; @@ -83,25 +81,6 @@ export class DataSourcePlugin implements Plugin ({ - dataSource: { - canManage: false, - }, - })); - - core.capabilities.registerSwitcher((request) => { - const { requestWorkspaceId, isDashboardAdmin } = getWorkspaceState(request); - // User can not manage data source in the workspace. - const canManage = - (manageableBy === ManageableBy.All && !requestWorkspaceId) || - (manageableBy === ManageableBy.DashboardAdmin && - isDashboardAdmin !== false && - !requestWorkspaceId); - - return { dataSource: { canManage } }; - }); - core.logging.configure( this.config$.pipe( map((dataSourceConfig) => ({ diff --git a/src/plugins/data_source_management/common/index.ts b/src/plugins/data_source_management/common/index.ts index 980bf8939456..2b9b3a4454bf 100644 --- a/src/plugins/data_source_management/common/index.ts +++ b/src/plugins/data_source_management/common/index.ts @@ -6,3 +6,4 @@ export const PLUGIN_ID = 'dataSourceManagement'; export const PLUGIN_NAME = 'Data sources'; export const DEFAULT_DATA_SOURCE_UI_SETTINGS_ID = 'defaultDataSource'; +export * from './types'; diff --git a/src/plugins/data_source_management/common/types.ts b/src/plugins/data_source_management/common/types.ts new file mode 100644 index 000000000000..23b2f437e248 --- /dev/null +++ b/src/plugins/data_source_management/common/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ManageableBy { + All = 'all', + DashboardAdmin = 'dashboard_admin', + None = 'none', +} diff --git a/src/plugins/data_source_management/config.ts b/src/plugins/data_source_management/config.ts new file mode 100644 index 000000000000..1a56a126a943 --- /dev/null +++ b/src/plugins/data_source_management/config.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema, TypeOf } from '@osd/config-schema'; + +export const configSchema = schema.object({ + manageableBy: schema.oneOf( + [schema.literal('all'), schema.literal('dashboard_admin'), schema.literal('none')], + { defaultValue: 'all' } + ), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/data_source_management/opensearch_dashboards.json b/src/plugins/data_source_management/opensearch_dashboards.json index a8790d04823d..fb37a16eea0f 100644 --- a/src/plugins/data_source_management/opensearch_dashboards.json +++ b/src/plugins/data_source_management/opensearch_dashboards.json @@ -6,5 +6,6 @@ "requiredPlugins": ["management", "indexPatternManagement"], "optionalPlugins": ["dataSource"], "requiredBundles": ["opensearchDashboardsReact", "dataSource", "opensearchDashboardsUtils"], - "extraPublicDirs": ["public/components/utils"] + "extraPublicDirs": ["public/components/utils"], + "configPath": ["data_source_management"] } diff --git a/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap index 86ce46dbfa27..368173c0d0a9 100644 --- a/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_view/__snapshots__/data_source_view.test.tsx.snap @@ -60,7 +60,7 @@ exports[`DataSourceView Should return error when provided datasource has been fi button={ } @@ -88,7 +88,16 @@ exports[`DataSourceView Should return error when provided datasource has been fi + coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)), wrapInPage: true, }, this.authMethodsRegistry, @@ -224,6 +221,11 @@ export class DataSourceManagementPlugin }, ]); + // when the feature flag is disabled, we don't need to register any of the mds components + if (!this.featureFlagStatus) { + return undefined; + } + const registerAuthenticationMethod = (authMethod: AuthenticationMethod) => { if (this.started) { throw new Error( diff --git a/src/plugins/data_source_management/server/index.ts b/src/plugins/data_source_management/server/index.ts index bf4fb4377c93..f3d229ea3f1d 100644 --- a/src/plugins/data_source_management/server/index.ts +++ b/src/plugins/data_source_management/server/index.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PluginInitializerContext } from '../../../core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server'; +import { ConfigSchema, configSchema } from '../config'; import { DataSourceManagementPlugin } from './plugin'; // This exports static code and TypeScript types, @@ -13,4 +14,8 @@ export function plugin(initializerContext: PluginInitializerContext) { return new DataSourceManagementPlugin(initializerContext); } +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + export { DataSourceManagementPluginSetup, DataSourceManagementPluginStart } from './types'; diff --git a/src/plugins/data_source_management/server/plugin.ts b/src/plugins/data_source_management/server/plugin.ts index 97ac8cacc237..de8dcf74cf96 100644 --- a/src/plugins/data_source_management/server/plugin.ts +++ b/src/plugins/data_source_management/server/plugin.ts @@ -5,6 +5,8 @@ // eslint-disable-next-line @osd/eslint/no-restricted-paths import { DataSourcePluginSetup } from 'src/plugins/data_source/server/types'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { CoreSetup, CoreStart, @@ -18,6 +20,9 @@ import { setupRoutes } from './routes'; import { DataSourceManagementPluginSetup, DataSourceManagementPluginStart } from './types'; import { OpenSearchDataSourceManagementPlugin } from './adaptors/opensearch_data_source_management_plugin'; import { PPLPlugin } from './adaptors/ppl_plugin'; +import { ConfigSchema } from '../config'; +import { getWorkspaceState } from '../../../../src/core/server/utils'; +import { ManageableBy } from '../common'; export interface DataSourceManagementPluginDependencies { dataSource: DataSourcePluginSetup; @@ -25,13 +30,15 @@ export interface DataSourceManagementPluginDependencies { export class DataSourceManagementPlugin implements Plugin { + private readonly config$: Observable; private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); + this.config$ = initializerContext.config.create(); } - public setup( + public async setup( core: CoreSetup, deps: { dataSource: DataSourceManagementPluginDependencies; @@ -39,6 +46,8 @@ export class DataSourceManagementPlugin ) { const { dataSource } = deps; + const config: ConfigSchema = await this.config$.pipe(first()).toPromise(); + const dataSourceEnabled = !!dataSource; const openSearchDataSourceManagementClient: ILegacyClusterClient = core.opensearch.legacy.createClient( @@ -51,6 +60,25 @@ export class DataSourceManagementPlugin this.logger.debug('dataSourceManagement: Setup'); const router = core.http.createRouter(); + const { manageableBy } = config; + core.capabilities.registerProvider(() => ({ + dataSource: { + canManage: false, + }, + })); + + core.capabilities.registerSwitcher((request) => { + const { requestWorkspaceId, isDashboardAdmin } = getWorkspaceState(request); + // User can not manage data source in the workspace. + const canManage = + (manageableBy === ManageableBy.All && !requestWorkspaceId) || + (manageableBy === ManageableBy.DashboardAdmin && + isDashboardAdmin !== false && + !requestWorkspaceId); + + return { dataSource: { canManage } }; + }); + if (dataSourceEnabled) { dataSource.registerCustomApiSchema(PPLPlugin); dataSource.registerCustomApiSchema(OpenSearchDataSourceManagementPlugin); diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index ef462374129e..36bc22c5d659 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -47,6 +47,7 @@ import { import { ManagementSetup } from '../../management/public'; import { DEFAULT_NAV_GROUPS, AppStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public'; +import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; @@ -148,7 +149,8 @@ export class IndexPatternManagementPlugin { ...params, basePath: core.http.basePath.get(), - setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + setBreadcrumbs: (breadCrumbs) => + coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)), wrapInPage: true, }, () => this.indexPatternManagementService.environmentService.getEnvironment().ml(), diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts index 8ba6b1e2fe90..f5a3ecceac35 100644 --- a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts +++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/index.ts @@ -28,4 +28,8 @@ * under the License. */ -export { reactRouterNavigate, reactRouterOnClickHandler } from './react_router_navigate'; +export { + reactRouterNavigate, + reactRouterOnClickHandler, + getScopedBreadcrumbs, +} from './react_router_navigate'; diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx new file mode 100644 index 000000000000..138e33906ac7 --- /dev/null +++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { scopedHistoryMock } from '../../../../core/public/mocks'; +import { getScopedBreadcrumbs } from './react_router_navigate'; + +describe('getScopedBreadcrumbs', () => { + it('should return scoped bread crumbs when given an array', () => { + const history = scopedHistoryMock.create({ + pathname: '/base', + }); + history.createHref.mockImplementation((location) => `/base${location.pathname}`); + const scopedBreadcrumbs = getScopedBreadcrumbs( + [ + { + text: 'Home', + href: '/', + }, + { + text: 'Dashboard', + href: '/dashboard', + }, + ], + history + ); + expect(scopedBreadcrumbs[0]).toEqual( + expect.objectContaining({ + href: '/base/', + text: 'Home', + }) + ); + expect(scopedBreadcrumbs[1]).toEqual( + expect.objectContaining({ + href: '/base/dashboard', + text: 'Dashboard', + }) + ); + }); +}); diff --git a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx index 5e367f8cb94b..ae249bdadea9 100644 --- a/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx +++ b/src/plugins/opensearch_dashboards_react/public/react_router_navigate/react_router_navigate.tsx @@ -28,7 +28,7 @@ * under the License. */ -import { ScopedHistory } from 'opensearch-dashboards/public'; +import { ChromeBreadcrumb, ScopedHistory } from 'opensearch-dashboards/public'; import { History } from 'history'; interface LocationObject { @@ -79,3 +79,14 @@ export const reactRouterOnClickHandler = ( event.preventDefault(); history.push(toLocationObject(to)); }; + +export const getScopedBreadcrumbs = ( + crumbs: ChromeBreadcrumb[] = [], + appHistory: ScopedHistory +) => { + const wrapBreadcrumb = (item: ChromeBreadcrumb, scopedHistory: ScopedHistory) => ({ + ...item, + ...(item.href ? reactRouterNavigate(scopedHistory, item.href) : {}), + }); + return crumbs.map((item) => wrapBreadcrumb(item, appHistory)); +}; diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 1247b56b5555..8529a9555681 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -66,6 +66,7 @@ import { bootstrap } from './ui_actions_bootstrap'; import { DEFAULT_NAV_GROUPS, DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { RecentWork } from './management_section/recent_work'; import { HOME_CONTENT_AREAS } from '../../../plugins/home/public'; +import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public'; export interface SavedObjectsManagementPluginSetup { actions: SavedObjectsManagementActionServiceSetup; @@ -173,7 +174,8 @@ export class SavedObjectsManagementPlugin mountParams: { ...params, basePath: core.http.basePath.get(), - setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + setBreadcrumbs: (breadCrumbs) => + coreStart.chrome.setBreadcrumbs(getScopedBreadcrumbs(breadCrumbs, params.history)), wrapInPage: true, }, dataSourceEnabled: !!dataSource, diff --git a/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap index f07fdfa682d8..27c9507a5493 100644 --- a/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap +++ b/src/plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap @@ -1,7 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = ` -Array [ + , + /> , -] + /> + `; exports[`MetricVisComponent should render correct structure for single metric 1`] = ` - + + showLabel={true} + /> + `; diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx index 8162eb7d1978..42e46054b9d8 100644 --- a/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -40,6 +40,7 @@ import { VisParams, MetricVisMetric } from '../types'; import { getFormatService } from '../services'; import { SchemaConfig } from '../../../visualizations/public'; import { Range } from '../../../expressions/public'; +import { VisualizationContainer } from '../../../visualizations/public'; import './metric_vis.scss'; @@ -214,12 +215,12 @@ class MetricVisComponent extends Component { } render() { - let metricsHtml; - if (this.props.visData) { - const metrics = this.processTableGroups(this.props.visData); - metricsHtml = metrics.map(this.renderMetric); - } - return metricsHtml; + const metrics = this.props.visData.rows ? this.processTableGroups(this.props.visData) : []; + return ( + + {metrics.length > 0 ? metrics.map(this.renderMetric) : null} + + ); } } diff --git a/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx b/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx index dcd3fcd0a727..14b0c28f2c56 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx +++ b/src/plugins/vis_type_metric/public/metric_vis_renderer.tsx @@ -28,14 +28,14 @@ * under the License. */ -import React, { lazy } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { VisualizationContainer } from '../../visualizations/public'; import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; import { MetricVisRenderValue } from './metric_vis_fn'; +import MetricVisComponent from './components/metric_vis_component'; + // @ts-ignore -const MetricVisComponent = lazy(() => import('./components/metric_vis_component')); export const metricVisRenderer: () => ExpressionRenderDefinition = () => ({ name: 'metric_vis', @@ -47,14 +47,12 @@ export const metricVisRenderer: () => ExpressionRenderDefinition - - , + , domNode ); }, diff --git a/src/plugins/vis_type_table/public/components/table_vis_app.test.tsx b/src/plugins/vis_type_table/public/components/table_vis_app.test.tsx index f2a347dcd0a2..58e38a346365 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_app.test.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_app.test.tsx @@ -9,7 +9,7 @@ import { coreMock } from '../../../../core/public/mocks'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { TableVisApp } from './table_vis_app'; import { TableVisConfig } from '../types'; -import { TableVisData } from '../table_vis_response_handler'; +import { TableVisData, FormattedTableContext } from '../table_vis_response_handler'; jest.mock('./table_vis_component_group', () => ({ TableVisComponentGroup: () => ( @@ -42,15 +42,24 @@ describe('TableVisApp', () => { } as unknown) as IInterpreterRenderHandlers; const visConfigMock = ({} as unknown) as TableVisConfig; - it('should render TableVisComponent if no split table', () => { - const visDataMock = { - table: { - columns: [], - rows: [], - formattedColumns: [], + const createMockFormattedTableContext = (rowCount: number): FormattedTableContext => ({ + columns: [{ id: 'col1', name: 'Column 1' }], + rows: Array(rowCount).fill({ col1: 'value' }), + formattedColumns: [ + { + id: 'col1', + title: 'Column 1', + formatter: {} as any, + filterable: true, }, + ], + }); + + it('should render TableVisComponent if no split table and has rows', () => { + const visDataMock: TableVisData = { + table: createMockFormattedTableContext(1), tableGroups: [], - } as TableVisData; + }; const { getByTestId } = render( { }); it('should render TableVisComponentGroup component if split direction is column', () => { - const visDataMock = { - tableGroups: [], + const visDataMock: TableVisData = { + tableGroups: [ + { + table: createMockFormattedTableContext(1), + title: 'Group 1', + }, + ], direction: 'column', - } as TableVisData; + }; const { container, getByTestId } = render( { handlers={handlersMock} /> ); - expect(container.outerHTML.includes('visTable')).toBe(true); + expect(container.querySelector('.visTable')).not.toBeNull(); expect(getByTestId('TableVisComponentGroup')).toBeInTheDocument(); }); it('should render TableVisComponentGroup component if split direction is row', () => { - const visDataMock = { - tableGroups: [], + const visDataMock: TableVisData = { + tableGroups: [ + { + table: createMockFormattedTableContext(1), + title: 'Group 1', + }, + ], direction: 'row', - } as TableVisData; + }; const { container, getByTestId } = render( { handlers={handlersMock} /> ); - expect(container.outerHTML.includes('visTable')).toBe(true); + expect(container.querySelector('.visTable')).not.toBeNull(); expect(getByTestId('TableVisComponentGroup')).toBeInTheDocument(); }); + + it('should render "No results found" when there are no rows', () => { + const visDataMock: TableVisData = { + table: createMockFormattedTableContext(0), + tableGroups: [], + }; + const { getByText } = render( + + ); + expect(getByText('No results found')).toBeInTheDocument(); + }); }); diff --git a/src/plugins/vis_type_table/public/components/table_vis_app.tsx b/src/plugins/vis_type_table/public/components/table_vis_app.tsx index cbde913452d1..ccdab8cdaf83 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_app.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_app.tsx @@ -16,6 +16,7 @@ import { TableVisConfig } from '../types'; import { TableVisComponent } from './table_vis_component'; import { TableVisComponentGroup } from './table_vis_component_group'; import { getTableUIState, TableUiState } from '../utils'; +import { VisualizationContainer } from '../../../visualizations/public'; interface TableVisAppProps { services: CoreStart; @@ -36,32 +37,35 @@ export const TableVisApp = ({ }, [handlers]); const tableUiState: TableUiState = getTableUIState(handlers.uiState as PersistedState); + const showNoResult = table ? table.rows.length === 0 : tableGroups?.length === 0; return ( - - {table ? ( - - ) : ( - - )} - + + + {table ? ( + + ) : ( + + )} + + ); diff --git a/src/plugins/vis_type_table/public/table_vis_renderer.tsx b/src/plugins/vis_type_table/public/table_vis_renderer.tsx index 8e467112528d..01b995e64fbc 100644 --- a/src/plugins/vis_type_table/public/table_vis_renderer.tsx +++ b/src/plugins/vis_type_table/public/table_vis_renderer.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { CoreStart } from 'opensearch-dashboards/public'; -import { VisualizationContainer } from '../../visualizations/public'; import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; import { TableVisRenderValue } from './table_vis_fn'; import { TableVisApp } from './components/table_vis_app'; @@ -23,13 +22,8 @@ export const getTableVisRenderer: ( unmountComponentAtNode(domNode); }); - const showNoResult = visData.table - ? visData.table.rows.length === 0 - : visData.tableGroups?.length === 0; render( - - - , + , domNode ); }, diff --git a/src/plugins/vis_type_table/public/utils/get_table_ui_state.ts b/src/plugins/vis_type_table/public/utils/get_table_ui_state.ts index 58fc6b472a40..67c1a1864f4b 100644 --- a/src/plugins/vis_type_table/public/utils/get_table_ui_state.ts +++ b/src/plugins/vis_type_table/public/utils/get_table_ui_state.ts @@ -14,8 +14,8 @@ export interface TableUiState { } export function getTableUIState(uiState: PersistedState): TableUiState { - const sort: ColumnSort = uiState.get('vis.sortColumn') || {}; - const colWidth: ColumnWidth[] = uiState.get('vis.columnsWidth') || []; + const sort: ColumnSort = uiState?.get('vis.sortColumn') || {}; + const colWidth: ColumnWidth[] = uiState?.get('vis.columnsWidth') || []; const setSort = (newSort: ColumnSort) => { uiState.set('vis.sortColumn', newSort); diff --git a/src/plugins/workspace/public/components/workspace_detail_app.tsx b/src/plugins/workspace/public/components/workspace_detail_app.tsx index 2236cd67c139..294851502567 100644 --- a/src/plugins/workspace/public/components/workspace_detail_app.tsx +++ b/src/plugins/workspace/public/components/workspace_detail_app.tsx @@ -5,6 +5,7 @@ import React, { useEffect } from 'react'; import { I18nProvider } from '@osd/i18n/react'; +import { i18n } from '@osd/i18n'; import { CoreStart } from 'opensearch-dashboards/public'; import { useObservable } from 'react-use'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -34,6 +35,9 @@ export const WorkspaceDetailApp = (props: WorkspaceDetailProps) => { breadcrumbs.push({ text: currentWorkspace.name, }); + breadcrumbs.push({ + text: i18n.translate('workspace.detail.breadcrumb', { defaultMessage: 'Workspace Detail' }), + }); } chrome?.setBreadcrumbs(breadcrumbs); }, [chrome, currentWorkspace, application]); diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx index 6b1779a8f847..fa3ab9a4ff06 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -4,12 +4,12 @@ */ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { WorkspaceMenu } from './workspace_menu'; import { coreMock } from '../../../../../core/public/mocks'; import { CoreStart } from '../../../../../core/public'; -import { BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { IntlProvider } from 'react-intl'; import { recentWorkspaceManager } from '../../recent_workspace_manager'; import { WORKSPACE_USE_CASES } from '../../../common/constants'; @@ -109,7 +109,7 @@ describe('', () => { expect(screen.getByText('Observability')).toBeInTheDocument(); }); - it('should navigate to the workspace', () => { + it('should navigate to the first feature of workspace use case', () => { coreStartMock.workspaces.workspaceList$.next([ { id: 'workspace-1', name: 'workspace 1', features: ['use-case-observability'] }, ]); @@ -134,6 +134,31 @@ describe('', () => { }); }); + it('should navigate to the workspace detail page when use case is all', () => { + coreStartMock.workspaces.workspaceList$.next([ + { id: 'workspace-1', name: 'workspace 1', features: ['use-case-all'] }, + ]); + + const originalLocation = window.location; + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + + render(); + fireEvent.click(screen.getByTestId('workspace-select-button')); + fireEvent.click(screen.getByText(/workspace 1/i)); + + expect(window.location.assign).toHaveBeenCalledWith( + 'https://test.com/w/workspace-1/app/workspace_detail' + ); + + Object.defineProperty(window, 'location', { + value: originalLocation, + }); + }); + it('should navigate to workspace management page', () => { coreStartMock.workspaces.currentWorkspace$.next({ id: 'workspace-1', diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index df276d8c43bb..aadfab05900d 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -29,7 +29,7 @@ import { WORKSPACE_DETAIL_APP_ID, } from '../../../common/constants'; import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; -import { CoreStart, WorkspaceObject } from '../../../../../core/public'; +import { ALL_USE_CASE_ID, CoreStart, WorkspaceObject } from '../../../../../core/public'; import { getFirstUseCaseOfFeatureConfigs } from '../../utils'; import { recentWorkspaceManager } from '../../recent_workspace_manager'; import { WorkspaceUseCase } from '../../types'; @@ -126,7 +126,9 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { const getWorkspaceListGroup = (filterWorkspaceList: WorkspaceObject[], itemType: string) => { const listItems = filterWorkspaceList.map((workspace: WorkspaceObject) => { - const appId = getUseCase(workspace)?.features[0] ?? WORKSPACE_DETAIL_APP_ID; + const useCase = getUseCase(workspace); + const appId = + (useCase?.id !== ALL_USE_CASE_ID && useCase?.features?.[0]) || WORKSPACE_DETAIL_APP_ID; const useCaseURL = formatUrlWithWorkspaceId( coreStart.application.getUrlForApp(appId, { absolute: false, @@ -138,6 +140,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { { initialsLength={2} /> } - label={ - - - {workspace.name} - - - } + label={workspace.name} onClick={() => { closePopover(); window.location.assign(useCaseURL); @@ -172,7 +165,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => {

{itemType === 'all' ? allWorkspacesTitle : recentWorkspacesTitle}

- + {listItems} @@ -210,16 +203,16 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { - + {currentWorkspaceName} - {getUseCase(currentWorkspace)?.title ?? ''} + {getUseCase(currentWorkspace)?.title ?? ''} { expect(setupMock.application.register).toHaveBeenCalledWith( expect.objectContaining({ id: 'workspace_detail', - navLinkStatus: AppNavLinkStatus.visible, + navLinkStatus: AppNavLinkStatus.hidden, }) ); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 5452bdd7f2fd..dd4dc78fdf2c 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -115,6 +115,12 @@ export class WorkspacePlugin if (app.id === 'home' && isAllUseCase) { return { navLinkStatus: AppNavLinkStatus.hidden }; } + + // show the overview page in all use case + if (app.id === WORKSPACE_DETAIL_APP_ID && isAllUseCase) { + return { navLinkStatus: AppNavLinkStatus.visible }; + } + if (isAppAccessibleInWorkspace(app, currentWorkspace, registeredUseCases)) { return; } @@ -334,9 +340,7 @@ export class WorkspacePlugin title: i18n.translate('workspace.settings.workspaceDetail', { defaultMessage: 'Workspace Detail', }), - navLinkStatus: core.chrome.navGroup.getNavGroupEnabled() - ? AppNavLinkStatus.visible - : AppNavLinkStatus.hidden, + navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { const { renderDetailApp } = await import('./application'); return mountWorkspaceApp(params, renderDetailApp); diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 5c91effc4146..0f31a70722a1 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -515,7 +515,7 @@ describe('workspace utils: prependWorkspaceToBreadcrumbs', () => { it('should not enrich breadcrumbs when out a workspace', async () => { const coreStart = coreMock.createStart(); prependWorkspaceToBreadcrumbs(coreStart, null, 'app1', undefined, {}); - expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledWith(undefined); + expect(coreStart.chrome.setBreadcrumbsEnricher).not.toHaveBeenCalled(); }); it('should enrich breadcrumbs when in a workspace and use workspace use case as current nav group', async () => { diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index e1093495c049..8e0846a7593a 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -295,6 +295,16 @@ export function prependWorkspaceToBreadcrumbs( core.chrome.setBreadcrumbsEnricher(undefined); return; } + + /** + * There has 3 cases + * nav group is enable + workspace enable + in a workspace -> workspace enricher + * nav group is enable + workspace enable + out a workspace -> nav group enricher + * nav group is enable + workspace disabled -> nav group enricher + * + * switch workspace will cause page refresh, breadcrumbs enricher will reset automatically + * so we don't need to have reset logic for workspace + */ if (currentWorkspace) { const useCase = getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features || []); // get workspace the only use case @@ -336,7 +346,5 @@ export function prependWorkspaceToBreadcrumbs( return [homeBreadcrumb, navGroupBreadcrumb, ...breadcrumbs]; } }); - } else { - core.chrome.setBreadcrumbsEnricher(undefined); } } diff --git a/yarn.lock b/yarn.lock index a3e3becf27d6..747e2210d233 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5310,14 +5310,14 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@*, browserslist@^4.21.10, browserslist@^4.21.5, browserslist@^4.21.9, browserslist@^4.22.1: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" - update-browserslist-db "^1.0.13" + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" + node-releases "^2.0.14" + update-browserslist-db "^1.1.0" bser@2.1.1: version "2.1.1" @@ -5526,11 +5526,16 @@ camelize@^1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= -caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001541: +caniuse-lite@^1.0.30001464: version "1.0.30001587" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz" integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== +caniuse-lite@^1.0.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -7415,10 +7420,10 @@ elasticsearch@^16.4.0, elasticsearch@^16.7.0: chalk "^1.0.0" lodash "^4.17.10" -electron-to-chromium@^1.4.535: - version "1.4.573" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.573.tgz#aa6e5edf86448bb9398f529357abcc6a17b6341d" - integrity sha512-tzxxvKDTO3V5vzN2F+3v9jrK9gEbCdf1YYJUx/zVq1cyzyh+x1ddeYNNWh0ZS2ETNCVK3+Pns1LHIBq4w20X2Q== +electron-to-chromium@^1.4.820: + version "1.5.0" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz#0d3123a9f09189b9c7ab4b5d6848d71b3c1fd0e8" + integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA== elegant-spinner@^1.0.1: version "1.0.1" @@ -7787,6 +7792,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" @@ -12861,10 +12871,10 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== node-stream-zip@^1.15.0: version "1.15.0" @@ -13623,6 +13633,11 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -17315,13 +17330,13 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" uri-js@^4.2.2: version "4.4.1"