mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Remove legacy navigation and related logic (#158094)
part of: https://github.com/elastic/kibana/issues/157847 closes: https://github.com/elastic/kibana/issues/145718 ### Background The new navigation became the default navigation for Security on 8.4. To have a smooth transition we added an advanced setting to use the old navigation. Since then the legacy navigation has become outdated, there are links in Security that are not accessible through it, such as the landing pages for the sub-sections: - `/security/dashboards` - `/security/explore` - `/security/manage` With the introduction of the new Security AI design, more of those landing pages that are not compatible with the legacy navigation design will be added (e.g. Rules). And it was starting to become overcrowded since there was no possibility to collapse groups. Also, over time it has become harder and harder to maintain both versions at the same time, all the new pages added to security were having to duplicate the navigation configurations for both versions and also test everything twice. On top of that, the legacy navigation won't be supported on the Security Serverless projects, everything will work with the new one exclusively. ## Docs The Security documentation assumes the new navigation is used everywhere, there's no mention of the old navigation, only one small section about the advanced setting (which is actually outdated): https://www.elastic.co/guide/en/security/8.7/advanced-settings.html#_enable_grouped_navigation Which will need to be removed for 8.9. ## Summary Cleans the old navigation and the advanced setting (`securitySolution:enableGroupedNav`) to turn it on. Removes the telemetry (which shows almost no usage of it) and external dependencies as well. Only the new navigation will be available. All links should work only with the `app_links` architecture from now on. Old Nav ❌  New nav ✅  --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3e3419d6c9
commit
6fd386c144
65 changed files with 818 additions and 4204 deletions
|
@ -1,501 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* as const
|
||||
*
|
||||
* The const assertion ensures that type widening does not occur
|
||||
* https://mariusschulz.com/blog/literal-type-widening-in-typescript
|
||||
* Please follow this convention when adding to this file
|
||||
*/
|
||||
|
||||
export const APP_ID = 'securitySolution' as const;
|
||||
export const APP_UI_ID = 'securitySolutionUI' as const;
|
||||
export const CASES_FEATURE_ID = 'securitySolutionCases' as const;
|
||||
export const SERVER_APP_ID = 'siem' as const;
|
||||
export const APP_NAME = 'Security' as const;
|
||||
export const APP_ICON = 'securityAnalyticsApp' as const;
|
||||
export const APP_ICON_SOLUTION = 'logoSecurity' as const;
|
||||
export const APP_PATH = `/app/security` as const;
|
||||
export const ADD_DATA_PATH = `/app/integrations/browse/security`;
|
||||
export const ADD_THREAT_INTELLIGENCE_DATA_PATH = `/app/integrations/browse/threat_intel`;
|
||||
export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern' as const;
|
||||
export const DEFAULT_DATE_FORMAT = 'dateFormat' as const;
|
||||
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz' as const;
|
||||
export const DEFAULT_DARK_MODE = 'theme:darkMode' as const;
|
||||
export const DEFAULT_INDEX_KEY = 'securitySolution:defaultIndex' as const;
|
||||
export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern' as const;
|
||||
export const DEFAULT_DATA_VIEW_ID = 'security-solution' as const;
|
||||
export const DEFAULT_TIME_FIELD = '@timestamp' as const;
|
||||
export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults' as const;
|
||||
export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults' as const;
|
||||
export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults' as const;
|
||||
export const DEFAULT_APP_REFRESH_INTERVAL = 'securitySolution:refreshIntervalDefaults' as const;
|
||||
export const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts' as const;
|
||||
export const DEFAULT_SIGNALS_INDEX = '.siem-signals' as const;
|
||||
export const DEFAULT_PREVIEW_INDEX = '.preview.alerts-security.alerts' as const;
|
||||
export const DEFAULT_LISTS_INDEX = '.lists' as const;
|
||||
export const DEFAULT_ITEMS_INDEX = '.items' as const;
|
||||
// The DEFAULT_MAX_SIGNALS value exists also in `x-pack/plugins/cases/common/constants.ts`
|
||||
// If either changes, engineer should ensure both values are updated
|
||||
export const DEFAULT_MAX_SIGNALS = 100 as const;
|
||||
export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100 as const;
|
||||
export const DEFAULT_ANOMALY_SCORE = 'securitySolution:defaultAnomalyScore' as const;
|
||||
export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000 as const;
|
||||
export const DEFAULT_FROM = 'now/d' as const;
|
||||
export const DEFAULT_TO = 'now/d' as const;
|
||||
export const DEFAULT_INTERVAL_PAUSE = true as const;
|
||||
export const DEFAULT_INTERVAL_TYPE = 'manual' as const;
|
||||
export const DEFAULT_INTERVAL_VALUE = 300000 as const; // ms
|
||||
export const DEFAULT_TIMEPICKER_QUICK_RANGES = 'timepicker:quickRanges' as const;
|
||||
export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled' as const;
|
||||
export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled' as const;
|
||||
export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51' as const;
|
||||
export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*' as const;
|
||||
export const ENDPOINT_METRICS_INDEX = '.ds-metrics-endpoint.metrics-*' as const;
|
||||
export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true as const;
|
||||
export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000 as const; // ms
|
||||
export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100 as const;
|
||||
export const SECURITY_FEATURE_ID = 'Security' as const;
|
||||
export const SECURITY_TAG_NAME = 'Security Solution' as const;
|
||||
export const DEFAULT_SPACE_ID = 'default' as const;
|
||||
export const DEFAULT_RELATIVE_DATE_THRESHOLD = 24 as const;
|
||||
|
||||
// Document path where threat indicator fields are expected. Fields are used
|
||||
// to enrich signals, and are copied to threat.enrichments.
|
||||
export const DEFAULT_INDICATOR_SOURCE_PATH = 'threat.indicator' as const;
|
||||
export const ENRICHMENT_DESTINATION_PATH = 'threat.enrichments' as const;
|
||||
export const DEFAULT_THREAT_INDEX_KEY = 'securitySolution:defaultThreatIndex' as const;
|
||||
export const DEFAULT_THREAT_INDEX_VALUE = ['logs-ti_*'] as const;
|
||||
export const DEFAULT_THREAT_MATCH_QUERY = '@timestamp >= "now-30d/d"' as const;
|
||||
|
||||
export enum SecurityPageName {
|
||||
administration = 'administration',
|
||||
alerts = 'alerts',
|
||||
blocklist = 'blocklist',
|
||||
/*
|
||||
* Warning: Computed values are not permitted in an enum with string valued members
|
||||
* All Cases page names must match `CasesDeepLinkId` in x-pack/plugins/cases/public/common/navigation/deep_links.ts
|
||||
*/
|
||||
case = 'cases', // must match `CasesDeepLinkId.cases`
|
||||
caseConfigure = 'cases_configure', // must match `CasesDeepLinkId.casesConfigure`
|
||||
caseCreate = 'cases_create', // must match `CasesDeepLinkId.casesCreate`
|
||||
/*
|
||||
* Warning: Computed values are not permitted in an enum with string valued members
|
||||
* All cloud security posture page names must match `CloudSecurityPosturePageId` in x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts
|
||||
*/
|
||||
cloudSecurityPostureBenchmarks = 'cloud_security_posture-benchmarks',
|
||||
cloudSecurityPostureDashboard = 'cloud_security_posture-dashboard',
|
||||
cloudSecurityPostureFindings = 'cloud_security_posture-findings',
|
||||
cloudSecurityPostureRules = 'cloud_security_posture-rules',
|
||||
dashboardsLanding = 'dashboards',
|
||||
dataQuality = 'data_quality',
|
||||
detections = 'detections',
|
||||
detectionAndResponse = 'detection_response',
|
||||
endpoints = 'endpoints',
|
||||
eventFilters = 'event_filters',
|
||||
exceptions = 'exceptions',
|
||||
exploreLanding = 'explore',
|
||||
hostIsolationExceptions = 'host_isolation_exceptions',
|
||||
hosts = 'hosts',
|
||||
hostsAnomalies = 'hosts-anomalies',
|
||||
hostsRisk = 'hosts-risk',
|
||||
hostsEvents = 'hosts-events',
|
||||
investigate = 'investigate',
|
||||
kubernetes = 'kubernetes',
|
||||
landing = 'get_started',
|
||||
network = 'network',
|
||||
networkAnomalies = 'network-anomalies',
|
||||
networkDns = 'network-dns',
|
||||
networkEvents = 'network-events',
|
||||
networkHttp = 'network-http',
|
||||
networkTls = 'network-tls',
|
||||
noPage = '',
|
||||
overview = 'overview',
|
||||
policies = 'policy',
|
||||
responseActionsHistory = 'response_actions_history',
|
||||
rules = 'rules',
|
||||
rulesCreate = 'rules-create',
|
||||
sessions = 'sessions',
|
||||
/*
|
||||
* Warning: Computed values are not permitted in an enum with string valued members
|
||||
* All threat intelligence page names must match `TIPageId` in x-pack/plugins/threat_intelligence/public/common/navigation/types.ts
|
||||
*/
|
||||
threatIntelligenceIndicators = 'threat_intelligence-indicators',
|
||||
timelines = 'timelines',
|
||||
timelinesTemplates = 'timelines-templates',
|
||||
trustedApps = 'trusted_apps',
|
||||
uncommonProcesses = 'uncommon_processes',
|
||||
users = 'users',
|
||||
usersAnomalies = 'users-anomalies',
|
||||
usersAuthentications = 'users-authentications',
|
||||
usersEvents = 'users-events',
|
||||
usersRisk = 'users-risk',
|
||||
entityAnalytics = 'entity-analytics',
|
||||
}
|
||||
|
||||
export const EXPLORE_PATH = '/explore' as const;
|
||||
export const DASHBOARDS_PATH = '/dashboards' as const;
|
||||
export const MANAGE_PATH = '/manage' as const;
|
||||
export const TIMELINES_PATH = '/timelines' as const;
|
||||
export const CASES_PATH = '/cases' as const;
|
||||
export const OVERVIEW_PATH = '/overview' as const;
|
||||
export const LANDING_PATH = '/get_started' as const;
|
||||
export const DATA_QUALITY_PATH = '/data_quality' as const;
|
||||
export const DETECTION_RESPONSE_PATH = '/detection_response' as const;
|
||||
export const DETECTIONS_PATH = '/detections' as const;
|
||||
export const ALERTS_PATH = '/alerts' as const;
|
||||
export const RULES_PATH = '/rules' as const;
|
||||
export const RULES_CREATE_PATH = `${RULES_PATH}/create` as const;
|
||||
export const EXCEPTIONS_PATH = '/exceptions' as const;
|
||||
export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const;
|
||||
export const HOSTS_PATH = '/hosts' as const;
|
||||
export const USERS_PATH = '/users' as const;
|
||||
export const KUBERNETES_PATH = '/kubernetes' as const;
|
||||
export const NETWORK_PATH = '/network' as const;
|
||||
export const MANAGEMENT_PATH = '/administration' as const;
|
||||
export const THREAT_INTELLIGENCE_PATH = '/threat_intelligence' as const;
|
||||
export const ENDPOINTS_PATH = `${MANAGEMENT_PATH}/endpoints` as const;
|
||||
export const POLICIES_PATH = `${MANAGEMENT_PATH}/policy` as const;
|
||||
export const TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/trusted_apps` as const;
|
||||
export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters` as const;
|
||||
export const HOST_ISOLATION_EXCEPTIONS_PATH =
|
||||
`${MANAGEMENT_PATH}/host_isolation_exceptions` as const;
|
||||
export const BLOCKLIST_PATH = `${MANAGEMENT_PATH}/blocklist` as const;
|
||||
export const RESPONSE_ACTIONS_HISTORY_PATH = `${MANAGEMENT_PATH}/response_actions_history` as const;
|
||||
export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const;
|
||||
export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const;
|
||||
export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const;
|
||||
export const APP_DETECTION_RESPONSE_PATH = `${APP_PATH}${DETECTION_RESPONSE_PATH}` as const;
|
||||
export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}` as const;
|
||||
|
||||
export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const;
|
||||
export const APP_RULES_PATH = `${APP_PATH}${RULES_PATH}` as const;
|
||||
export const APP_EXCEPTIONS_PATH = `${APP_PATH}${EXCEPTIONS_PATH}` as const;
|
||||
|
||||
export const APP_HOSTS_PATH = `${APP_PATH}${HOSTS_PATH}` as const;
|
||||
export const APP_USERS_PATH = `${APP_PATH}${USERS_PATH}` as const;
|
||||
export const APP_NETWORK_PATH = `${APP_PATH}${NETWORK_PATH}` as const;
|
||||
export const APP_KUBERNETES_PATH = `${APP_PATH}${KUBERNETES_PATH}` as const;
|
||||
export const APP_TIMELINES_PATH = `${APP_PATH}${TIMELINES_PATH}` as const;
|
||||
export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}` as const;
|
||||
export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}` as const;
|
||||
export const APP_POLICIES_PATH = `${APP_PATH}${POLICIES_PATH}` as const;
|
||||
export const APP_TRUSTED_APPS_PATH = `${APP_PATH}${TRUSTED_APPS_PATH}` as const;
|
||||
export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}` as const;
|
||||
export const APP_HOST_ISOLATION_EXCEPTIONS_PATH =
|
||||
`${APP_PATH}${HOST_ISOLATION_EXCEPTIONS_PATH}` as const;
|
||||
export const APP_BLOCKLIST_PATH = `${APP_PATH}${BLOCKLIST_PATH}` as const;
|
||||
export const APP_RESPONSE_ACTIONS_HISTORY_PATH =
|
||||
`${APP_PATH}${RESPONSE_ACTIONS_HISTORY_PATH}` as const;
|
||||
export const APP_ENTITY_ANALYTICS_PATH = `${APP_PATH}${ENTITY_ANALYTICS_PATH}` as const;
|
||||
export const APP_DATA_QUALITY_PATH = `${APP_PATH}${DATA_QUALITY_PATH}` as const;
|
||||
|
||||
// cloud logs to exclude from default index pattern
|
||||
export const EXCLUDE_ELASTIC_CLOUD_INDICES = ['-*elastic-cloud-logs-*'];
|
||||
|
||||
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
|
||||
export const INCLUDE_INDEX_PATTERN = [
|
||||
'apm-*-transaction*',
|
||||
'auditbeat-*',
|
||||
'endgame-*',
|
||||
'filebeat-*',
|
||||
'logs-*',
|
||||
'packetbeat-*',
|
||||
'traces-apm*',
|
||||
'winlogbeat-*',
|
||||
];
|
||||
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events, and the exclude index pattern */
|
||||
export const DEFAULT_INDEX_PATTERN = [...INCLUDE_INDEX_PATTERN, ...EXCLUDE_ELASTIC_CLOUD_INDICES];
|
||||
|
||||
/** This Kibana Advanced Setting enables the grouped navigation in Security Solution */
|
||||
export const ENABLE_GROUPED_NAVIGATION = 'securitySolution:enableGroupedNav' as const;
|
||||
|
||||
/** This Kibana Advanced Setting enables the `Security news` feed widget */
|
||||
export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed' as const;
|
||||
|
||||
/** This Kibana Advanced Setting enables the warnings for CCS read permissions */
|
||||
export const ENABLE_CCS_READ_WARNING_SETTING = 'securitySolution:enableCcsWarning' as const;
|
||||
|
||||
/** This Kibana Advanced Setting sets the auto refresh interval for the detections all rules table */
|
||||
export const DEFAULT_RULES_TABLE_REFRESH_SETTING = 'securitySolution:rulesTableRefresh' as const;
|
||||
|
||||
/** This Kibana Advanced Setting specifies the URL of the News feed widget */
|
||||
export const NEWS_FEED_URL_SETTING = 'securitySolution:newsFeedUrl' as const;
|
||||
|
||||
/** The default value for News feed widget */
|
||||
export const NEWS_FEED_URL_SETTING_DEFAULT = 'https://feeds.elastic.co/security-solution' as const;
|
||||
|
||||
/** This Kibana Advanced Setting specifies the URLs of `IP Reputation Links`*/
|
||||
export const IP_REPUTATION_LINKS_SETTING = 'securitySolution:ipReputationLinks' as const;
|
||||
|
||||
/** The default value for `IP Reputation Links` */
|
||||
export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[
|
||||
{ "name": "virustotal.com", "url_template": "https://www.virustotal.com/gui/search/{{ip}}" },
|
||||
{ "name": "talosIntelligence.com", "url_template": "https://talosintelligence.com/reputation_center/lookup?search={{ip}}" }
|
||||
]`;
|
||||
|
||||
/** This Kibana Advanced Setting shows related integrations on the Rules Table */
|
||||
export const SHOW_RELATED_INTEGRATIONS_SETTING =
|
||||
'securitySolution:showRelatedIntegrations' as const;
|
||||
|
||||
/** This Kibana Advanced Setting enables extended rule execution logging to Event Log */
|
||||
export const EXTENDED_RULE_EXECUTION_LOGGING_ENABLED_SETTING =
|
||||
'securitySolution:extendedRuleExecutionLoggingEnabled' as const;
|
||||
|
||||
/** This Kibana Advanced Setting sets minimum log level starting from which execution logs will be written to Event Log */
|
||||
export const EXTENDED_RULE_EXECUTION_LOGGING_MIN_LEVEL_SETTING =
|
||||
'securitySolution:extendedRuleExecutionLoggingMinLevel' as const;
|
||||
|
||||
/**
|
||||
* Id for the notifications alerting type
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const LEGACY_NOTIFICATIONS_ID = `siem.notifications` as const;
|
||||
|
||||
/**
|
||||
* Internal actions route
|
||||
*/
|
||||
export const UPDATE_OR_CREATE_LEGACY_ACTIONS = '/internal/api/detection/legacy/notifications';
|
||||
|
||||
/**
|
||||
* Exceptions management routes
|
||||
*/
|
||||
|
||||
export const SHARED_EXCEPTION_LIST_URL = `/api${EXCEPTIONS_PATH}/shared` as const;
|
||||
|
||||
/**
|
||||
* Detection engine routes
|
||||
*/
|
||||
export const DETECTION_ENGINE_URL = '/api/detection_engine' as const;
|
||||
export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges` as const;
|
||||
export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index` as const;
|
||||
|
||||
export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules` as const;
|
||||
export const DETECTION_ENGINE_RULES_URL_FIND = `${DETECTION_ENGINE_RULES_URL}/_find` as const;
|
||||
export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags` as const;
|
||||
export const DETECTION_ENGINE_RULES_BULK_ACTION =
|
||||
`${DETECTION_ENGINE_RULES_URL}/_bulk_action` as const;
|
||||
export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/preview` as const;
|
||||
export const DETECTION_ENGINE_RULES_BULK_DELETE =
|
||||
`${DETECTION_ENGINE_RULES_URL}/_bulk_delete` as const;
|
||||
export const DETECTION_ENGINE_RULES_BULK_CREATE =
|
||||
`${DETECTION_ENGINE_RULES_URL}/_bulk_create` as const;
|
||||
export const DETECTION_ENGINE_RULES_BULK_UPDATE =
|
||||
`${DETECTION_ENGINE_RULES_URL}/_bulk_update` as const;
|
||||
|
||||
export const INTERNAL_RISK_SCORE_URL = '/internal/risk_score' as const;
|
||||
export const DEV_TOOL_PREBUILT_CONTENT =
|
||||
`${INTERNAL_RISK_SCORE_URL}/prebuilt_content/dev_tool/{console_id}` as const;
|
||||
export const devToolPrebuiltContentUrl = (spaceId: string, consoleId: string) =>
|
||||
`/s/${spaceId}${INTERNAL_RISK_SCORE_URL}/prebuilt_content/dev_tool/${consoleId}` as const;
|
||||
export const PREBUILT_SAVED_OBJECTS_BULK_CREATE = `${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_create/{template_name}`;
|
||||
export const prebuiltSavedObjectsBulkCreateUrl = (templateName: string) =>
|
||||
`${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_create/${templateName}` as const;
|
||||
export const PREBUILT_SAVED_OBJECTS_BULK_DELETE = `${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/{template_name}`;
|
||||
export const prebuiltSavedObjectsBulkDeleteUrl = (templateName: string) =>
|
||||
`${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/${templateName}` as const;
|
||||
export const RISK_SCORE_CREATE_INDEX = `${INTERNAL_RISK_SCORE_URL}/indices/create`;
|
||||
export const RISK_SCORE_DELETE_INDICES = `${INTERNAL_RISK_SCORE_URL}/indices/delete`;
|
||||
export const RISK_SCORE_CREATE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/create`;
|
||||
export const RISK_SCORE_DELETE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/delete`;
|
||||
/**
|
||||
* Internal detection engine routes
|
||||
*/
|
||||
export const INTERNAL_DETECTION_ENGINE_URL = '/internal/detection_engine' as const;
|
||||
export const DETECTION_ENGINE_ALERTS_INDEX_URL =
|
||||
`${INTERNAL_DETECTION_ENGINE_URL}/signal/index` as const;
|
||||
|
||||
/**
|
||||
* Telemetry detection endpoint for any previews requested of what data we are
|
||||
* providing through UI/UX and for e2e tests.
|
||||
* curl http//localhost:5601/internal/security_solution/telemetry
|
||||
* to see the contents
|
||||
*/
|
||||
export const SECURITY_TELEMETRY_URL = `/internal/security_solution/telemetry` as const;
|
||||
|
||||
export const TIMELINE_RESOLVE_URL = '/api/timeline/resolve' as const;
|
||||
export const TIMELINE_URL = '/api/timeline' as const;
|
||||
export const TIMELINES_URL = '/api/timelines' as const;
|
||||
export const TIMELINE_FAVORITE_URL = '/api/timeline/_favorite' as const;
|
||||
export const TIMELINE_DRAFT_URL = `${TIMELINE_URL}/_draft` as const;
|
||||
export const TIMELINE_EXPORT_URL = `${TIMELINE_URL}/_export` as const;
|
||||
export const TIMELINE_IMPORT_URL = `${TIMELINE_URL}/_import` as const;
|
||||
export const TIMELINE_PREPACKAGED_URL = `${TIMELINE_URL}/_prepackaged` as const;
|
||||
|
||||
export const NOTE_URL = '/api/note' as const;
|
||||
export const PINNED_EVENT_URL = '/api/pinned_event' as const;
|
||||
export const SOURCERER_API_URL = '/internal/security_solution/sourcerer' as const;
|
||||
export const RISK_SCORE_INDEX_STATUS_API_URL = '/internal/risk_score/index_status' as const;
|
||||
|
||||
/**
|
||||
* Default signals index key for kibana.dev.yml
|
||||
*/
|
||||
export const SIGNALS_INDEX_KEY = 'signalsIndex' as const;
|
||||
|
||||
export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals` as const;
|
||||
export const DETECTION_ENGINE_SIGNALS_STATUS_URL =
|
||||
`${DETECTION_ENGINE_SIGNALS_URL}/status` as const;
|
||||
export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/search` as const;
|
||||
export const DETECTION_ENGINE_SIGNALS_MIGRATION_URL =
|
||||
`${DETECTION_ENGINE_SIGNALS_URL}/migration` as const;
|
||||
export const DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL =
|
||||
`${DETECTION_ENGINE_SIGNALS_URL}/migration_status` as const;
|
||||
export const DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL =
|
||||
`${DETECTION_ENGINE_SIGNALS_URL}/finalize_migration` as const;
|
||||
|
||||
export const ALERTS_AS_DATA_URL = '/internal/rac/alerts' as const;
|
||||
export const ALERTS_AS_DATA_FIND_URL = `${ALERTS_AS_DATA_URL}/find` as const;
|
||||
|
||||
/**
|
||||
* Common naming convention for an unauthenticated user
|
||||
*/
|
||||
export const UNAUTHENTICATED_USER = 'Unauthenticated' as const;
|
||||
|
||||
/*
|
||||
Licensing requirements
|
||||
*/
|
||||
export const MINIMUM_ML_LICENSE = 'platinum' as const;
|
||||
|
||||
/*
|
||||
Machine Learning constants
|
||||
*/
|
||||
export const ML_GROUP_ID = 'security' as const;
|
||||
export const LEGACY_ML_GROUP_ID = 'siem' as const;
|
||||
export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID] as const;
|
||||
|
||||
export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions' as const;
|
||||
export const NOTIFICATION_THROTTLE_RULE = 'rule' as const;
|
||||
|
||||
export const showAllOthersBucket: string[] = [
|
||||
'destination.ip',
|
||||
'event.action',
|
||||
'event.category',
|
||||
'event.dataset',
|
||||
'event.module',
|
||||
'signal.rule.threat.tactic.name',
|
||||
'source.ip',
|
||||
'destination.ip',
|
||||
'user.name',
|
||||
];
|
||||
|
||||
export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_' as const;
|
||||
|
||||
export const RISKY_USERS_INDEX_PREFIX = 'ml_user_risk_score_' as const;
|
||||
|
||||
export const TRANSFORM_STATES = {
|
||||
ABORTING: 'aborting',
|
||||
FAILED: 'failed',
|
||||
INDEXING: 'indexing',
|
||||
STARTED: 'started',
|
||||
STOPPED: 'stopped',
|
||||
STOPPING: 'stopping',
|
||||
WAITING: 'waiting',
|
||||
};
|
||||
|
||||
export const WARNING_TRANSFORM_STATES = new Set([
|
||||
TRANSFORM_STATES.ABORTING,
|
||||
TRANSFORM_STATES.FAILED,
|
||||
TRANSFORM_STATES.STOPPED,
|
||||
TRANSFORM_STATES.STOPPING,
|
||||
]);
|
||||
|
||||
export const STARTED_TRANSFORM_STATES = new Set([
|
||||
TRANSFORM_STATES.INDEXING,
|
||||
TRANSFORM_STATES.STARTED,
|
||||
]);
|
||||
|
||||
/**
|
||||
* How many rules to update at a time is set to 50 from errors coming from
|
||||
* the slow environments such as cloud when the rule updates are > 100 we were
|
||||
* seeing timeout issues.
|
||||
*
|
||||
* Since there is not timeout options at the alerting API level right now, we are
|
||||
* at the mercy of the Elasticsearch server client/server default timeouts and what
|
||||
* we are doing could be considered a workaround to not being able to increase the timeouts.
|
||||
*
|
||||
* However, other bad effects and saturation of connections beyond 50 makes this a "noisy neighbor"
|
||||
* if we don't limit its number of connections as we increase the number of rules that can be
|
||||
* installed at a time.
|
||||
*
|
||||
* Lastly, we saw weird issues where Chrome on upstream 408 timeouts will re-call the REST route
|
||||
* which in turn could create additional connections we want to avoid.
|
||||
*
|
||||
* See file import_rules_route.ts for another area where 50 was chosen, therefore I chose
|
||||
* 50 here to mimic it as well. If you see this re-opened or what similar to it, consider
|
||||
* reducing the 50 above to a lower number.
|
||||
*
|
||||
* See the original ticket here:
|
||||
* https://github.com/elastic/kibana/issues/94418
|
||||
*/
|
||||
export const MAX_RULES_TO_UPDATE_IN_PARALLEL = 50;
|
||||
|
||||
export const LIMITED_CONCURRENCY_ROUTE_TAG_PREFIX = `${APP_ID}:limitedConcurrency`;
|
||||
|
||||
/**
|
||||
* Max number of rules to display on UI in table, max number of rules that can be edited in a single bulk edit API request
|
||||
* We limit number of rules in bulk edit API, because rulesClient doesn't support bulkGet of rules by ids.
|
||||
* Given this limitation, current implementation fetches each rule separately through rulesClient.resolve method.
|
||||
* As max number of rules displayed on a page is 100, max 100 rules can be bulk edited by passing their ids to API.
|
||||
* We decided add this limit(number of ids less than 100) in bulk edit API as well, to prevent a huge number of single rule fetches
|
||||
*/
|
||||
export const RULES_TABLE_MAX_PAGE_SIZE = 100;
|
||||
|
||||
/**
|
||||
* Local storage keys we use to store the state of our new features tours we currently show in the app.
|
||||
*
|
||||
* NOTE: As soon as we want to show tours for new features in the upcoming release,
|
||||
* we will need to update these constants with the corresponding version.
|
||||
*/
|
||||
export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
|
||||
RULE_MANAGEMENT_PAGE: 'securitySolution.rulesManagementPage.newFeaturesTour.v8.6',
|
||||
};
|
||||
|
||||
export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY =
|
||||
'securitySolution.ruleDetails.ruleExecutionLog.showMetrics.v8.2';
|
||||
|
||||
// TODO: https://github.com/elastic/kibana/pull/142950
|
||||
/**
|
||||
* Error codes that can be thrown during _bulk_action API dry_run call and be processed and displayed to end user
|
||||
*/
|
||||
export enum BulkActionsDryRunErrCode {
|
||||
IMMUTABLE = 'IMMUTABLE',
|
||||
MACHINE_LEARNING_AUTH = 'MACHINE_LEARNING_AUTH',
|
||||
MACHINE_LEARNING_INDEX_PATTERN = 'MACHINE_LEARNING_INDEX_PATTERN',
|
||||
}
|
||||
|
||||
export const RISKY_HOSTS_DOC_LINK =
|
||||
'https://www.elastic.co/guide/en/security/current/host-risk-score.html';
|
||||
export const RISKY_USERS_DOC_LINK =
|
||||
'https://www.elastic.co/guide/en/security/current/user-risk-score.html';
|
||||
|
||||
export const MAX_NUMBER_OF_NEW_TERMS_FIELDS = 3;
|
||||
|
||||
export const BULK_ADD_TO_TIMELINE_LIMIT = 2000;
|
||||
|
||||
export const DEFAULT_DETECTION_PAGE_FILTERS = [
|
||||
{
|
||||
title: 'Status',
|
||||
fieldName: 'kibana.alert.workflow_status',
|
||||
selectedOptions: ['open'],
|
||||
hideActionBar: true,
|
||||
},
|
||||
{
|
||||
title: 'Severity',
|
||||
fieldName: 'kibana.alert.severity',
|
||||
selectedOptions: [],
|
||||
hideActionBar: true,
|
||||
},
|
||||
{
|
||||
title: 'User',
|
||||
fieldName: 'user.name',
|
||||
},
|
||||
{
|
||||
title: 'Host',
|
||||
fieldName: 'host.name',
|
||||
},
|
||||
];
|
|
@ -7,7 +7,18 @@
|
|||
|
||||
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { BrowserFields } from '@kbn/timelines-plugin/common';
|
||||
import { DEFAULT_INDEX_PATTERN } from '../common/constants';
|
||||
|
||||
const DEFAULT_INDEX_PATTERN = [
|
||||
'apm-*-transaction*',
|
||||
'auditbeat-*',
|
||||
'endgame-*',
|
||||
'filebeat-*',
|
||||
'logs-*',
|
||||
'packetbeat-*',
|
||||
'traces-apm*',
|
||||
'winlogbeat-*',
|
||||
'-*elastic-cloud-logs-*',
|
||||
];
|
||||
|
||||
export const mockBrowserFields: BrowserFields = {
|
||||
agent: {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { cloudDefendPages } from './constants';
|
||||
import { getSecuritySolutionLink, getSecuritySolutionNavTab } from './security_solution_links';
|
||||
import { getSecuritySolutionLink } from './security_solution_links';
|
||||
import { Chance } from 'chance';
|
||||
import type { CloudDefendPage } from './types';
|
||||
|
||||
|
@ -23,17 +23,3 @@ describe('getSecuritySolutionLink', () => {
|
|||
expect(link.title).toEqual(cloudDefendPages[cloudDefendPage].name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecuritySolutionNavTab', () => {
|
||||
it('gets the correct nav tab properties', () => {
|
||||
const cloudDefendPage = chance.pickone<CloudDefendPage>(['policies']);
|
||||
const basePath = chance.word();
|
||||
|
||||
const navTab = getSecuritySolutionNavTab(cloudDefendPage, basePath);
|
||||
|
||||
expect(navTab.id).toEqual(cloudDefendPages[cloudDefendPage].id);
|
||||
expect(navTab.name).toEqual(cloudDefendPages[cloudDefendPage].name);
|
||||
expect(navTab.href).toEqual(`${basePath}${cloudDefendPages[cloudDefendPage].path}`);
|
||||
expect(navTab.disabled).toEqual(!!cloudDefendPages[cloudDefendPage].disabled);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,13 +14,6 @@ interface CloudDefendLinkItem<TId extends string = CloudDefendPageId> {
|
|||
path: string;
|
||||
}
|
||||
|
||||
interface CloudDefendNavTab<TId extends string = CloudDefendPageId> {
|
||||
id: TId;
|
||||
name: string;
|
||||
href: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloud_defend link properties of a Cloud Defend page for navigation in the security solution.
|
||||
* @param cloudDefendPage the name of the cloud defend page.
|
||||
|
@ -34,18 +27,3 @@ export const getSecuritySolutionLink = <TId extends string = CloudDefendPageId>(
|
|||
path: cloudDefendPages[cloudDefendPage].path,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the link properties of a Cloud Defend page for navigation in the old security solution navigation.
|
||||
* @param cloudDefendPage the name of the cloud defend page.
|
||||
* @param basePath the base path for links.
|
||||
*/
|
||||
export const getSecuritySolutionNavTab = <TId extends string = CloudDefendPageId>(
|
||||
cloudDefendPage: CloudDefendPage,
|
||||
basePath: string
|
||||
): CloudDefendNavTab<TId> => ({
|
||||
id: cloudDefendPages[cloudDefendPage].id as TId,
|
||||
name: cloudDefendPages[cloudDefendPage].name,
|
||||
href: `${basePath}${cloudDefendPages[cloudDefendPage].path}`,
|
||||
disabled: !!cloudDefendPages[cloudDefendPage].disabled,
|
||||
});
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
import { CloudDefendPlugin } from './plugin';
|
||||
|
||||
export type { CloudDefendSecuritySolutionContext } from './types';
|
||||
export {
|
||||
getSecuritySolutionLink,
|
||||
getSecuritySolutionNavTab,
|
||||
} from './common/navigation/security_solution_links';
|
||||
export { getSecuritySolutionLink } from './common/navigation/security_solution_links';
|
||||
export { CLOUD_DEFEND_BASE_PATH } from './common/navigation/constants';
|
||||
export type { CloudDefendPageId } from './common/navigation/types';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { cloudPosturePages } from './constants';
|
||||
import { getSecuritySolutionLink, getSecuritySolutionNavTab } from './security_solution_links';
|
||||
import { getSecuritySolutionLink } from './security_solution_links';
|
||||
import { Chance } from 'chance';
|
||||
import type { CspPage } from './types';
|
||||
|
||||
|
@ -28,22 +28,3 @@ describe('getSecuritySolutionLink', () => {
|
|||
expect(link.title).toEqual(cloudPosturePages[cspPage].name);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecuritySolutionNavTab', () => {
|
||||
it('gets the correct nav tab properties', () => {
|
||||
const cspPage = chance.pickone<CspPage>([
|
||||
'dashboard',
|
||||
'findings',
|
||||
'benchmarks',
|
||||
'vulnerability_dashboard',
|
||||
]);
|
||||
const basePath = chance.word();
|
||||
|
||||
const navTab = getSecuritySolutionNavTab(cspPage, basePath);
|
||||
|
||||
expect(navTab.id).toEqual(cloudPosturePages[cspPage].id);
|
||||
expect(navTab.name).toEqual(cloudPosturePages[cspPage].name);
|
||||
expect(navTab.href).toEqual(`${basePath}${cloudPosturePages[cspPage].path}`);
|
||||
expect(navTab.disabled).toEqual(!!cloudPosturePages[cspPage].disabled);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,13 +14,6 @@ interface CloudSecurityPostureLinkItem<TId extends string = CloudSecurityPosture
|
|||
path: string;
|
||||
}
|
||||
|
||||
interface CloudSecurityPostureNavTab<TId extends string = CloudSecurityPosturePageId> {
|
||||
id: TId;
|
||||
name: string;
|
||||
href: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloud security posture link properties of a CSP page for navigation in the security solution.
|
||||
* @param cloudSecurityPosturePage the name of the cloud posture page.
|
||||
|
@ -32,18 +25,3 @@ export const getSecuritySolutionLink = <TId extends string = CloudSecurityPostur
|
|||
title: cloudPosturePages[cloudSecurityPosturePage].name,
|
||||
path: cloudPosturePages[cloudSecurityPosturePage].path,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the cloud security posture link properties of a CSP page for navigation in the old security solution navigation.
|
||||
* @param cloudSecurityPosturePage the name of the cloud posture page.
|
||||
* @param basePath the base path for links.
|
||||
*/
|
||||
export const getSecuritySolutionNavTab = <TId extends string = CloudSecurityPosturePageId>(
|
||||
cloudSecurityPosturePage: CspPage,
|
||||
basePath: string
|
||||
): CloudSecurityPostureNavTab<TId> => ({
|
||||
id: cloudPosturePages[cloudSecurityPosturePage].id as TId,
|
||||
name: cloudPosturePages[cloudSecurityPosturePage].name,
|
||||
href: `${basePath}${cloudPosturePages[cloudSecurityPosturePage].path}`,
|
||||
disabled: !!cloudPosturePages[cloudSecurityPosturePage].disabled,
|
||||
});
|
||||
|
|
|
@ -9,10 +9,7 @@ import { CspPlugin } from './plugin';
|
|||
export type { CspSecuritySolutionContext } from './types';
|
||||
export { CLOUD_SECURITY_POSTURE_BASE_PATH } from './common/navigation/constants';
|
||||
export type { CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
export {
|
||||
getSecuritySolutionLink,
|
||||
getSecuritySolutionNavTab,
|
||||
} from './common/navigation/security_solution_links';
|
||||
export { getSecuritySolutionLink } from './common/navigation/security_solution_links';
|
||||
|
||||
export type { CspClientPluginSetup, CspClientPluginStart } from './types';
|
||||
|
||||
|
|
|
@ -220,9 +220,6 @@ export const INCLUDE_INDEX_PATTERN = [
|
|||
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events, and the exclude index pattern */
|
||||
export const DEFAULT_INDEX_PATTERN = [...INCLUDE_INDEX_PATTERN, ...EXCLUDE_ELASTIC_CLOUD_INDICES];
|
||||
|
||||
/** This Kibana Advanced Setting enables the grouped navigation in Security Solution */
|
||||
export const ENABLE_GROUPED_NAVIGATION = 'securitySolution:enableGroupedNav' as const;
|
||||
|
||||
/** This Kibana Advanced Setting enables the `Security news` feed widget */
|
||||
export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed' as const;
|
||||
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* 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 { getDeepLinks, hasFeaturesCapability } from '.';
|
||||
import type { AppDeepLink, Capabilities } from '@kbn/core/public';
|
||||
import { SecurityPageName } from '../types';
|
||||
import { mockGlobalState } from '../../common/mock';
|
||||
import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../../common/constants';
|
||||
import { createCapabilities } from '../../common/links/test_utils';
|
||||
|
||||
const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null =>
|
||||
deepLinks.reduce((deepLinkFound: AppDeepLink | null, deepLink) => {
|
||||
if (deepLinkFound !== null) {
|
||||
return deepLinkFound;
|
||||
}
|
||||
if (deepLink.id === id) {
|
||||
return deepLink;
|
||||
}
|
||||
if (deepLink.deepLinks) {
|
||||
return findDeepLink(id, deepLink.deepLinks);
|
||||
}
|
||||
return null;
|
||||
}, null);
|
||||
|
||||
const basicLicense = 'basic';
|
||||
const platinumLicense = 'platinum';
|
||||
|
||||
describe('deepLinks', () => {
|
||||
describe('hasFeaturesCapability', () => {
|
||||
it('returns true when features is undefined', () => {
|
||||
expect(
|
||||
hasFeaturesCapability(undefined, createCapabilities({ siem: { show: true } }))
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a all basic license deep links in the premium deep links', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense);
|
||||
const platinumLinks = getDeepLinks(mockGlobalState.app.enableExperimental, platinumLicense);
|
||||
|
||||
const testAllBasicInPlatinum = (
|
||||
basicDeepLinks: AppDeepLink[],
|
||||
platinumDeepLinks: AppDeepLink[]
|
||||
) => {
|
||||
basicDeepLinks.forEach((basicDeepLink) => {
|
||||
const platinumDeepLink = platinumDeepLinks.find(({ id }) => id === basicDeepLink.id);
|
||||
expect(platinumDeepLink).toBeTruthy();
|
||||
|
||||
if (platinumDeepLink && basicDeepLink.deepLinks) {
|
||||
expect(platinumDeepLink.deepLinks).toBeTruthy();
|
||||
|
||||
if (platinumDeepLink.deepLinks) {
|
||||
testAllBasicInPlatinum(basicDeepLink.deepLinks, platinumDeepLink.deepLinks);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
testAllBasicInPlatinum(basicLinks, platinumLinks);
|
||||
});
|
||||
|
||||
it('should not return premium deep links in basic license deep links', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense);
|
||||
const platinumLinks = getDeepLinks(mockGlobalState.app.enableExperimental, platinumLicense);
|
||||
|
||||
[
|
||||
SecurityPageName.hostsAnomalies,
|
||||
SecurityPageName.networkAnomalies,
|
||||
SecurityPageName.caseConfigure,
|
||||
].forEach((premiumDeepLinkId) => {
|
||||
expect(findDeepLink(premiumDeepLinkId, platinumLinks)).toBeTruthy();
|
||||
expect(findDeepLink(premiumDeepLinkId, basicLinks)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return case links for basic license with only read_cases capabilities', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
|
||||
[CASES_FEATURE_ID]: { read_cases: true },
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
} as unknown as Capabilities);
|
||||
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return case links with NO deepLinks for basic license with only read_cases capabilities', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
|
||||
[CASES_FEATURE_ID]: { read_cases: true },
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
} as unknown as Capabilities);
|
||||
expect(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length === 0).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return case links with deepLinks for basic license with permissive capabilities', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
|
||||
[CASES_FEATURE_ID]: {
|
||||
create_cases: true,
|
||||
read_cases: true,
|
||||
update_cases: true,
|
||||
delete_cases: true,
|
||||
push_cases: true,
|
||||
},
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
} as unknown as Capabilities);
|
||||
|
||||
expect(
|
||||
(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length ?? 0) > 0
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return case links with deepLinks for basic license with permissive capabilities and security disabled', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, platinumLicense, {
|
||||
[CASES_FEATURE_ID]: {
|
||||
create_cases: true,
|
||||
read_cases: true,
|
||||
update_cases: true,
|
||||
delete_cases: true,
|
||||
push_cases: true,
|
||||
},
|
||||
[SERVER_APP_ID]: { show: false },
|
||||
} as unknown as Capabilities);
|
||||
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return NO case links for basic license with NO cases capabilities', () => {
|
||||
const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, {
|
||||
[CASES_FEATURE_ID]: {
|
||||
create_cases: false,
|
||||
read_cases: false,
|
||||
update_cases: false,
|
||||
delete_cases: false,
|
||||
push_cases: false,
|
||||
},
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
} as unknown as Capabilities);
|
||||
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return empty links for any license', () => {
|
||||
const emptyDeepLinks = getDeepLinks(
|
||||
mockGlobalState.app.enableExperimental,
|
||||
basicLicense,
|
||||
{} as unknown as Capabilities
|
||||
);
|
||||
expect(emptyDeepLinks.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return case links for basic license with undefined capabilities', () => {
|
||||
const basicLinks = getDeepLinks(
|
||||
mockGlobalState.app.enableExperimental,
|
||||
basicLicense,
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return case deepLinks for basic license with undefined capabilities', () => {
|
||||
const basicLinks = getDeepLinks(
|
||||
mockGlobalState.app.enableExperimental,
|
||||
basicLicense,
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(
|
||||
(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length ?? 0) > 0
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return users link', () => {
|
||||
const deepLinks = getDeepLinks({
|
||||
...mockGlobalState.app.enableExperimental,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.users, deepLinks)).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('experimental flags', () => {
|
||||
it('should return NO kubernetes link when enableExperimental.kubernetesEnabled === false', () => {
|
||||
const deepLinks = getDeepLinks({
|
||||
...mockGlobalState.app.enableExperimental,
|
||||
kubernetesEnabled: false,
|
||||
});
|
||||
|
||||
expect(findDeepLink(SecurityPageName.kubernetes, deepLinks)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return kubernetes link when enableExperimental.kubernetesEnabled === true', () => {
|
||||
const deepLinks = getDeepLinks({
|
||||
...mockGlobalState.app.enableExperimental,
|
||||
kubernetesEnabled: true,
|
||||
});
|
||||
expect(findDeepLink(SecurityPageName.kubernetes, deepLinks)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,639 +0,0 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import { getSecuritySolutionLink as getCloudDefendSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public';
|
||||
import { getSecuritySolutionLink as getCloudPostureSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { getSecuritySolutionDeepLink } from '@kbn/threat-intelligence-plugin/public';
|
||||
import type { LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
import { getCasesDeepLinks } from '@kbn/cases-plugin/public';
|
||||
import {
|
||||
CREATE_CASES_CAPABILITY,
|
||||
DELETE_CASES_CAPABILITY,
|
||||
PUSH_CASES_CAPABILITY,
|
||||
READ_CASES_CAPABILITY,
|
||||
UPDATE_CASES_CAPABILITY,
|
||||
} from '@kbn/cases-plugin/common';
|
||||
import type { AppDeepLink, AppUpdater, Capabilities } from '@kbn/core/public';
|
||||
import { AppNavLinkStatus } from '@kbn/core/public';
|
||||
import type { Subject, Subscription } from 'rxjs';
|
||||
import { SecurityPageName } from '../types';
|
||||
import {
|
||||
ALERTS,
|
||||
BLOCKLIST,
|
||||
CREATE_NEW_RULE,
|
||||
DASHBOARDS,
|
||||
DATA_QUALITY,
|
||||
DETECT,
|
||||
DETECTION_RESPONSE,
|
||||
ENDPOINTS,
|
||||
EVENT_FILTERS,
|
||||
EXCEPTIONS,
|
||||
EXPLORE,
|
||||
GETTING_STARTED,
|
||||
HOST_ISOLATION_EXCEPTIONS,
|
||||
HOSTS,
|
||||
INVESTIGATE,
|
||||
KUBERNETES,
|
||||
MANAGE,
|
||||
NETWORK,
|
||||
OVERVIEW,
|
||||
POLICIES,
|
||||
RESPONSE_ACTIONS_HISTORY,
|
||||
ENTITY_ANALYTICS,
|
||||
RULES,
|
||||
TIMELINES,
|
||||
TRUSTED_APPLICATIONS,
|
||||
USERS,
|
||||
} from '../translations';
|
||||
import {
|
||||
ALERTS_PATH,
|
||||
BLOCKLIST_PATH,
|
||||
CASES_FEATURE_ID,
|
||||
CASES_PATH,
|
||||
DATA_QUALITY_PATH,
|
||||
DETECTION_RESPONSE_PATH,
|
||||
ENDPOINTS_PATH,
|
||||
EVENT_FILTERS_PATH,
|
||||
EXCEPTIONS_PATH,
|
||||
HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
HOSTS_PATH,
|
||||
KUBERNETES_PATH,
|
||||
LANDING_PATH,
|
||||
NETWORK_PATH,
|
||||
OVERVIEW_PATH,
|
||||
POLICIES_PATH,
|
||||
RESPONSE_ACTIONS_HISTORY_PATH,
|
||||
ENTITY_ANALYTICS_PATH,
|
||||
RULES_CREATE_PATH,
|
||||
RULES_PATH,
|
||||
SERVER_APP_ID,
|
||||
TIMELINES_PATH,
|
||||
TRUSTED_APPS_PATH,
|
||||
USERS_PATH,
|
||||
} from '../../../common/constants';
|
||||
import type { ExperimentalFeatures } from '../../../common/experimental_features';
|
||||
import { appLinks$, hasCapabilities } from '../../common/links';
|
||||
import type { AppLinkItems } from '../../common/links/types';
|
||||
|
||||
export const FEATURE = {
|
||||
general: `${SERVER_APP_ID}.show`,
|
||||
casesCreate: `${CASES_FEATURE_ID}.${CREATE_CASES_CAPABILITY}`,
|
||||
casesRead: `${CASES_FEATURE_ID}.${READ_CASES_CAPABILITY}`,
|
||||
casesUpdate: `${CASES_FEATURE_ID}.${UPDATE_CASES_CAPABILITY}`,
|
||||
casesDelete: `${CASES_FEATURE_ID}.${DELETE_CASES_CAPABILITY}`,
|
||||
casesPush: `${CASES_FEATURE_ID}.${PUSH_CASES_CAPABILITY}`,
|
||||
} as const;
|
||||
|
||||
type FeatureKey = typeof FEATURE[keyof typeof FEATURE];
|
||||
|
||||
/**
|
||||
* The format of defining features supports OR and AND mechanism. To specify features in an OR fashion
|
||||
* they can be defined in a single level array like: [requiredFeature1, requiredFeature2]. If either of these features
|
||||
* is satisfied the deeplinks would be included. To require that the features be AND'd together a second level array
|
||||
* can be specified: [feature1, [feature2, feature3]] this would result in feature1 || (feature2 && feature3). To specify
|
||||
* features that all must be and'd together an example would be: [[feature1, feature2]], this would result in the boolean
|
||||
* operation feature1 && feature2.
|
||||
*
|
||||
* The final format is to specify a single feature, this would be like: features: feature1, which is the same as
|
||||
* features: [feature1]
|
||||
*/
|
||||
type Features = FeatureKey | Array<FeatureKey | FeatureKey[]>;
|
||||
|
||||
type SecuritySolutionDeepLink = AppDeepLink & {
|
||||
isPremium?: boolean;
|
||||
features?: Features;
|
||||
/**
|
||||
* Displays deep link when feature flag is enabled.
|
||||
*/
|
||||
experimentalKey?: keyof ExperimentalFeatures;
|
||||
/**
|
||||
* Hides deep link when feature flag is enabled.
|
||||
*/
|
||||
hideWhenExperimentalKey?: keyof ExperimentalFeatures;
|
||||
deepLinks?: SecuritySolutionDeepLink[];
|
||||
};
|
||||
|
||||
export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [
|
||||
{
|
||||
id: SecurityPageName.landing,
|
||||
title: GETTING_STARTED,
|
||||
path: LANDING_PATH,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.getStarted', {
|
||||
defaultMessage: 'Getting started',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.dashboards,
|
||||
title: DASHBOARDS,
|
||||
path: OVERVIEW_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
searchable: false,
|
||||
order: 9000,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.dashboards', {
|
||||
defaultMessage: 'Dashboards',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.overview,
|
||||
title: OVERVIEW,
|
||||
path: OVERVIEW_PATH,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.overview', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.detectionAndResponse,
|
||||
title: DETECTION_RESPONSE,
|
||||
path: DETECTION_RESPONSE_PATH,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.detectionAndResponse', {
|
||||
defaultMessage: 'Detection & Response',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
...getCloudPostureSecuritySolutionLink<SecurityPageName>('dashboard'),
|
||||
features: [FEATURE.general],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.entityAnalytics,
|
||||
title: ENTITY_ANALYTICS,
|
||||
path: ENTITY_ANALYTICS_PATH,
|
||||
features: [FEATURE.general],
|
||||
isPremium: true,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.entityAnalytics', {
|
||||
defaultMessage: 'Entity Analytics',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.dataQuality,
|
||||
title: DATA_QUALITY,
|
||||
path: DATA_QUALITY_PATH,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.dataQualityDashboard', {
|
||||
defaultMessage: 'Data quality',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.detections,
|
||||
title: DETECT,
|
||||
path: ALERTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
title: ALERTS,
|
||||
path: ALERTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9001,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.rules,
|
||||
title: RULES,
|
||||
path: RULES_PATH,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.rules', {
|
||||
defaultMessage: 'Rules',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.rulesCreate,
|
||||
title: CREATE_NEW_RULE,
|
||||
path: RULES_CREATE_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
searchable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.exceptions,
|
||||
title: EXCEPTIONS,
|
||||
path: EXCEPTIONS_PATH,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.exceptions', {
|
||||
defaultMessage: 'Exception lists',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...getCloudPostureSecuritySolutionLink<SecurityPageName>('findings'),
|
||||
features: [FEATURE.general],
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9002,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.exploreLanding,
|
||||
title: EXPLORE,
|
||||
path: HOSTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9005,
|
||||
searchable: false,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.hosts,
|
||||
title: HOSTS,
|
||||
path: HOSTS_PATH,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.uncommonProcesses,
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', {
|
||||
defaultMessage: 'Uncommon Processes',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/uncommonProcesses`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/anomalies`,
|
||||
isPremium: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsEvents,
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/events`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostsRisk,
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.risk', {
|
||||
defaultMessage: 'Host risk',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/hostRisk`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.sessions,
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.sessions', {
|
||||
defaultMessage: 'Sessions',
|
||||
}),
|
||||
path: `${HOSTS_PATH}/sessions`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.network,
|
||||
title: NETWORK,
|
||||
path: NETWORK_PATH,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.network', {
|
||||
defaultMessage: 'Network',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.networkDns,
|
||||
title: i18n.translate('xpack.securitySolution.search.network.dns', {
|
||||
defaultMessage: 'DNS',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/dns`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkHttp,
|
||||
title: i18n.translate('xpack.securitySolution.search.network.http', {
|
||||
defaultMessage: 'HTTP',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/http`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkTls,
|
||||
title: i18n.translate('xpack.securitySolution.search.network.tls', {
|
||||
defaultMessage: 'TLS',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/tls`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.search.network.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/anomalies`,
|
||||
isPremium: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.networkEvents,
|
||||
title: i18n.translate('xpack.securitySolution.search.network.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: `${NETWORK_PATH}/events`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.users,
|
||||
title: USERS,
|
||||
path: USERS_PATH,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.users', {
|
||||
defaultMessage: 'Users',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.usersAuthentications,
|
||||
title: i18n.translate('xpack.securitySolution.search.users.authentications', {
|
||||
defaultMessage: 'Authentications',
|
||||
}),
|
||||
path: `${USERS_PATH}/authentications`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersAnomalies,
|
||||
title: i18n.translate('xpack.securitySolution.search.users.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: `${USERS_PATH}/anomalies`,
|
||||
isPremium: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersRisk,
|
||||
title: i18n.translate('xpack.securitySolution.search.users.risk', {
|
||||
defaultMessage: 'User risk',
|
||||
}),
|
||||
path: `${USERS_PATH}/userRisk`,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.usersEvents,
|
||||
title: i18n.translate('xpack.securitySolution.search.users.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: `${USERS_PATH}/events`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...getSecuritySolutionDeepLink<SecurityPageName>('indicators'),
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9006,
|
||||
features: [FEATURE.general],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.kubernetes,
|
||||
title: KUBERNETES,
|
||||
path: KUBERNETES_PATH,
|
||||
experimentalKey: 'kubernetesEnabled',
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.kubernetes', {
|
||||
defaultMessage: 'Kubernetes',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.investigate,
|
||||
title: INVESTIGATE,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
features: [FEATURE.general, FEATURE.casesRead],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.investigate', {
|
||||
defaultMessage: 'Investigate',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.timelines,
|
||||
title: TIMELINES,
|
||||
path: TIMELINES_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9003,
|
||||
features: [FEATURE.general],
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.timelines', {
|
||||
defaultMessage: 'Timelines',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.timelinesTemplates,
|
||||
title: i18n.translate('xpack.securitySolution.search.timeline.templates', {
|
||||
defaultMessage: 'Templates',
|
||||
}),
|
||||
path: `${TIMELINES_PATH}/template`,
|
||||
},
|
||||
],
|
||||
},
|
||||
getCasesDeepLinks<SecuritySolutionDeepLink>({
|
||||
basePath: CASES_PATH,
|
||||
extend: {
|
||||
[SecurityPageName.case]: {
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9004,
|
||||
features: [FEATURE.casesRead],
|
||||
},
|
||||
[SecurityPageName.caseConfigure]: {
|
||||
features: [FEATURE.casesUpdate],
|
||||
isPremium: true,
|
||||
},
|
||||
[SecurityPageName.caseCreate]: {
|
||||
features: [FEATURE.casesCreate],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.administration,
|
||||
title: MANAGE,
|
||||
path: ENDPOINTS_PATH,
|
||||
features: [FEATURE.general],
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
order: 9007,
|
||||
searchable: false,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.manage', {
|
||||
defaultMessage: 'Manage',
|
||||
}),
|
||||
],
|
||||
deepLinks: [
|
||||
{
|
||||
id: SecurityPageName.endpoints,
|
||||
title: ENDPOINTS,
|
||||
path: ENDPOINTS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.policies,
|
||||
title: POLICIES,
|
||||
path: POLICIES_PATH,
|
||||
experimentalKey: 'policyListEnabled',
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.trustedApps,
|
||||
title: TRUSTED_APPLICATIONS,
|
||||
path: TRUSTED_APPS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.eventFilters,
|
||||
title: EVENT_FILTERS,
|
||||
path: EVENT_FILTERS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hostIsolationExceptions,
|
||||
title: HOST_ISOLATION_EXCEPTIONS,
|
||||
path: HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.blocklist,
|
||||
title: BLOCKLIST,
|
||||
path: BLOCKLIST_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.responseActionsHistory,
|
||||
title: RESPONSE_ACTIONS_HISTORY,
|
||||
path: RESPONSE_ACTIONS_HISTORY_PATH,
|
||||
},
|
||||
{
|
||||
...getCloudPostureSecuritySolutionLink<SecurityPageName>('benchmarks'),
|
||||
},
|
||||
{
|
||||
...getCloudDefendSecuritySolutionLink<SecurityPageName>('policies'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* A function that generates the plugin deepLinks structure
|
||||
* used by Kibana to build the global side navigation and application search results
|
||||
* @param enableExperimental ExperimentalFeatures arg
|
||||
* @param licenseType optional string for license level, if not provided basic is assumed.
|
||||
* @param capabilities optional arg for app start capabilities
|
||||
*/
|
||||
export function getDeepLinks(
|
||||
enableExperimental: ExperimentalFeatures,
|
||||
licenseType?: LicenseType,
|
||||
capabilities?: Capabilities
|
||||
): AppDeepLink[] {
|
||||
const filterDeepLinks = (securityDeepLinks: SecuritySolutionDeepLink[]): AppDeepLink[] =>
|
||||
securityDeepLinks.reduce(
|
||||
(
|
||||
deepLinks: AppDeepLink[],
|
||||
{ isPremium, features, experimentalKey, hideWhenExperimentalKey, ...deepLink }
|
||||
) => {
|
||||
if (licenseType && isPremium && !isPremiumLicense(licenseType)) {
|
||||
return deepLinks;
|
||||
}
|
||||
if (experimentalKey && !enableExperimental[experimentalKey]) {
|
||||
return deepLinks;
|
||||
}
|
||||
|
||||
if (hideWhenExperimentalKey && enableExperimental[hideWhenExperimentalKey]) {
|
||||
return deepLinks;
|
||||
}
|
||||
|
||||
if (capabilities != null && !hasFeaturesCapability(features, capabilities)) {
|
||||
return deepLinks;
|
||||
}
|
||||
if (deepLink.deepLinks) {
|
||||
deepLinks.push({ ...deepLink, deepLinks: filterDeepLinks(deepLink.deepLinks) });
|
||||
} else {
|
||||
deepLinks.push(deepLink);
|
||||
}
|
||||
return deepLinks;
|
||||
},
|
||||
[]
|
||||
);
|
||||
return filterDeepLinks(securitySolutionsDeepLinks);
|
||||
}
|
||||
|
||||
export function hasFeaturesCapability(
|
||||
features: Features | undefined,
|
||||
capabilities: Capabilities
|
||||
): boolean {
|
||||
if (!features) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasCapabilities(features, capabilities);
|
||||
}
|
||||
|
||||
export function isPremiumLicense(licenseType?: LicenseType): boolean {
|
||||
return (
|
||||
licenseType === 'gold' ||
|
||||
licenseType === 'platinum' ||
|
||||
licenseType === 'enterprise' ||
|
||||
licenseType === 'trial'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* New deep links code starts here.
|
||||
* All the code above will be removed once the appLinks migration is over.
|
||||
* The code below manages the new implementation using the unified appLinks.
|
||||
*/
|
||||
|
||||
const formatDeepLinks = (appLinks: AppLinkItems): AppDeepLink[] =>
|
||||
appLinks.map((appLink) => ({
|
||||
id: appLink.id,
|
||||
path: appLink.path,
|
||||
title: appLink.title,
|
||||
searchable: !appLink.globalSearchDisabled,
|
||||
...(appLink.globalNavPosition != null
|
||||
? { navLinkStatus: AppNavLinkStatus.visible, order: appLink.globalNavPosition }
|
||||
: { navLinkStatus: AppNavLinkStatus.hidden }),
|
||||
...(appLink.globalSearchKeywords != null ? { keywords: appLink.globalSearchKeywords } : {}),
|
||||
...(appLink.links && appLink.links?.length
|
||||
? {
|
||||
deepLinks: formatDeepLinks(appLink.links),
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Registers any change in appLinks to be updated in app deepLinks
|
||||
*/
|
||||
export const registerDeepLinksUpdater = (appUpdater$: Subject<AppUpdater>): Subscription => {
|
||||
return appLinks$.subscribe((appLinks) => {
|
||||
appUpdater$.next(() => ({
|
||||
navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent main security link to switch to visible after update
|
||||
deepLinks: formatDeepLinks(appLinks),
|
||||
}));
|
||||
});
|
||||
};
|
|
@ -1,239 +0,0 @@
|
|||
/*
|
||||
* 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 { getSecuritySolutionNavTab as getSecuritySolutionCSPNavTab } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { getSecuritySolutionNavTab as getSecuritySolutionTINavTab } from '@kbn/threat-intelligence-plugin/public';
|
||||
import { getSecuritySolutionNavTab as getSecuritySolutionCloudDefendNavTab } from '@kbn/cloud-defend-plugin/public';
|
||||
import * as i18n from '../translations';
|
||||
import type { SecurityNav, SecurityNavGroup } from '../../common/components/navigation/types';
|
||||
import { SecurityNavGroupKey } from '../../common/components/navigation/types';
|
||||
import {
|
||||
APP_OVERVIEW_PATH,
|
||||
APP_DETECTION_RESPONSE_PATH,
|
||||
APP_RULES_PATH,
|
||||
APP_ALERTS_PATH,
|
||||
APP_EXCEPTIONS_PATH,
|
||||
APP_HOSTS_PATH,
|
||||
APP_NETWORK_PATH,
|
||||
APP_TIMELINES_PATH,
|
||||
APP_CASES_PATH,
|
||||
APP_ENDPOINTS_PATH,
|
||||
APP_POLICIES_PATH,
|
||||
APP_TRUSTED_APPS_PATH,
|
||||
APP_EVENT_FILTERS_PATH,
|
||||
APP_BLOCKLIST_PATH,
|
||||
SecurityPageName,
|
||||
APP_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
APP_USERS_PATH,
|
||||
APP_KUBERNETES_PATH,
|
||||
APP_LANDING_PATH,
|
||||
APP_RESPONSE_ACTIONS_HISTORY_PATH,
|
||||
APP_ENTITY_ANALYTICS_PATH,
|
||||
APP_DATA_QUALITY_PATH,
|
||||
APP_PATH,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const navTabs: SecurityNav = {
|
||||
[SecurityPageName.landing]: {
|
||||
id: SecurityPageName.landing,
|
||||
name: i18n.GETTING_STARTED,
|
||||
href: APP_LANDING_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'get_started',
|
||||
},
|
||||
[SecurityPageName.overview]: {
|
||||
id: SecurityPageName.overview,
|
||||
name: i18n.OVERVIEW,
|
||||
href: APP_OVERVIEW_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'overview',
|
||||
},
|
||||
[SecurityPageName.detectionAndResponse]: {
|
||||
id: SecurityPageName.detectionAndResponse,
|
||||
name: i18n.DETECTION_RESPONSE,
|
||||
href: APP_DETECTION_RESPONSE_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'detection_response',
|
||||
},
|
||||
[SecurityPageName.alerts]: {
|
||||
id: SecurityPageName.alerts,
|
||||
name: i18n.ALERTS,
|
||||
href: APP_ALERTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'alerts',
|
||||
},
|
||||
[SecurityPageName.rules]: {
|
||||
id: SecurityPageName.rules,
|
||||
name: i18n.RULES,
|
||||
href: APP_RULES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'rules',
|
||||
},
|
||||
[SecurityPageName.exceptions]: {
|
||||
id: SecurityPageName.exceptions,
|
||||
name: i18n.EXCEPTIONS,
|
||||
href: APP_EXCEPTIONS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'exceptions',
|
||||
},
|
||||
[SecurityPageName.hosts]: {
|
||||
id: SecurityPageName.hosts,
|
||||
name: i18n.HOSTS,
|
||||
href: APP_HOSTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'host',
|
||||
},
|
||||
[SecurityPageName.users]: {
|
||||
id: SecurityPageName.users,
|
||||
name: i18n.USERS,
|
||||
href: APP_USERS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'users',
|
||||
},
|
||||
[SecurityPageName.network]: {
|
||||
id: SecurityPageName.network,
|
||||
name: i18n.NETWORK,
|
||||
href: APP_NETWORK_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'network',
|
||||
},
|
||||
[SecurityPageName.kubernetes]: {
|
||||
id: SecurityPageName.kubernetes,
|
||||
name: i18n.KUBERNETES,
|
||||
href: APP_KUBERNETES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'kubernetes',
|
||||
},
|
||||
[SecurityPageName.timelines]: {
|
||||
id: SecurityPageName.timelines,
|
||||
name: i18n.TIMELINES,
|
||||
href: APP_TIMELINES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
[SecurityPageName.case]: {
|
||||
id: SecurityPageName.case,
|
||||
name: i18n.CASE,
|
||||
href: APP_CASES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'cases',
|
||||
},
|
||||
[SecurityPageName.endpoints]: {
|
||||
id: SecurityPageName.endpoints,
|
||||
name: i18n.ENDPOINTS,
|
||||
href: APP_ENDPOINTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.policies]: {
|
||||
id: SecurityPageName.policies,
|
||||
name: i18n.POLICIES,
|
||||
href: APP_POLICIES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.trustedApps]: {
|
||||
id: SecurityPageName.trustedApps,
|
||||
name: i18n.TRUSTED_APPLICATIONS,
|
||||
href: APP_TRUSTED_APPS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.eventFilters]: {
|
||||
id: SecurityPageName.eventFilters,
|
||||
name: i18n.EVENT_FILTERS,
|
||||
href: APP_EVENT_FILTERS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.hostIsolationExceptions]: {
|
||||
id: SecurityPageName.hostIsolationExceptions,
|
||||
name: i18n.HOST_ISOLATION_EXCEPTIONS,
|
||||
href: APP_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.blocklist]: {
|
||||
id: SecurityPageName.blocklist,
|
||||
name: i18n.BLOCKLIST,
|
||||
href: APP_BLOCKLIST_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.responseActionsHistory]: {
|
||||
id: SecurityPageName.responseActionsHistory,
|
||||
name: i18n.RESPONSE_ACTIONS_HISTORY,
|
||||
href: APP_RESPONSE_ACTIONS_HISTORY_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.threatIntelligenceIndicators]: {
|
||||
...getSecuritySolutionTINavTab<SecurityPageName>('indicators', APP_PATH),
|
||||
urlKey: 'indicators',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureFindings]: {
|
||||
...getSecuritySolutionCSPNavTab<SecurityPageName>('findings', APP_PATH),
|
||||
urlKey: 'findings',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureDashboard]: {
|
||||
...getSecuritySolutionCSPNavTab<SecurityPageName>('dashboard', APP_PATH),
|
||||
urlKey: 'cloud_posture',
|
||||
},
|
||||
[SecurityPageName.cloudSecurityPostureBenchmarks]: {
|
||||
...getSecuritySolutionCSPNavTab<SecurityPageName>('benchmarks', APP_PATH),
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.cloudDefendPolicies]: {
|
||||
...getSecuritySolutionCloudDefendNavTab<SecurityPageName>('policies', APP_PATH),
|
||||
urlKey: 'administration',
|
||||
},
|
||||
[SecurityPageName.entityAnalytics]: {
|
||||
id: SecurityPageName.entityAnalytics,
|
||||
name: i18n.ENTITY_ANALYTICS,
|
||||
href: APP_ENTITY_ANALYTICS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'entity_analytics',
|
||||
},
|
||||
[SecurityPageName.dataQuality]: {
|
||||
id: SecurityPageName.dataQuality,
|
||||
name: i18n.DATA_QUALITY,
|
||||
href: APP_DATA_QUALITY_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'data_quality',
|
||||
},
|
||||
};
|
||||
|
||||
export const securityNavGroup: SecurityNavGroup = {
|
||||
[SecurityNavGroupKey.dashboards]: {
|
||||
id: SecurityNavGroupKey.dashboards,
|
||||
name: i18n.DASHBOARDS,
|
||||
},
|
||||
[SecurityNavGroupKey.detect]: {
|
||||
id: SecurityNavGroupKey.detect,
|
||||
name: i18n.DETECT,
|
||||
},
|
||||
[SecurityNavGroupKey.findings]: {
|
||||
id: SecurityNavGroupKey.findings,
|
||||
name: i18n.FINDINGS,
|
||||
},
|
||||
[SecurityNavGroupKey.explore]: {
|
||||
id: SecurityNavGroupKey.explore,
|
||||
name: i18n.EXPLORE,
|
||||
},
|
||||
[SecurityNavGroupKey.intelligence]: {
|
||||
id: SecurityNavGroupKey.intelligence,
|
||||
name: i18n.THREAT_INTELLIGENCE,
|
||||
},
|
||||
[SecurityNavGroupKey.investigate]: {
|
||||
id: SecurityNavGroupKey.investigate,
|
||||
name: i18n.INVESTIGATE,
|
||||
},
|
||||
[SecurityNavGroupKey.manage]: {
|
||||
id: SecurityNavGroupKey.manage,
|
||||
name: i18n.MANAGE,
|
||||
},
|
||||
};
|
|
@ -25,8 +25,6 @@ import {
|
|||
} from './bottom_bar';
|
||||
import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline';
|
||||
import { useShowPagesWithEmptyView } from '../../../common/utils/empty_view/use_show_pages_with_empty_view';
|
||||
import { useIsPolicySettingsBarVisible } from '../../../management/pages/policy/view/policy_hooks';
|
||||
import { useIsGroupedNavigationEnabled } from '../../../common/components/navigation/helpers';
|
||||
import { useSyncFlyoutStateWithUrl } from '../../../flyout/url/use_sync_flyout_state_with_url';
|
||||
|
||||
const NO_DATA_PAGE_MAX_WIDTH = 950;
|
||||
|
@ -59,16 +57,12 @@ const StyledKibanaPageTemplate = styled(KibanaPageTemplate)<
|
|||
|
||||
export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplateProps, 'ref'>> =
|
||||
React.memo(({ children, ...rest }) => {
|
||||
const solutionNav = useSecuritySolutionNavigation();
|
||||
const isPolicySettingsVisible = useIsPolicySettingsBarVisible();
|
||||
const solutionNavProps = useSecuritySolutionNavigation();
|
||||
const [isTimelineBottomBarVisible] = useShowTimeline();
|
||||
const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []);
|
||||
const { show: isShowingTimelineOverlay } = useDeepEqualSelector((state) =>
|
||||
getTimelineShowStatus(state, TimelineId.active)
|
||||
);
|
||||
const isGroupedNavEnabled = useIsGroupedNavigationEnabled();
|
||||
const addBottomPadding =
|
||||
isTimelineBottomBarVisible || isPolicySettingsVisible || isGroupedNavEnabled;
|
||||
|
||||
// The bottomBar by default has a set 'dark' colorMode that doesn't match the global colorMode from the Advanced Settings
|
||||
// To keep the mode in sync, we pass in the globalColorMode to the bottom bar here
|
||||
|
@ -91,10 +85,9 @@ export const SecuritySolutionTemplateWrapper: React.FC<Omit<KibanaPageTemplatePr
|
|||
ref={flyoutRef}
|
||||
>
|
||||
<StyledKibanaPageTemplate
|
||||
$addBottomPadding={addBottomPadding}
|
||||
$isShowingTimelineOverlay={isShowingTimelineOverlay}
|
||||
paddingSize="none"
|
||||
solutionNav={solutionNav}
|
||||
solutionNav={solutionNavProps}
|
||||
restrictWidth={showEmptyState ? NO_DATA_PAGE_MAX_WIDTH : false}
|
||||
{...rest}
|
||||
>
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import type { NavLinkItem } from '../navigation/types';
|
||||
import type { NavigationLink } from '../../links/types';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { LandingLinksIcons } from './landing_links_icons';
|
||||
import * as telemetry from '../../lib/telemetry';
|
||||
|
||||
const DEFAULT_NAV_ITEM: NavLinkItem = {
|
||||
const DEFAULT_NAV_ITEM: NavigationLink = {
|
||||
id: SecurityPageName.overview,
|
||||
title: 'TEST LABEL',
|
||||
description: 'TEST DESCRIPTION',
|
||||
|
|
|
@ -10,11 +10,11 @@ import styled from 'styled-components';
|
|||
|
||||
import { NavItemBetaBadge } from '../navigation/nav_item_beta_badge';
|
||||
import { SecuritySolutionLinkAnchor, withSecuritySolutionLink } from '../links';
|
||||
import type { NavLinkItem } from '../navigation/types';
|
||||
import type { NavigationLink } from '../../links/types';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../lib/telemetry';
|
||||
|
||||
interface LandingLinksImagesProps {
|
||||
items: NavLinkItem[];
|
||||
items: NavigationLink[];
|
||||
}
|
||||
|
||||
const Link = styled.a`
|
||||
|
|
|
@ -9,19 +9,19 @@ import { render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
import { BETA } from '@kbn/kubernetes-security-plugin/common/translations';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import type { NavLinkItem } from '../navigation/types';
|
||||
import type { NavigationLink } from '../../links/types';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { LandingLinksImages, LandingImageCards } from './landing_links_images';
|
||||
import * as telemetry from '../../lib/telemetry';
|
||||
|
||||
const DEFAULT_NAV_ITEM: NavLinkItem = {
|
||||
const DEFAULT_NAV_ITEM: NavigationLink = {
|
||||
id: SecurityPageName.overview,
|
||||
title: 'TEST LABEL',
|
||||
description: 'TEST DESCRIPTION',
|
||||
image: 'TEST_IMAGE.png',
|
||||
};
|
||||
|
||||
const BETA_NAV_ITEM: NavLinkItem = {
|
||||
const BETA_NAV_ITEM: NavigationLink = {
|
||||
id: SecurityPageName.kubernetes,
|
||||
title: 'TEST LABEL',
|
||||
description: 'TEST DESCRIPTION',
|
||||
|
|
|
@ -18,11 +18,11 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { withSecuritySolutionLink } from '../links';
|
||||
import { NavItemBetaBadge } from '../navigation/nav_item_beta_badge';
|
||||
import type { NavLinkItem } from '../navigation/types';
|
||||
import type { NavigationLink } from '../../links/types';
|
||||
import { TELEMETRY_EVENT, track } from '../../lib/telemetry';
|
||||
|
||||
interface LandingImagesProps {
|
||||
items: NavLinkItem[];
|
||||
items: NavigationLink[];
|
||||
}
|
||||
|
||||
const PrimaryEuiTitle = styled(EuiTitle)`
|
||||
|
|
|
@ -12,13 +12,10 @@ import { getAppLandingUrl } from '../../link_to/redirect_to_landing';
|
|||
|
||||
import type { GetSecuritySolutionUrl } from '../../link_to';
|
||||
import { getAncestorLinksInfo } from '../../../links';
|
||||
import type { GenericNavRecord } from '../types';
|
||||
|
||||
export const getLeadingBreadcrumbsForSecurityPage = (
|
||||
pageName: SecurityPageName,
|
||||
getSecuritySolutionUrl: GetSecuritySolutionUrl,
|
||||
navTabs: GenericNavRecord,
|
||||
isGroupedNavigationEnabled: boolean
|
||||
getSecuritySolutionUrl: GetSecuritySolutionUrl
|
||||
): [ChromeBreadcrumb, ...ChromeBreadcrumb[]] => {
|
||||
const landingPath = getSecuritySolutionUrl({ deepLinkId: SecurityPageName.landing });
|
||||
|
||||
|
@ -27,16 +24,10 @@ export const getLeadingBreadcrumbsForSecurityPage = (
|
|||
href: getAppLandingUrl(landingPath),
|
||||
};
|
||||
|
||||
const breadcrumbs: ChromeBreadcrumb[] = getAncestorLinksInfo(pageName).map(({ title, id }) => {
|
||||
const newTitle = title;
|
||||
// Get title from navTabs because pages title on the new structure might be different.
|
||||
const oldTitle = navTabs[id] ? navTabs[id].name : title;
|
||||
|
||||
return {
|
||||
text: isGroupedNavigationEnabled ? newTitle : oldTitle,
|
||||
href: getSecuritySolutionUrl({ deepLinkId: id }),
|
||||
};
|
||||
});
|
||||
const breadcrumbs: ChromeBreadcrumb[] = getAncestorLinksInfo(pageName).map(({ title, id }) => ({
|
||||
text: title,
|
||||
href: getSecuritySolutionUrl({ deepLinkId: id }),
|
||||
}));
|
||||
|
||||
return [siemRootBreadcrumb, ...breadcrumbs];
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,14 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { last, omit } from 'lodash/fp';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { last } from 'lodash/fp';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import type { ChromeBreadcrumb } from '@kbn/core/public';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { getTrailingBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../explore/hosts/pages/details/utils';
|
||||
import { getTrailingBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../explore/network/pages/details';
|
||||
import { getTrailingBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../explore/network/pages/details/utils';
|
||||
import { getTrailingBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../../detections/pages/detection_engine/rules/utils';
|
||||
import { getTrailingBreadcrumbs as geExceptionsBreadcrumbs } from '../../../../exceptions/utils/pages.utils';
|
||||
import { getTrailingBreadcrumbs as getCSPBreadcrumbs } from '../../../../cloud_security_posture/breadcrumbs';
|
||||
|
@ -24,46 +23,39 @@ import { SecurityPageName } from '../../../../app/types';
|
|||
import type { RouteSpyState } from '../../../utils/route/types';
|
||||
import { timelineActions } from '../../../../timelines/store/timeline';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import type { GenericNavRecord, NavigateToUrl } from '../types';
|
||||
import { getLeadingBreadcrumbsForSecurityPage } from './get_breadcrumbs_for_page';
|
||||
import type { GetSecuritySolutionUrl } from '../../link_to';
|
||||
import { useGetSecuritySolutionUrl } from '../../link_to';
|
||||
import { useIsGroupedNavigationEnabled } from '../helpers';
|
||||
import { TELEMETRY_EVENT, track } from '../../../lib/telemetry';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { useRouteSpy } from '../../../utils/route/use_route_spy';
|
||||
|
||||
export interface ObjectWithNavTabs {
|
||||
navTabs: GenericNavRecord;
|
||||
}
|
||||
|
||||
export const useSetBreadcrumbs = () => {
|
||||
export const useBreadcrumbs = ({ isEnabled }: { isEnabled: boolean }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [routeProps] = useRouteSpy();
|
||||
const getSecuritySolutionUrl = useGetSecuritySolutionUrl();
|
||||
const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled();
|
||||
|
||||
return (
|
||||
spyState: RouteSpyState & ObjectWithNavTabs,
|
||||
chrome: StartServices['chrome'],
|
||||
navigateToUrl: NavigateToUrl
|
||||
) => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
spyState,
|
||||
getSecuritySolutionUrl,
|
||||
isGroupedNavigationEnabled
|
||||
);
|
||||
const {
|
||||
chrome: { setBreadcrumbs },
|
||||
application: { navigateToUrl },
|
||||
} = useKibana().services;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
const breadcrumbs = getBreadcrumbsForRoute(routeProps, getSecuritySolutionUrl);
|
||||
if (!breadcrumbs) {
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.setBreadcrumbs(
|
||||
setBreadcrumbs(
|
||||
breadcrumbs.map((breadcrumb) => ({
|
||||
...breadcrumb,
|
||||
...(breadcrumb.href && !breadcrumb.onClick
|
||||
? {
|
||||
onClick: (ev) => {
|
||||
ev.preventDefault();
|
||||
const trakedPath = breadcrumb.href?.split('?')[0] ?? 'unknown';
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.BREADCRUMB}${trakedPath}`);
|
||||
const trackedPath = breadcrumb.href?.split('?')[0] ?? 'unknown';
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.BREADCRUMB}${trackedPath}`);
|
||||
dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
@ -73,40 +65,26 @@ export const useSetBreadcrumbs = () => {
|
|||
: {}),
|
||||
}))
|
||||
);
|
||||
};
|
||||
}, [routeProps, isEnabled, dispatch, getSecuritySolutionUrl, setBreadcrumbs, navigateToUrl]);
|
||||
};
|
||||
|
||||
export const getBreadcrumbsForRoute = (
|
||||
object: RouteSpyState & ObjectWithNavTabs,
|
||||
getSecuritySolutionUrl: GetSecuritySolutionUrl,
|
||||
isGroupedNavigationEnabled: boolean
|
||||
spyState: RouteSpyState,
|
||||
getSecuritySolutionUrl: GetSecuritySolutionUrl
|
||||
): ChromeBreadcrumb[] | null => {
|
||||
const spyState = omit('navTabs', object) as RouteSpyState;
|
||||
|
||||
if (
|
||||
!spyState ||
|
||||
!object.navTabs ||
|
||||
!spyState.pageName ||
|
||||
!spyState?.pageName ||
|
||||
// cases manages its own breadcrumbs, return null
|
||||
spyState.pageName === SecurityPageName.case
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newMenuLeadingBreadcrumbs = getLeadingBreadcrumbsForSecurityPage(
|
||||
const leadingBreadcrumbs = getLeadingBreadcrumbsForSecurityPage(
|
||||
spyState.pageName,
|
||||
getSecuritySolutionUrl,
|
||||
object.navTabs,
|
||||
isGroupedNavigationEnabled
|
||||
getSecuritySolutionUrl
|
||||
);
|
||||
|
||||
// last newMenuLeadingBreadcrumbs is the current page
|
||||
const pageBreadcrumb = newMenuLeadingBreadcrumbs[newMenuLeadingBreadcrumbs.length - 1];
|
||||
const siemRootBreadcrumb = newMenuLeadingBreadcrumbs[0];
|
||||
|
||||
const leadingBreadcrumbs = isGroupedNavigationEnabled
|
||||
? newMenuLeadingBreadcrumbs
|
||||
: [siemRootBreadcrumb, pageBreadcrumb];
|
||||
|
||||
return emptyLastBreadcrumbUrl([
|
||||
...leadingBreadcrumbs,
|
||||
...getTrailingBreadcrumbsForRoutes(spyState, getSecuritySolutionUrl),
|
||||
|
|
|
@ -5,19 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
import type { SecurityPageName } from '../../../../common/constants';
|
||||
import { ENABLE_GROUPED_NAVIGATION } from '../../../../common/constants';
|
||||
import { needsUrlState } from '../../links';
|
||||
|
||||
export const getSearch = (pageName: SecurityPageName, globalQueryString: string): string =>
|
||||
needsUrlState(pageName) && globalQueryString.length > 0 ? `?${globalQueryString}` : '';
|
||||
|
||||
/**
|
||||
* Hook to check if the new grouped navigation is enabled on both experimental flag and advanced settings
|
||||
* TODO: remove this function when flag and setting not needed
|
||||
*/
|
||||
export const useIsGroupedNavigationEnabled = () => {
|
||||
const [groupedNavSettingEnabled] = useUiSetting$<boolean>(ENABLE_GROUPED_NAVIGATION);
|
||||
return groupedNavSettingEnabled;
|
||||
};
|
||||
|
|
|
@ -12,11 +12,11 @@ import { SecurityPageName } from '../../../../app/types';
|
|||
import { TestProviders } from '../../../mock';
|
||||
import { BOTTOM_BAR_HEIGHT, EUI_HEADER_HEIGHT, SecuritySideNav } from './security_side_nav';
|
||||
import type { SolutionSideNavProps } from '@kbn/security-solution-side-nav';
|
||||
import type { NavLinkItem } from '../types';
|
||||
import type { NavigationLink } from '../../../links/types';
|
||||
import { track } from '../../../lib/telemetry';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
|
||||
const manageNavLink: NavLinkItem = {
|
||||
const manageNavLink: NavigationLink = {
|
||||
id: SecurityPageName.administration,
|
||||
title: 'manage',
|
||||
description: 'manage description',
|
||||
|
@ -30,7 +30,7 @@ const manageNavLink: NavLinkItem = {
|
|||
},
|
||||
],
|
||||
};
|
||||
const alertsNavLink: NavLinkItem = {
|
||||
const alertsNavLink: NavigationLink = {
|
||||
id: SecurityPageName.alerts,
|
||||
title: 'alerts',
|
||||
description: 'alerts description',
|
||||
|
|
|
@ -5,112 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTab, EuiTabs, EuiBetaBadge } from '@elastic/eui';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { useNavigation } from '../../../lib/kibana';
|
||||
import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry';
|
||||
import type { TabNavigationProps, TabNavigationItemProps } from './types';
|
||||
import { BETA } from '../../../translations';
|
||||
|
||||
const TabNavigationItemComponent = ({
|
||||
disabled,
|
||||
hrefWithSearch,
|
||||
id,
|
||||
name,
|
||||
isSelected,
|
||||
isBeta,
|
||||
betaOptions,
|
||||
}: TabNavigationItemProps) => {
|
||||
const { getAppUrl, navigateTo } = useNavigation();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateTo({ path: hrefWithSearch });
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
|
||||
},
|
||||
[navigateTo, hrefWithSearch, id]
|
||||
);
|
||||
|
||||
const appHref = getAppUrl({
|
||||
path: hrefWithSearch,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiTab
|
||||
data-href={appHref}
|
||||
data-test-subj={`navigation-${id}`}
|
||||
disabled={disabled}
|
||||
isSelected={isSelected}
|
||||
href={appHref}
|
||||
onClick={handleClick}
|
||||
append={isBeta && <EuiBetaBadge label={betaOptions?.text ?? BETA} size="s" />}
|
||||
>
|
||||
{name}
|
||||
</EuiTab>
|
||||
);
|
||||
};
|
||||
|
||||
const TabNavigationItem = React.memo(TabNavigationItemComponent);
|
||||
|
||||
export const TabNavigationComponent: React.FC<TabNavigationProps> = ({ navTabs, tabName }) => {
|
||||
const mapLocationToTab = useCallback(
|
||||
(): string =>
|
||||
getOr(
|
||||
'',
|
||||
'id',
|
||||
Object.values(navTabs).find((item) => tabName === item.id)
|
||||
),
|
||||
[tabName, navTabs]
|
||||
);
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
useEffect(() => {
|
||||
const currentTabSelected = mapLocationToTab();
|
||||
|
||||
if (currentTabSelected !== selectedTabId) {
|
||||
setSelectedTabId(currentTabSelected);
|
||||
}
|
||||
|
||||
// we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies)
|
||||
}, [tabName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
|
||||
const { search } = useLocation();
|
||||
|
||||
const renderTabs = useMemo(
|
||||
() =>
|
||||
Object.values(navTabs).map((tab) => {
|
||||
const isSelected = selectedTabId === tab.id;
|
||||
return (
|
||||
<TabNavigationItem
|
||||
key={`navigation-${tab.id}`}
|
||||
id={tab.id}
|
||||
hrefWithSearch={tab.href + search}
|
||||
name={tab.name}
|
||||
disabled={tab.disabled}
|
||||
isSelected={isSelected}
|
||||
isBeta={tab.isBeta}
|
||||
betaOptions={tab.betaOptions}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
[navTabs, selectedTabId, search]
|
||||
);
|
||||
|
||||
return <EuiTabs>{renderTabs}</EuiTabs>;
|
||||
};
|
||||
|
||||
TabNavigationComponent.displayName = 'TabNavigationComponent';
|
||||
|
||||
export const TabNavigation = React.memo(
|
||||
TabNavigationComponent,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.display === nextProps.display &&
|
||||
prevProps.tabName === nextProps.tabName &&
|
||||
deepEqual(prevProps.navTabs, nextProps.navTabs)
|
||||
);
|
||||
|
||||
TabNavigation.displayName = 'TabNavigation';
|
||||
export { TabNavigation } from './tab_navigation';
|
||||
|
|
|
@ -9,11 +9,13 @@ import { mount } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { navTabsHostDetails } from '../../../../explore/hosts/pages/details/nav_tabs';
|
||||
import { HostsTableType } from '../../../../explore/hosts/store/model';
|
||||
import type { RouteSpyState } from '../../../utils/route/types';
|
||||
import { TabNavigationComponent } from '.';
|
||||
import { TabNavigationComponent } from './tab_navigation';
|
||||
import type { TabNavigationProps } from './types';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
|
||||
const mockUseRouteSpy = jest.fn();
|
||||
jest.mock('../../../utils/route/use_route_spy', () => ({
|
||||
useRouteSpy: () => mockUseRouteSpy(),
|
||||
}));
|
||||
jest.mock('../../link_to');
|
||||
jest.mock('../../../lib/kibana/kibana_react', () => {
|
||||
const originalModule = jest.requireActual('../../../lib/kibana/kibana_react');
|
||||
|
@ -50,38 +52,31 @@ const hostName = 'siem-window';
|
|||
describe('Table Navigation', () => {
|
||||
const mockHasMlUserPermissions = true;
|
||||
const mockRiskyHostEnabled = true;
|
||||
mockUseRouteSpy.mockReturnValue([{ tabName: HostsTableType.authentications }]);
|
||||
|
||||
const mockProps: TabNavigationProps & RouteSpyState = {
|
||||
pageName: SecurityPageName.hosts,
|
||||
pathName: '/hosts',
|
||||
detailName: hostName,
|
||||
search: '',
|
||||
tabName: HostsTableType.authentications,
|
||||
const mockProps: TabNavigationProps = {
|
||||
navTabs: navTabsHostDetails({
|
||||
hostName,
|
||||
hasMlUserPermissions: mockHasMlUserPermissions,
|
||||
isRiskyHostsEnabled: mockRiskyHostEnabled,
|
||||
}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('it mounts with correct tab highlighted', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = wrapper.find(
|
||||
const authNavigationTab = wrapper.find(
|
||||
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
|
||||
expect(tableNavigationTab.prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it changes active tab when nav changes by props', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = () =>
|
||||
expect(authNavigationTab.prop('isSelected')).toBeTruthy();
|
||||
const eventsNavigationTab = () =>
|
||||
wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeFalsy();
|
||||
wrapper.setProps({
|
||||
tabName: HostsTableType.events,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeTruthy();
|
||||
expect(eventsNavigationTab().prop('isSelected')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('it carries the url state in the link', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 { EuiTab, EuiTabs, EuiBetaBadge } from '@elastic/eui';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { useNavigation } from '../../../lib/kibana';
|
||||
import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry';
|
||||
import type { TabNavigationProps, TabNavigationItemProps } from './types';
|
||||
import { BETA } from '../../../translations';
|
||||
import { useRouteSpy } from '../../../utils/route/use_route_spy';
|
||||
|
||||
const TabNavigationItemComponent = ({
|
||||
disabled,
|
||||
hrefWithSearch,
|
||||
id,
|
||||
name,
|
||||
isSelected,
|
||||
isBeta,
|
||||
betaOptions,
|
||||
}: TabNavigationItemProps) => {
|
||||
const { getAppUrl, navigateTo } = useNavigation();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateTo({ path: hrefWithSearch });
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
|
||||
},
|
||||
[navigateTo, hrefWithSearch, id]
|
||||
);
|
||||
|
||||
const appHref = getAppUrl({
|
||||
path: hrefWithSearch,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiTab
|
||||
data-href={appHref}
|
||||
data-test-subj={`navigation-${id}`}
|
||||
disabled={disabled}
|
||||
isSelected={isSelected}
|
||||
href={appHref}
|
||||
onClick={handleClick}
|
||||
append={isBeta && <EuiBetaBadge label={betaOptions?.text ?? BETA} size="s" />}
|
||||
>
|
||||
{name}
|
||||
</EuiTab>
|
||||
);
|
||||
};
|
||||
|
||||
const TabNavigationItem = React.memo(TabNavigationItemComponent);
|
||||
|
||||
export const TabNavigationComponent: React.FC<TabNavigationProps> = ({ navTabs }) => {
|
||||
const [{ tabName }] = useRouteSpy();
|
||||
const mapLocationToTab = useCallback(
|
||||
(): string =>
|
||||
getOr(
|
||||
'',
|
||||
'id',
|
||||
Object.values(navTabs).find((item) => tabName === item.id)
|
||||
),
|
||||
[tabName, navTabs]
|
||||
);
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
useEffect(() => {
|
||||
const currentTabSelected = mapLocationToTab();
|
||||
|
||||
if (currentTabSelected !== selectedTabId) {
|
||||
setSelectedTabId(currentTabSelected);
|
||||
}
|
||||
|
||||
// we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies)
|
||||
}, [tabName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
|
||||
const { search } = useLocation();
|
||||
|
||||
const renderTabs = useMemo(
|
||||
() =>
|
||||
Object.values(navTabs).map((tab) => {
|
||||
const isSelected = selectedTabId === tab.id;
|
||||
return (
|
||||
<TabNavigationItem
|
||||
key={`navigation-${tab.id}`}
|
||||
id={tab.id}
|
||||
hrefWithSearch={tab.href + search}
|
||||
name={tab.name}
|
||||
disabled={tab.disabled}
|
||||
isSelected={isSelected}
|
||||
isBeta={tab.isBeta}
|
||||
betaOptions={tab.betaOptions}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
[navTabs, selectedTabId, search]
|
||||
);
|
||||
|
||||
return <EuiTabs>{renderTabs}</EuiTabs>;
|
||||
};
|
||||
|
||||
TabNavigationComponent.displayName = 'TabNavigationComponent';
|
||||
|
||||
export const TabNavigation = React.memo(TabNavigationComponent, (prevProps, nextProps) =>
|
||||
deepEqual(prevProps.navTabs, nextProps.navTabs)
|
||||
);
|
||||
|
||||
TabNavigation.displayName = 'TabNavigation';
|
|
@ -5,12 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SecuritySolutionTabNavigationProps } from '../types';
|
||||
import type { NavTab } from '../types';
|
||||
|
||||
export interface TabNavigationProps extends SecuritySolutionTabNavigationProps {
|
||||
pathName: string;
|
||||
pageName: string;
|
||||
tabName?: string;
|
||||
export interface TabNavigationProps {
|
||||
navTabs: Record<string, NavTab>;
|
||||
}
|
||||
|
||||
export interface TabNavigationItemProps {
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useRouteSpy } from '../../utils/route/use_route_spy';
|
||||
import { navTabs } from '../../../app/home/home_navigations';
|
||||
|
||||
import type { SecuritySolutionTabNavigationProps } from './types';
|
||||
import { TabNavigationWithBreadcrumbs } from './tab_navigation_with_breadcrumbs';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const original = jest.requireActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useHistory: () => ({
|
||||
useHistory: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const mockSetBreadcrumbs = jest.fn();
|
||||
|
||||
jest.mock('./breadcrumbs', () => ({
|
||||
useSetBreadcrumbs: () => mockSetBreadcrumbs,
|
||||
}));
|
||||
const mockGetUrlForApp = jest.fn();
|
||||
const mockNavigateToUrl = jest.fn();
|
||||
jest.mock('../../lib/kibana/kibana_react', () => {
|
||||
return {
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
chrome: undefined,
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: mockGetUrlForApp,
|
||||
navigateToUrl: mockNavigateToUrl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
jest.mock('../link_to');
|
||||
jest.mock('../../utils/route/use_route_spy');
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: jest.fn(() => ({
|
||||
search: '',
|
||||
})),
|
||||
useHistory: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SIEM Navigation', () => {
|
||||
const mockProps: SecuritySolutionTabNavigationProps = {
|
||||
navTabs,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('it calls setBreadcrumbs with correct path on mount', () => {
|
||||
(useRouteSpy as jest.Mock).mockReturnValueOnce([
|
||||
{
|
||||
pageName: 'hosts',
|
||||
pathName: '/',
|
||||
savedQuery: undefined,
|
||||
search: '',
|
||||
state: undefined,
|
||||
tabName: 'authentications',
|
||||
},
|
||||
]);
|
||||
|
||||
render(<TabNavigationWithBreadcrumbs {...mockProps} />);
|
||||
|
||||
expect(mockSetBreadcrumbs).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{
|
||||
detailName: undefined,
|
||||
navTabs,
|
||||
pageName: 'hosts',
|
||||
pathName: '/',
|
||||
search: '',
|
||||
state: undefined,
|
||||
tabName: 'authentications',
|
||||
flowTarget: undefined,
|
||||
savedQuery: undefined,
|
||||
},
|
||||
undefined,
|
||||
mockNavigateToUrl
|
||||
);
|
||||
});
|
||||
|
||||
test('it calls setBreadcrumbs with correct path on update', () => {
|
||||
(useRouteSpy as jest.Mock)
|
||||
.mockReturnValueOnce([
|
||||
{
|
||||
pageName: 'hosts',
|
||||
pathName: '/',
|
||||
savedQuery: undefined,
|
||||
search: '',
|
||||
state: undefined,
|
||||
tabName: 'authentications',
|
||||
},
|
||||
])
|
||||
.mockReturnValue([
|
||||
{
|
||||
pageName: 'network',
|
||||
pathName: '/',
|
||||
savedQuery: undefined,
|
||||
search: '',
|
||||
state: undefined,
|
||||
tabName: 'authentications',
|
||||
},
|
||||
]);
|
||||
|
||||
const { rerender } = render(<TabNavigationWithBreadcrumbs {...mockProps} />);
|
||||
|
||||
rerender(<TabNavigationWithBreadcrumbs {...mockProps} />);
|
||||
|
||||
expect(mockSetBreadcrumbs).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{
|
||||
detailName: undefined,
|
||||
flowTarget: undefined,
|
||||
navTabs,
|
||||
search: '',
|
||||
pageName: 'network',
|
||||
pathName: '/',
|
||||
state: undefined,
|
||||
tabName: 'authentications',
|
||||
},
|
||||
undefined,
|
||||
mockNavigateToUrl
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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 React, { useEffect } from 'react';
|
||||
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { useRouteSpy } from '../../utils/route/use_route_spy';
|
||||
import { useSetBreadcrumbs } from './breadcrumbs';
|
||||
import { TabNavigation } from './tab_navigation';
|
||||
import type { SecuritySolutionTabNavigationProps } from './types';
|
||||
|
||||
export function TabNavigationWithBreadcrumbs({
|
||||
navTabs,
|
||||
display,
|
||||
}: SecuritySolutionTabNavigationProps): JSX.Element {
|
||||
const [routeState] = useRouteSpy();
|
||||
const {
|
||||
chrome,
|
||||
application: { getUrlForApp, navigateToUrl },
|
||||
} = useKibana().services;
|
||||
const setBreadcrumbs = useSetBreadcrumbs();
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeState.pathName && !routeState.pageName) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBreadcrumbs({ ...routeState, navTabs }, chrome, navigateToUrl);
|
||||
}, [chrome, routeState, navTabs, getUrlForApp, navigateToUrl, setBreadcrumbs]);
|
||||
|
||||
return (
|
||||
<TabNavigation
|
||||
display={display}
|
||||
navTabs={navTabs}
|
||||
pageName={routeState.pageName}
|
||||
pathName={routeState.pathName}
|
||||
tabName={routeState.tabName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TabNavigationWithBreadcrumbs.displayName = 'TabNavigationWithBreadcrumbs';
|
|
@ -5,25 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { IconType } from '@elastic/eui';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import type { LinkCategories } from '../../links';
|
||||
|
||||
export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean };
|
||||
|
||||
export interface NavGroupTab {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export enum SecurityNavGroupKey {
|
||||
dashboards = 'dashboards',
|
||||
detect = 'detect',
|
||||
findings = 'findings',
|
||||
explore = 'explore',
|
||||
intelligence = 'intelligence',
|
||||
investigate = 'investigate',
|
||||
manage = 'manage',
|
||||
}
|
||||
import type { SecurityPageName } from '../../../app/types';
|
||||
|
||||
export type UrlStateType =
|
||||
| 'administration'
|
||||
|
@ -48,7 +30,6 @@ export type UrlStateType =
|
|||
| 'entity_analytics'
|
||||
| 'data_quality';
|
||||
|
||||
export type SecurityNavGroup = Record<SecurityNavGroupKey, NavGroupTab>;
|
||||
export interface NavTab {
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -61,58 +42,3 @@ export interface NavTab {
|
|||
text: string;
|
||||
};
|
||||
}
|
||||
export const securityNavKeys = [
|
||||
SecurityPageName.alerts,
|
||||
SecurityPageName.blocklist,
|
||||
SecurityPageName.detectionAndResponse,
|
||||
SecurityPageName.case,
|
||||
SecurityPageName.endpoints,
|
||||
SecurityPageName.landing,
|
||||
SecurityPageName.policies,
|
||||
SecurityPageName.eventFilters,
|
||||
SecurityPageName.exceptions,
|
||||
SecurityPageName.hostIsolationExceptions,
|
||||
SecurityPageName.hosts,
|
||||
SecurityPageName.network,
|
||||
SecurityPageName.overview,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
SecurityPageName.rules,
|
||||
SecurityPageName.timelines,
|
||||
SecurityPageName.trustedApps,
|
||||
SecurityPageName.users,
|
||||
SecurityPageName.kubernetes,
|
||||
SecurityPageName.threatIntelligenceIndicators,
|
||||
SecurityPageName.cloudSecurityPostureDashboard,
|
||||
SecurityPageName.cloudSecurityPostureFindings,
|
||||
SecurityPageName.cloudSecurityPostureBenchmarks,
|
||||
SecurityPageName.cloudDefendPolicies,
|
||||
SecurityPageName.entityAnalytics,
|
||||
SecurityPageName.dataQuality,
|
||||
] as const;
|
||||
export type SecurityNavKey = typeof securityNavKeys[number];
|
||||
|
||||
export type SecurityNav = Record<SecurityNavKey, NavTab>;
|
||||
|
||||
export type GenericNavRecord = Record<string, NavTab>;
|
||||
|
||||
export interface SecuritySolutionTabNavigationProps {
|
||||
display?: 'default' | 'condensed';
|
||||
navTabs: GenericNavRecord;
|
||||
}
|
||||
|
||||
export type NavigateToUrl = (url: string) => void;
|
||||
export interface NavLinkItem {
|
||||
categories?: LinkCategories;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
icon?: IconType;
|
||||
id: SecurityPageName;
|
||||
links?: NavLinkItem[];
|
||||
image?: string;
|
||||
title: string;
|
||||
skipUrlState?: boolean;
|
||||
isBeta?: boolean;
|
||||
betaOptions?: {
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,289 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`useSecuritySolutionNavigation should create navigation config 1`] = `
|
||||
Object {
|
||||
"canBeCollapsed": true,
|
||||
"icon": "logoSecurity",
|
||||
"items": Array [
|
||||
Object {
|
||||
"id": "main",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/get_started",
|
||||
"data-test-subj": "navigation-get_started",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/get_started",
|
||||
"id": "get_started",
|
||||
"isSelected": false,
|
||||
"name": "Get started",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
},
|
||||
Object {
|
||||
"id": "dashboards",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/overview",
|
||||
"data-test-subj": "navigation-overview",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/overview",
|
||||
"id": "overview",
|
||||
"isSelected": false,
|
||||
"name": "Overview",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/detection_response",
|
||||
"data-test-subj": "navigation-detection_response",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/detection_response",
|
||||
"id": "detection_response",
|
||||
"isSelected": false,
|
||||
"name": "Detection & Response",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-dashboard",
|
||||
"data-test-subj": "navigation-cloud_security_posture-dashboard",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-dashboard",
|
||||
"id": "cloud_security_posture-dashboard",
|
||||
"isSelected": false,
|
||||
"name": "Cloud Security Posture",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/entity-analytics",
|
||||
"data-test-subj": "navigation-entity-analytics",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/entity-analytics",
|
||||
"id": "entity-analytics",
|
||||
"isSelected": false,
|
||||
"name": "Entity Analytics",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/data_quality",
|
||||
"data-test-subj": "navigation-data_quality",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/data_quality",
|
||||
"id": "data_quality",
|
||||
"isSelected": false,
|
||||
"name": "Data Quality",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Dashboards",
|
||||
},
|
||||
Object {
|
||||
"id": "detect",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/alerts",
|
||||
"data-test-subj": "navigation-alerts",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/alerts",
|
||||
"id": "alerts",
|
||||
"isSelected": false,
|
||||
"name": "Alerts",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/rules",
|
||||
"data-test-subj": "navigation-rules",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/rules",
|
||||
"id": "rules",
|
||||
"isSelected": false,
|
||||
"name": "Rules",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/exceptions",
|
||||
"data-test-subj": "navigation-exceptions",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/exceptions",
|
||||
"id": "exceptions",
|
||||
"isSelected": false,
|
||||
"name": "Shared Exception Lists",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Detect",
|
||||
},
|
||||
Object {
|
||||
"id": "findings",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-findings",
|
||||
"data-test-subj": "navigation-cloud_security_posture-findings",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-findings",
|
||||
"id": "cloud_security_posture-findings",
|
||||
"isSelected": false,
|
||||
"name": "Findings",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Findings",
|
||||
},
|
||||
Object {
|
||||
"id": "explore",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/hosts",
|
||||
"data-test-subj": "navigation-hosts",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/hosts",
|
||||
"id": "hosts",
|
||||
"isSelected": true,
|
||||
"name": "Hosts",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/network",
|
||||
"data-test-subj": "navigation-network",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/network",
|
||||
"id": "network",
|
||||
"isSelected": false,
|
||||
"name": "Network",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/users",
|
||||
"data-test-subj": "navigation-users",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/users",
|
||||
"id": "users",
|
||||
"isSelected": false,
|
||||
"name": "Users",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Explore",
|
||||
},
|
||||
Object {
|
||||
"id": "intelligence",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/threat_intelligence-indicators",
|
||||
"data-test-subj": "navigation-threat_intelligence-indicators",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/threat_intelligence-indicators",
|
||||
"id": "threat_intelligence-indicators",
|
||||
"isSelected": false,
|
||||
"name": "Indicators",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Intelligence",
|
||||
},
|
||||
Object {
|
||||
"id": "investigate",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/timelines",
|
||||
"data-test-subj": "navigation-timelines",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/timelines",
|
||||
"id": "timelines",
|
||||
"isSelected": false,
|
||||
"name": "Timelines",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cases",
|
||||
"data-test-subj": "navigation-cases",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cases",
|
||||
"id": "cases",
|
||||
"isSelected": false,
|
||||
"name": "Cases",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Investigate",
|
||||
},
|
||||
Object {
|
||||
"id": "manage",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/endpoints",
|
||||
"data-test-subj": "navigation-endpoints",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/endpoints",
|
||||
"id": "endpoints",
|
||||
"isSelected": false,
|
||||
"name": "Endpoints",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/trusted_apps",
|
||||
"data-test-subj": "navigation-trusted_apps",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/trusted_apps",
|
||||
"id": "trusted_apps",
|
||||
"isSelected": false,
|
||||
"name": "Trusted applications",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/event_filters",
|
||||
"data-test-subj": "navigation-event_filters",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/event_filters",
|
||||
"id": "event_filters",
|
||||
"isSelected": false,
|
||||
"name": "Event filters",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/host_isolation_exceptions",
|
||||
"data-test-subj": "navigation-host_isolation_exceptions",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/host_isolation_exceptions",
|
||||
"id": "host_isolation_exceptions",
|
||||
"isSelected": false,
|
||||
"name": "Host isolation exceptions",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/blocklist",
|
||||
"data-test-subj": "navigation-blocklist",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/blocklist",
|
||||
"id": "blocklist",
|
||||
"isSelected": false,
|
||||
"name": "Blocklist",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/response_actions_history",
|
||||
"data-test-subj": "navigation-response_actions_history",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/response_actions_history",
|
||||
"id": "response_actions_history",
|
||||
"isSelected": false,
|
||||
"name": "Response actions history",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-benchmarks",
|
||||
"data-test-subj": "navigation-cloud_security_posture-benchmarks",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cloud_security_posture-benchmarks",
|
||||
"id": "cloud_security_posture-benchmarks",
|
||||
"isSelected": false,
|
||||
"name": "Cloud Posture Benchmarks",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Manage",
|
||||
},
|
||||
],
|
||||
"name": "Security",
|
||||
}
|
||||
`;
|
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { useKibana } from '../../../lib/kibana/kibana_react';
|
||||
import { useGetUserCasesPermissions } from '../../../lib/kibana';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
import { useSecuritySolutionNavigation } from '.';
|
||||
import { useRouteSpy } from '../../../utils/route/use_route_spy';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import { TestProviders } from '../../../mock';
|
||||
import { CASES_FEATURE_ID } from '../../../../../common/constants';
|
||||
import { useTourContext } from '../../guided_onboarding_tour';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
import {
|
||||
noCasesPermissions,
|
||||
readCasesCapabilities,
|
||||
readCasesPermissions,
|
||||
} from '../../../../cases_test_utils';
|
||||
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
|
||||
import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz/mocks';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../user_privileges/__mocks__';
|
||||
|
||||
jest.mock('../../../lib/kibana/kibana_react');
|
||||
jest.mock('../../../lib/kibana');
|
||||
const originalKibanaLib = jest.requireActual('../../../lib/kibana');
|
||||
|
||||
// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object
|
||||
// The returned permissions object will indicate that the user does not have permissions by default
|
||||
const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock;
|
||||
mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions);
|
||||
|
||||
jest.mock('../../../hooks/use_selector');
|
||||
jest.mock('../../../hooks/use_experimental_features');
|
||||
jest.mock('../../../utils/route/use_route_spy');
|
||||
jest.mock('../../guided_onboarding_tour');
|
||||
jest.mock('../../user_privileges');
|
||||
|
||||
const mockUseUserPrivileges = useUserPrivileges as jest.Mock;
|
||||
|
||||
describe('useSecuritySolutionNavigation', () => {
|
||||
const mockRouteSpy = [
|
||||
{
|
||||
detailName: '',
|
||||
flowTarget: '',
|
||||
pathName: '',
|
||||
search: '',
|
||||
state: '',
|
||||
tabName: '',
|
||||
pageName: SecurityPageName.hosts,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy);
|
||||
mockUseUserPrivileges.mockImplementation(getUserPrivilegesMockDefaultValue);
|
||||
(useTourContext as jest.Mock).mockReturnValue({ isTourShown: false });
|
||||
|
||||
const cases = mockCasesContract();
|
||||
cases.helpers.getUICapabilities.mockReturnValue(readCasesPermissions());
|
||||
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: {
|
||||
cases,
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: (appId: string, options?: { path?: string; deepLinkId?: boolean }) =>
|
||||
`${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`,
|
||||
capabilities: {
|
||||
siem: {
|
||||
show: true,
|
||||
crud: true,
|
||||
},
|
||||
[CASES_FEATURE_ID]: readCasesCapabilities(),
|
||||
},
|
||||
},
|
||||
chrome: {
|
||||
setBreadcrumbs: jest.fn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockUseUserPrivileges.mockReset();
|
||||
});
|
||||
|
||||
it('should create navigation config', async () => {
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
expect(result.current).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO: [kubernetes] remove when no longer experimental
|
||||
it('should include kubernetes when feature flag is on', async () => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
const dashboards = result?.current?.items?.find(({ id }) => id === 'dashboards');
|
||||
const hasKubernetes =
|
||||
dashboards?.items?.some(({ id }) => id === SecurityPageName.kubernetes) ?? false;
|
||||
|
||||
expect(hasKubernetes).toBe(true);
|
||||
});
|
||||
|
||||
it('should omit host isolation exceptions if no authz', () => {
|
||||
mockUseUserPrivileges.mockImplementation(() => ({
|
||||
endpointPrivileges: getEndpointAuthzInitialStateMock({
|
||||
canReadHostIsolationExceptions: false,
|
||||
}),
|
||||
}));
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
const items = result.current?.items;
|
||||
expect(items).toBeDefined();
|
||||
expect(
|
||||
items!
|
||||
.find((item) => item.id === 'manage')
|
||||
?.items?.find((item) => item.id === 'host_isolation_exceptions')
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should omit response actions history if hook reports false', () => {
|
||||
mockUseUserPrivileges.mockImplementation(() => ({
|
||||
endpointPrivileges: getEndpointAuthzInitialStateMock({ canReadActionsLogManagement: false }),
|
||||
}));
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
const items = result.current?.items;
|
||||
expect(items).toBeDefined();
|
||||
expect(
|
||||
items!
|
||||
.find((item) => item.id === 'manage')
|
||||
?.items?.find((item) => item.id === 'response_actions_history')
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('Permission gated routes', () => {
|
||||
describe('cases', () => {
|
||||
it('should display the cases navigation item when the user has read permissions', () => {
|
||||
(useGetUserCasesPermissions as jest.Mock).mockReturnValue(readCasesPermissions());
|
||||
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
const caseNavItem = (result.current?.items || [])[6].items?.find(
|
||||
(item) => item['data-test-subj'] === 'navigation-cases'
|
||||
);
|
||||
expect(caseNavItem).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cases",
|
||||
"data-test-subj": "navigation-cases",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/cases",
|
||||
"id": "cases",
|
||||
"isSelected": false,
|
||||
"name": "Cases",
|
||||
"onClick": [Function],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not display the cases navigation item when the user does not have read permissions', () => {
|
||||
(useGetUserCasesPermissions as jest.Mock).mockReturnValue(noCasesPermissions());
|
||||
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
|
||||
const caseNavItem = (result.current?.items || [])[3].items?.find(
|
||||
(item) => item['data-test-subj'] === 'navigation-cases'
|
||||
);
|
||||
expect(caseNavItem).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,46 +4,4 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { omit } from 'lodash/fp';
|
||||
import { usePrimaryNavigation } from './use_primary_navigation';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { useSetBreadcrumbs } from '../breadcrumbs';
|
||||
import { useRouteSpy } from '../../../utils/route/use_route_spy';
|
||||
import { navTabs } from '../../../../app/home/home_navigations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import type { GenericNavRecord } from '../types';
|
||||
|
||||
/**
|
||||
* @description - This hook provides the structure necessary by the KibanaPageTemplate for rendering the primary security_solution side navigation.
|
||||
* TODO: Consolidate & re-use the logic in the hooks in this directory that are replicated from the tab_navigation to maintain breadcrumbs, telemetry, etc...
|
||||
*/
|
||||
export const useSecuritySolutionNavigation = () => {
|
||||
const [routeProps] = useRouteSpy();
|
||||
|
||||
const {
|
||||
chrome,
|
||||
application: { getUrlForApp, navigateToUrl },
|
||||
} = useKibana().services;
|
||||
|
||||
const disabledNavTabs = [
|
||||
...(!useIsExperimentalFeatureEnabled('kubernetesEnabled') ? ['kubernetes'] : []),
|
||||
];
|
||||
const enabledNavTabs: GenericNavRecord = omit(disabledNavTabs, navTabs);
|
||||
|
||||
const setBreadcrumbs = useSetBreadcrumbs();
|
||||
|
||||
useEffect(() => {
|
||||
if (!routeProps.pathName && !routeProps.pageName) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBreadcrumbs({ ...routeProps, navTabs }, chrome, navigateToUrl);
|
||||
}, [routeProps, chrome, getUrlForApp, navigateToUrl, enabledNavTabs, setBreadcrumbs]);
|
||||
|
||||
return usePrimaryNavigation({
|
||||
navTabs: enabledNavTabs,
|
||||
pageName: routeProps.pageName,
|
||||
});
|
||||
};
|
||||
export { useSecuritySolutionNavigation } from './use_security_solution_navigation';
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* 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 type { TabNavigationProps } from '../tab_navigation/types';
|
||||
import type { GenericNavRecord } from '../types';
|
||||
|
||||
export interface PrimaryNavigationItemsProps {
|
||||
navTabs: GenericNavRecord;
|
||||
selectedTabId: string;
|
||||
}
|
||||
|
||||
export type PrimaryNavigationProps = Omit<TabNavigationProps, 'pathName' | 'tabName'>;
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* 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 type React from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types';
|
||||
|
||||
import { securityNavGroup } from '../../../../app/home/home_navigations';
|
||||
import { getSearch } from '../helpers';
|
||||
import type { PrimaryNavigationItemsProps } from './types';
|
||||
import { useKibana } from '../../../lib/kibana/kibana_react';
|
||||
import { useGetUserCasesPermissions } from '../../../lib/kibana';
|
||||
import { useNavigation } from '../../../lib/kibana/hooks';
|
||||
import type { NavTab } from '../types';
|
||||
import { SecurityNavGroupKey } from '../types';
|
||||
import { SecurityPageName } from '../../../../../common/constants';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import { useGlobalQueryString } from '../../../utils/global_query_string';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../lib/telemetry';
|
||||
|
||||
export const usePrimaryNavigationItems = ({
|
||||
navTabs,
|
||||
selectedTabId,
|
||||
}: PrimaryNavigationItemsProps): Array<EuiSideNavItemType<{}>> => {
|
||||
const { navigateTo, getAppUrl } = useNavigation();
|
||||
const globalQueryString = useGlobalQueryString();
|
||||
|
||||
const getSideNav = useCallback(
|
||||
(tab: NavTab) => {
|
||||
const { id, name, disabled } = tab;
|
||||
const isSelected = selectedTabId === id;
|
||||
const urlSearch = getSearch(tab.id as SecurityPageName, globalQueryString);
|
||||
|
||||
const handleClick = (ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.LEGACY_NAVIGATION}${id}`);
|
||||
navigateTo({ deepLinkId: id, path: urlSearch });
|
||||
};
|
||||
|
||||
const appHref = getAppUrl({ deepLinkId: id, path: urlSearch });
|
||||
|
||||
return {
|
||||
'data-href': appHref,
|
||||
'data-test-subj': `navigation-${id}`,
|
||||
disabled,
|
||||
href: appHref,
|
||||
id,
|
||||
isSelected,
|
||||
name,
|
||||
onClick: handleClick,
|
||||
};
|
||||
},
|
||||
[getAppUrl, navigateTo, selectedTabId, globalQueryString]
|
||||
);
|
||||
|
||||
const navItemsToDisplay = usePrimaryNavigationItemsToDisplay(navTabs);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
navItemsToDisplay.map((item) => ({
|
||||
...item,
|
||||
items: item.items.map((t: NavTab) => getSideNav(t)),
|
||||
})),
|
||||
[getSideNav, navItemsToDisplay]
|
||||
);
|
||||
};
|
||||
|
||||
function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
||||
const hasCasesReadPermissions = useGetUserCasesPermissions().read;
|
||||
const { canReadActionsLogManagement, canReadHostIsolationExceptions } =
|
||||
useUserPrivileges().endpointPrivileges;
|
||||
const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled');
|
||||
|
||||
const uiCapabilities = useKibana().services.application.capabilities;
|
||||
return useMemo(
|
||||
() =>
|
||||
uiCapabilities.siem.show
|
||||
? [
|
||||
{
|
||||
id: 'main',
|
||||
name: '',
|
||||
items: [navTabs[SecurityPageName.landing]],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.dashboards],
|
||||
items: [
|
||||
navTabs[SecurityPageName.overview],
|
||||
navTabs[SecurityPageName.detectionAndResponse],
|
||||
navTabs[SecurityPageName.cloudSecurityPostureDashboard],
|
||||
navTabs[SecurityPageName.entityAnalytics],
|
||||
navTabs[SecurityPageName.dataQuality],
|
||||
...(navTabs[SecurityPageName.kubernetes] != null
|
||||
? [navTabs[SecurityPageName.kubernetes]]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.detect],
|
||||
items: [
|
||||
navTabs[SecurityPageName.alerts],
|
||||
navTabs[SecurityPageName.rules],
|
||||
navTabs[SecurityPageName.exceptions],
|
||||
],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.findings],
|
||||
items: [navTabs[SecurityPageName.cloudSecurityPostureFindings]],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.explore],
|
||||
items: [
|
||||
navTabs[SecurityPageName.hosts],
|
||||
navTabs[SecurityPageName.network],
|
||||
...(navTabs[SecurityPageName.users] != null
|
||||
? [navTabs[SecurityPageName.users]]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.intelligence],
|
||||
items: [navTabs[SecurityPageName.threatIntelligenceIndicators]],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.investigate],
|
||||
items: hasCasesReadPermissions
|
||||
? [navTabs[SecurityPageName.timelines], navTabs[SecurityPageName.case]]
|
||||
: [navTabs[SecurityPageName.timelines]],
|
||||
},
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.manage],
|
||||
items: [
|
||||
// TODO: also hide other management pages based on authz privileges
|
||||
navTabs[SecurityPageName.endpoints],
|
||||
...(isPolicyListEnabled ? [navTabs[SecurityPageName.policies]] : []),
|
||||
navTabs[SecurityPageName.trustedApps],
|
||||
navTabs[SecurityPageName.eventFilters],
|
||||
...(canReadHostIsolationExceptions
|
||||
? [navTabs[SecurityPageName.hostIsolationExceptions]]
|
||||
: []),
|
||||
navTabs[SecurityPageName.blocklist],
|
||||
...(canReadActionsLogManagement
|
||||
? [navTabs[SecurityPageName.responseActionsHistory]]
|
||||
: []),
|
||||
navTabs[SecurityPageName.cloudSecurityPostureBenchmarks],
|
||||
],
|
||||
},
|
||||
]
|
||||
: hasCasesReadPermissions
|
||||
? [
|
||||
{
|
||||
...securityNavGroup[SecurityNavGroupKey.investigate],
|
||||
items: [navTabs[SecurityPageName.case]],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[
|
||||
uiCapabilities.siem.show,
|
||||
navTabs,
|
||||
hasCasesReadPermissions,
|
||||
canReadHostIsolationExceptions,
|
||||
canReadActionsLogManagement,
|
||||
isPolicyListEnabled,
|
||||
]
|
||||
);
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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 React, { useEffect, useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import type { PrimaryNavigationProps } from './types';
|
||||
import { usePrimaryNavigationItems } from './use_navigation_items';
|
||||
import { useIsGroupedNavigationEnabled } from '../helpers';
|
||||
import { SecuritySideNav } from '../security_side_nav';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
|
||||
const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', {
|
||||
defaultMessage: 'Security',
|
||||
});
|
||||
|
||||
export const usePrimaryNavigation = ({
|
||||
navTabs,
|
||||
pageName,
|
||||
}: PrimaryNavigationProps): KibanaPageTemplateProps['solutionNav'] => {
|
||||
const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled();
|
||||
const mapLocationToTab = useCallback(
|
||||
(): string => navTabs[pageName]?.id ?? '',
|
||||
[pageName, navTabs]
|
||||
);
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
|
||||
useEffect(() => {
|
||||
const currentTabSelected = mapLocationToTab();
|
||||
|
||||
if (currentTabSelected !== selectedTabId) {
|
||||
setSelectedTabId(currentTabSelected);
|
||||
}
|
||||
|
||||
// we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies)
|
||||
}, [pageName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
|
||||
const navItems = usePrimaryNavigationItems({
|
||||
navTabs,
|
||||
selectedTabId,
|
||||
});
|
||||
|
||||
const { isSidebarEnabled$ } = useKibana().services;
|
||||
|
||||
const isSidebarEnabled = useObservable(isSidebarEnabled$);
|
||||
|
||||
if (!isSidebarEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
canBeCollapsed: true,
|
||||
name: translatedNavTitle,
|
||||
icon: 'logoSecurity',
|
||||
...(isGroupedNavigationEnabled
|
||||
? {
|
||||
children: <SecuritySideNav />,
|
||||
closeFlyoutButtonPosition: 'inside',
|
||||
}
|
||||
: { items: navItems }),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { useSecuritySolutionNavigation } from './use_security_solution_navigation';
|
||||
|
||||
const mockSetBreadcrumbs = jest.fn();
|
||||
jest.mock('../breadcrumbs', () => ({
|
||||
useBreadcrumbs: () => mockSetBreadcrumbs,
|
||||
}));
|
||||
|
||||
const mockIsSidebarEnabled$ = new BehaviorSubject(true);
|
||||
jest.mock('../../../lib/kibana/kibana_react', () => {
|
||||
return {
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
isSidebarEnabled$: mockIsSidebarEnabled$.asObservable(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Security Solution Navigation', () => {
|
||||
beforeEach(() => {
|
||||
mockIsSidebarEnabled$.next(true);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return proper navigation props', () => {
|
||||
const { result } = renderHook(useSecuritySolutionNavigation);
|
||||
expect(result.current).toEqual(
|
||||
expect.objectContaining({
|
||||
canBeCollapsed: true,
|
||||
name: 'Security',
|
||||
icon: 'logoSecurity',
|
||||
closeFlyoutButtonPosition: 'inside',
|
||||
})
|
||||
);
|
||||
expect(result.current?.children).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return undefined props when disabled', () => {
|
||||
mockIsSidebarEnabled$.next(false);
|
||||
const { result } = renderHook(useSecuritySolutionNavigation);
|
||||
expect(result.current).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { useBreadcrumbs } from '../breadcrumbs';
|
||||
import { SecuritySideNav } from '../security_side_nav';
|
||||
|
||||
const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', {
|
||||
defaultMessage: 'Security',
|
||||
});
|
||||
|
||||
export const useSecuritySolutionNavigation = (): KibanaPageTemplateProps['solutionNav'] => {
|
||||
const { isSidebarEnabled$ } = useKibana().services;
|
||||
const isSidebarEnabled = useObservable(isSidebarEnabled$);
|
||||
|
||||
useBreadcrumbs({
|
||||
isEnabled: true, // TODO: use isSidebarEnabled$ when serverless breadcrumb is ready
|
||||
});
|
||||
|
||||
if (!isSidebarEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
canBeCollapsed: true,
|
||||
name: translatedNavTitle,
|
||||
icon: 'logoSecurity',
|
||||
children: <SecuritySideNav />,
|
||||
closeFlyoutButtonPosition: 'inside',
|
||||
};
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* 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 { navTabs } from '../../app/home/home_navigations';
|
||||
import { getTitle } from './use_update_browser_title';
|
||||
|
||||
describe('Helpers Url_State', () => {
|
||||
describe('getTitle', () => {
|
||||
test('host page name', () => {
|
||||
const result = getTitle('hosts', navTabs);
|
||||
expect(result).toEqual('Hosts');
|
||||
});
|
||||
test('network page name', () => {
|
||||
const result = getTitle('network', navTabs);
|
||||
expect(result).toEqual('Network');
|
||||
});
|
||||
test('overview page name', () => {
|
||||
const result = getTitle('overview', navTabs);
|
||||
expect(result).toEqual('Overview');
|
||||
});
|
||||
test('timelines page name', () => {
|
||||
const result = getTitle('timelines', navTabs);
|
||||
expect(result).toEqual('Timelines');
|
||||
});
|
||||
test('Not existing', () => {
|
||||
const result = getTitle('IamHereButNotReally', navTabs);
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,26 +6,14 @@
|
|||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { navTabs } from '../../app/home/home_navigations';
|
||||
import { useIsGroupedNavigationEnabled } from '../components/navigation/helpers';
|
||||
import type { NavTab } from '../components/navigation/types';
|
||||
import { getLinkInfo } from '../links';
|
||||
import { useRouteSpy } from '../utils/route/use_route_spy';
|
||||
|
||||
export const useUpdateBrowserTitle = () => {
|
||||
const isGroupedNavEnabled = useIsGroupedNavigationEnabled();
|
||||
const [{ pageName }] = useRouteSpy();
|
||||
const linkInfo = getLinkInfo(pageName);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isGroupedNavEnabled) {
|
||||
document.title = `${getTitle(pageName, navTabs)} - Kibana`;
|
||||
} else {
|
||||
document.title = `${linkInfo?.title ?? ''} - Kibana`;
|
||||
}
|
||||
}, [pageName, isGroupedNavEnabled, linkInfo]);
|
||||
};
|
||||
|
||||
export const getTitle = (pageName: string, tabs: Record<string, NavTab>): string => {
|
||||
return tabs[pageName] != null ? tabs[pageName].name : '';
|
||||
document.title = `${linkInfo?.title ?? ''} - Kibana`;
|
||||
}, [pageName, linkInfo]);
|
||||
};
|
||||
|
|
|
@ -68,7 +68,6 @@ export enum TELEMETRY_EVENT {
|
|||
|
||||
// Breadcrumbs
|
||||
BREADCRUMB = 'breadcrumb_',
|
||||
LEGACY_NAVIGATION = 'legacy_navigation_',
|
||||
}
|
||||
|
||||
export const getTelemetryEvent = {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 type { Subject, Subscription } from 'rxjs';
|
||||
import { AppNavLinkStatus } from '@kbn/core/public';
|
||||
import type { AppDeepLink, AppUpdater } from '@kbn/core/public';
|
||||
import { appLinks$ } from './links';
|
||||
import type { AppLinkItems } from './types';
|
||||
|
||||
const formatDeepLinks = (appLinks: AppLinkItems): AppDeepLink[] =>
|
||||
appLinks.map((appLink) => ({
|
||||
id: appLink.id,
|
||||
path: appLink.path,
|
||||
title: appLink.title,
|
||||
searchable: !appLink.globalSearchDisabled,
|
||||
...(appLink.globalNavPosition != null
|
||||
? { navLinkStatus: AppNavLinkStatus.visible, order: appLink.globalNavPosition }
|
||||
: { navLinkStatus: AppNavLinkStatus.hidden }),
|
||||
...(appLink.globalSearchKeywords != null ? { keywords: appLink.globalSearchKeywords } : {}),
|
||||
...(appLink.links && appLink.links?.length
|
||||
? {
|
||||
deepLinks: formatDeepLinks(appLink.links),
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Registers any change in appLinks to be updated in app deepLinks
|
||||
*/
|
||||
export const registerDeepLinksUpdater = (appUpdater$: Subject<AppUpdater>): Subscription => {
|
||||
return appLinks$.subscribe((appLinks) => {
|
||||
appUpdater$.next(() => ({
|
||||
navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent main security link to switch to visible after update
|
||||
deepLinks: formatDeepLinks(appLinks),
|
||||
}));
|
||||
});
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import { get, isArray } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
import { useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
@ -169,11 +169,11 @@ export const hasCapabilities = <T>(
|
|||
linkCapabilities: LinkCapabilities,
|
||||
userCapabilities: Capabilities
|
||||
): boolean => {
|
||||
if (!isArray(linkCapabilities)) {
|
||||
if (!Array.isArray(linkCapabilities)) {
|
||||
return !!get(userCapabilities, linkCapabilities, false);
|
||||
} else {
|
||||
return linkCapabilities.some((linkCapabilityKeyOr) => {
|
||||
if (isArray(linkCapabilityKeyOr)) {
|
||||
if (Array.isArray(linkCapabilityKeyOr)) {
|
||||
return linkCapabilityKeyOr.every((linkCapabilityKeyAnd) =>
|
||||
get(userCapabilities, linkCapabilityKeyAnd, false)
|
||||
);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { createMemoryHistory } from 'history';
|
|||
import type { RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import { render as reactRender } from '@testing-library/react';
|
||||
import type { Action, Reducer, Store } from 'redux';
|
||||
import type { AppDeepLink } from '@kbn/core/public';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { PLUGIN_ID } from '@kbn/fleet-plugin/common';
|
||||
|
@ -24,6 +23,7 @@ import type {
|
|||
} from '@testing-library/react-hooks/src/types/react';
|
||||
import type { UseBaseQueryResult } from '@tanstack/react-query';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type { AppLinkItems } from '../../links/types';
|
||||
import { ExperimentalFeaturesService } from '../../experimental_features_service';
|
||||
import { applyIntersectionObserverMock } from '../intersection_observer_mock';
|
||||
import { ConsoleManager } from '../../../management/components/console';
|
||||
|
@ -41,7 +41,7 @@ import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock
|
|||
import type { ExperimentalFeatures } from '../../../../common/experimental_features';
|
||||
import { APP_UI_ID, APP_PATH } from '../../../../common/constants';
|
||||
import { KibanaContextProvider, KibanaServices } from '../../lib/kibana';
|
||||
import { getDeepLinks } from '../../../app/deep_links';
|
||||
import { links } from '../../links/app_links';
|
||||
import { fleetGetPackageHttpMock } from '../../../management/mocks';
|
||||
import { allowedExperimentalValues } from '../../../../common/experimental_features';
|
||||
|
||||
|
@ -339,7 +339,7 @@ const createCoreStartMock = (
|
|||
): ReturnType<typeof coreMock.createStart> => {
|
||||
const coreStart = coreMock.createStart({ basePath: '/mock' });
|
||||
|
||||
const deepLinkPaths = getDeepLinkPaths(getDeepLinks(mockGlobalState.app.enableExperimental));
|
||||
const linkPaths = getLinksPaths(links);
|
||||
|
||||
// Mock the certain APP Ids returned by `application.getUrlForApp()`
|
||||
coreStart.application.getUrlForApp.mockImplementation((appId, { deepLinkId, path } = {}) => {
|
||||
|
@ -347,9 +347,9 @@ const createCoreStartMock = (
|
|||
case PLUGIN_ID:
|
||||
return '/app/fleet';
|
||||
case APP_UI_ID:
|
||||
return `${APP_PATH}${
|
||||
deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''
|
||||
}${path ?? ''}`;
|
||||
return `${APP_PATH}${deepLinkId && linkPaths[deepLinkId] ? linkPaths[deepLinkId] : ''}${
|
||||
path ?? ''
|
||||
}`;
|
||||
default:
|
||||
return `${appId} not mocked!`;
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ const createCoreStartMock = (
|
|||
coreStart.application.navigateToApp.mockImplementation((appId, { deepLinkId, path } = {}) => {
|
||||
if (appId === APP_UI_ID) {
|
||||
history.push(
|
||||
`${deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''}${path ?? ''}`
|
||||
`${deepLinkId && linkPaths[deepLinkId] ? linkPaths[deepLinkId] : ''}${path ?? ''}`
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
|
@ -372,13 +372,13 @@ const createCoreStartMock = (
|
|||
return coreStart;
|
||||
};
|
||||
|
||||
const getDeepLinkPaths = (deepLinks: AppDeepLink[]): Record<string, string> => {
|
||||
return deepLinks.reduce((result: Record<string, string>, deepLink) => {
|
||||
if (deepLink.path) {
|
||||
result[deepLink.id] = deepLink.path;
|
||||
const getLinksPaths = (appLinks: AppLinkItems): Record<string, string> => {
|
||||
return appLinks.reduce((result: Record<string, string>, link) => {
|
||||
if (link.path) {
|
||||
result[link.id] = link.path;
|
||||
}
|
||||
if (deepLink.deepLinks) {
|
||||
return { ...result, ...getDeepLinkPaths(deepLink.deepLinks) };
|
||||
if (link.links) {
|
||||
return { ...result, ...getLinksPaths(link.links) };
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
|
|
|
@ -30,11 +30,6 @@ jest.mock('../../containers/sourcerer', () => ({
|
|||
useSourcererDataView: () => mockUseSourcererDataView(),
|
||||
}));
|
||||
|
||||
const mockedUseIsGroupedNavigationEnabled = jest.fn();
|
||||
jest.mock('../../components/navigation/helpers', () => ({
|
||||
useIsGroupedNavigationEnabled: () => mockedUseIsGroupedNavigationEnabled(),
|
||||
}));
|
||||
|
||||
const mockSiemUserCanRead = jest.fn(() => true);
|
||||
jest.mock('../../lib/kibana', () => {
|
||||
const original = jest.requireActual('../../lib/kibana');
|
||||
|
@ -73,123 +68,75 @@ describe('use show timeline', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
describe('useIsGroupedNavigationEnabled false', () => {
|
||||
beforeAll(() => {
|
||||
mockedUseIsGroupedNavigationEnabled.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('shows timeline for routes on default', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides timeline for blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules/create' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
});
|
||||
it('shows timeline for partial blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
it('hides timeline for sub blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/administration/policy' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
it('shows timeline for routes on default', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIsGroupedNavigationEnabled true', () => {
|
||||
beforeAll(() => {
|
||||
mockedUseIsGroupedNavigationEnabled.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('shows timeline for routes on default', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides timeline for blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules/create' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
});
|
||||
it('shows timeline for partial blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
it('hides timeline for sub blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/administration/policy' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
it('hides timeline for blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules/create' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourcererDataView', () => {
|
||||
it('should show timeline when indices exist', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: true, dataViewId: 'test' });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should show timeline when dataViewId is null', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: false, dataViewId: null });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should not show timeline when dataViewId is not null and indices does not exist', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: false, dataViewId: 'test' });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([false]);
|
||||
it('shows timeline for partial blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Security solution capabilities', () => {
|
||||
it('should show timeline when user has read capabilities', () => {
|
||||
mockSiemUserCanRead.mockReturnValueOnce(true);
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should not show timeline when user does not have read capabilities', () => {
|
||||
mockSiemUserCanRead.mockReturnValueOnce(false);
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([false]);
|
||||
it('hides timeline for sub blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/administration/policy' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourcererDataView', () => {
|
||||
it('should show timeline when indices exist', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: true, dataViewId: 'test' });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should show timeline when dataViewId is null', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: false, dataViewId: null });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should not show timeline when dataViewId is not null and indices does not exist', () => {
|
||||
mockUseSourcererDataView.mockReturnValueOnce({ indicesExist: false, dataViewId: 'test' });
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([false]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Security solution capabilities', () => {
|
||||
it('should show timeline when user has read capabilities', () => {
|
||||
mockSiemUserCanRead.mockReturnValueOnce(true);
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([true]);
|
||||
});
|
||||
|
||||
it('should not show timeline when user does not have read capabilities', () => {
|
||||
mockSiemUserCanRead.mockReturnValueOnce(false);
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
expect(result.current).toEqual([false]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,38 +9,17 @@ import { useCallback, useMemo } from 'react';
|
|||
import { matchPath } from 'react-router-dom';
|
||||
|
||||
import { getLinksWithHiddenTimeline } from '../../links';
|
||||
import { useIsGroupedNavigationEnabled } from '../../components/navigation/helpers';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { useSourcererDataView } from '../../containers/sourcerer';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
|
||||
const DEPRECATED_HIDDEN_TIMELINE_ROUTES: readonly string[] = [
|
||||
`/cases/configure`,
|
||||
'/administration',
|
||||
'/rules/create',
|
||||
'/get_started',
|
||||
'/explore',
|
||||
'/dashboards',
|
||||
'/manage',
|
||||
'/cloud_security_posture*',
|
||||
];
|
||||
|
||||
const isTimelinePathVisible = (
|
||||
currentPath: string,
|
||||
isGroupedNavigationEnabled: boolean
|
||||
): boolean => {
|
||||
const isTimelinePathVisible = (currentPath: string): boolean => {
|
||||
const groupLinksWithHiddenTimelinePaths = getLinksWithHiddenTimeline().map((l) => l.path);
|
||||
|
||||
const hiddenTimelineRoutes = isGroupedNavigationEnabled
|
||||
? groupLinksWithHiddenTimelinePaths
|
||||
: DEPRECATED_HIDDEN_TIMELINE_ROUTES;
|
||||
|
||||
const hiddenTimelineRoutes = groupLinksWithHiddenTimelinePaths;
|
||||
return !hiddenTimelineRoutes.find((route) => matchPath(currentPath, route));
|
||||
};
|
||||
|
||||
export const useShowTimelineForGivenPath = () => {
|
||||
const isGroupedNavigationEnabled = useIsGroupedNavigationEnabled();
|
||||
|
||||
const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const userHasSecuritySolutionVisible = useKibana().services.application.capabilities.siem.show;
|
||||
|
||||
|
@ -54,9 +33,9 @@ export const useShowTimelineForGivenPath = () => {
|
|||
if (!isTimelineAllowed) {
|
||||
return false;
|
||||
}
|
||||
return isTimelinePathVisible(pathname, isGroupedNavigationEnabled);
|
||||
return isTimelinePathVisible(pathname);
|
||||
},
|
||||
[isTimelineAllowed, isGroupedNavigationEnabled]
|
||||
[isTimelineAllowed]
|
||||
);
|
||||
|
||||
return getIsTimelineVisible;
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
import { SecurityPageName } from '../../../app/types';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { DashboardsLandingPage } from '.';
|
||||
import type { NavLinkItem } from '../../../common/components/navigation/types';
|
||||
import type { NavigationLink } from '../../../common/links';
|
||||
import { useCapabilities } from '../../../common/lib/kibana';
|
||||
import * as telemetry from '../../../common/lib/telemetry';
|
||||
|
||||
|
@ -28,7 +28,7 @@ const spyTrack = jest.spyOn(telemetry, 'track');
|
|||
const OVERVIEW_ITEM_LABEL = 'Overview';
|
||||
const DETECTION_RESPONSE_ITEM_LABEL = 'Detection & Response';
|
||||
|
||||
const APP_DASHBOARD_LINKS: NavLinkItem = {
|
||||
const APP_DASHBOARD_LINKS: NavigationLink = {
|
||||
id: SecurityPageName.dashboards,
|
||||
title: 'Dashboards',
|
||||
links: [
|
||||
|
|
|
@ -44,7 +44,7 @@ import { AlertsTableComponent } from '../../../../detections/components/alerts_t
|
|||
import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping';
|
||||
import { useDataTableFilters } from '../../../../common/hooks/use_data_table_filters';
|
||||
import { isMlRule } from '../../../../../common/machine_learning/helpers';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import { InputsModelId } from '../../../../common/store/inputs/constants';
|
||||
import {
|
||||
useDeepEqualSelector,
|
||||
|
@ -820,7 +820,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<TabNavigationWithBreadcrumbs navTabs={pageTabs} />
|
||||
<TabNavigation navTabs={pageTabs} />
|
||||
<EuiSpacer />
|
||||
</Display>
|
||||
<StyledMinHeightTabContainer>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
|
||||
export enum AllRulesTabs {
|
||||
|
@ -33,7 +33,7 @@ export const RulesTableToolbar = React.memo(() => {
|
|||
[]
|
||||
);
|
||||
|
||||
return <TabNavigationWithBreadcrumbs navTabs={ruleTabs} />;
|
||||
return <TabNavigation navTabs={ruleTabs} />;
|
||||
});
|
||||
|
||||
RulesTableToolbar.displayName = 'RulesTableToolbar';
|
||||
|
|
|
@ -19,7 +19,7 @@ import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
|||
import { SpyRoute } from '../../../common/utils/route/spy_routes';
|
||||
import { getAlertDetailsTabUrl } from '../../../common/components/link_to';
|
||||
import { AlertDetailRouteType } from './types';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
import { getAlertDetailsNavTabs } from './utils/navigation';
|
||||
import { SecurityPageName } from '../../../../common/constants';
|
||||
import { eventID } from '../../../../common/endpoint/models/event';
|
||||
|
@ -73,7 +73,7 @@ export const AlertDetailsPage = memo(() => {
|
|||
{hasData && (
|
||||
<>
|
||||
<AlertDetailsHeader loading={loading} ruleName={ruleName} timestamp={timestamp} />
|
||||
<TabNavigationWithBreadcrumbs navTabs={getAlertDetailsNavTabs(eventId)} />
|
||||
<TabNavigation navTabs={getAlertDetailsNavTabs(eventId)} />
|
||||
<EuiSpacer size="l" />
|
||||
<Switch>
|
||||
<Route exact path={getAlertDetailsTabUrl(eventId, AlertDetailRouteType.summary)}>
|
||||
|
|
|
@ -35,7 +35,7 @@ import { hostToCriteria } from '../../../../common/components/ml/criteria/host_t
|
|||
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities';
|
||||
import { scoreIntervalToDateTime } from '../../../../common/components/ml/score/score_interval_to_datetime';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import { HostOverview } from '../../../../overview/components/host_overview';
|
||||
import { SiemSearchBar } from '../../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper';
|
||||
|
@ -249,7 +249,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
</>
|
||||
)}
|
||||
|
||||
<TabNavigationWithBreadcrumbs
|
||||
<TabNavigation
|
||||
navTabs={navTabsHostDetails({
|
||||
hasMlUserPermissions: hasMlUserPermissions(capabilities),
|
||||
isRiskyHostsEnabled: isPlatinumOrTrialLicense,
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
kibanaObservable,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
import { inputsActions } from '../../../common/store/inputs';
|
||||
import type { State } from '../../../common/store';
|
||||
import { createStore } from '../../../common/store';
|
||||
|
@ -138,7 +138,7 @@ describe('Hosts - rendering', () => {
|
|||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(TabNavigationWithBreadcrumbs).exists()).toBe(true);
|
||||
expect(wrapper.find(TabNavigation).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it should add the new filters after init', async () => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { FiltersGlobal } from '../../../common/components/filters_global';
|
|||
import { HeaderPage } from '../../../common/components/header_page';
|
||||
import { LastEventTime } from '../../../common/components/last_event_time';
|
||||
import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
import { HostsKpiComponent } from '../components/kpi_hosts';
|
||||
import { SiemSearchBar } from '../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper';
|
||||
|
@ -212,7 +212,7 @@ const HostsComponent = () => {
|
|||
|
||||
<EuiSpacer />
|
||||
|
||||
<TabNavigationWithBreadcrumbs
|
||||
<TabNavigation
|
||||
navTabs={navTabsHosts({
|
||||
hasMlUserPermissions: hasMlUserPermissions(capabilities),
|
||||
isRiskyHostsEnabled: capabilities.isPlatinumOrTrialLicense,
|
||||
|
|
|
@ -43,7 +43,7 @@ import { SecurityPageName } from '../../../../app/types';
|
|||
import { useSourcererDataView } from '../../../../common/containers/sourcerer';
|
||||
import { useInvalidFilterQuery } from '../../../../common/hooks/use_invalid_filter_query';
|
||||
import { LandingPageComponent } from '../../../../common/components/landing_page';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import { getNetworkDetailsPageFilter } from '../../../../common/components/visualization_actions/utils';
|
||||
import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions';
|
||||
import { AlertCountByRuleByStatus } from '../../../../common/components/alert_count_by_status';
|
||||
|
@ -239,7 +239,7 @@ const NetworkDetailsComponent: React.FC = () => {
|
|||
</>
|
||||
)}
|
||||
|
||||
<TabNavigationWithBreadcrumbs
|
||||
<TabNavigation
|
||||
navTabs={navTabsNetworkDetails(ip, hasMlUserPermissions(capabilities), flowTarget)}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -22,7 +22,7 @@ import { EmbeddedMap } from '../components/embeddables/embedded_map';
|
|||
import { FiltersGlobal } from '../../../common/components/filters_global';
|
||||
import { HeaderPage } from '../../../common/components/header_page';
|
||||
import { LastEventTime } from '../../../common/components/last_event_time';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
|
||||
import { NetworkKpiComponent } from '../components/kpi_network';
|
||||
import { SiemSearchBar } from '../../../common/components/search_bar';
|
||||
|
@ -207,7 +207,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
|
|||
<>
|
||||
<Display show={!globalFullScreen}>
|
||||
<EuiSpacer />
|
||||
<TabNavigationWithBreadcrumbs navTabs={navTabsNetwork(hasMlUserPermissions)} />
|
||||
<TabNavigation navTabs={navTabsNetwork(hasMlUserPermissions)} />
|
||||
<EuiSpacer />
|
||||
</Display>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import { InputsModelId } from '../../../../common/store/inputs/constants';
|
|||
import { SecurityPageName } from '../../../../app/types';
|
||||
import { FiltersGlobal } from '../../../../common/components/filters_global';
|
||||
import { HeaderPage } from '../../../../common/components/header_page';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import { SiemSearchBar } from '../../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper';
|
||||
import { useGlobalTime } from '../../../../common/containers/use_global_time';
|
||||
|
@ -238,7 +238,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
</>
|
||||
)}
|
||||
|
||||
<TabNavigationWithBreadcrumbs
|
||||
<TabNavigation
|
||||
navTabs={navTabsUsersDetails(
|
||||
detailName,
|
||||
hasMlUserPermissions(capabilities),
|
||||
|
|
|
@ -18,7 +18,7 @@ import { InputsModelId } from '../../../common/store/inputs/constants';
|
|||
import { SecurityPageName } from '../../../app/types';
|
||||
import { FiltersGlobal } from '../../../common/components/filters_global';
|
||||
import { HeaderPage } from '../../../common/components/header_page';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
|
||||
import { SiemSearchBar } from '../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper';
|
||||
|
@ -211,7 +211,7 @@ const UsersComponent = () => {
|
|||
|
||||
<EuiSpacer />
|
||||
|
||||
<TabNavigationWithBreadcrumbs navTabs={navTabs} />
|
||||
<TabNavigation navTabs={navTabs} />
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Router } from 'react-router-dom';
|
|||
|
||||
import '../../../common/mock/match_media';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { TabNavigationWithBreadcrumbs } from '../../../common/components/navigation/tab_navigation_with_breadcrumbs';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
import { Users } from './users';
|
||||
import { useSourcererDataView } from '../../../common/containers/sourcerer';
|
||||
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
|
||||
|
@ -100,6 +100,6 @@ describe('Users - rendering', () => {
|
|||
</Router>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(TabNavigationWithBreadcrumbs).exists()).toBe(true);
|
||||
expect(wrapper.find(TabNavigation).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { combineLatestWith } from 'rxjs/operators';
|
||||
import type * as H from 'history';
|
||||
import type {
|
||||
AppMountParameters,
|
||||
|
@ -35,17 +33,10 @@ import { initTelemetry, TelemetryService } from './common/lib/telemetry';
|
|||
import { KibanaServices } from './common/lib/kibana/services';
|
||||
import { SOLUTION_NAME } from './common/translations';
|
||||
|
||||
import {
|
||||
APP_ID,
|
||||
APP_UI_ID,
|
||||
APP_PATH,
|
||||
APP_ICON_SOLUTION,
|
||||
ENABLE_GROUPED_NAVIGATION,
|
||||
} from '../common/constants';
|
||||
import { APP_ID, APP_UI_ID, APP_PATH, APP_ICON_SOLUTION } from '../common/constants';
|
||||
|
||||
import { getDeepLinks, registerDeepLinksUpdater } from './app/deep_links';
|
||||
import type { LinksPermissions } from './common/links';
|
||||
import { updateAppLinks } from './common/links';
|
||||
import { updateAppLinks, type LinksPermissions } from './common/links';
|
||||
import { registerDeepLinksUpdater } from './common/links/deep_links';
|
||||
import { navLinks$ } from './common/links/nav_links';
|
||||
import { licenseService } from './common/hooks/use_license';
|
||||
import type { SecuritySolutionUiConfigType } from './common/types';
|
||||
|
@ -493,12 +484,11 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
*/
|
||||
async registerAppLinks(core: CoreStart, plugins: StartPlugins) {
|
||||
const { links, getFilteredLinks } = await this.lazyApplicationLinks();
|
||||
|
||||
const { license$ } = plugins.licensing;
|
||||
const newNavEnabled$ = core.uiSettings.get$<boolean>(ENABLE_GROUPED_NAVIGATION, true);
|
||||
|
||||
let appLinksSubscription: Subscription | null = null;
|
||||
license$.pipe(combineLatestWith(newNavEnabled$)).subscribe(async ([license, newNavEnabled]) => {
|
||||
registerDeepLinksUpdater(this.appUpdater$);
|
||||
|
||||
license$.subscribe(async (license) => {
|
||||
const linksPermissions: LinksPermissions = {
|
||||
experimentalFeatures: this.experimentalFeatures,
|
||||
capabilities: core.application.capabilities,
|
||||
|
@ -508,25 +498,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
linksPermissions.license = license;
|
||||
}
|
||||
|
||||
if (appLinksSubscription) {
|
||||
appLinksSubscription.unsubscribe();
|
||||
appLinksSubscription = null;
|
||||
}
|
||||
|
||||
if (newNavEnabled) {
|
||||
appLinksSubscription = registerDeepLinksUpdater(this.appUpdater$);
|
||||
} else {
|
||||
// old nav links update
|
||||
this.appUpdater$.next(() => ({
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
deepLinks: getDeepLinks(
|
||||
this.experimentalFeatures,
|
||||
license.type,
|
||||
core.application.capabilities
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
// set initial links to not block rendering
|
||||
updateAppLinks(links, linksPermissions);
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
DEFAULT_THREAT_INDEX_KEY,
|
||||
DEFAULT_THREAT_INDEX_VALUE,
|
||||
DEFAULT_TO,
|
||||
ENABLE_GROUPED_NAVIGATION,
|
||||
ENABLE_NEWS_FEED_SETTING,
|
||||
IP_REPUTATION_LINKS_SETTING,
|
||||
IP_REPUTATION_LINKS_SETTING_DEFAULT,
|
||||
|
@ -149,23 +148,6 @@ export const initUiSettings = (
|
|||
requiresPageReload: true,
|
||||
schema: schema.number(),
|
||||
},
|
||||
[ENABLE_GROUPED_NAVIGATION]: {
|
||||
name: i18n.translate('xpack.securitySolution.uiSettings.enableGroupedNavigation', {
|
||||
defaultMessage: 'New streamlined navigation',
|
||||
}),
|
||||
value: true,
|
||||
type: 'boolean',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.uiSettings.enableGroupedNavigationDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'<p>Improve your experience with the new navigation organized and optimized around the most important workflows.</p>',
|
||||
}
|
||||
),
|
||||
category: [APP_ID],
|
||||
requiresPageReload: false,
|
||||
schema: schema.boolean(),
|
||||
},
|
||||
[ENABLE_NEWS_FEED_SETTING]: {
|
||||
name: i18n.translate('xpack.securitySolution.uiSettings.enableNewsFeedLabel', {
|
||||
defaultMessage: 'News feed',
|
||||
|
|
|
@ -11,11 +11,7 @@ export { THREAT_INTELLIGENCE_BASE_PATH } from './constants/navigation';
|
|||
|
||||
export type { TIPageId } from './types';
|
||||
|
||||
export {
|
||||
getSecuritySolutionDeepLink,
|
||||
getSecuritySolutionLink,
|
||||
getSecuritySolutionNavTab,
|
||||
} from './utils/security_solution_links';
|
||||
export { getSecuritySolutionLink } from './utils/security_solution_links';
|
||||
|
||||
export function plugin() {
|
||||
return new ThreatIntelligencePlugin();
|
||||
|
|
|
@ -6,24 +6,7 @@
|
|||
*/
|
||||
|
||||
import { threatIntelligencePages } from '../constants/navigation';
|
||||
import {
|
||||
getSecuritySolutionDeepLink,
|
||||
getSecuritySolutionLink,
|
||||
getSecuritySolutionNavTab,
|
||||
} from './security_solution_links';
|
||||
|
||||
describe('getSecuritySolutionDeepLink', () => {
|
||||
it('gets the correct deeplink properties', () => {
|
||||
const threatIntelligencePage = 'indicators';
|
||||
|
||||
const link = getSecuritySolutionDeepLink(threatIntelligencePage);
|
||||
|
||||
expect(link.id).toEqual(threatIntelligencePages[threatIntelligencePage].id);
|
||||
expect(link.keywords).toEqual(threatIntelligencePages[threatIntelligencePage].keywords);
|
||||
expect(link.path).toEqual(threatIntelligencePages[threatIntelligencePage].path);
|
||||
expect(link.title).toEqual(threatIntelligencePages[threatIntelligencePage].newNavigationName);
|
||||
});
|
||||
});
|
||||
import { getSecuritySolutionLink } from './security_solution_links';
|
||||
|
||||
describe('getSecuritySolutionLink', () => {
|
||||
it('gets the correct link properties', () => {
|
||||
|
@ -40,19 +23,3 @@ describe('getSecuritySolutionLink', () => {
|
|||
expect(link.title).toEqual(threatIntelligencePages[threatIntelligencePage].newNavigationName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSecuritySolutionNavTab', () => {
|
||||
it('gets the correct navtab properties', () => {
|
||||
const threatIntelligencePage = 'indicators';
|
||||
const basePath = 'threat_intelligence/';
|
||||
|
||||
const navTab = getSecuritySolutionNavTab(threatIntelligencePage, basePath);
|
||||
|
||||
expect(navTab.disabled).toEqual(threatIntelligencePages[threatIntelligencePage].disabled);
|
||||
expect(navTab.href).toEqual(
|
||||
`${basePath}${threatIntelligencePages[threatIntelligencePage].path}`
|
||||
);
|
||||
expect(navTab.id).toEqual(threatIntelligencePages[threatIntelligencePage].id);
|
||||
expect(navTab.name).toEqual(threatIntelligencePages[threatIntelligencePage].oldNavigationName);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,31 +8,6 @@
|
|||
import { TIPage, TIPageId } from '../types';
|
||||
import { threatIntelligencePages } from '../constants/navigation';
|
||||
|
||||
/**
|
||||
* Properties used in the Security Solution plugin to enable deep linking.
|
||||
* The properties come from SecuritySolutionDeepLink (x-pack/plugins/security_solution/public/app/deep_links/index.ts).
|
||||
*
|
||||
* If we want to control more from within the Threat Intelligence plugin, we can keep growing this interface.
|
||||
*/
|
||||
interface TIDeepLink<TId extends string = TIPageId> {
|
||||
/**
|
||||
* Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL.
|
||||
*/
|
||||
keywords?: string[];
|
||||
/**
|
||||
* Identifier to represent this sublink, should be unique for this application.
|
||||
*/
|
||||
id: TId;
|
||||
/**
|
||||
* URL path to access this link, relative to the application's appRoute.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Title to label represent this deep link.
|
||||
**/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties used in the Security Solution plugin to add links to the navigation.
|
||||
* The properties come from LinkItem (x-pack/plugins/security_solution/public/common/links/types.ts).
|
||||
|
@ -62,45 +37,6 @@ interface TILinkItem<TId extends string = TIPageId> {
|
|||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties used in the Security Solution plugin to add links to the old navigation.
|
||||
* The properties comes from NavTab (x-pack/plugins/security_solution/public/common/components/navigation/types.ts).
|
||||
*
|
||||
* If we want to control more from within the Threat Intelligence plugin, we can keep growing this interface.
|
||||
*/
|
||||
interface TINavTab<TId extends string = TIPageId> {
|
||||
/**
|
||||
* Nav tab id.
|
||||
*/
|
||||
id: TId;
|
||||
/**
|
||||
* Name displayed in the sidenav.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Page's path to navigate to when clicked.
|
||||
*/
|
||||
href: string;
|
||||
/**
|
||||
* Disables nav tab.
|
||||
*/
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the threat intelligence properties of a TI page for deep linking in the security solution.
|
||||
* @param threatIntelligencePage the name of the threat intelligence page.
|
||||
* @returns a {@link TIDeepLink}
|
||||
*/
|
||||
export const getSecuritySolutionDeepLink = <TId extends string = TIPageId>(
|
||||
threatIntelligencePage: TIPage
|
||||
): TIDeepLink<TId> => ({
|
||||
id: threatIntelligencePages[threatIntelligencePage].id as TId,
|
||||
title: threatIntelligencePages[threatIntelligencePage].newNavigationName,
|
||||
path: threatIntelligencePages[threatIntelligencePage].path,
|
||||
keywords: threatIntelligencePages[threatIntelligencePage].keywords,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the threat intelligence properties of a TI page for navigation in the security solution.
|
||||
* @param threatIntelligencePage the name of the threat intelligence page.
|
||||
|
@ -115,19 +51,3 @@ export const getSecuritySolutionLink = <TId extends string = TIPageId>(
|
|||
description: threatIntelligencePages[threatIntelligencePage].description,
|
||||
globalSearchKeywords: threatIntelligencePages[threatIntelligencePage].globalSearchKeywords,
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the threat intelligence properties of a TI page for navigation in the old security solution navigation.
|
||||
* @param threatIntelligencePage the name of the threat intelligence page.
|
||||
* @param basePath the base path for links.
|
||||
* @returns a {@link TINavTab}
|
||||
*/
|
||||
export const getSecuritySolutionNavTab = <TId extends string = TIPageId>(
|
||||
threatIntelligencePage: TIPage,
|
||||
basePath: string
|
||||
): TINavTab<TId> => ({
|
||||
id: threatIntelligencePages[threatIntelligencePage].id as TId,
|
||||
name: threatIntelligencePages[threatIntelligencePage].oldNavigationName,
|
||||
href: `${basePath}${threatIntelligencePages[threatIntelligencePage].path}`,
|
||||
disabled: threatIntelligencePages[threatIntelligencePage].disabled,
|
||||
});
|
||||
|
|
|
@ -29528,7 +29528,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexDescription": "<p>Liste d'index Threat Intelligence séparés par des virgules à partir de laquelle l'application Security collecte les indicateurs.</p>",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>Période de temps par défaut dans le filtre de temps Security.</p>",
|
||||
"xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>Active les avertissements de vérification des privilèges dans les règles relatives aux index CCS</p>",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigationDescription": "<p>Améliorez votre expérience avec la nouvelle navigation organisée et optimisée autour des workflows les plus importants.</p>",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>Active le fil d'actualités</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledDescription": "<p>Active l'exécution de règle étendue pour le logging dans les index .kibana-event-log-*. Affiche les événements d'exécution simples sur la page Détails de la règle.</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDescription": "<p>Définit le niveau de log minimum à partir duquel les règles écrivent des logs étendus dans les index .kibana-event-log-*. Ceci affecte uniquement les événements de type Message, les autres événements sont écrits dans .kibana-event-log-* quel que soit ce paramètre et leur niveau de log.</p>",
|
||||
|
@ -33413,39 +33412,6 @@
|
|||
"xpack.securitySolution.search.administration.eventFilters": "Filtres d'événements",
|
||||
"xpack.securitySolution.search.administration.hostIsolationExceptions": "Exceptions d'isolation de l'hôte",
|
||||
"xpack.securitySolution.search.administration.trustedApps": "Applications de confiance",
|
||||
"xpack.securitySolution.search.alerts": "Alertes",
|
||||
"xpack.securitySolution.search.dashboards": "Tableaux de bord",
|
||||
"xpack.securitySolution.search.dataQualityDashboard": "Qualité des données",
|
||||
"xpack.securitySolution.search.detect": "Détecter",
|
||||
"xpack.securitySolution.search.detectionAndResponse": "Détection et réponse",
|
||||
"xpack.securitySolution.search.entityAnalytics": "Analyse des entités",
|
||||
"xpack.securitySolution.search.exceptions": "Listes d'exceptions",
|
||||
"xpack.securitySolution.search.explore": "Explorer",
|
||||
"xpack.securitySolution.search.getStarted": "Premiers pas",
|
||||
"xpack.securitySolution.search.hosts": "Hôtes",
|
||||
"xpack.securitySolution.search.hosts.anomalies": "Anomalies",
|
||||
"xpack.securitySolution.search.hosts.events": "Événements",
|
||||
"xpack.securitySolution.search.hosts.risk": "Risque de l'hôte",
|
||||
"xpack.securitySolution.search.hosts.sessions": "Sessions",
|
||||
"xpack.securitySolution.search.hosts.uncommonProcesses": "Processus inhabituels",
|
||||
"xpack.securitySolution.search.investigate": "Examiner",
|
||||
"xpack.securitySolution.search.kubernetes": "Kubernetes",
|
||||
"xpack.securitySolution.search.manage": "Gérer",
|
||||
"xpack.securitySolution.search.network": "Réseau",
|
||||
"xpack.securitySolution.search.network.anomalies": "Anomalies",
|
||||
"xpack.securitySolution.search.network.dns": "DNS",
|
||||
"xpack.securitySolution.search.network.events": "Événements",
|
||||
"xpack.securitySolution.search.network.http": "HTTP",
|
||||
"xpack.securitySolution.search.network.tls": "TLS",
|
||||
"xpack.securitySolution.search.overview": "Aperçu",
|
||||
"xpack.securitySolution.search.rules": "Règles",
|
||||
"xpack.securitySolution.search.timeline.templates": "Modèles",
|
||||
"xpack.securitySolution.search.timelines": "Chronologies",
|
||||
"xpack.securitySolution.search.users": "Utilisateurs",
|
||||
"xpack.securitySolution.search.users.anomalies": "Anomalies",
|
||||
"xpack.securitySolution.search.users.authentications": "Authentifications",
|
||||
"xpack.securitySolution.search.users.events": "Événements",
|
||||
"xpack.securitySolution.search.users.risk": "Risque de l'utilisateur",
|
||||
"xpack.securitySolution.sections.actionForm.addResponseActionButtonLabel": "Ajouter une action de réponse",
|
||||
"xpack.securitySolution.selector.grouping.hostName.label": "Nom d'hôte",
|
||||
"xpack.securitySolution.selector.grouping.sourceIP.label": "IP source",
|
||||
|
@ -33787,7 +33753,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "Index de menaces",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "Période du filtre de temps",
|
||||
"xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "Avertissement lié aux privilèges de la règle CCS",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigation": "Nouvelle navigation simplifiée",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedLabel": "Fil d'actualités",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledLabel": "Logging étendu de l’exécution des règles",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDebug": "Déboguer",
|
||||
|
|
|
@ -29509,7 +29509,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexDescription": "<p>セキュリティアプリが指標を収集するThreat Intelligenceインデックスのカンマ区切りのリスト。</p>",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>セキュリティ時間フィルダーのデフォルトの期間です。</p>",
|
||||
"xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>CCSインデックスのルールで権限チェック警告を有効にします</p>",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigationDescription": "<p>最も重要なワークフローを中心に整理され、最適化された新しいナビゲーションで、エクスペリエンスを改善します。</p>",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>ニュースフィードを有効にします</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledDescription": "<p>.kibana-event-log-*インデックスへの拡張ルール実行ログを有効にします。[ルール詳細]ページでプレーン実行イベントが表示されます。</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDescription": "<p>最小ログレベルを設定します。ルールは、このレベル以上の拡張ログを.kibana-event-log-*インデックスに書き込みます。これはメッセージタイプのイベントにのみ影響します。他のイベントは、この設定とログレベルに関係なく、.kibana-event-log-*に書き込まれます。</p>",
|
||||
|
@ -33394,39 +33393,6 @@
|
|||
"xpack.securitySolution.search.administration.eventFilters": "イベントフィルター",
|
||||
"xpack.securitySolution.search.administration.hostIsolationExceptions": "ホスト分離例外",
|
||||
"xpack.securitySolution.search.administration.trustedApps": "信頼できるアプリケーション",
|
||||
"xpack.securitySolution.search.alerts": "アラート",
|
||||
"xpack.securitySolution.search.dashboards": "ダッシュボード",
|
||||
"xpack.securitySolution.search.dataQualityDashboard": "データ品質",
|
||||
"xpack.securitySolution.search.detect": "検知",
|
||||
"xpack.securitySolution.search.detectionAndResponse": "検出と対応",
|
||||
"xpack.securitySolution.search.entityAnalytics": "エンティティ分析",
|
||||
"xpack.securitySolution.search.exceptions": "例外リスト",
|
||||
"xpack.securitySolution.search.explore": "探索",
|
||||
"xpack.securitySolution.search.getStarted": "はじめて使う",
|
||||
"xpack.securitySolution.search.hosts": "ホスト",
|
||||
"xpack.securitySolution.search.hosts.anomalies": "異常",
|
||||
"xpack.securitySolution.search.hosts.events": "イベント",
|
||||
"xpack.securitySolution.search.hosts.risk": "ホストリスク",
|
||||
"xpack.securitySolution.search.hosts.sessions": "セッション",
|
||||
"xpack.securitySolution.search.hosts.uncommonProcesses": "非共通プロセス",
|
||||
"xpack.securitySolution.search.investigate": "調査",
|
||||
"xpack.securitySolution.search.kubernetes": "Kubernetes",
|
||||
"xpack.securitySolution.search.manage": "管理",
|
||||
"xpack.securitySolution.search.network": "ネットワーク",
|
||||
"xpack.securitySolution.search.network.anomalies": "異常",
|
||||
"xpack.securitySolution.search.network.dns": "DNS",
|
||||
"xpack.securitySolution.search.network.events": "イベント",
|
||||
"xpack.securitySolution.search.network.http": "HTTP",
|
||||
"xpack.securitySolution.search.network.tls": "TLS",
|
||||
"xpack.securitySolution.search.overview": "概要",
|
||||
"xpack.securitySolution.search.rules": "ルール",
|
||||
"xpack.securitySolution.search.timeline.templates": "テンプレート",
|
||||
"xpack.securitySolution.search.timelines": "タイムライン",
|
||||
"xpack.securitySolution.search.users": "ユーザー",
|
||||
"xpack.securitySolution.search.users.anomalies": "異常",
|
||||
"xpack.securitySolution.search.users.authentications": "認証",
|
||||
"xpack.securitySolution.search.users.events": "イベント",
|
||||
"xpack.securitySolution.search.users.risk": "ユーザーリスク",
|
||||
"xpack.securitySolution.sections.actionForm.addResponseActionButtonLabel": "対応アクションの追加",
|
||||
"xpack.securitySolution.selector.grouping.hostName.label": "ホスト名",
|
||||
"xpack.securitySolution.selector.grouping.sourceIP.label": "ソース IP",
|
||||
|
@ -33768,7 +33734,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "脅威インデックス",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "時間フィルターの期間",
|
||||
"xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "CCSルール権限警告",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigation": "新しい合理化されたナビゲーション",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedLabel": "ニュースフィード",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledLabel": "拡張ルール実行ログ",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDebug": "デバッグ",
|
||||
|
|
|
@ -29505,7 +29505,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexDescription": "<p>Security 应用从中收集指标的威胁情报索引的逗号分隔列表。</p>",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeDescription": "<p>Security 时间筛选中的默认时段。</p>",
|
||||
"xpack.securitySolution.uiSettings.enableCcsWarningDescription": "<p>在规则中为 CCS 索引启用权限检查警告</p>",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigationDescription": "<p>利用围绕最重要的工作流进行组织和优化的新导航改进您的体验。</p>",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedDescription": "<p>启用新闻源</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledDescription": "<p>对 .kibana-event-log-* 索引启用扩展规则执行日志记录。在“规则详情”页面上显示纯执行事件。</p>",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDescription": "<p>设置最低日志级别,规则将从该级别起将扩展日志写入到 .kibana-event-log-* 索引。这只会影响类型为“消息”的事件,其他事件将写入到 .kibana-event-log-*,而不论此设置及其日志级别如何。</p>",
|
||||
|
@ -33390,39 +33389,6 @@
|
|||
"xpack.securitySolution.search.administration.eventFilters": "事件筛选",
|
||||
"xpack.securitySolution.search.administration.hostIsolationExceptions": "主机隔离例外",
|
||||
"xpack.securitySolution.search.administration.trustedApps": "受信任的应用程序",
|
||||
"xpack.securitySolution.search.alerts": "告警",
|
||||
"xpack.securitySolution.search.dashboards": "仪表板",
|
||||
"xpack.securitySolution.search.dataQualityDashboard": "数据质量",
|
||||
"xpack.securitySolution.search.detect": "检测",
|
||||
"xpack.securitySolution.search.detectionAndResponse": "检测和响应",
|
||||
"xpack.securitySolution.search.entityAnalytics": "实体分析",
|
||||
"xpack.securitySolution.search.exceptions": "例外列表",
|
||||
"xpack.securitySolution.search.explore": "浏览",
|
||||
"xpack.securitySolution.search.getStarted": "入门",
|
||||
"xpack.securitySolution.search.hosts": "主机",
|
||||
"xpack.securitySolution.search.hosts.anomalies": "异常",
|
||||
"xpack.securitySolution.search.hosts.events": "事件",
|
||||
"xpack.securitySolution.search.hosts.risk": "主机风险",
|
||||
"xpack.securitySolution.search.hosts.sessions": "会话",
|
||||
"xpack.securitySolution.search.hosts.uncommonProcesses": "不常见进程",
|
||||
"xpack.securitySolution.search.investigate": "调查",
|
||||
"xpack.securitySolution.search.kubernetes": "Kubernetes",
|
||||
"xpack.securitySolution.search.manage": "管理",
|
||||
"xpack.securitySolution.search.network": "网络",
|
||||
"xpack.securitySolution.search.network.anomalies": "异常",
|
||||
"xpack.securitySolution.search.network.dns": "DNS",
|
||||
"xpack.securitySolution.search.network.events": "事件",
|
||||
"xpack.securitySolution.search.network.http": "HTTP",
|
||||
"xpack.securitySolution.search.network.tls": "TLS",
|
||||
"xpack.securitySolution.search.overview": "概览",
|
||||
"xpack.securitySolution.search.rules": "规则",
|
||||
"xpack.securitySolution.search.timeline.templates": "模板",
|
||||
"xpack.securitySolution.search.timelines": "时间线",
|
||||
"xpack.securitySolution.search.users": "用户",
|
||||
"xpack.securitySolution.search.users.anomalies": "异常",
|
||||
"xpack.securitySolution.search.users.authentications": "身份验证",
|
||||
"xpack.securitySolution.search.users.events": "事件",
|
||||
"xpack.securitySolution.search.users.risk": "用户风险",
|
||||
"xpack.securitySolution.sections.actionForm.addResponseActionButtonLabel": "添加响应操作",
|
||||
"xpack.securitySolution.selector.grouping.hostName.label": "主机名",
|
||||
"xpack.securitySolution.selector.grouping.sourceIP.label": "源 IP",
|
||||
|
@ -33764,7 +33730,6 @@
|
|||
"xpack.securitySolution.uiSettings.defaultThreatIndexLabel": "威胁索引",
|
||||
"xpack.securitySolution.uiSettings.defaultTimeRangeLabel": "时间筛选时段",
|
||||
"xpack.securitySolution.uiSettings.enableCcsReadWarningLabel": "CCS 规则权限警告",
|
||||
"xpack.securitySolution.uiSettings.enableGroupedNavigation": "新的精简导航",
|
||||
"xpack.securitySolution.uiSettings.enableNewsFeedLabel": "新闻源",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingEnabledLabel": "扩展规则执行日志记录",
|
||||
"xpack.securitySolution.uiSettings.extendedRuleExecutionLoggingMinLevelDebug": "故障排查",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue