diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index 1ac5c385f8ed5..300497126c3e5 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -31,6 +31,33 @@ default, the trigger condition is set at 85% or more averaged over the last 5 minutes. The alert is grouped across all the nodes of the cluster by running checks on a schedule time of 1 minute with a re-notify internal of 1 day. +[discrete] +[[kibana-alerts-disk-usage-threshold]] +== Disk usage threshold + +This alert is triggered when a node is nearly at disk capacity. By +default, the trigger condition is set at 80% or more averaged over the last 5 +minutes. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 1 day. + +[discrete] +[[kibana-alerts-jvm-memory-threshold]] +== JVM memory threshold + +This alert is triggered when a node runs a consistently high JVM memory usage. By +default, the trigger condition is set at 85% or more averaged over the last 5 +minutes. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 1 day. + +[discrete] +[[kibana-alerts-missing-monitoring-data]] +== Missing monitoring data + +This alert is triggered when any stack products nodes or instances stop sending +monitoring data. By default, the trigger condition is set to missing for 15 minutes +looking back 1 day. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 6 hours. + NOTE: Some action types are subscription features, while others are free. For a comparison of the Elastic subscription levels, see the alerting section of the {subscriptions}[Subscriptions page]. diff --git a/src/plugins/kibana_usage_collection/tsconfig.json b/src/plugins/kibana_usage_collection/tsconfig.json new file mode 100644 index 0000000000000..d664d936f6667 --- /dev/null +++ b/src/plugins/kibana_usage_collection/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/*", + "server/**/**/*", + "../../../typings/*" + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../../plugins/usage_collection/tsconfig.json" }, + ] +} diff --git a/tasks/config/run.js b/tasks/config/run.js index eddcb0bdd59d0..e96011816ed4d 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -177,6 +177,10 @@ module.exports = function () { 'test/server_integration/http/ssl_redirect/config.js', '--config', 'test/server_integration/http/cache/config.js', + '--config', + 'test/server_integration/http/ssl_with_p12/config.js', + '--config', + 'test/server_integration/http/ssl_with_p12_intermediate/config.js', '--bail', '--debug', '--kibana-install-dir', diff --git a/test/tsconfig.json b/test/tsconfig.json index bd678c66db7c4..a6cc2d34639b7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -24,6 +24,7 @@ { "path": "../src/plugins/usage_collection/tsconfig.json" }, { "path": "../src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "../src/plugins/telemetry/tsconfig.json" }, + { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../src/plugins/newsfeed/tsconfig.json" } ] } diff --git a/tsconfig.json b/tsconfig.json index 6085326dde1ed..73646291e3d08 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "src/plugins/usage_collection/**/*", "src/plugins/telemetry_collection_manager/**/*", "src/plugins/telemetry/**/*", + "src/plugins/kibana_usage_collection/**/*", "src/plugins/newsfeed/**/*" // In the build we actually exclude **/public/**/* from this config so that // we can run the TSC on both this and the .browser version of this config @@ -29,6 +30,7 @@ { "path": "./src/plugins/usage_collection/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, + { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" } ] } diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 7f40b8e18723d..bb1bdc08cafd6 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -8,6 +8,7 @@ { "path": "./src/plugins/usage_collection/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, + { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, ] } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts index aa9fbc20fc0b0..5fe72fd57f3ed 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts @@ -63,6 +63,7 @@ export interface DeleteAgentPolicyRequest { export interface DeleteAgentPolicyResponse { id: string; + name: string; } export interface GetFullAgentPolicyRequest { diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts index 61669ab876d80..171902471e178 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts @@ -52,5 +52,6 @@ export interface DeletePackagePoliciesRequest { export type DeletePackagePoliciesResponse = Array<{ id: string; + name?: string; success: boolean; }>; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx index 0266b4a90abe4..ab8a9c99227c5 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx @@ -63,7 +63,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ chil notifications.toasts.addSuccess( i18n.translate('xpack.ingestManager.deleteAgentPolicy.successSingleNotificationTitle', { defaultMessage: "Deleted agent policy '{id}'", - values: { id: agentPolicy }, + values: { id: data.name || data.id }, }) ); if (onSuccessCallback.current) { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx index 9242c6eb86225..ff5961b94f726 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/package_policy_delete_provider.tsx @@ -106,7 +106,7 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ 'xpack.ingestManager.deletePackagePolicy.successSingleNotificationTitle', { defaultMessage: "Deleted integration '{id}'", - values: { id: successfulResults[0].id }, + values: { id: successfulResults[0].name || successfulResults[0].id }, } ); notifications.toasts.addSuccess(successMessage); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index 83cbb9ccb728c..bc37338f04394 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -173,6 +173,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Agent data states const [showInactive, setShowInactive] = useState(false); const [showUpgradeable, setShowUpgradeable] = useState(false); + // Table and search states const [search, setSearch] = useState(defaultKuery); const [selectionMode, setSelectionMode] = useState('manual'); @@ -188,11 +189,20 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const [isStatusFilterOpen, setIsStatutsFilterOpen] = useState(false); const [selectedStatus, setSelectedStatus] = useState([]); + const isUsingFilter = + search.trim() || + selectedAgentPolicies.length || + selectedStatus.length || + showInactive || + showUpgradeable; + const clearFilters = useCallback(() => { setSearch(''); setSelectedAgentPolicies([]); setSelectedStatus([]); - }, [setSearch, setSelectedAgentPolicies, setSelectedStatus]); + setShowInactive(false); + setShowUpgradeable(false); + }, [setSearch, setSelectedAgentPolicies, setSelectedStatus, setShowInactive, setShowUpgradeable]); // Add a agent policy id to current search const addAgentPolicyFilter = (policyId: string) => { @@ -638,7 +648,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { id="xpack.ingestManager.agentList.loadingAgentsMessage" defaultMessage="Loading agents…" /> - ) : search.trim() || selectedAgentPolicies.length || selectedStatus.length ? ( + ) : isUsingFilter ? ( = ({ const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const { setEstimatedModelMemoryLimit, setFormState } = actions; - const { form, isJobCreated } = state; + const { form, isJobCreated, estimatedModelMemoryLimit } = state; const { computeFeatureInfluence, eta, @@ -159,6 +160,7 @@ export const AdvancedStepForm: FC = ({ outlierFraction, predictionFieldName, randomizeSeed, + useEstimatedMml, } = form; const [numTopClassesOptions, setNumTopClassesOptions] = useState([ @@ -204,7 +206,9 @@ export const AdvancedStepForm: FC = ({ if (success) { if (modelMemoryLimit !== expectedMemory) { setEstimatedModelMemoryLimit(expectedMemory); - setFormState({ modelMemoryLimit: expectedMemory }); + if (useEstimatedMml === true) { + setFormState({ modelMemoryLimit: expectedMemory }); + } } } else { // Check which field is invalid @@ -481,18 +485,35 @@ export const AdvancedStepForm: FC = ({ } )} > - setFormState({ modelMemoryLimit: e.target.value })} - isInvalid={modelMemoryLimitValidationResult !== null} - data-test-subj="mlAnalyticsCreateJobWizardModelMemoryInput" - /> + <> + setFormState({ modelMemoryLimit: e.target.value })} + isInvalid={modelMemoryLimitValidationResult !== null} + data-test-subj="mlAnalyticsCreateJobWizardModelMemoryInput" + /> + + + setFormState({ + useEstimatedMml: !useEstimatedMml, + }) + } + data-test-subj="mlAnalyticsCreateJobWizardUseEstimatedMml" + /> + diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index dd9ecc963840a..9c166f32f1d34 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -91,6 +91,7 @@ export const ConfigurationStepForm: FC = ({ requiredFieldsError, sourceIndex, trainingPercent, + useEstimatedMml, } = form; const toastNotifications = getToastNotifications(); @@ -164,7 +165,8 @@ export const ConfigurationStepForm: FC = ({ const debouncedGetExplainData = debounce(async () => { const jobTypeChanged = previousJobType !== jobType; - const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; + const shouldUpdateModelMemoryLimit = + (!firstUpdate.current || !modelMemoryLimit) && useEstimatedMml === true; const shouldUpdateEstimatedMml = !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === ''; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 898bd7aa183cd..533a5eb6f5c34 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -93,6 +93,7 @@ export interface State { sourceIndexFieldsCheckFailed: boolean; standardizationEnabled: undefined | string; trainingPercent: number; + useEstimatedMml: boolean; }; disabled: boolean; indexPatternsMap: SourceIndexMap; @@ -161,6 +162,7 @@ export const getInitialState = (): State => ({ sourceIndexFieldsCheckFailed: false, standardizationEnabled: 'true', trainingPercent: 80, + useEstimatedMml: true, }, jobConfig: {}, disabled: diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js index d0ca1bc6bbde6..ef97d78b4f745 100644 --- a/x-pack/plugins/monitoring/public/services/clusters.js +++ b/x-pack/plugins/monitoring/public/services/clusters.js @@ -25,7 +25,7 @@ let once = false; let inTransit = false; export function monitoringClustersProvider($injector) { - return (clusterUuid, ccs, codePaths) => { + return async (clusterUuid, ccs, codePaths) => { const { min, max } = Legacy.shims.timefilter.getBounds(); // append clusterUuid if the parameter is given @@ -36,74 +36,73 @@ export function monitoringClustersProvider($injector) { const $http = $injector.get('$http'); - function getClusters() { - return $http - .post(url, { + async function getClusters() { + try { + const response = await $http.post(url, { ccs, timeRange: { min: min.toISOString(), max: max.toISOString(), }, codePaths, - }) - .then((response) => response.data) - .then((data) => { - return formatClusters(data); // return set of clusters - }) - .catch((err) => { - const Private = $injector.get('Private'); - const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); - return ajaxErrorHandlers(err); }); + return formatClusters(response.data); + } catch (err) { + const Private = $injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + } } - function ensureAlertsEnabled() { - return $http.post('../api/monitoring/v1/alerts/enable', {}).catch((err) => { + async function ensureAlertsEnabled() { + try { + return $http.post('../api/monitoring/v1/alerts/enable', {}); + } catch (err) { const Private = $injector.get('Private'); const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); - }); + } } - function ensureMetricbeatEnabled() { + async function ensureMetricbeatEnabled() { if (Legacy.shims.isCloud) { - return Promise.resolve(); + return; } const globalState = $injector.get('globalState'); - return $http - .post('../api/monitoring/v1/elasticsearch_settings/check/internal_monitoring', { - ccs: globalState.ccs, - }) - .then(({ data }) => { - showInternalMonitoringToast({ - legacyIndices: data.legacy_indices, - metricbeatIndices: data.mb_indices, - }); - }) - .catch((err) => { - const Private = $injector.get('Private'); - const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); - return ajaxErrorHandlers(err); + try { + const response = await $http.post( + '../api/monitoring/v1/elasticsearch_settings/check/internal_monitoring', + { + ccs: globalState.ccs, + } + ); + const { data } = response; + showInternalMonitoringToast({ + legacyIndices: data.legacy_indices, + metricbeatIndices: data.mb_indices, }); + } catch (err) { + const Private = $injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + } } if (!once && !inTransit) { inTransit = true; - return getClusters().then((clusters) => { - if (clusters.length) { - Promise.all([ensureAlertsEnabled(), ensureMetricbeatEnabled()]) - .then(([{ data }]) => { - showSecurityToast(data); - once = true; - }) - .catch(() => { - // Intentionally swallow the error as this will retry the next page load - }) - .finally(() => (inTransit = false)); + const clusters = await getClusters(); + if (clusters.length) { + try { + const [{ data }] = await Promise.all([ensureAlertsEnabled(), ensureMetricbeatEnabled()]); + showSecurityToast(data); + once = true; + } catch (_err) { + // Intentionally swallow the error as this will retry the next page load } - return clusters; - }); + inTransit = false; + } + return clusters; } - return getClusters(); + return await getClusters(); }; } diff --git a/x-pack/plugins/monitoring/public/views/loading/index.js b/x-pack/plugins/monitoring/public/views/loading/index.js index 5ca523899067d..b5d79e1d2b652 100644 --- a/x-pack/plugins/monitoring/public/views/loading/index.js +++ b/x-pack/plugins/monitoring/public/views/loading/index.js @@ -53,15 +53,18 @@ uiRoutes.when('/loading', { (clusters) => { if (!clusters || !clusters.length) { window.location.hash = '#/no-data'; + $scope.$apply(); return; } if (clusters.length === 1) { // Bypass the cluster listing if there is just 1 cluster window.history.replaceState(null, null, '#/overview'); + $scope.$apply(); return; } window.history.replaceState(null, null, '#/home'); + $scope.$apply(); } ); } diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.test.ts index e3d69820ebb05..5605641992e1a 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.test.ts @@ -36,7 +36,7 @@ describe('DiskUsageAlert', () => { expect(alert.type).toBe(ALERT_DISK_USAGE); expect(alert.label).toBe('Disk Usage'); expect(alert.defaultThrottle).toBe('1d'); - expect(alert.defaultParams).toStrictEqual({ threshold: 90, duration: '5m' }); + expect(alert.defaultParams).toStrictEqual({ threshold: 80, duration: '5m' }); expect(alert.actionVariables).toStrictEqual([ { name: 'nodes', description: 'The list of nodes reporting high disk usage.' }, { name: 'count', description: 'The number of nodes reporting high disk usage.' }, diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts index c577550de8617..34c640de79625 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts @@ -54,7 +54,7 @@ export class DiskUsageAlert extends BaseAlert { public label = DiskUsageAlert.LABEL; protected defaultParams = { - threshold: 90, + threshold: 80, duration: '5m', }; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index 6ed237a055b5c..57d01dc6a1100 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -22,9 +22,9 @@ describe('MissingMonitoringDataAlert', () => { const alert = new MissingMonitoringDataAlert(); expect(alert.type).toBe(ALERT_MISSING_MONITORING_DATA); expect(alert.label).toBe('Missing monitoring data'); - expect(alert.defaultThrottle).toBe('1d'); + expect(alert.defaultThrottle).toBe('6h'); // @ts-ignore - expect(alert.defaultParams).toStrictEqual({ limit: '1d', duration: '5m' }); + expect(alert.defaultParams).toStrictEqual({ limit: '1d', duration: '15m' }); // @ts-ignore expect(alert.actionVariables).toStrictEqual([ { name: 'stackProducts', description: 'The stack products missing monitoring data.' }, diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts index 252d005f1e4a6..5b4542a4439ca 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts @@ -50,7 +50,7 @@ const FIRING = i18n.translate('xpack.monitoring.alerts.missingData.firing', { defaultMessage: 'firing', }); -const DEFAULT_DURATION = '5m'; +const DEFAULT_DURATION = '15m'; const DEFAULT_LIMIT = '1d'; // Go a bit farther back because we need to detect the difference between seeing the monitoring data versus just not looking far enough back @@ -77,6 +77,8 @@ export class MissingMonitoringDataAlert extends BaseAlert { } as CommonAlertParamDetail, }; + public defaultThrottle: string = '6h'; + public type = ALERT_MISSING_MONITORING_DATA; public label = i18n.translate('xpack.monitoring.alerts.missingData.label', { defaultMessage: 'Missing monitoring data', diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 4c9e3bc06037e..c3fc6bd1ae1dd 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -5,7 +5,7 @@ "private": true, "license": "Elastic-License", "scripts": { - "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix", + "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/detections/mitre/mitre_tactics_techniques.ts --fix", "build-beat-doc": "node scripts/beat_docs/build.js && node ../../../scripts/eslint ./server/utils/beat_schema/fields.ts --fix", "build-graphql-types": "node scripts/generate_types_from_graphql.js", "cypress:open": "cypress open --config-file ./cypress/cypress.json", diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts index fb8deeec8309c..027aa7fd699e4 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts @@ -78,9 +78,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0009', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.collectionDescription', - { - defaultMessage: 'Collection (TA0009)', - } + { defaultMessage: 'Collection (TA0009)' } ), value: 'collection', }, @@ -120,9 +118,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0007', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.discoveryDescription', - { - defaultMessage: 'Discovery (TA0007)', - } + { defaultMessage: 'Discovery (TA0007)' } ), value: 'discovery', }, @@ -132,9 +128,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0002', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.executionDescription', - { - defaultMessage: 'Execution (TA0002)', - } + { defaultMessage: 'Execution (TA0002)' } ), value: 'execution', }, @@ -144,9 +138,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0010', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.exfiltrationDescription', - { - defaultMessage: 'Exfiltration (TA0010)', - } + { defaultMessage: 'Exfiltration (TA0010)' } ), value: 'exfiltration', }, @@ -156,9 +148,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0040', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.impactDescription', - { - defaultMessage: 'Impact (TA0040)', - } + { defaultMessage: 'Impact (TA0040)' } ), value: 'impact', }, @@ -168,9 +158,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0001', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.initialAccessDescription', - { - defaultMessage: 'Initial Access (TA0001)', - } + { defaultMessage: 'Initial Access (TA0001)' } ), value: 'initialAccess', }, @@ -190,9 +178,7 @@ export const tacticsOptions: MitreTacticsOptions[] = [ reference: 'https://attack.mitre.org/tactics/TA0003', text: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTactics.persistenceDescription', - { - defaultMessage: 'Persistence (TA0003)', - } + { defaultMessage: 'Persistence (TA0003)' } ), value: 'persistence', }, @@ -1998,9 +1984,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bitsJobsDescription', - { - defaultMessage: 'BITS Jobs (T1197)', - } + { defaultMessage: 'BITS Jobs (T1197)' } ), id: 'T1197', name: 'BITS Jobs', @@ -2033,9 +2017,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.bootkitDescription', - { - defaultMessage: 'Bootkit (T1067)', - } + { defaultMessage: 'Bootkit (T1067)' } ), id: 'T1067', name: 'Bootkit', @@ -2090,9 +2072,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.cmstpDescription', - { - defaultMessage: 'CMSTP (T1191)', - } + { defaultMessage: 'CMSTP (T1191)' } ), id: 'T1191', name: 'CMSTP', @@ -2367,9 +2347,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.dcShadowDescription', - { - defaultMessage: 'DCShadow (T1207)', - } + { defaultMessage: 'DCShadow (T1207)' } ), id: 'T1207', name: 'DCShadow', @@ -2688,9 +2666,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.emondDescription', - { - defaultMessage: 'Emond (T1519)', - } + { defaultMessage: 'Emond (T1519)' } ), id: 'T1519', name: 'Emond', @@ -3053,9 +3029,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hookingDescription', - { - defaultMessage: 'Hooking (T1179)', - } + { defaultMessage: 'Hooking (T1179)' } ), id: 'T1179', name: 'Hooking', @@ -3231,9 +3205,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.keychainDescription', - { - defaultMessage: 'Keychain (T1142)', - } + { defaultMessage: 'Keychain (T1142)' } ), id: 'T1142', name: 'Keychain', @@ -3310,9 +3282,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.launchctlDescription', - { - defaultMessage: 'Launchctl (T1152)', - } + { defaultMessage: 'Launchctl (T1152)' } ), id: 'T1152', name: 'Launchctl', @@ -3334,9 +3304,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.loginItemDescription', - { - defaultMessage: 'Login Item (T1162)', - } + { defaultMessage: 'Login Item (T1162)' } ), id: 'T1162', name: 'Login Item', @@ -3402,9 +3370,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.mshtaDescription', - { - defaultMessage: 'Mshta (T1170)', - } + { defaultMessage: 'Mshta (T1170)' } ), id: 'T1170', name: 'Mshta', @@ -3778,9 +3744,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rcCommonDescription', - { - defaultMessage: 'Rc.common (T1163)', - } + { defaultMessage: 'Rc.common (T1163)' } ), id: 'T1163', name: 'Rc.common', @@ -3835,9 +3799,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.regsvr32Description', - { - defaultMessage: 'Regsvr32 (T1117)', - } + { defaultMessage: 'Regsvr32 (T1117)' } ), id: 'T1117', name: 'Regsvr32', @@ -3936,9 +3898,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rootkitDescription', - { - defaultMessage: 'Rootkit (T1014)', - } + { defaultMessage: 'Rootkit (T1014)' } ), id: 'T1014', name: 'Rootkit', @@ -3949,9 +3909,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.rundll32Description', - { - defaultMessage: 'Rundll32 (T1085)', - } + { defaultMessage: 'Rundll32 (T1085)' } ), id: 'T1085', name: 'Rundll32', @@ -4050,9 +4008,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.scriptingDescription', - { - defaultMessage: 'Scripting (T1064)', - } + { defaultMessage: 'Scripting (T1064)' } ), id: 'T1064', name: 'Scripting', @@ -4217,9 +4173,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sourceDescription', - { - defaultMessage: 'Source (T1153)', - } + { defaultMessage: 'Source (T1153)' } ), id: 'T1153', name: 'Source', @@ -4351,9 +4305,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.sudoDescription', - { - defaultMessage: 'Sudo (T1169)', - } + { defaultMessage: 'Sudo (T1169)' } ), id: 'T1169', name: 'Sudo', @@ -4529,9 +4481,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.timestompDescription', - { - defaultMessage: 'Timestomp (T1099)', - } + { defaultMessage: 'Timestomp (T1099)' } ), id: 'T1099', name: 'Timestomp', @@ -4564,9 +4514,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.trapDescription', - { - defaultMessage: 'Trap (T1154)', - } + { defaultMessage: 'Trap (T1154)' } ), id: 'T1154', name: 'Trap', @@ -4698,9 +4646,7 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.webShellDescription', - { - defaultMessage: 'Web Shell (T1100)', - } + { defaultMessage: 'Web Shell (T1100)' } ), id: 'T1100', name: 'Web Shell', diff --git a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js index 5c31b3fad685a..aa4112d8a6f97 100644 --- a/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js +++ b/x-pack/plugins/security_solution/scripts/extract_tactics_techniques_mitre.js @@ -13,9 +13,10 @@ const fetch = require('node-fetch'); const { camelCase } = require('lodash'); const { resolve } = require('path'); -const OUTPUT_DIRECTORY = resolve('public', 'pages', 'detection_engine', 'mitre'); -const MITRE_ENTREPRISE_ATTACK_URL = - 'https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json'; +const OUTPUT_DIRECTORY = resolve('public', 'detections', 'mitre'); +// Revert to https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json once we support sub-techniques +const MITRE_ENTERPRISE_ATTACK_URL = + 'https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v6.3/enterprise-attack/enterprise-attack.json'; const getTacticsOptions = (tactics) => tactics.map((t) => @@ -63,7 +64,7 @@ const getIdReference = (references) => ); async function main() { - fetch(MITRE_ENTREPRISE_ATTACK_URL) + fetch(MITRE_ENTERPRISE_ATTACK_URL) .then((res) => res.json()) .then((json) => { const mitreData = json.objects; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx index 2685e62eb9a6c..2ef887d0627cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx @@ -74,4 +74,51 @@ describe('EmailActionConnectorFields renders', () => { expect(wrapper.find('[data-test-subj="emailUserInput"]').length > 0).toBeFalsy(); expect(wrapper.find('[data-test-subj="emailPasswordInput"]').length > 0).toBeFalsy(); }); + + test('should display a message to remember username and password when creating a connector with authentication', () => { + const actionConnector = { + actionTypeId: '.email', + config: { + hasAuth: true, + }, + secrets: {}, + } as EmailActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message when editing an authenticated email connector explaining why username and password must be re-entered', () => { + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: { + from: 'test@test.com', + hasAuth: true, + }, + } as EmailActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx index 1e92e9fc2519c..111f6c9a47da9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx @@ -12,6 +12,7 @@ import { EuiFieldPassword, EuiSwitch, EuiFormRow, + EuiText, EuiTitle, EuiSpacer, EuiCallOut, @@ -56,7 +57,7 @@ export const EmailActionConnectorFields: React.FunctionComponent } @@ -202,17 +203,7 @@ export const EmailActionConnectorFields: React.FunctionComponent {hasAuth ? ( <> - {action.id ? ( - <> - - - - - ) : null} + {getEncryptedFieldNotifyLabel(!action.id)} + + + + + + + ); + } + return ( + + + + + + ); +} + // eslint-disable-next-line import/no-default-export export { EmailActionConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index 61923d8f78b51..f476522c2bf5a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -68,7 +68,7 @@ describe('jira connector validation', () => { errors: { apiUrl: ['URL is required.'], email: [], - apiToken: ['API token or Password is required'], + apiToken: ['API token or password is required'], projectKey: ['Project key is required'], }, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx index 2cac1819d552d..6d055f4fdb9f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx @@ -96,4 +96,60 @@ describe('JiraActionConnectorFields renders', () => { wrapper.find('[data-test-subj="connector-jira-apiToken-form-input"]').length > 0 ).toBeTruthy(); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + actionTypeId: '.jira', + isPreconfigured: false, + secrets: {}, + config: {}, + } as JiraActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + email: 'email', + apiToken: 'token', + }, + id: 'test', + actionTypeId: '.jira', + isPreconfigured: false, + name: 'jira', + config: { + apiUrl: 'https://test/', + projectKey: 'CK', + }, + } as JiraActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx index 2ab9843c143b9..35c6bd7af00ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.tsx @@ -6,12 +6,15 @@ import React, { useCallback } from 'react'; import { + EuiCallOut, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldPassword, EuiSpacer, + EuiText, + EuiTitle, } from '@elastic/eui'; import { isEmpty } from 'lodash'; @@ -133,6 +136,20 @@ const JiraConnectorFields: React.FC + + + +

{i18n.JIRA_AUTHENTICATION_LABEL}

+
+
+
+ + + + {getEncryptedFieldNotifyLabel(!action.id)} + + + + {i18n.JIRA_REMEMBER_VALUES_LABEL} + + ); + } + return ( + + ); +} + // eslint-disable-next-line import/no-default-export export { JiraConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts index 019133b03d55f..f6db56e188322 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const JIRA_DESC = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.jira.selectMessageText', { - defaultMessage: 'Push or update data to a new issue in Jira', + defaultMessage: 'Create an incident in Jira.', } ); @@ -55,31 +55,54 @@ export const JIRA_PROJECT_KEY_REQUIRED = i18n.translate( } ); +export const JIRA_AUTHENTICATION_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.jira.authenticationLabel', + { + defaultMessage: 'Authentication', + } +); + +export const JIRA_REMEMBER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.jira.rememberValuesLabel', + { + defaultMessage: + 'Remember these values. You must reenter them each time you edit the connector.', + } +); + +export const JIRA_REENTER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.jira.reenterValuesLabel', + { + defaultMessage: + 'Authentication credentials are encrypted. Please reenter values for these fields.', + } +); + export const JIRA_EMAIL_LABEL = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.jira.emailTextFieldLabel', { - defaultMessage: 'Email or Username', + defaultMessage: 'Username or email address', } ); export const JIRA_EMAIL_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredEmailTextField', { - defaultMessage: 'Email or Username is required', + defaultMessage: 'Username or email address is required', } ); export const JIRA_API_TOKEN_LABEL = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.jira.apiTokenTextFieldLabel', { - defaultMessage: 'API token or Password', + defaultMessage: 'API token or password', } ); export const JIRA_API_TOKEN_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredApiTokenTextField', { - defaultMessage: 'API token or Password is required', + defaultMessage: 'API token or password is required', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx index 53e68e6453690..18978be7b4680 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx @@ -49,4 +49,56 @@ describe('PagerDutyActionConnectorFields renders', () => { ); expect(wrapper.find('[data-test-subj="pagerdutyRoutingKeyInput"]').length > 0).toBeTruthy(); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + actionTypeId: '.pagerduty', + secrets: {}, + config: {}, + } as PagerDutyActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + routingKey: 'test', + }, + id: 'test', + actionTypeId: '.pagerduty', + name: 'pagerduty', + config: { + apiUrl: 'http:\\test', + }, + } as PagerDutyActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx index 6399e1f80984c..ad2d5b3be5268 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; -import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; +import { EuiCallOut, EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ActionConnectorFieldsProps } from '../../../../types'; @@ -53,7 +53,7 @@ const PagerDutyActionConnectorFields: React.FunctionComponent } @@ -66,26 +66,61 @@ const PagerDutyActionConnectorFields: React.FunctionComponent - 0 && routingKey !== undefined} - name="routingKey" - readOnly={readOnly} - value={routingKey || ''} - data-test-subj="pagerdutyRoutingKeyInput" - onChange={(e: React.ChangeEvent) => { - editActionSecrets('routingKey', e.target.value); - }} - onBlur={() => { - if (!routingKey) { - editActionSecrets('routingKey', ''); - } - }} - /> + + {getEncryptedFieldNotifyLabel(!action.id)} + 0 && routingKey !== undefined} + name="routingKey" + readOnly={readOnly} + value={routingKey || ''} + data-test-subj="pagerdutyRoutingKeyInput" + onChange={(e: React.ChangeEvent) => { + editActionSecrets('routingKey', e.target.value); + }} + onBlur={() => { + if (!routingKey) { + editActionSecrets('routingKey', ''); + } + }} + /> + ); }; +function getEncryptedFieldNotifyLabel(isCreate: boolean) { + if (isCreate) { + return ( + + + + + + + + ); + } + return ( + + + + + + ); +} + // eslint-disable-next-line import/no-default-export export { PagerDutyActionConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index b73eb72f137c1..937fe61e887ea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -68,7 +68,7 @@ describe('resilient connector validation', () => { errors: { apiUrl: ['URL is required.'], apiKeyId: [], - apiKeySecret: ['API key secret is required'], + apiKeySecret: ['Secret is required'], orgId: ['Organization ID is required'], }, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx index 7e242f1f501d8..6dede85736fcd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx @@ -97,4 +97,60 @@ describe('ResilientActionConnectorFields renders', () => { wrapper.find('[data-test-subj="connector-resilient-apiKeySecret-form-input"]').length > 0 ).toBeTruthy(); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + actionTypeId: '.resilient', + isPreconfigured: false, + config: {}, + secrets: {}, + } as ResilientActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + apiKeyId: 'key', + apiKeySecret: 'secret', + }, + id: 'test', + actionTypeId: '.resilient', + isPreconfigured: false, + name: 'resilient', + config: { + apiUrl: 'https://test/', + orgId: '201', + }, + } as ResilientActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx index 7965e216f1d6c..fe2aa341a7111 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.tsx @@ -6,12 +6,15 @@ import React, { useCallback } from 'react'; import { + EuiCallOut, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldPassword, EuiSpacer, + EuiText, + EuiTitle, } from '@elastic/eui'; import { isEmpty } from 'lodash'; @@ -133,6 +136,20 @@ const ResilientConnectorFields: React.FC + + + +

