diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 9914bbb67dba8..4761c010decf1 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -104,4 +104,4 @@ xpack.observabilityAiAssistantManagement.visibilityEnabled: false xpack.searchSynonyms.enabled: true # Query Rules UI -xpack.searchQueryRules.enabled: false +xpack.searchQueryRules.enabled: true diff --git a/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts index 383f85a07bbd7..0bb64f5451730 100644 --- a/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/platform/plugins/private/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -139,6 +139,7 @@ export const applicationUsageSchema = { searchInferenceEndpoints: commonSchema, searchPlayground: commonSchema, searchSynonyms: commonSchema, + searchQueryRules: commonSchema, enterpriseSearchAnalytics: commonSchema, enterpriseSearchApplications: commonSchema, enterpriseSearchAISearch: commonSchema, diff --git a/src/platform/plugins/shared/telemetry/schema/oss_platform.json b/src/platform/plugins/shared/telemetry/schema/oss_platform.json index 818671af27d45..bde90e13be0f7 100644 --- a/src/platform/plugins/shared/telemetry/schema/oss_platform.json +++ b/src/platform/plugins/shared/telemetry/schema/oss_platform.json @@ -2491,6 +2491,137 @@ } } }, + "searchQueryRules": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "enterpriseSearchAnalytics": { "properties": { "appId": { diff --git a/x-pack/solutions/search/plugins/search_query_rules/kibana.jsonc b/x-pack/solutions/search/plugins/search_query_rules/kibana.jsonc index 3146b2c38688b..11640ccb81c0a 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/kibana.jsonc +++ b/x-pack/solutions/search/plugins/search_query_rules/kibana.jsonc @@ -16,6 +16,7 @@ "features", ], "optionalPlugins": [ + "cloud", "console", "searchNavigation", "share", diff --git a/x-pack/solutions/search/plugins/search_query_rules/public/components/overview/overview.tsx b/x-pack/solutions/search/plugins/search_query_rules/public/components/overview/overview.tsx index 0de2c2866e38e..91d0aac8bf219 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/public/components/overview/overview.tsx +++ b/x-pack/solutions/search/plugins/search_query_rules/public/components/overview/overview.tsx @@ -33,10 +33,12 @@ import { CreateRulesetModal } from './create_ruleset_modal'; import { QueryRulesPageTemplate } from '../../layout/query_rules_page_template'; import { useUsageTracker } from '../../hooks/use_usage_tracker'; import { AnalyticsEvents } from '../../analytics/constants'; +import { useQueryRulesBreadcrumbs } from '../../hooks/use_query_rules_breadcrumbs'; export const QueryRulesOverview = () => { const usageTracker = useUsageTracker(); const { colorMode } = useEuiTheme(); + useQueryRulesBreadcrumbs(); const { data: queryRulesData, isInitialLoading, isError, error } = useFetchQueryRulesSets(); const [isCreateModalVisible, setIsCreateModalVisible] = useState(false); diff --git a/x-pack/solutions/search/plugins/search_query_rules/public/components/query_ruleset_detail/query_ruleset_detail.tsx b/x-pack/solutions/search/plugins/search_query_rules/public/components/query_ruleset_detail/query_ruleset_detail.tsx index 56e1a56d6c309..f9a52c77e557e 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/public/components/query_ruleset_detail/query_ruleset_detail.tsx +++ b/x-pack/solutions/search/plugins/search_query_rules/public/components/query_ruleset_detail/query_ruleset_detail.tsx @@ -42,6 +42,7 @@ import { useQueryRulesetDetailState } from './use_query_ruleset_detail_state'; import { useFetchQueryRulesetExist } from '../../hooks/use_fetch_ruleset_exists'; import { AnalyticsEvents } from '../../analytics/constants'; import { useUsageTracker } from '../../hooks/use_usage_tracker'; +import { useQueryRulesBreadcrumbs } from '../../hooks/use_query_rules_breadcrumbs'; export interface QueryRulesetDetailProps { createMode?: boolean; @@ -55,6 +56,7 @@ export const QueryRulesetDetail: React.FC = ({ createMo const { rulesetId = '' } = useParams<{ rulesetId?: string; }>(); + useQueryRulesBreadcrumbs(rulesetId); const { data: rulesetExists, isLoading: isFailsafeLoading } = useFetchQueryRulesetExist(rulesetId); const useTracker = useUsageTracker(); diff --git a/x-pack/solutions/search/plugins/search_query_rules/public/hooks/use_query_rules_breadcrumbs.tsx b/x-pack/solutions/search/plugins/search_query_rules/public/hooks/use_query_rules_breadcrumbs.tsx new file mode 100644 index 0000000000000..6c4a45c8eb866 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_query_rules/public/hooks/use_query_rules_breadcrumbs.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { useKibana } from './use_kibana'; + +const QUERY_RULES_BREADCRUMB_TEXT = i18n.translate( + 'xpack.searchQueryRules.breadcrumbs.queryRules', + { + defaultMessage: 'Query Rules', + } +); + +export const useQueryRulesBreadcrumbs = (rulesetId?: string) => { + const { searchNavigation, history, cloud } = useKibana().services; + const isServerless = cloud?.isServerlessEnabled ?? false; + + useEffect(() => { + if (!isServerless) { + searchNavigation?.breadcrumbs.setSearchBreadCrumbs([ + { + text: i18n.translate('xpack.searchQueryRules.breadcrumbs.relevance', { + defaultMessage: 'Relevance', + }), + }, + ...(rulesetId && rulesetId.trim().length > 0 + ? [ + { + text: QUERY_RULES_BREADCRUMB_TEXT, + ...reactRouterNavigate(history, '/'), + }, + { + text: rulesetId, + }, + ] + : [ + { + text: QUERY_RULES_BREADCRUMB_TEXT, + }, + ]), + ]); + } else { + if (rulesetId && rulesetId.trim().length > 0) { + searchNavigation?.breadcrumbs.setSearchBreadCrumbs([ + { + text: rulesetId, + }, + ]); + } + } + + return () => { + // Clear breadcrumbs on unmount; + searchNavigation?.breadcrumbs.clearBreadcrumbs(); + }; + }, [searchNavigation, history, rulesetId, isServerless]); +}; diff --git a/x-pack/solutions/search/plugins/search_query_rules/public/plugin.ts b/x-pack/solutions/search/plugins/search_query_rules/public/plugin.ts index a8c4a353ccffa..5b47bdf26793b 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/public/plugin.ts +++ b/x-pack/solutions/search/plugins/search_query_rules/public/plugin.ts @@ -26,7 +26,7 @@ export class QueryRulesPlugin core: CoreSetup, _: AppPluginSetupDependencies ): SearchQueryRulesPluginSetup { - if (!core.settings.client.get(QUERY_RULES_UI_FLAG, false)) { + if (!core.settings.client.get(QUERY_RULES_UI_FLAG, true)) { return {}; } core.application.register({ diff --git a/x-pack/solutions/search/plugins/search_query_rules/public/types.ts b/x-pack/solutions/search/plugins/search_query_rules/public/types.ts index 0376f756bc472..d8fd1cada5818 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/public/types.ts +++ b/x-pack/solutions/search/plugins/search_query_rules/public/types.ts @@ -10,12 +10,14 @@ import { AppMountParameters, CoreStart } from '@kbn/core/public'; import type { ConsolePluginStart } from '@kbn/console-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; export * from '../common/types'; export interface AppPluginStartDependencies { history: AppMountParameters['history']; console?: ConsolePluginStart; share?: SharePluginStart; + cloud?: CloudStart; searchNavigation?: SearchNavigationPluginStart; usageCollection?: UsageCollectionStart; } diff --git a/x-pack/solutions/search/plugins/search_query_rules/tsconfig.json b/x-pack/solutions/search/plugins/search_query_rules/tsconfig.json index 78bfded065b66..7c6a241b4087e 100644 --- a/x-pack/solutions/search/plugins/search_query_rules/tsconfig.json +++ b/x-pack/solutions/search/plugins/search_query_rules/tsconfig.json @@ -32,6 +32,7 @@ "@kbn/deeplinks-analytics", "@kbn/analytics", "@kbn/usage-collection-plugin", + "@kbn/cloud-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts index 97fd852fa1094..cbe4c0a3d90d5 100644 --- a/x-pack/test/functional_search/tests/classic_navigation.ts +++ b/x-pack/test/functional_search/tests/classic_navigation.ts @@ -48,6 +48,7 @@ export default function searchSolutionNavigation({ { id: 'SearchApplications', label: 'Search Applications' }, { id: 'BehavioralAnalytics', label: 'Behavioral Analytics' }, { id: 'Relevance', label: 'Relevance' }, + { id: 'QueryRules', label: 'Query Rules' }, { id: 'InferenceEndpoints', label: 'Inference Endpoints' }, { id: 'Synonyms', label: 'Synonyms' }, { id: 'GettingStarted', label: 'Getting started' }, diff --git a/x-pack/test/functional_search/tests/solution_navigation.ts b/x-pack/test/functional_search/tests/solution_navigation.ts index 30ccb3e7080db..edf197401a59d 100644 --- a/x-pack/test/functional_search/tests/solution_navigation.ts +++ b/x-pack/test/functional_search/tests/solution_navigation.ts @@ -47,6 +47,7 @@ export default function searchSolutionNavigation({ await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Search applications' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Behavioral Analytics' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Query Rules' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'App Search' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Workplace Search' }); @@ -295,6 +296,7 @@ export default function searchSolutionNavigation({ 'build', 'searchPlayground', 'searchSynonyms:synonyms', + 'searchQueryRules', 'enterpriseSearchApplications:searchApplications', 'enterpriseSearchAnalytics', 'relevance', diff --git a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts index 010a549ea3a0f..583f03d127eb1 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts @@ -15,6 +15,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('../common/platform_security/roles.ts')); loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts')); loadTestFile(require.resolve('./search_synonyms/search_synonyms_overview')); - loadTestFile(require.resolve('./search_query_rules/search_query_rules_overview')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index dd7021aebe800..974072e9285ea 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -29,5 +29,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./custom_role_access')); loadTestFile(require.resolve('./inference_management')); + loadTestFile(require.resolve('./search_query_rules/search_query_rules_overview')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index c4b77b51b2513..a66d665fb95a7 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -265,6 +265,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await solutionNavigation.sidenav.expectLinkExists({ text: 'Relevance' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Synonyms' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Query Rules' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Analyze' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Discover' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dashboards' }); @@ -287,6 +288,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { 'dev_tools', 'searchPlayground', 'searchSynonyms', + 'searchQueryRules', 'relevance', 'searchInferenceEndpoints', 'analyze',