[Security Solution] Move datatable to package (#150899)

## Summary

[Related issue](https://github.com/elastic/kibana/issues/150603)

This PR extracts the DataTableComponent, related redux infrastructure
and some helpers into standalone package.

### Checklist

Delete any items that are not applicable to this PR.

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Sergi Massaneda <sergi.massaneda@elastic.co>
This commit is contained in:
Luke 2023-04-17 21:02:28 +02:00 committed by GitHub
parent 22956209ac
commit 153994d810
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
171 changed files with 3795 additions and 332 deletions

1
.github/CODEOWNERS vendored
View file

@ -547,6 +547,7 @@ packages/security-solution/side_nav @elastic/security-threat-hunting-explore
packages/security-solution/storybook/config @elastic/security-threat-hunting-explore
x-pack/test/security_functional/plugins/test_endpoints @elastic/kibana-security
packages/kbn-securitysolution-autocomplete @elastic/security-solution-platform
x-pack/packages/kbn-securitysolution-data-table @elastic/security-threat-hunting-investigations
packages/kbn-securitysolution-ecs @elastic/security-threat-hunting-explore
packages/kbn-securitysolution-es-utils @elastic/security-solution-platform
packages/kbn-securitysolution-exception-list-components @elastic/security-solution-platform

View file

@ -80,6 +80,7 @@
"savedObjects": "src/plugins/saved_objects",
"savedObjectsFinder": "src/plugins/saved_objects_finder",
"savedObjectsManagement": "src/plugins/saved_objects_management",
"securitySolutionDataTable": "packages/kbn-securitysolution-data-table",
"server": "src/legacy/server",
"share": "src/plugins/share",
"sharedUXPackages": "packages/shared-ux",

View file

@ -548,6 +548,7 @@
"@kbn/security-solution-storybook-config": "link:packages/security-solution/storybook/config",
"@kbn/security-test-endpoints-plugin": "link:x-pack/test/security_functional/plugins/test_endpoints",
"@kbn/securitysolution-autocomplete": "link:packages/kbn-securitysolution-autocomplete",
"@kbn/securitysolution-data-table": "link:x-pack/packages/kbn-securitysolution-data-table",
"@kbn/securitysolution-ecs": "link:packages/kbn-securitysolution-ecs",
"@kbn/securitysolution-es-utils": "link:packages/kbn-securitysolution-es-utils",
"@kbn/securitysolution-exception-list-components": "link:packages/kbn-securitysolution-exception-list-components",

View file

@ -7,13 +7,13 @@
*/
import type { FieldSpec } from '@kbn/data-views-plugin/common';
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
import React, { useCallback, useEffect } from 'react';
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
import { GroupSelector, isNoneGroup } from '..';
import { groupActions, groupByIdSelector } from './state';
import type { GroupOption } from './types';
import { Action, defaultGroup, GroupMap } from './types';
import { GroupSelector, isNoneGroup } from '..';
import { getTelemetryEvent } from '../telemetry/const';
export interface UseGetGroupSelectorArgs {

View file

@ -45,6 +45,7 @@ export const storybookAliases = {
presentation: 'src/plugins/presentation_util/storybook',
security_solution: 'x-pack/plugins/security_solution/.storybook',
security_solution_packages: 'packages/security-solution/storybook/config',
security_solution_data_table: 'x-pack/packages/kbn-securitysolution-data-table/.storybook',
shared_ux: 'packages/shared-ux/storybook/config',
threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook',
triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook',

View file

@ -1088,6 +1088,8 @@
"@kbn/security-test-endpoints-plugin/*": ["x-pack/test/security_functional/plugins/test_endpoints/*"],
"@kbn/securitysolution-autocomplete": ["packages/kbn-securitysolution-autocomplete"],
"@kbn/securitysolution-autocomplete/*": ["packages/kbn-securitysolution-autocomplete/*"],
"@kbn/securitysolution-data-table": ["x-pack/packages/kbn-securitysolution-data-table"],
"@kbn/securitysolution-data-table/*": ["x-pack/packages/kbn-securitysolution-data-table/*"],
"@kbn/securitysolution-ecs": ["packages/kbn-securitysolution-ecs"],
"@kbn/securitysolution-ecs/*": ["packages/kbn-securitysolution-ecs/*"],
"@kbn/securitysolution-es-utils": ["packages/kbn-securitysolution-es-utils"],

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,35 @@
# Security Solutions's Data Table
## Motivation
The idea behind this package is to have a reusable data table component, embedding the features
available to alerts table in security solution plugin.
## How to use this
Standalone examples will follow. In the meantime:
Consult the following file to get the idea of what is necessary to reuse the component
`x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx`
The following file:
`kibana/x-pack/plugins/security_solution/public/common/store/reducer.ts`
showcases the redux store setup for the package.
## The most important public api members
- DataTableComponent itself
- dataTableReducer
### Extras
Be sure to check out provided helpers
## Storybook
General look of the component can be checked visually running the following storybook:
`yarn storybook security_solution_data_table`
Note that all the interactions are mocked.

View file

@ -0,0 +1,501 @@
/*
* 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',
},
];

View file

@ -5,13 +5,10 @@
* 2.0.
*/
import { Direction } from '@kbn/timelines-plugin/common';
import * as runtimeTypes from 'io-ts';
import type { VIEW_SELECTION } from '../../constants';
export enum Direction {
asc = 'asc',
desc = 'desc',
}
export { Direction };
export type SortDirectionTable = 'none' | 'asc' | 'desc' | Direction;
export interface SortColumnTable {
@ -21,8 +18,6 @@ export interface SortColumnTable {
sortDirection: SortDirectionTable;
}
export type { TableById } from '../../../public/common/store/data_table/types';
export enum TableId {
usersPageEvents = 'users-page-events',
hostsPageEvents = 'hosts-page-events',
@ -67,10 +62,16 @@ const TableIdLiteralRt = runtimeTypes.union([
runtimeTypes.literal(TableId.test),
runtimeTypes.literal(TableId.rulePreview),
runtimeTypes.literal(TableId.kubernetesPageSessions),
runtimeTypes.literal(TableId.alertsOnCasePage),
]);
export type TableIdLiteral = runtimeTypes.TypeOf<typeof TableIdLiteralRt>;
export const VIEW_SELECTION = {
gridView: 'gridView',
eventRenderedView: 'eventRenderedView',
} as const;
export type ViewSelectionTypes = keyof typeof VIEW_SELECTION;
export type ViewSelection = typeof VIEW_SELECTION[ViewSelectionTypes];

View file

@ -0,0 +1,73 @@
/*
* 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.
*/
type EmptyObject = Record<string | number, never>;
export enum FlowTargetSourceDest {
destination = 'destination',
source = 'source',
}
export type ExpandedEventType =
| {
panelView?: 'eventDetail';
params?: {
eventId: string;
indexName: string;
refetch?: () => void;
};
}
| EmptyObject;
export type ExpandedHostType =
| {
panelView?: 'hostDetail';
params?: {
hostName: string;
};
}
| EmptyObject;
export type ExpandedNetworkType =
| {
panelView?: 'networkDetail';
params?: {
ip: string;
flowTarget: FlowTargetSourceDest;
};
}
| EmptyObject;
export type ExpandedUserType =
| {
panelView?: 'userDetail';
params?: {
userName: string;
};
}
| EmptyObject;
export type ExpandedDetailType =
| ExpandedEventType
| ExpandedHostType
| ExpandedNetworkType
| ExpandedUserType;
export enum TimelineTabs {
query = 'query',
graph = 'graph',
notes = 'notes',
pinned = 'pinned',
eql = 'eql',
session = 'session',
}
export type ExpandedDetailTimeline = {
[tab in TimelineTabs]?: ExpandedDetailType;
};
export type ExpandedDetail = Partial<Record<string, ExpandedDetailType>>;

View file

@ -0,0 +1,107 @@
/*
* 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 { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui';
import type { IFieldSubType } from '@kbn/es-query';
import type { FieldBrowserOptions } from '@kbn/triggers-actions-ui-plugin/public';
import type { ComponentType, JSXElementConstructor } from 'react';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import { OnRowSelected } from '../../../components/data_table/types';
import type { SortColumnTable } from '../data_table';
import { SetEventsDeleted, SetEventsLoading } from '..';
export type ColumnHeaderType = 'not-filtered' | 'text-filter';
/** Uniquely identifies a column */
export type ColumnId = string;
/** The specification of a column header */
export type ColumnHeaderOptions = Pick<
EuiDataGridColumn,
| 'actions'
| 'defaultSortDirection'
| 'display'
| 'displayAsText'
| 'id'
| 'initialWidth'
| 'isSortable'
| 'schema'
| 'isExpandable'
| 'isResizable'
> & {
aggregatable?: boolean;
category?: string;
columnHeaderType: ColumnHeaderType;
description?: string | null;
esTypes?: string[];
example?: string | number | null;
format?: string;
linkField?: string;
placeholder?: string;
subType?: IFieldSubType;
type?: string;
};
export interface HeaderActionProps {
width: number;
browserFields: BrowserFields;
columnHeaders: ColumnHeaderOptions[];
fieldBrowserOptions?: FieldBrowserOptions;
isEventViewer?: boolean;
isSelectAllChecked: boolean;
onSelectAll: ({ isSelected }: { isSelected: boolean }) => void;
showEventsSelect: boolean;
showSelectAllCheckbox: boolean;
sort: SortColumnTable[];
tabType: string;
timelineId: string;
}
export type HeaderCellRender = ComponentType | ComponentType<HeaderActionProps>;
type GenericActionRowCellRenderProps = Pick<
EuiDataGridCellValueElementProps,
'rowIndex' | 'columnId'
>;
export type RowCellRender =
| JSXElementConstructor<GenericActionRowCellRenderProps>
| ((props: GenericActionRowCellRenderProps) => JSX.Element)
| JSXElementConstructor<ActionProps>
| ((props: ActionProps) => JSX.Element);
export interface ActionProps {
action?: RowCellRender;
ariaRowindex: number;
checked: boolean;
columnId: string;
columnValues: string;
data: TimelineNonEcsData[];
disabled?: boolean;
ecsData: Ecs;
eventId: string;
eventIdToNoteIds?: Readonly<Record<string, string[]>>;
index: number;
isEventPinned?: boolean;
isEventViewer?: boolean;
loadingEventIds: Readonly<string[]>;
onEventDetailsPanelOpened: () => void;
onRowSelected: OnRowSelected;
onRuleChange?: () => void;
refetch?: () => void;
rowIndex: number;
setEventsDeleted: SetEventsDeleted;
setEventsLoading: SetEventsLoading;
showCheckboxes: boolean;
showNotes?: boolean;
tabType?: string;
timelineId: string;
toggleShowNotes?: () => void;
width?: number;
}

View file

@ -0,0 +1,19 @@
/*
* 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.
*/
export * from './data_table';
export * from './header_actions';
export * from './session_view';
export const FILTER_OPEN = 'open' as const;
export const FILTER_CLOSED = 'closed' as const;
export const FILTER_ACKNOWLEDGED = 'acknowledged' as const;
export type SetEventsLoading = (params: { eventIds: string[]; isLoading: boolean }) => void;
export type SetEventsDeleted = (params: { eventIds: string[]; isDeleted: boolean }) => void;
export { TimelineTabs } from './detail_panel';

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
export interface ESProcessorConfig {
on_failure?: Processor[];
ignore_failure?: boolean;
if?: string;
tag?: string;
[key: string]: unknown;
}
export interface Processor {
[typeName: string]: ESProcessorConfig;
}

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
export interface SessionViewConfig {
sessionEntityId: string;
jumpToEntityId?: string;
jumpToCursor?: string;
investigatedAlertId?: string;
}

View file

@ -5,8 +5,7 @@
* 2.0.
*/
import type { ColumnHeaderType } from '../../../../../common/types';
import type { ColumnHeaderOptions } from '../../../../../common/types/timeline';
import type { ColumnHeaderOptions, ColumnHeaderType } from '../../../common/types';
import { DEFAULT_TABLE_COLUMN_MIN_WIDTH, DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH } from '../constants';
export const defaultColumnHeaderType: ColumnHeaderType = 'not-filtered';

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { mount } from 'enzyme';
import { set } from '@kbn/safer-lodash-set/fp';
import { omit } from 'lodash/fp';
@ -18,9 +19,9 @@ import {
allowSorting,
} from './helpers';
import { DEFAULT_TABLE_COLUMN_MIN_WIDTH, DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH } from '../constants';
import type { ColumnHeaderOptions } from '../../../../../common/types';
import { mockBrowserFields } from '../../../containers/source/mock';
import type { ColumnHeaderOptions } from '../../../common/types';
import { defaultHeaders } from '../../../store/data_table/defaults';
import { mockBrowserFields } from '../../../mock/mock_source';
window.matchMedia = jest.fn().mockImplementation((query) => {
return {

View file

@ -5,18 +5,15 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { EuiDataGridColumnActions } from '@elastic/eui';
import { keyBy } from 'lodash/fp';
import React from 'react';
import { eventRenderedViewColumns } from '../../../../detections/configurations/security_solution_detections/columns';
import type {
BrowserField,
BrowserFields,
} from '../../../../../common/search_strategy/index_fields';
import type { ColumnHeaderOptions } from '../../../../../common/types/timeline';
import { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common';
import { DEFAULT_TABLE_COLUMN_MIN_WIDTH, DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH } from '../constants';
import { defaultColumnHeaderType } from '../../../store/data_table/defaults';
import { ColumnHeaderOptions } from '../../../common/types';
const defaultActions: EuiDataGridColumnActions = {
showSortAsc: true,
@ -152,6 +149,48 @@ export const getSchema = (type: string | undefined): BUILT_IN_SCHEMA | undefined
}
};
const eventRenderedViewColumns: ColumnHeaderOptions[] = [
{
columnHeaderType: defaultColumnHeaderType,
id: '@timestamp',
displayAsText: i18n.translate(
'securitySolutionDataTable.EventRenderedView.timestampTitle.column',
{
defaultMessage: 'Timestamp',
}
),
initialWidth: DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH + 50,
actions: false,
isExpandable: false,
isResizable: false,
},
{
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.translate('securitySolutionDataTable.EventRenderedView.ruleTitle.column', {
defaultMessage: 'Rule',
}),
id: 'kibana.alert.rule.name',
initialWidth: DEFAULT_TABLE_COLUMN_MIN_WIDTH + 50,
linkField: 'kibana.alert.rule.uuid',
actions: false,
isExpandable: false,
isResizable: false,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'eventSummary',
displayAsText: i18n.translate(
'securitySolutionDataTable.EventRenderedView.eventSummary.column',
{
defaultMessage: 'Event Summary',
}
),
actions: false,
isExpandable: false,
isResizable: false,
},
];
/** Enriches the column headers with field details from the specified browserFields */
export const getColumnHeaders = (
headers: ColumnHeaderOptions[],

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
export const REMOVE_COLUMN = i18n.translate(
'xpack.securitySolution.columnHeaders.flyout.pane.removeColumnButtonLabel',
'securitySolutionDataTable.columnHeaders.flyout.pane.removeColumnButtonLabel',
{
defaultMessage: 'Remove column',
}

View file

@ -10,3 +10,12 @@ export const DEFAULT_TABLE_COLUMN_MIN_WIDTH = 180; // px
/** The default minimum width of a column of type `date` */
export const DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH = 190; // px
/** The default minimum width of a column (when a width for the column type is not specified) */
export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px
/** The minimum width of a resized column */
export const RESIZED_COLUMN_MIN_WITH = 70; // px
/** The default minimum width of a column of type `date` */
export const DEFAULT_DATE_COLUMN_MIN_WIDTH = 190; // px

View file

@ -0,0 +1,107 @@
/*
* 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 { CellActionsProvider } from '@kbn/cell-actions';
import { I18nProvider } from '@kbn/i18n-react';
import { CellValueElementProps } from '@kbn/timelines-plugin/common';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful-dnd';
// eslint-disable-next-line @kbn/eslint/module_migration
import { ThemeProvider } from 'styled-components';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { euiDarkVars } from '@kbn/ui-theme';
import { Store } from 'redux';
import { createStore as createReduxStore } from 'redux';
import type { Action } from '@kbn/ui-actions-plugin/public';
import { EuiButtonEmpty } from '@elastic/eui';
import { mockGlobalState } from '../../mock/global_state';
import { getMappedNonEcsValue } from './utils';
import { TableId } from '../..';
import { mockTimelineData } from '../../mock/mock_timeline_data';
import { DataTableComponent } from '.';
export default {
component: DataTableComponent,
title: 'DataTableComponent',
};
const createStore = (state: any) => createReduxStore(() => state, state);
interface Props {
children?: React.ReactNode;
store?: Store;
onDragEnd?: (result: DropResult, provided: ResponderProvided) => void;
cellActions?: Action[];
}
const StoryCellRenderer: React.FC<CellValueElementProps> = ({ columnId, data }) => (
<>
{getMappedNonEcsValue({
data,
fieldName: columnId,
})?.reduce((x) => x[0]) ?? ''}
</>
);
const StoryProviders: React.FC<Props> = ({ children, onDragEnd = () => {}, cellActions = [] }) => {
const store = createStore(mockGlobalState);
const queryClient = new QueryClient();
return (
<I18nProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<CellActionsProvider getTriggerCompatibleActions={() => Promise.resolve(cellActions)}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</I18nProvider>
);
};
const MockFieldBrowser = () => {
return (
<EuiButtonEmpty
color="text"
data-test-subj="show-field-browser"
iconType="tableOfContents"
size="xs"
onClick={() => window.alert('Not implemented')}
>
Field Browser
</EuiButtonEmpty>
);
};
export function Example() {
return (
<StoryProviders>
<DataTableComponent
browserFields={{}}
data={mockTimelineData}
id={TableId.test}
renderCellValue={StoryCellRenderer}
leadingControlColumns={[]}
unitCountText="10 events"
pagination={{
pageSize: 25,
pageIndex: 0,
onChangeItemsPerPage: () => {},
onChangePage: () => {},
}}
loadPage={() => {}}
rowRenderers={[]}
totalItems={mockTimelineData.length}
getFieldBrowser={() => <MockFieldBrowser />}
/>
</StoryProviders>
);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { ColumnHeaderOptions } from '../../../../common/types';
import type { ColumnHeaderOptions } from '../../common/types';
import {
hasCellActions,
mapSortDirectionToDirection,
@ -14,7 +14,7 @@ import {
} from './helpers';
import { euiThemeVars } from '@kbn/ui-theme';
import { mockDnsEvent } from '../../mock';
import { mockDnsEvent } from '../../mock/mock_timeline_data';
describe('helpers', () => {
describe('mapSortDirectionToDirection', () => {

View file

@ -10,9 +10,12 @@ import { isEmpty } from 'lodash/fp';
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import type { SortColumnTable } from '../../../../common/types';
import type { TimelineItem, TimelineNonEcsData } from '../../../../common/search_strategy';
import type { ColumnHeaderOptions, SortDirection } from '../../../../common/types/timeline';
import {
ColumnHeaderOptions,
TimelineItem,
TimelineNonEcsData,
} from '@kbn/timelines-plugin/common';
import type { SortColumnTable, SortDirectionTable } from '../../common/types';
/**
* Creates mapping of eventID -> fieldData for given fieldsToKeep. Used to store additional field
@ -42,7 +45,7 @@ export const isEventBuildingBlockType = (event: Ecs): boolean =>
!isEmpty(event.kibana?.alert?.building_block_type);
/** Maps (Redux) `SortDirection` to the `direction` values used by `EuiDataGrid` */
export const mapSortDirectionToDirection = (sortDirection: SortDirection): 'asc' | 'desc' => {
export const mapSortDirectionToDirection = (sortDirection: SortDirectionTable): 'asc' | 'desc' => {
switch (sortDirection) {
case 'asc': // fall through
case 'desc':

View file

@ -13,12 +13,14 @@ import { DataTableComponent } from '.';
import { REMOVE_COLUMN } from './column_headers/translations';
import { useMountAppended } from '../../utils/use_mount_appended';
import type { EuiDataGridColumn } from '@elastic/eui';
import { defaultHeaders, mockGlobalState, mockTimelineData, TestProviders } from '../../mock';
import { mockBrowserFields } from '../../containers/source/mock';
import { getMappedNonEcsValue } from '../../../timelines/components/timeline/body/data_driven_columns';
import type { CellValueElementProps } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import { SecurityCellActionsTrigger } from '../cell_actions';
import { TableId } from '../../common/types';
import { defaultHeaders } from '../../mock/header';
import { mockGlobalState } from '../../mock/global_state';
import { mockTimelineData } from '../../mock/mock_timeline_data';
import { TestProviders } from '../../mock/test_providers';
import { CellValueElementProps } from '@kbn/timelines-plugin/common';
import { mockBrowserFields } from '../../mock/mock_source';
import { getMappedNonEcsValue } from './utils';
const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
@ -41,20 +43,6 @@ jest.mock('./column_headers/helpers', () => ({
getColumnHeaders: () => mockGetColumnHeaders(),
}));
jest.mock('@kbn/kibana-react-plugin/public', () => {
const originalModule = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...originalModule,
useKibana: () => ({
services: {
triggersActionsUi: {
getFieldBrowser: jest.fn(),
},
},
}),
};
});
jest.mock('../../hooks/use_selector', () => ({
useShallowEqualSelector: () => mockGlobalState.dataTable.tableById['table-test'],
useDeepEqualSelector: () => mockGlobalState.dataTable.tableById['table-test'],
@ -104,6 +92,7 @@ describe('DataTable', () => {
onChangeItemsPerPage: jest.fn(),
onChangePage: jest.fn(),
},
getFieldBrowser: jest.fn(),
};
beforeEach(() => {
@ -178,13 +167,17 @@ describe('DataTable', () => {
const data = mockTimelineData.slice(0, 1);
const wrapper = mount(
<TestProviders>
<DataTableComponent {...props} data={data} />
<DataTableComponent
cellActionsTriggerId="mockCellActionsTrigger"
{...props}
data={data}
/>
</TestProviders>
);
wrapper.update();
expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith({
triggerId: SecurityCellActionsTrigger.DEFAULT,
triggerId: 'mockCellActionsTrigger',
fields: [
{
name: '@timestamp',
@ -200,10 +193,10 @@ describe('DataTable', () => {
});
});
test('does not render cell actions if disableCellActions is true', () => {
test('does not render cell actions if cellActionsTriggerId is not specified', () => {
const wrapper = mount(
<TestProviders>
<DataTableComponent {...props} data={mockTimelineData.slice(0, 1)} disableCellActions />
<DataTableComponent {...props} data={mockTimelineData.slice(0, 1)} />
</TestProviders>
);
wrapper.update();
@ -219,7 +212,11 @@ describe('DataTable', () => {
mockUseDataGridColumnsCellActions.mockReturnValueOnce([]);
const wrapper = mount(
<TestProviders>
<DataTableComponent {...props} data={mockTimelineData.slice(0, 1)} />
<DataTableComponent
cellActionsTriggerId="mockCellActionsTrigger"
{...props}
data={mockTimelineData.slice(0, 1)}
/>
</TestProviders>
);
wrapper.update();
@ -237,7 +234,11 @@ describe('DataTable', () => {
mockUseDataGridColumnsCellActions.mockReturnValueOnce([[() => <div />]]);
const wrapper = mount(
<TestProviders>
<DataTableComponent {...props} data={mockTimelineData.slice(0, 1)} />
<DataTableComponent
cellActionsTriggerId="mockCellActionsTrigger"
{...props}
data={mockTimelineData.slice(0, 1)}
/>
</TestProviders>
);
wrapper.update();

View file

@ -5,6 +5,8 @@
* 2.0.
*/
/* eslint-disable @kbn/eslint/module_migration */
import type {
EuiDataGridRefProps,
EuiDataGridColumn,
@ -14,6 +16,7 @@ import type {
EuiDataGridControlColumn,
EuiDataGridPaginationProps,
EuiDataGridRowHeightsOptions,
EuiDataGridProps,
} from '@elastic/eui';
import { EuiDataGrid, EuiProgress } from '@elastic/eui';
import { getOr } from 'lodash/fp';
@ -23,44 +26,60 @@ import { useDispatch } from 'react-redux';
import styled, { ThemeContext } from 'styled-components';
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
import type { FieldBrowserOptions } from '@kbn/triggers-actions-ui-plugin/public';
import type {
FieldBrowserOptions,
FieldBrowserProps,
} from '@kbn/triggers-actions-ui-plugin/public';
import { i18n } from '@kbn/i18n';
import {
useDataGridColumnsSecurityCellActions,
SecurityCellActionsTrigger,
type UseDataGridColumnsSecurityCellActionsProps,
} from '../cell_actions';
import type {
BrowserFields,
CellValueElementProps,
ColumnHeaderOptions,
RowRenderer,
} from '../../../../common/types/timeline';
import type { TimelineItem } from '../../../../common/search_strategy/timeline';
TimelineItem,
} from '@kbn/timelines-plugin/common';
import { useDataGridColumnsCellActions } from '@kbn/cell-actions';
import { DataTableModel, DataTableState } from '../../store/data_table/types';
import { getColumnHeader, getColumnHeaders } from './column_headers/helpers';
import { addBuildingBlockStyle, mapSortDirectionToDirection, mapSortingColumns } from './helpers';
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
import { REMOVE_COLUMN } from './column_headers/translations';
import { dataTableActions, dataTableSelectors } from '../../store/data_table';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
import { useKibana } from '../../lib/kibana';
import { getPageRowIndex } from './pagination';
import { UnitCount } from '../toolbar/unit';
import { useShallowEqualSelector } from '../../hooks/use_selector';
import { tableDefaults } from '../../store/data_table/defaults';
const DATA_TABLE_ARIA_LABEL = i18n.translate('xpack.securitySolution.dataTable.ariaLabel', {
const DATA_TABLE_ARIA_LABEL = i18n.translate('securitySolutionDataTable.dataTable.ariaLabel', {
defaultMessage: 'Alerts',
});
export interface DataTableProps {
type GetFieldBrowser = (props: FieldBrowserProps) => void;
type NonCustomizableGridProps =
| 'id'
| 'data-test-subj'
| 'aria-label'
| 'aria-labelledby'
| 'columns'
| 'columnVisibility'
| 'gridStyle'
| 'leadingControlColumns'
| 'toolbarVisibility'
| 'rowCount'
| 'renderCellValue'
| 'sorting'
| 'onColumnResize'
| 'pagination'
| 'rowHeightsOptions';
interface BaseDataTableProps {
additionalControls?: React.ReactNode;
browserFields: BrowserFields;
bulkActions?: BulkActionsProp;
data: TimelineItem[];
disableCellActions?: boolean;
fieldBrowserOptions?: FieldBrowserOptions;
id: string;
leadingControlColumns: EuiDataGridControlColumn[];
@ -73,8 +92,12 @@ export interface DataTableProps {
totalItems: number;
rowHeightsOptions?: EuiDataGridRowHeightsOptions;
isEventRenderedView?: boolean;
getFieldBrowser: GetFieldBrowser;
cellActionsTriggerId?: string;
}
export type DataTableProps = BaseDataTableProps & Omit<EuiDataGridProps, NonCustomizableGridProps>;
const ES_LIMIT_COUNT = 9999;
const gridStyle = (isEventRenderedView: boolean | undefined = false): EuiDataGridStyle => ({
@ -115,7 +138,6 @@ export const DataTableComponent = React.memo<DataTableProps>(
browserFields,
bulkActions = true,
data,
disableCellActions = false,
fieldBrowserOptions,
hasCrudPermissions,
id,
@ -128,12 +150,14 @@ export const DataTableComponent = React.memo<DataTableProps>(
totalItems,
rowHeightsOptions,
isEventRenderedView = false,
getFieldBrowser,
cellActionsTriggerId,
...otherProps
}) => {
const {
triggersActionsUi: { getFieldBrowser },
} = useKibana().services;
const getDataTable = dataTableSelectors.getTableByIdSelector();
const dataTable = useShallowEqualSelector((state) => getDataTable(state, id) ?? tableDefaults);
const dataTable = useShallowEqualSelector<DataTableModel, DataTableState>(
(state) => getDataTable(state, id) ?? tableDefaults
);
const { columns, selectedEventIds, showCheckboxes, sort, isLoading, defaultColumns } =
dataTable;
@ -303,8 +327,8 @@ export const DataTableComponent = React.memo<DataTableProps>(
[dispatch, id]
);
const columnsCellActionsProps = useMemo<UseDataGridColumnsSecurityCellActionsProps>(() => {
const fields = disableCellActions
const columnsCellActionsProps = useMemo(() => {
const fields = !cellActionsTriggerId
? []
: columnHeaders.map((column) => ({
name: column.id,
@ -317,16 +341,16 @@ export const DataTableComponent = React.memo<DataTableProps>(
}));
return {
triggerId: SecurityCellActionsTrigger.DEFAULT,
triggerId: cellActionsTriggerId || '',
fields,
metadata: {
scopeId: id,
},
dataGridRef,
};
}, [disableCellActions, columnHeaders, data, id]);
}, [columnHeaders, cellActionsTriggerId, id, data]);
const columnsCellActions = useDataGridColumnsSecurityCellActions(columnsCellActionsProps);
const columnsCellActions = useDataGridColumnsCellActions(columnsCellActionsProps);
const columnsWithCellActions: EuiDataGridColumn[] = useMemo(
() =>
@ -417,6 +441,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
<>
<EuiDataGridContainer hideLastPage={totalItems > ES_LIMIT_COUNT}>
<EuiDataGrid
{...otherProps}
id={'body-data-grid'}
data-test-subj="body-data-grid"
aria-label={DATA_TABLE_ARIA_LABEL}

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { ColumnHeaderOptions, ColumnId } from '../../../../common/types';
import type { SortDirectionTable as SortDirection } from '../../../../common/types/data_table';
import type { ColumnHeaderOptions, ColumnId } from '../../common/types';
import type { SortDirectionTable as SortDirection } from '../../common/types/data_table';
export type KueryFilterQueryKind = 'kuery' | 'lucene' | 'eql';

View file

@ -0,0 +1,22 @@
/*
* 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 { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
export const getMappedNonEcsValue = ({
data,
fieldName,
}: {
data: TimelineNonEcsData[];
fieldName: string;
}): string[] | undefined => {
const item = data.find((d) => d.field === fieldName);
if (item != null && item.value != null) {
return item.value;
}
return undefined;
};

View file

@ -0,0 +1,39 @@
/*
* 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 { TimelineItem } from '@kbn/timelines-plugin/common';
export type AlertWorkflowStatus = 'open' | 'closed' | 'acknowledged';
export interface CustomBulkAction {
key: string;
label: string;
disableOnQuery?: boolean;
disabledLabel?: string;
onClick: (items?: TimelineItem[]) => void;
['data-test-subj']?: string;
}
export type CustomBulkActionProp = Omit<CustomBulkAction, 'onClick'> & {
onClick: (eventIds: string[]) => void;
};
export interface BulkActionsObjectProp {
alertStatusActions?: boolean;
onAlertStatusActionSuccess?: OnUpdateAlertStatusSuccess;
onAlertStatusActionFailure?: OnUpdateAlertStatusError;
customBulkActions?: CustomBulkAction[];
}
export type OnUpdateAlertStatusSuccess = (
updated: number,
conflicts: number,
status: AlertWorkflowStatus
) => void;
export type OnUpdateAlertStatusError = (status: AlertWorkflowStatus, error: Error) => void;
export type BulkActionsProp = boolean | BulkActionsObjectProp;

View file

@ -0,0 +1,11 @@
/*
* 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 * as i18n from './translations';
export * from './styles';
export const defaultUnit = (n: number) => i18n.ALERTS_UNIT(n);

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
// eslint-disable-next-line @kbn/eslint/module_migration
import styled from 'styled-components';
export const UnitCount = styled.span`
font-size: ${({ theme }) => theme.eui.euiFontSizeXS};
font-weight: ${({ theme }) => theme.eui.euiFontWeightSemiBold};
border-right: ${({ theme }) => theme.eui.euiBorderThin};
margin-right: ${({ theme }) => theme.eui.euiSizeS};
padding-right: ${({ theme }) => theme.eui.euiSizeM};
`;

View file

@ -0,0 +1,14 @@
/*
* 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';
export const ALERTS_UNIT = (totalCount: number) =>
i18n.translate('securitySolutionDataTable.eventsTab.unit', {
values: { totalCount },
defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`,
});

View file

@ -0,0 +1,16 @@
/*
* 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 { shallowEqual, useSelector } from 'react-redux';
export type TypedUseSelectorHook = <TSelected, TState>(
selector: (state: TState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
) => TSelected;
export const useShallowEqualSelector: TypedUseSelectorHook = (selector) =>
useSelector(selector, shallowEqual);

View file

@ -0,0 +1,43 @@
/*
* 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.
*/
export { DataTableComponent } from './components/data_table';
export { dataTableActions, dataTableSelectors } from './store/data_table';
export { getTableByIdSelector } from './store/data_table/selectors';
export { dataTableReducer } from './store/data_table/reducer';
export {
tableDefaults,
defaultColumnHeaderType,
defaultHeaders,
} from './store/data_table/defaults';
export type { TableState, DataTableState, TableById } from './store/data_table/types';
export type { DataTableModel, SubsetDataTableModel } from './store/data_table/model';
export {
Direction,
tableEntity,
FILTER_OPEN,
TimelineTabs,
TableId,
TableEntityType,
} from './common/types';
export type {
TableIdLiteral,
ViewSelection,
SortDirectionTable,
SortColumnTable,
} from './common/types';
export { getColumnHeaders } from './components/data_table/column_headers/helpers';
export {
getEventIdToDataMapping,
addBuildingBlockStyle,
isEventBuildingBlockType,
} from './components/data_table/helpers';
export { getPageRowIndex } from './components/data_table/pagination';

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
roots: ['<rootDir>/x-pack/packages/kbn-securitysolution-data-table'],
rootDir: '../../..',
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/securitysolution-data-table",
"owner": "@elastic/security-threat-hunting-investigations"
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
export const demoEndpointLibraryLoadEvent: Ecs = {
file: {
path: ['C:\\Windows\\System32\\bcrypt.dll'],
hash: {
md5: ['00439016776de367bad087d739a03797'],
sha1: ['2c4ba5c1482987d50a182bad915f52cd6611ee63'],
sha256: ['e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd'],
},
name: ['bcrypt.dll'],
},
host: {
os: {
full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
name: ['Windows'],
version: ['1809 (10.0.17763.1697)'],
family: ['windows'],
kernel: ['1809 (10.0.17763.1697)'],
platform: ['windows'],
},
mac: ['aa:bb:cc:dd:ee:ff'],
name: ['win2019-endpoint-1'],
architecture: ['x86_64'],
ip: ['10.1.2.3'],
id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
},
event: {
category: ['library'],
kind: ['event'],
created: ['2021-02-05T21:27:23.921Z'],
module: ['endpoint'],
action: ['load'],
type: ['start'],
id: ['LzzWB9jjGmCwGMvk++++Da5H'],
dataset: ['endpoint.events.library'],
},
process: {
name: ['sshd.exe'],
pid: [9644],
entity_id: [
'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTk2NDQtMTMyNTcwMzQwNDEuNzgyMTczODAw',
],
executable: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe'],
},
agent: {
type: ['endpoint'],
},
user: {
name: ['SYSTEM'],
domain: ['NT AUTHORITY'],
},
message: ['Endpoint DLL load event'],
timestamp: '2021-02-05T21:27:23.921Z',
_id: 'IAUYdHcBGrBB52F2zo8Q',
};

View file

@ -0,0 +1,80 @@
/*
* 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 { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
export const demoEndpointProcessExecutionMalwarePreventionAlert: Ecs = {
process: {
hash: {
md5: ['177afc1eb0be88eb9983fb74111260c4'],
sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
},
entity_id: [
'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTY5MjAtMTMyNDg5OTk2OTAuNDgzMzA3NzAw',
],
executable: [
'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
],
name: [
'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
],
pid: [6920],
args: [
'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
],
},
host: {
os: {
full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
name: ['Windows'],
version: ['1809 (10.0.17763.1518)'],
platform: ['windows'],
family: ['windows'],
kernel: ['1809 (10.0.17763.1518)'],
},
mac: ['aa:bb:cc:dd:ee:ff'],
architecture: ['x86_64'],
ip: ['10.1.2.3'],
id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
name: ['win2019-endpoint-1'],
},
file: {
mtime: ['2020-11-04T21:40:51.494Z'],
path: [
'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
],
owner: ['sean'],
hash: {
md5: ['177afc1eb0be88eb9983fb74111260c4'],
sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
},
name: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe'],
extension: ['exe'],
size: [1604112],
},
event: {
category: ['malware', 'intrusion_detection', 'process'],
outcome: ['success'],
severity: [73],
code: ['malicious_file'],
action: ['execution'],
id: ['LsuMZVr+sdhvehVM++++Gp2Y'],
kind: ['alert'],
created: ['2020-11-04T21:41:30.533Z'],
module: ['endpoint'],
type: ['info', 'start', 'denied'],
dataset: ['endpoint.alerts'],
},
agent: {
type: ['endpoint'],
},
timestamp: '2020-11-04T21:41:30.533Z',
message: ['Malware Prevention Alert'],
_id: '0dA2lXUBn9bLIbfPkY7d',
};

View file

@ -0,0 +1,64 @@
/*
* 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 { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
export const demoEndpointRegistryModificationEvent: Ecs = {
host: {
os: {
full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
name: ['Windows'],
version: ['1809 (10.0.17763.1697)'],
family: ['windows'],
kernel: ['1809 (10.0.17763.1697)'],
platform: ['windows'],
},
mac: ['aa:bb:cc:dd:ee:ff'],
name: ['win2019-endpoint-1'],
architecture: ['x86_64'],
ip: ['10.1.2.3'],
id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
},
event: {
category: ['registry'],
kind: ['event'],
created: ['2021-02-04T13:44:31.559Z'],
module: ['endpoint'],
action: ['modification'],
type: ['change'],
id: ['LzzWB9jjGmCwGMvk++++CbOn'],
dataset: ['endpoint.events.registry'],
},
process: {
name: ['GoogleUpdate.exe'],
pid: [7408],
entity_id: [
'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTc0MDgtMTMyNTY5MTk4NDguODY4NTI0ODAw',
],
executable: ['C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe'],
},
registry: {
hive: ['HKLM'],
key: [
'SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState',
],
path: [
'HKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValue',
],
value: ['StateValue'],
},
agent: {
type: ['endpoint'],
},
user: {
name: ['SYSTEM'],
domain: ['NT AUTHORITY'],
},
message: ['Endpoint registry event'],
timestamp: '2021-02-04T13:44:31.559Z',
_id: '4cxLbXcBGrBB52F2uOfF',
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,57 @@
/*
* 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 { TableId } from '../common/types';
import { defaultHeaders } from './header';
// FIXME add strong typings
export const mockGlobalState: any = {
app: {
notesById: {},
errors: [
{ id: 'error-id-1', title: 'title-1', message: ['error-message-1'] },
{ id: 'error-id-2', title: 'title-2', message: ['error-message-2'] },
],
},
dataTable: {
tableById: {
[TableId.test]: {
columns: defaultHeaders,
defaultColumns: defaultHeaders,
dataViewId: 'security-solution-default',
deletedEventIds: [],
expandedDetail: {},
filters: [],
indexNames: ['.alerts-security.alerts-default'],
isSelectAllChecked: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
loadingEventIds: [],
selectedEventIds: {},
showCheckboxes: false,
sort: [
{
columnId: '@timestamp',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
graphEventId: '',
sessionViewConfig: null,
selectAll: false,
id: TableId.test,
title: '',
initialized: true,
updated: 1663882629000,
isLoading: false,
queryFields: [],
totalCount: 0,
},
},
},
};

View file

@ -0,0 +1,144 @@
/*
* 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 { defaultColumnHeaderType } from '@kbn/timelines-plugin/common/types';
import type { ColumnHeaderOptions } from '../common/types';
import {
DEFAULT_COLUMN_MIN_WIDTH,
DEFAULT_DATE_COLUMN_MIN_WIDTH,
} from '../components/data_table/constants';
export const defaultHeaders: ColumnHeaderOptions[] = [
{
category: 'base',
columnHeaderType: defaultColumnHeaderType,
description:
'Date/time when the event originated.\nFor log events this is the date/time when the event was generated, and not when it was read.\nRequired field for all events.',
example: '2016-05-23T08:05:34.853Z',
id: '@timestamp',
type: 'date',
esTypes: ['date'],
aggregatable: true,
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
},
{
category: 'event',
columnHeaderType: defaultColumnHeaderType,
description:
"Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.",
example: '7',
id: 'event.severity',
type: 'number',
esTypes: ['long'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'event',
columnHeaderType: defaultColumnHeaderType,
description:
'Event category.\nThis contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.',
example: 'user-management',
id: 'event.category',
type: 'string',
esTypes: ['keyword'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'event',
columnHeaderType: defaultColumnHeaderType,
description:
'The action captured by the event.\nThis describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.',
example: 'user-password-change',
id: 'event.action',
type: 'string',
esTypes: ['keyword'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'host',
columnHeaderType: defaultColumnHeaderType,
description:
'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.',
example: '',
id: 'host.name',
type: 'string',
esTypes: ['keyword'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'source',
columnHeaderType: defaultColumnHeaderType,
description: 'IP address of the source.\nCan be one or multiple IPv4 or IPv6 addresses.',
example: '',
id: 'source.ip',
type: 'ip',
esTypes: ['ip'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'destination',
columnHeaderType: defaultColumnHeaderType,
description: 'IP address of the destination.\nCan be one or multiple IPv4 or IPv6 addresses.',
example: '',
id: 'destination.ip',
type: 'ip',
esTypes: ['ip'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
aggregatable: true,
category: 'destination',
columnHeaderType: defaultColumnHeaderType,
description: 'Bytes sent from the source to the destination',
example: '123',
format: 'bytes',
id: 'destination.bytes',
type: 'number',
esTypes: ['long'],
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'user',
columnHeaderType: defaultColumnHeaderType,
description: 'Short name or login of the user.',
example: 'albert',
id: 'user.name',
type: 'string',
esTypes: ['keyword'],
aggregatable: true,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'base',
columnHeaderType: defaultColumnHeaderType,
description: 'Each document has an _id that uniquely identifies it',
example: 'Y-6TfmcB0WOhS6qyMv3s',
id: '_id',
type: 'string',
esTypes: [], // empty for _id
aggregatable: false,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
{
category: 'base',
columnHeaderType: defaultColumnHeaderType,
description:
'For log events the message field contains the log message.\nIn other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.',
example: 'Hello World',
id: 'message',
type: 'string',
esTypes: ['text'],
aggregatable: false,
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
},
];

View file

@ -0,0 +1,27 @@
/*
* 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 { IStorage } from '@kbn/kibana-utils-plugin/public';
export const localStorageMock = (): IStorage => {
let store: Record<string, unknown> = {};
return {
getItem: (key: string) => {
return store[key] || null;
},
setItem: (key: string, value: unknown) => {
store[key] = value;
},
clear() {
store = {};
},
removeItem(key: string) {
delete store[key];
},
};
};

View file

@ -0,0 +1,515 @@
/*
* 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 { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { BrowserFields } from '@kbn/timelines-plugin/common';
import { DEFAULT_INDEX_PATTERN } from '../common/constants';
export const mockBrowserFields: BrowserFields = {
agent: {
fields: {
'agent.ephemeral_id': {
aggregatable: true,
category: 'agent',
description:
'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
example: '8a4f500f',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'agent.ephemeral_id',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'agent.hostname': {
aggregatable: true,
category: 'agent',
description: null,
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'agent.hostname',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'agent.id': {
aggregatable: true,
category: 'agent',
description:
'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
example: '8a4f500d',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'agent.id',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'agent.name': {
aggregatable: true,
category: 'agent',
description:
'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.',
example: 'foo',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'agent.name',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
},
},
auditd: {
fields: {
'auditd.data.a0': {
aggregatable: true,
category: 'auditd',
description: null,
example: null,
format: '',
indexes: ['auditbeat'],
name: 'auditd.data.a0',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'auditd.data.a1': {
aggregatable: true,
category: 'auditd',
description: null,
example: null,
format: '',
indexes: ['auditbeat'],
name: 'auditd.data.a1',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'auditd.data.a2': {
aggregatable: true,
category: 'auditd',
description: null,
example: null,
format: '',
indexes: ['auditbeat'],
name: 'auditd.data.a2',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
},
},
base: {
fields: {
'@timestamp': {
aggregatable: true,
category: 'base',
description:
'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
example: '2016-05-23T08:05:34.853Z',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: '@timestamp',
searchable: true,
type: 'date',
esTypes: ['date'],
readFromDocValues: true,
},
_id: {
category: 'base',
description: 'Each document has an _id that uniquely identifies it',
example: 'Y-6TfmcB0WOhS6qyMv3s',
name: '_id',
type: 'string',
esTypes: [],
searchable: true,
aggregatable: false,
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
},
message: {
category: 'base',
description:
'For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.',
example: 'Hello World',
name: 'message',
type: 'string',
esTypes: ['text'],
searchable: true,
aggregatable: false,
format: 'string',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
},
},
},
client: {
fields: {
'client.address': {
aggregatable: true,
category: 'client',
description:
'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'client.address',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'client.bytes': {
aggregatable: true,
category: 'client',
description: 'Bytes sent from the client to the server.',
example: '184',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'client.bytes',
searchable: true,
type: 'number',
esTypes: ['long'],
},
'client.domain': {
aggregatable: true,
category: 'client',
description: 'Client domain.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'client.domain',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'client.geo.country_iso_code': {
aggregatable: true,
category: 'client',
description: 'Country ISO code.',
example: 'CA',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'client.geo.country_iso_code',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
},
},
cloud: {
fields: {
'cloud.account.id': {
aggregatable: true,
category: 'cloud',
description:
'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.',
example: '666777888999',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'cloud.account.id',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'cloud.availability_zone': {
aggregatable: true,
category: 'cloud',
description: 'Availability zone in which this host is running.',
example: 'us-east-1c',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'cloud.availability_zone',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
},
},
container: {
fields: {
'container.id': {
aggregatable: true,
category: 'container',
description: 'Unique container id.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'container.id',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'container.image.name': {
aggregatable: true,
category: 'container',
description: 'Name of the image the container was built on.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'container.image.name',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'container.image.tag': {
aggregatable: true,
category: 'container',
description: 'Container image tag.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'container.image.tag',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
},
},
destination: {
fields: {
'destination.address': {
aggregatable: true,
category: 'destination',
description:
'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'destination.address',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'destination.bytes': {
aggregatable: true,
category: 'destination',
description: 'Bytes sent from the destination to the source.',
example: '184',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'destination.bytes',
searchable: true,
type: 'number',
esTypes: ['long'],
},
'destination.domain': {
aggregatable: true,
category: 'destination',
description: 'Destination domain.',
example: null,
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'destination.domain',
searchable: true,
type: 'string',
esTypes: ['keyword'],
},
'destination.ip': {
aggregatable: true,
category: 'destination',
description:
'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'destination.ip',
searchable: true,
type: 'ip',
esTypes: ['ip'],
},
'destination.port': {
aggregatable: true,
category: 'destination',
description: 'Port of the destination.',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'destination.port',
searchable: true,
type: 'number',
esTypes: ['long'],
},
},
},
event: {
fields: {
'event.end': {
category: 'event',
description:
'event.end contains the date when the event ended or when the activity was last observed.',
example: null,
format: '',
indexes: DEFAULT_INDEX_PATTERN,
name: 'event.end',
searchable: true,
type: 'date',
esTypes: ['date'],
aggregatable: true,
},
'event.action': {
category: 'event',
description:
'The action captured by the event. This describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.',
example: 'user-password-change',
name: 'event.action',
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
format: 'string',
indexes: DEFAULT_INDEX_PATTERN,
},
'event.category': {
category: 'event',
description:
'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. `event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.',
example: 'authentication',
name: 'event.category',
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
format: 'string',
indexes: DEFAULT_INDEX_PATTERN,
},
'event.severity': {
category: 'event',
description:
"The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in `log.syslog.severity.code`. `event.severity` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the `log.syslog.severity.code` to `event.severity`.",
example: 7,
name: 'event.severity',
type: 'number',
esTypes: ['long'],
format: 'number',
searchable: true,
aggregatable: true,
indexes: DEFAULT_INDEX_PATTERN,
},
},
},
host: {
fields: {
'host.name': {
category: 'host',
description:
'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.',
name: 'host.name',
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
format: 'string',
indexes: DEFAULT_INDEX_PATTERN,
},
},
},
source: {
fields: {
'source.ip': {
aggregatable: true,
category: 'source',
description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'source.ip',
searchable: true,
type: 'ip',
esTypes: ['ip'],
},
'source.port': {
aggregatable: true,
category: 'source',
description: 'Port of the source.',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'source.port',
searchable: true,
type: 'number',
esTypes: ['long'],
},
},
},
user: {
fields: {
'user.name': {
category: 'user',
description: 'Short name or login of the user.',
example: 'albert',
name: 'user.name',
type: 'string',
esTypes: ['keyword'],
searchable: true,
aggregatable: true,
format: 'string',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
},
},
},
nestedField: {
fields: {
'nestedField.firstAttributes': {
aggregatable: false,
category: 'nestedField',
description: '',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'nestedField.firstAttributes',
searchable: true,
type: 'string',
subType: {
nested: {
path: 'nestedField',
},
},
},
'nestedField.secondAttributes': {
aggregatable: false,
category: 'nestedField',
description: '',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'nestedField.secondAttributes',
searchable: true,
type: 'string',
subType: {
nested: {
path: 'nestedField',
},
},
},
'nestedField.thirdAttributes': {
aggregatable: false,
category: 'nestedField',
description: '',
example: '',
format: '',
indexes: ['auditbeat', 'filebeat', 'packetbeat'],
name: 'nestedField.thirdAttributes',
searchable: true,
type: 'date',
subType: {
nested: {
path: 'nestedField',
},
},
},
},
},
};
export const mockRuntimeMappings: MappingRuntimeFields = {
'@a.runtime.field': {
script: {
source: 'emit("Radical dude: " + doc[\'host.name\'].value)',
},
type: 'keyword',
},
};

View file

@ -0,0 +1,74 @@
/*
* 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 { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
export { demoTimelineData as mockTimelineData } from './demo_data/timeline';
export { demoEndpointRegistryModificationEvent as mockEndpointRegistryModificationEvent } from './demo_data/endpoint/registry_modification_event';
export { demoEndpointLibraryLoadEvent as mockEndpointLibraryLoadEvent } from './demo_data/endpoint/library_load_event';
export { demoEndpointProcessExecutionMalwarePreventionAlert as mockEndpointProcessExecutionMalwarePreventionAlert } from './demo_data/endpoint/process_execution_malware_prevention_alert';
export const mockDnsEvent: Ecs = {
_id: 'VUTUqm0BgJt5sZM7nd5g',
destination: {
domain: ['ten.one.one.one'],
port: [53],
bytes: [137],
ip: ['10.1.1.1'],
geo: {
continent_name: ['Oceania'],
location: {
lat: [-33.494],
lon: [143.2104],
},
country_iso_code: ['AU'],
country_name: ['Australia'],
city_name: [''],
},
},
host: {
architecture: ['armv7l'],
id: ['host-id'],
os: {
family: ['debian'],
platform: ['raspbian'],
version: ['9 (stretch)'],
name: ['Raspbian GNU/Linux'],
kernel: ['4.19.57-v7+'],
},
name: ['iot.example.com'],
},
dns: {
question: {
name: ['lookup.example.com'],
type: ['A'],
},
response_code: ['NOERROR'],
resolved_ip: ['10.1.2.3'],
},
timestamp: '2019-10-08T10:05:23.241Z',
network: {
community_id: ['1:network-community_id'],
direction: ['outbound'],
bytes: [177],
transport: ['udp'],
protocol: ['dns'],
},
event: {
duration: [6937500],
category: ['network_traffic'],
dataset: ['dns'],
kind: ['event'],
end: ['2019-10-08T10:05:23.248Z'],
start: ['2019-10-08T10:05:23.241Z'],
},
source: {
port: [58732],
bytes: [40],
ip: ['10.9.9.9'],
},
};

View file

@ -0,0 +1,86 @@
/*
* 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.
*/
/* eslint-disable @kbn/eslint/module_migration */
import { euiDarkVars } from '@kbn/ui-theme';
import { I18nProvider } from '@kbn/i18n-react';
import React from 'react';
import type { DropResult, ResponderProvided } from 'react-beautiful-dnd';
import { DragDropContext } from 'react-beautiful-dnd';
import { Provider as ReduxStoreProvider } from 'react-redux';
import type { Store } from 'redux';
import { ThemeProvider } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createStore as createReduxStore } from 'redux';
import type { Action } from '@kbn/ui-actions-plugin/public';
import { CellActionsProvider } from '@kbn/cell-actions';
import { mockGlobalState } from './global_state';
import { localStorageMock } from './mock_local_storage';
interface Props {
children?: React.ReactNode;
store?: Store;
onDragEnd?: (result: DropResult, provided: ResponderProvided) => void;
cellActions?: Action[];
}
Object.defineProperty(window, 'localStorage', {
value: localStorageMock(),
});
window.scrollTo = jest.fn();
const createStore = (state: any) => createReduxStore(() => {}, state);
/** A utility for wrapping children in the providers required to run most tests */
export const TestProvidersComponent: React.FC<Props> = ({
children,
store = createStore(mockGlobalState),
onDragEnd = jest.fn(),
cellActions = [],
}) => {
const queryClient = new QueryClient();
return (
<I18nProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<CellActionsProvider getTriggerCompatibleActions={() => Promise.resolve(cellActions)}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</QueryClientProvider>
</ThemeProvider>
</ReduxStoreProvider>
</I18nProvider>
);
};
/**
* A utility for wrapping children in the providers required to run most tests
* WITH user privileges provider.
*/
const TestProvidersWithPrivilegesComponent: React.FC<Props> = ({
children,
store = createStore(mockGlobalState),
onDragEnd = jest.fn(),
cellActions = [],
}) => (
<I18nProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<CellActionsProvider getTriggerCompatibleActions={() => Promise.resolve(cellActions)}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</ThemeProvider>
</ReduxStoreProvider>
</I18nProvider>
);
export const TestProviders = React.memo(TestProvidersComponent);
export const TestProvidersWithPrivileges = React.memo(TestProvidersWithPrivilegesComponent);

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/securitysolution-data-table",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -6,10 +6,14 @@
*/
import actionCreatorFactory from 'typescript-fsa';
import type { SessionViewConfig } from '../../../../common/types/session_view';
import type { ExpandedDetailType } from '../../../../common/types/detail_panel';
import type { TimelineNonEcsData } from '../../../../common/search_strategy';
import type { ColumnHeaderOptions, SortColumnTable, ViewSelection } from '../../../../common/types';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import type { ExpandedDetailType } from '../../common/types/detail_panel';
import type {
ColumnHeaderOptions,
SessionViewConfig,
SortColumnTable,
ViewSelection,
} from '../../common/types';
import type { InitialyzeDataTableSettings, DataTablePersistInput } from './types';
const actionCreator = actionCreatorFactory('x-pack/security_solution/data-table');

View file

@ -4,7 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ColumnHeaderOptions, ColumnHeaderType } from '../../../../common/types';
import { ColumnHeaderOptions, ColumnHeaderType, VIEW_SELECTION } from '../../common/types';
import {
DEFAULT_TABLE_COLUMN_MIN_WIDTH,
DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH,
@ -14,7 +15,6 @@ import * as i18n from './translations';
export const defaultColumnHeaderType: ColumnHeaderType = 'not-filtered';
import { VIEW_SELECTION } from '../../../../common/constants';
export const defaultHeaders: ColumnHeaderOptions[] = [
{
columnHeaderType: defaultColumnHeaderType,

View file

@ -12,8 +12,7 @@ import {
updateDataTableColumnWidth,
} from './helpers';
import { mockGlobalState } from '../../mock/global_state';
import type { SortColumnTable } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import { SortColumnTable, TableId } from '../../common/types';
import type { DataTableModelSettings } from './model';
const id = 'foo';
@ -69,7 +68,7 @@ describe('setInitializeDataTableSettings', () => {
describe('updateDataTableColumnOrder', () => {
test('it returns the columns in the new expected order', () => {
const originalIdOrder = defaultTableById[TableId.test].columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
const originalIdOrder = defaultTableById[TableId.test].columns.map((x: any) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
// the new order swaps the positions of the first and second columns:
const newIdOrder = [originalIdOrder[1], originalIdOrder[0], ...originalIdOrder.slice(2)]; // ['event.severity', '@timestamp', 'event.category', '...']
@ -94,7 +93,7 @@ describe('updateDataTableColumnOrder', () => {
});
test('it omits unknown column IDs when re-ordering columns', () => {
const originalIdOrder = defaultTableById[TableId.test].columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
const originalIdOrder = defaultTableById[TableId.test].columns.map((x: any) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
const unknownColumId = 'does.not.exist';
const newIdOrder = [originalIdOrder[0], unknownColumId, ...originalIdOrder.slice(1)]; // ['@timestamp', 'does.not.exist', 'event.severity', 'event.category', '...']

View file

@ -9,9 +9,8 @@ import { omit, union } from 'lodash/fp';
import { isEmpty } from 'lodash';
import type { EuiDataGridColumn } from '@elastic/eui';
import type { SessionViewConfig } from '../../../../common/types/session_view';
import type { ExpandedDetail, ExpandedDetailType } from '../../../../common/types/detail_panel';
import type { ColumnHeaderOptions, SortColumnTable } from '../../../../common/types';
import type { ExpandedDetail, ExpandedDetailType } from '../../common/types/detail_panel';
import type { ColumnHeaderOptions, SessionViewConfig, SortColumnTable } from '../../common/types';
import type { TableToggleDetailPanel } from './actions';
import type { DataTablePersistInput, TableById } from './types';
import type { DataTableModelSettings } from './model';

View file

@ -7,10 +7,10 @@
import type { EuiDataGridColumn } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import type { ExpandedDetail } from '../../../../common/types/detail_panel';
import type { SessionViewConfig } from '../../../../common/types/session_view';
import type { TimelineNonEcsData } from '../../../../common/search_strategy';
import type { ColumnHeaderOptions, SortColumnTable, ViewSelection } from '../../../../common/types';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import { ExpandedDetail } from '../../common/types/detail_panel';
import { ColumnHeaderOptions, SessionViewConfig, SortColumnTable } from '../../common/types';
import { ViewSelection } from '../../common/types';
export interface DataTableModelSettings {
defaultColumns: Array<

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import {

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getOr } from 'lodash/fp';
import { createSelector } from 'reselect';
import { tableDefaults, getDataTableManageDefaults } from './defaults';

View file

@ -8,14 +8,14 @@
import { i18n } from '@kbn/i18n';
export const LOADING_EVENTS = i18n.translate(
'xpack.securitySolution.dataTable.loadingEventsDataLabel',
'securitySolutionDataTable.dataTable.loadingEventsDataLabel',
{
defaultMessage: 'Loading Events',
}
);
export const UNIT = (totalCount: number) =>
i18n.translate('xpack.securitySolution.dataTable.unit', {
i18n.translate('securitySolutionDataTable.dataTable.unit', {
values: { totalCount },
defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`,
});

View file

@ -6,7 +6,7 @@
*/
import type { EuiDataGridColumn } from '@elastic/eui';
import type { ColumnHeaderOptions, SortColumnTable } from '../../../../common/types';
import type { ColumnHeaderOptions, SortColumnTable } from '../../common/types';
import type { DataTableModel, DataTableModelSettings } from './model';
/** The state of all timelines is stored here */

View file

@ -0,0 +1,24 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": ["jest", "node", "react"]
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["target/**/*"],
"kbn_references": [
"@kbn/cell-actions",
"@kbn/timelines-plugin",
"@kbn/es-query",
"@kbn/triggers-actions-ui-plugin",
"@kbn/securitysolution-ecs",
"@kbn/rule-registry-plugin",
"@kbn/safer-lodash-set",
"@kbn/i18n",
"@kbn/ui-theme",
"@kbn/kibana-react-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/i18n-react",
"@kbn/ui-actions-plugin"
]
}

View file

@ -0,0 +1,31 @@
/*
* 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.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import { mount } from 'enzyme';
type WrapperOf<F extends (...args: any) => any> = (...args: Parameters<F>) => ReturnType<F>;
export type MountAppended = WrapperOf<typeof mount>;
export const useMountAppended = () => {
let root: HTMLElement;
beforeEach(() => {
root = document.createElement('div');
root.id = 'root';
document.body.appendChild(root);
});
afterEach(() => {
document.body.removeChild(root);
});
const mountAppended: MountAppended = (node, options) =>
mount(node, { ...options, attachTo: root });
return mountAppended;
};

View file

@ -15,9 +15,9 @@ import type { IFieldSubType } from '@kbn/es-query';
import type { FieldBrowserOptions } from '@kbn/triggers-actions-ui-plugin/public';
import type { ComponentType, JSXElementConstructor, ReactNode } from 'react';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import type { SortColumnTable } from '@kbn/securitysolution-data-table';
import type { OnRowSelected, SetEventsDeleted, SetEventsLoading } from '..';
import type { BrowserFields, TimelineNonEcsData } from '../../search_strategy';
import type { SortColumnTable } from '../data_table';
export type ColumnHeaderType = 'not-filtered' | 'text-filter';

View file

@ -8,7 +8,6 @@
import type { Status } from '../detection_engine/schemas/common';
export * from './timeline';
export * from './data_table';
export * from './detail_panel';
export * from './header_actions';
export * from './session_view';

View file

@ -6,7 +6,6 @@
*/
import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock';
import { TableId, TimelineId } from '../../../../common/types';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
@ -17,6 +16,8 @@ import { createStore } from '../../../common/store';
import { createFilterInCellActionFactory } from './filter_in';
import type { SecurityCellActionExecutionContext } from '../../types';
import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock';
import { TableId } from '@kbn/securitysolution-data-table';
import { TimelineId } from '../../../../common/types';
const services = createStartServicesMock();
const mockFilterManager = services.data.query.filterManager;

View file

@ -6,7 +6,6 @@
*/
import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock';
import { TableId, TimelineId } from '../../../../common/types';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
@ -17,6 +16,8 @@ import { createStore } from '../../../common/store';
import { createFilterOutCellActionFactory } from './filter_out';
import type { SecurityCellActionExecutionContext } from '../../types';
import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock';
import { TimelineId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
const services = createStartServicesMock();
const mockFilterManager = services.data.query.filterManager;

View file

@ -6,12 +6,11 @@
*/
import type { SecurityAppStore } from '../../../common/store/types';
import { TableId } from '../../../../common/types';
import { TableId, dataTableActions } from '@kbn/securitysolution-data-table';
import { createToggleColumnCellActionFactory } from './toggle_column';
import type { CellActionExecutionContext } from '@kbn/cell-actions';
import { mockGlobalState } from '../../../common/mock';
import { dataTableActions } from '../../../common/store/data_table';
const mockDispatch = jest.fn();
const mockGetState = jest.fn().mockReturnValue(mockGlobalState);

View file

@ -7,13 +7,16 @@
import { i18n } from '@kbn/i18n';
import { createCellActionFactory, type CellActionTemplate } from '@kbn/cell-actions';
import {
defaultColumnHeaderType,
tableDefaults,
dataTableSelectors,
} from '@kbn/securitysolution-data-table';
import { fieldHasCellActions } from '../../utils';
import type { SecurityAppStore } from '../../../common/store';
import { getScopedActions, isInTableScope, isTimelineScope } from '../../../helpers';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { defaultColumnHeaderType, tableDefaults } from '../../../common/store/data_table/defaults';
import { timelineSelectors } from '../../../timelines/store/timeline';
import { dataTableSelectors } from '../../../common/store/data_table';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import type { SecurityCellAction } from '../../types';
import { SecurityCellActionType } from '../../constants';

View file

@ -18,6 +18,7 @@ import type {
import type { RouteProps } from 'react-router-dom';
import type { AppMountParameters } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { TableState } from '@kbn/securitysolution-data-table';
import type { ExploreReducer, ExploreState } from '../explore';
import type { StartServices } from '../types';
@ -34,7 +35,6 @@ export interface RenderAppProps extends AppMountParameters {
import type { State, SubPluginsInitReducer } from '../common/store';
import type { Immutable } from '../../common/endpoint/types';
import type { AppAction } from '../common/store/actions';
import type { TableState } from '../common/store/data_table/types';
import type { GroupModel } from '../common/store/grouping';
export { SecurityPageName } from '../../common/constants';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { TableId } from '../../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { render } from '@testing-library/react';
import React from 'react';
import { RowAction } from '.';

View file

@ -9,6 +9,7 @@ import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { dataTableActions } from '@kbn/securitysolution-data-table';
import { RightPanelKey } from '../../../../flyout/right';
import type {
SetEventsDeleted,
@ -20,7 +21,6 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/
import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy';
import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline';
import { dataTableActions } from '../../../store/data_table';
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
type Props = EuiDataGridCellValueElementProps & {

View file

@ -10,6 +10,8 @@ import type { EuiDataGridCellValueElementProps, EuiDataGridControlColumn } from
import type { ComponentType } from 'react';
import React from 'react';
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
import { addBuildingBlockStyle, getPageRowIndex } from '@kbn/securitysolution-data-table';
import type { SortColumnTable } from '@kbn/securitysolution-data-table';
import type {
BrowserFields,
TimelineItem,
@ -22,10 +24,7 @@ import type {
ControlColumnProps,
OnRowSelected,
OnSelectAll,
SortColumnTable,
} from '../../../../common/types';
import { addBuildingBlockStyle } from '../data_table/helpers';
import { getPageRowIndex } from '../data_table/pagination';
import { RowAction } from './row_action';
const EmptyHeaderCellRender: ComponentType = () => null;

View file

@ -11,7 +11,6 @@ import React from 'react';
import type { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd';
import '../../mock/match_media';
import { TableId, TimelineId } from '../../../../common/types';
import { mockBrowserFields } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers';
@ -24,6 +23,8 @@ import {
getStyle,
} from './draggable_wrapper';
import { useMountAppended } from '../../utils/use_mount_appended';
import { TimelineId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
jest.mock('../../lib/kibana');

View file

@ -18,7 +18,7 @@ import { Draggable, Droppable } from 'react-beautiful-dnd';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { dragAndDropActions } from '../../store/drag_and_drop';
import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants';

View file

@ -10,10 +10,10 @@ import type { Dispatch } from 'redux';
import type { ActionCreator } from 'typescript-fsa';
import { getFieldIdFromDraggable, getProviderIdFromDraggable } from '@kbn/securitysolution-t-grid';
import { TableId } from '@kbn/securitysolution-data-table';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import { getScopedActions } from '../../../helpers';
import type { ColumnHeaderOptions } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import type { BrowserField, BrowserFields } from '../../../../common/search_strategy';
import { dragAndDropActions } from '../../store/actions';
import type { IdToDataProvider } from '../../store/drag_and_drop/model';

View file

@ -18,9 +18,8 @@ import {
onKeyDownFocusHandler,
} from '@kbn/timelines-plugin/public';
import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table';
import { isInTableScope, isTimelineScope } from '../../../helpers';
import { tableDefaults } from '../../store/data_table/defaults';
import { dataTableSelectors } from '../../store/data_table';
import { ADD_TIMELINE_BUTTON_CLASS_NAME } from '../../../timelines/components/flyout/add_timeline_button';
import { timelineSelectors } from '../../../timelines/store/timeline';
import type { BrowserFields } from '../../containers/source';

View file

@ -7,7 +7,7 @@
import { render } from '@testing-library/react';
import React from 'react';
import { TableId } from '../../../../common/types';
import { TableId, dataTableActions } from '@kbn/securitysolution-data-table';
import { HostsType } from '../../../explore/hosts/store/model';
import { TestProviders } from '../../mock';
import type { EventsQueryTabBodyComponentProps } from './events_query_tab_body';
@ -15,7 +15,6 @@ import { EventsQueryTabBody, ALERTS_EVENTS_HISTOGRAM_ID } from './events_query_t
import { useGlobalFullScreen } from '../../containers/use_full_screen';
import { licenseService } from '../../hooks/use_license';
import { mockHistory } from '../../mock/router';
import { dataTableActions } from '../../store/data_table';
const mockGetDefaultControlColumn = jest.fn();
jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({

View file

@ -10,8 +10,9 @@ import { useDispatch } from 'react-redux';
import { EuiCheckbox } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import type { CustomBulkAction, TableId } from '../../../../common/types';
import { dataTableActions } from '../../store/data_table';
import { dataTableActions } from '@kbn/securitysolution-data-table';
import type { TableId } from '@kbn/securitysolution-data-table';
import type { CustomBulkAction } from '../../../../common/types';
import { RowRendererId } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../events_viewer';
import { eventsDefaultModel } from '../events_viewer/default_model';
@ -45,6 +46,7 @@ import {
useReplaceUrlParams,
} from '../../utils/global_query_string/helpers';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
import { SecurityCellActionsTrigger } from '../cell_actions';
export const ALERTS_EVENTS_HISTOGRAM_ID = 'alertsOrEventsHistogramQuery';
@ -181,6 +183,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
)}
<StatefulEventsViewer
additionalFilters={toggleExternalAlertsCheckbox}
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
start={startDate}
end={endDate}
leadingControlColumns={leadingControlColumns}

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { tableDefaults } from '../../store/data_table/defaults';
import type { SubsetDataTableModel } from '../../store/data_table/model';
import { tableDefaults } from '@kbn/securitysolution-data-table';
import type { SubsetDataTableModel } from '@kbn/securitysolution-data-table';
import { defaultEventHeaders } from './default_event_headers';
export const eventsDefaultModel: SubsetDataTableModel = {

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import type { ViewSelection } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import type { ViewSelection } from '@kbn/securitysolution-data-table';
import { TableId } from '@kbn/securitysolution-data-table';
import type { CombineQueries } from '../../lib/kuery';
import { buildTimeRangeFilter, combineQueries } from '../../lib/kuery';

View file

@ -23,7 +23,7 @@ import { getDefaultControlColumn } from '../../../timelines/components/timeline/
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import type { UseFieldBrowserOptionsProps } from '../../../timelines/components/fields_browser';
import { useGetUserCasesPermissions } from '../../lib/kibana';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { mount } from 'enzyme';
jest.mock('../../lib/kibana');

View file

@ -5,6 +5,17 @@
* 2.0.
*/
import {
dataTableActions,
DataTableComponent,
defaultHeaders,
getEventIdToDataMapping,
} from '@kbn/securitysolution-data-table';
import type {
SubsetDataTableModel,
TableId,
ViewSelection,
} from '@kbn/securitysolution-data-table';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { AlertConsumers } from '@kbn/rule-data-utils';
import React, { useRef, useCallback, useMemo, useEffect, useState, useContext } from 'react';
@ -25,10 +36,7 @@ import type {
OnSelectAll,
SetEventsDeleted,
SetEventsLoading,
TableId,
ViewSelection,
} from '../../../../common/types';
import { dataTableActions } from '../../store/data_table';
import { InputsModelId } from '../../store/inputs/constants';
import type { State } from '../../store';
import { inputsActions } from '../../store/actions';
@ -46,7 +54,6 @@ import {
useSessionViewNavigation,
useSessionView,
} from '../../../timelines/components/timeline/session_tab_content/use_session_view';
import type { SubsetDataTableModel } from '../../store/data_table/model';
import {
EventsContainerLoading,
FullScreenContainer,
@ -57,13 +64,10 @@ import {
import { getDefaultViewSelection, getCombinedFilterQuery } from './helpers';
import { useTimelineEvents } from './use_timelines_events';
import { TableContext, EmptyTable, TableLoading } from './shared';
import { DataTableComponent } from '../data_table';
import type { AlertWorkflowStatus } from '../../types';
import { useQueryInspector } from '../page/manage_query';
import type { SetQuery } from '../../containers/use_global_time/types';
import { defaultHeaders } from '../../store/data_table/defaults';
import { checkBoxControlColumn, transformControlColumns } from '../control_columns';
import { getEventIdToDataMapping } from '../data_table/helpers';
import { RightTopMenu } from './right_top_menu';
import { useAlertBulkActions } from './use_alert_bulk_actions';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
@ -76,7 +80,6 @@ const SECURITY_ALERTS_CONSUMERS = [AlertConsumers.SIEM];
export interface EventsViewerProps {
defaultModel: SubsetDataTableModel;
disableCellActions?: boolean;
end: string;
entityType?: EntityType;
tableId: TableId;
@ -95,6 +98,7 @@ export interface EventsViewerProps {
indexNames?: string[];
bulkActions: boolean | BulkActionsProp;
additionalRightMenuOptions?: React.ReactNode[];
cellActionsTriggerId?: string;
}
/**
@ -103,27 +107,27 @@ export interface EventsViewerProps {
* NOTE: As of writting, it is not used in the Case_View component
*/
const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux> = ({
additionalFilters,
additionalRightMenuOptions,
bulkActions,
cellActionsTriggerId,
clearSelected,
currentFilter,
defaultModel,
disableCellActions,
end,
entityType = 'events',
tableId,
hasCrudPermissions = true,
indexNames,
leadingControlColumns,
pageFilters,
currentFilter,
onRuleChange,
pageFilters,
renderCellValue,
rowRenderers,
start,
sourcererScope,
additionalFilters,
hasCrudPermissions = true,
unit = defaultUnit,
indexNames,
bulkActions,
setSelected,
clearSelected,
additionalRightMenuOptions,
sourcererScope,
start,
tableId,
unit = defaultUnit,
}) => {
const dispatch = useDispatch();
const theme: EuiTheme = useContext(ThemeContext);
@ -151,7 +155,11 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
} = defaultModel,
} = useSelector((state: State) => eventsViewerSelector(state, tableId));
const { uiSettings, data } = useKibana().services;
const {
uiSettings,
data,
triggersActionsUi: { getFieldBrowser },
} = useKibana().services;
const [tableView, setTableView] = useState<ViewSelection>(
getDefaultViewSelection({
@ -565,11 +573,11 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
<ScrollableFlexItem grow={1}>
<StatefulEventContext.Provider value={activeStatefulEventContext}>
<DataTableComponent
cellActionsTriggerId={cellActionsTriggerId}
additionalControls={alertBulkActions}
unitCountText={unitCountText}
browserFields={browserFields}
data={nonDeletedEvents}
disableCellActions={disableCellActions}
id={tableId}
loadPage={loadPage}
renderCellValue={renderCellValue}
@ -582,6 +590,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
pagination={pagination}
isEventRenderedView={tableView === 'eventRenderedView'}
rowHeightsOptions={rowHeightsOptions}
getFieldBrowser={getFieldBrowser}
/>
</StatefulEventContext.Provider>
</ScrollableFlexItem>

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import type { ViewSelection } from '@kbn/securitysolution-data-table';
import { TableId } from '@kbn/securitysolution-data-table';
import React, { useMemo } from 'react';
import type { CSSProperties } from 'styled-components';
import styled from 'styled-components';
import type { ViewSelection } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { InspectButton } from '../inspect';
import { UpdatedFlexGroup, UpdatedFlexItem } from './styles';

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { getTableByIdSelector } from '@kbn/securitysolution-data-table';
import { createSelector } from 'reselect';
import { getTableByIdSelector } from '../../../store/data_table/selectors';
import {
getTimelineSelector,
globalFiltersQuerySelector,

View file

@ -11,7 +11,7 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { i18n } from '@kbn/i18n';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
import type { ViewSelection } from '../../../../../common/types';
import type { ViewSelection } from '@kbn/securitysolution-data-table';
import { ALERTS_TABLE_VIEW_SELECTION_KEY } from '../../../../../common/constants';
const storage = new Storage(localStorage);

View file

@ -6,9 +6,9 @@
*/
import { EuiLoadingSpinner } from '@elastic/eui';
import type { TableId } from '@kbn/securitysolution-data-table';
import React, { lazy, Suspense, useMemo } from 'react';
import type { TimelineItem } from '../../../../common/search_strategy';
import type { TableId } from '../../../../common/types';
import type { AlertWorkflowStatus } from '../../types';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';

View file

@ -28,12 +28,11 @@ import type {
TimelineStrategyResponseType,
} from '@kbn/timelines-plugin/common/search_strategy';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import { dataTableActions, Direction, TableId } from '@kbn/securitysolution-data-table';
import { TimelineEventsQueries } from '../../../../common/search_strategy';
import type { KueryFilterQueryKind } from '../../../../common/types';
import { Direction, TableId } from '../../../../common/types';
import type { ESQuery } from '../../../../common/typed_json';
import { useAppToasts } from '../../hooks/use_app_toasts';
import { dataTableActions } from '../../store/data_table';
import { ERROR_TIMELINE_EVENTS } from './translations';
import type { AlertWorkflowStatus } from '../../types';
import { getSearchTransactionName, useStartTransaction } from '../../lib/apm/use_start_transaction';

View file

@ -12,7 +12,7 @@ import { mockTimelineData, mockTimelineModel, TestProviders } from '../../mock';
import { useShallowEqualSelector } from '../../hooks/use_selector';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { licenseService } from '../../hooks/use_license';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { useTourContext } from '../guided_onboarding_tour';
import { GuidedOnboardingTourStep, SecurityTourStep } from '../guided_onboarding_tour/tour_step';
import { SecurityStepId } from '../guided_onboarding_tour/tour_config';

View file

@ -10,6 +10,7 @@ import { useDispatch } from 'react-redux';
import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import styled from 'styled-components';
import { TimelineTabs, TableId } from '@kbn/securitysolution-data-table';
import {
eventHasNotes,
getEventType,
@ -19,7 +20,7 @@ import { getScopedActions, isTimelineScope } from '../../../helpers';
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
import type { ActionProps, OnPinEvent } from '../../../../common/types';
import { TableId, TimelineId, TimelineTabs } from '../../../../common/types';
import { TimelineId } from '../../../../common/types';
import { AddEventNoteAction } from './add_note_icon_item';
import { PinEventAction } from './pin_event_action';
import { useShallowEqualSelector } from '../../hooks/use_selector';

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { tableDefaults } from '../../store/data_table/defaults';
import type { SubsetDataTableModel } from '../../store/data_table/model';
import { tableDefaults } from '@kbn/securitysolution-data-table';
import type { SubsetDataTableModel } from '@kbn/securitysolution-data-table';
import type { ColumnHeaderOptions } from '../../../../common/types/timeline';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';

View file

@ -12,7 +12,7 @@ import { TEST_ID, SessionsView, defaultSessionsFilter } from '.';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { SessionsComponentsProps } from './types';
import { useGetUserCasesPermissions } from '../../lib/kibana';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { licenseService } from '../../hooks/use_license';
import { mount } from 'enzyme';
import type { EventsViewerProps } from '../events_viewer';

View file

@ -10,7 +10,7 @@ import type { Filter } from '@kbn/es-query';
import { ENTRY_SESSION_ENTITY_ID_PROPERTY, EventAction } from '@kbn/session-view-plugin/public';
import { useDispatch } from 'react-redux';
import { EVENT_ACTION } from '@kbn/rule-data-utils';
import { TableId } from '../../../../common/types';
import { TableId, dataTableActions } from '@kbn/securitysolution-data-table';
import { useAddBulkToTimelineAction } from '../../../detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline';
import type { SessionsComponentsProps } from './types';
import type { ESBoolQuery } from '../../../../common/typed_json';
@ -22,11 +22,11 @@ import * as i18n from './translations';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
import { useLicense } from '../../hooks/use_license';
import { dataTableActions } from '../../store/data_table';
import { eventsDefaultModel } from '../events_viewer/default_model';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
import { SecurityCellActionsTrigger } from '../cell_actions';
export const TEST_ID = 'security_solution:sessions_viewer:sessions_view';
@ -150,6 +150,7 @@ const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({
return (
<div data-test-subj={TEST_ID}>
<StatefulEventsViewer
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
pageFilters={sessionsFilter}
defaultModel={getSessionsDefaultModel(columns, defaultColumns)}
end={endDate}

View file

@ -6,12 +6,12 @@
*/
import type { Filter } from '@kbn/es-query';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { TableIdLiteral } from '../../../../common/types';
import type { TableId } from '@kbn/securitysolution-data-table';
import type { QueryTabBodyProps } from '../../../explore/hosts/pages/navigation/types';
import type { ColumnHeaderOptions } from '../../../../common/types/timeline';
export interface SessionsComponentsProps extends Pick<QueryTabBodyProps, 'endDate' | 'startDate'> {
tableId: TableIdLiteral;
tableId: TableId;
pageFilters: Filter[];
defaultFilters?: Filter[];
entityType?: EntityType;

View file

@ -11,20 +11,21 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { ConnectedProps } from 'react-redux';
import { connect, useDispatch } from 'react-redux';
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import {
dataTableSelectors,
dataTableActions,
tableEntity,
} from '@kbn/securitysolution-data-table';
import type { DataTableModel, DataTableState, TableId } from '@kbn/securitysolution-data-table';
import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants';
import type {
CustomBulkActionProp,
SetEventsDeleted,
SetEventsLoading,
TableId,
} from '../../../../../common/types';
import { tableEntity } from '../../../../../common/types';
import { BulkActions } from '.';
import { useBulkActionItems } from './use_bulk_action_items';
import { dataTableActions, dataTableSelectors } from '../../../store/data_table';
import type { DataTableModel } from '../../../store/data_table/model';
import type { AlertWorkflowStatus, Refetch } from '../../../types';
import type { DataTableState } from '../../../store/data_table/types';
import type { OnUpdateAlertStatusError, OnUpdateAlertStatusSuccess } from './types';
import type { inputsModel } from '../../../store';
import { inputsSelectors } from '../../../store';

View file

@ -6,8 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { TableEntityType } from '../../../../../common/types';
import { TableEntityType } from '@kbn/securitysolution-data-table';
const ENTITY_TYPE_PLURAL = (entityType: TableEntityType, count: number) => {
switch (entityType) {

View file

@ -21,7 +21,7 @@ import {
detectionAlertsTables,
} from './helpers';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
/** the following scopes are NOT detection alert tables */
const otherScopes = [

View file

@ -52,7 +52,7 @@ import {
ALERT_WORKFLOW_STATUS,
ALERT_WORKFLOW_USER,
} from '@kbn/rule-data-utils';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import type { TimelineEventsType } from '../../../../common/types/timeline';
import { TimelineId } from '../../../../common/types/timeline';

View file

@ -25,7 +25,7 @@ import { createStore } from '../../store';
import type { Props } from './top_n';
import { StatefulTopN } from '.';
import { TimelineId } from '../../../../common/types/timeline';
import { TableId } from '../../../../common/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { detectionAlertsTables } from './helpers';
jest.mock('react-router-dom', () => {

View file

@ -9,13 +9,16 @@ import { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { TableId } from '../../../../common/types';
import {
dataTableActions,
dataTableSelectors,
tableDefaults,
TableId,
} from '@kbn/securitysolution-data-table';
import { useInitializeUrlParam } from '../../utils/global_query_string';
import { URL_PARAM_KEY } from '../use_url_state';
import type { FlyoutUrlState } from './types';
import { dataTableActions, dataTableSelectors } from '../../store/data_table';
import { useShallowEqualSelector } from '../use_selector';
import { tableDefaults } from '../../store/data_table/defaults';
export const useInitFlyoutFromUrlParam = () => {
const [urlDetails, setUrlDetails] = useState<FlyoutUrlState | null>(null);

View file

@ -8,13 +8,16 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import {
dataTableActions,
dataTableSelectors,
tableDefaults,
TableId,
} from '@kbn/securitysolution-data-table';
import { ALERTS_PATH } from '../../../../common/constants';
import { useUpdateUrlParam } from '../../utils/global_query_string';
import { TableId } from '../../../../common/types';
import { useShallowEqualSelector } from '../use_selector';
import { URL_PARAM_KEY } from '../use_url_state';
import { dataTableActions, dataTableSelectors } from '../../store/data_table';
import { tableDefaults } from '../../store/data_table/defaults';
import type { FlyoutUrlState } from './types';
export const useSyncFlyoutUrlParam = () => {

View file

@ -5,17 +5,19 @@
* 2.0.
*/
import type { TableId } from '@kbn/securitysolution-data-table';
import {
tableDefaults,
dataTableSelectors,
dataTableActions,
} from '@kbn/securitysolution-data-table';
import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import type { TableId } from '../../../common/types';
import { dataTableSelectors } from '../store/data_table';
import {
updateShowBuildingBlockAlertsFilter,
updateShowThreatIndicatorAlertsFilter,
} from '../store/data_table/actions';
import { tableDefaults } from '../store/data_table/defaults';
import { useShallowEqualSelector } from './use_selector';
const { updateShowBuildingBlockAlertsFilter, updateShowThreatIndicatorAlertsFilter } =
dataTableActions;
export type UseDataTableFilters = (tableId: TableId) => {
showBuildingBlockAlerts: boolean;
setShowBuildingBlockAlerts: (value: boolean) => void;

View file

@ -9,6 +9,7 @@ import type { EuiDataGridColumnCellActionProps } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { getPageRowIndex } from '@kbn/securitysolution-data-table';
import type { TimelineNonEcsData } from '../../../../common/search_strategy';
import type { DataProvider } from '../../../../common/types';
import { TimelineId } from '../../../../common/types';
@ -20,7 +21,6 @@ import {
import { escapeDataProviderId } from '../../components/drag_and_drop/helpers';
import { EmptyComponent, useKibanaServices } from './helpers';
import { addProvider } from '../../../timelines/store/timeline/actions';
import { getPageRowIndex } from '../../components/data_table/pagination';
export const getAddToTimelineCellAction = ({
data,

Some files were not shown because too many files have changed in this diff Show more