{i18n.API_KEY_LABEL}

+
+
+
+ + + + {getEncryptedFieldNotifyLabel(!action.id)} + + + + {i18n.REMEMBER_VALUES_LABEL} + + ); + } + return ( + + ); +} + // eslint-disable-next-line import/no-default-export export { ResilientConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts index 71ad05abfdecf..b45f898d7d809 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const DESC = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.selectMessageText', { - defaultMessage: 'Push or update data to a new incident in Resilient.', + defaultMessage: 'Create an incident in IBM Resilient.', } ); @@ -55,31 +55,53 @@ export const ORG_ID_REQUIRED = i18n.translate( } ); +export const API_KEY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKey', + { + defaultMessage: 'API key', + } +); + +export const REMEMBER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.rememberValuesLabel', + { + defaultMessage: + 'Remember these values. You must reenter them each time you edit the connector.', + } +); + +export const REENTER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.reenterValuesLabel', + { + defaultMessage: 'ID and secret are encrypted. Please reenter values for these fields.', + } +); + export const API_KEY_ID_LABEL = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeyId', { - defaultMessage: 'API key ID', + defaultMessage: 'ID', } ); export const API_KEY_ID_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredApiKeyIdTextField', { - defaultMessage: 'API key ID is required', + defaultMessage: 'ID is required', } ); export const API_KEY_SECRET_LABEL = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.apiKeySecret', { - defaultMessage: 'API key secret', + defaultMessage: 'Secret', } ); export const API_KEY_SECRET_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredApiKeySecretTextField', { - defaultMessage: 'API key secret is required', + defaultMessage: 'Secret is required', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx index 216e6967833b2..b666db4024b12 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx @@ -83,4 +83,59 @@ describe('ServiceNowActionConnectorFields renders', () => { wrapper.find('[data-test-subj="connector-servicenow-password-form-input"]').length > 0 ).toBeTruthy(); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + actionTypeId: '.servicenow', + isPreconfigured: false, + config: {}, + secrets: {}, + } as ServiceNowActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + username: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.servicenow', + isPreconfigured: false, + name: 'servicenow', + config: { + apiUrl: 'https://test/', + }, + } as ServiceNowActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx index a8f1ed8d55447..d351b32c3bb06 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx @@ -6,6 +6,7 @@ import React, { useCallback } from 'react'; import { + EuiCallOut, EuiFieldText, EuiFlexGroup, EuiFlexItem, @@ -13,6 +14,8 @@ import { EuiFieldPassword, EuiSpacer, EuiLink, + EuiText, + EuiTitle, } from '@elastic/eui'; import { isEmpty } from 'lodash'; @@ -89,7 +92,7 @@ const ServiceNowConnectorFields: React.FC } @@ -113,6 +116,20 @@ const ServiceNowConnectorFields: React.FC + + + +

{i18n.AUTHENTICATION_LABEL}

+
+
+
+ + + + {getEncryptedFieldNotifyLabel(!action.id)} + + + + {i18n.REMEMBER_VALUES_LABEL} + + ); + } + return ( + + ); +} + // eslint-disable-next-line import/no-default-export export { ServiceNowConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts index 48544945836d9..67e94bc136cf9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts @@ -41,6 +41,28 @@ export const API_URL_INVALID = i18n.translate( } ); +export const AUTHENTICATION_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel', + { + defaultMessage: 'Authentication', + } +); + +export const REMEMBER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.rememberValuesLabel', + { + defaultMessage: + 'Remember these values. You must reenter them each time you edit the connector.', + } +); + +export const REENTER_VALUES_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.reenterValuesLabel', + { + defaultMessage: 'Username and password are encrypted. Please reenter values for these fields.', + } +); + export const USERNAME_LABEL = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx index 5bc778830b6e6..f87c2ad99fb4f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx @@ -44,4 +44,54 @@ describe('SlackActionFields renders', () => { 'http:\\test' ); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + actionTypeId: '.email', + config: {}, + secrets: {}, + } as SlackActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + webhookUrl: 'http:\\test', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: {}, + } as SlackActionConnector; + const deps = { + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, + }; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx index aa3a1932eacdb..3e744eff07797 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; -import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; +import { EuiCallOut, EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ActionConnectorFieldsProps } from '../../../../types'; @@ -27,7 +27,7 @@ const SlackActionFields: React.FunctionComponent } @@ -40,27 +40,62 @@ const SlackActionFields: React.FunctionComponent - 0 && webhookUrl !== undefined} - name="webhookUrl" - readOnly={readOnly} - placeholder="Example: https://hooks.slack.com/services" - value={webhookUrl || ''} - data-test-subj="slackWebhookUrlInput" - onChange={(e) => { - editActionSecrets('webhookUrl', e.target.value); - }} - onBlur={() => { - if (!webhookUrl) { - editActionSecrets('webhookUrl', ''); - } - }} - /> + + {getEncryptedFieldNotifyLabel(!action.id)} + 0 && webhookUrl !== undefined} + name="webhookUrl" + readOnly={readOnly} + placeholder="Example: https://hooks.slack.com/services" + value={webhookUrl || ''} + data-test-subj="slackWebhookUrlInput" + onChange={(e) => { + editActionSecrets('webhookUrl', e.target.value); + }} + onBlur={() => { + if (!webhookUrl) { + editActionSecrets('webhookUrl', ''); + } + }} + /> + ); }; +function getEncryptedFieldNotifyLabel(isCreate: boolean) { + if (isCreate) { + return ( + + + + + + + + ); + } + return ( + + + + + + ); +} + // eslint-disable-next-line import/no-default-export export { SlackActionFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx index 7f2bed6c41f3b..45e4c566f7a27 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx @@ -44,4 +44,55 @@ describe('WebhookActionConnectorFields renders', () => { expect(wrapper.find('[data-test-subj="webhookUserInput"]').length > 0).toBeTruthy(); expect(wrapper.find('[data-test-subj="webhookPasswordInput"]').length > 0).toBeTruthy(); }); + + test('should display a message on create to remember credentials', () => { + const actionConnector = { + secrets: {}, + actionTypeId: '.webhook', + isPreconfigured: false, + config: {}, + } as WebhookActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toEqual(0); + }); + + test('should display a message on edit to re-enter credentials', () => { + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.webhook', + isPreconfigured: false, + name: 'webhook', + config: { + method: 'PUT', + url: 'http:\\test', + headers: { 'content-type': 'text' }, + }, + } as WebhookActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} + readOnly={false} + /> + ); + expect(wrapper.find('[data-test-subj="reenterValuesMessage"]').length).toBeGreaterThan(0); + expect(wrapper.find('[data-test-subj="rememberValuesMessage"]').length).toEqual(0); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx index 52160441adb5b..e4f5ef023a529 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.tsx @@ -7,6 +7,7 @@ import React, { Fragment, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { + EuiCallOut, EuiFieldPassword, EuiFieldText, EuiFormRow, @@ -18,6 +19,7 @@ import { EuiDescriptionList, EuiDescriptionListDescription, EuiDescriptionListTitle, + EuiText, EuiTitle, EuiSwitch, EuiButtonEmpty, @@ -266,6 +268,26 @@ const WebhookActionConnectorFields: React.FunctionComponent + + + + +

+ +

+
+
+
+ + + + {getEncryptedFieldNotifyLabel(!action.id)} + + + + + + ); + } + return ( + + ); +} + // eslint-disable-next-line import/no-default-export export { WebhookActionConnectorFields as default }; diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index fd69bb32ae286..297eb2e9b4540 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -25,6 +25,7 @@ { "path": "../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../../src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "../../src/plugins/telemetry/tsconfig.json" }, + { "path": "../../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../../src/plugins/newsfeed/tsconfig.json" } ] } diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index e0a0b63d3c6a5..79309369386cf 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -13,10 +13,7 @@ "plugins/apm/e2e/cypress/**/*", "plugins/apm/scripts/**/*", "plugins/licensing/**/*", - "plugins/global_search/**/*", - "../src/plugins/usage_collection/**/*", - "../src/plugins/telemetry_collection_manager/**/*", - "../src/plugins/telemetry/**/*" + "plugins/global_search/**/*" ], "compilerOptions": { "paths": { @@ -37,6 +34,7 @@ { "path": "../src/plugins/usage_collection/tsconfig.json" }, { "path": "../src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "../src/plugins/telemetry/tsconfig.json" }, + { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../src/plugins/newsfeed/tsconfig.json" }, ] }