From 38c7cf2824a966cf67ef12204837ee9601f4fbdc Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Tue, 15 Dec 2020 00:09:30 -0500
Subject: [PATCH 1/7] CCR read exceptions all branches

---
 x-pack/plugins/monitoring/common/constants.ts |  22 ++
 .../plugins/monitoring/common/types/alerts.ts |  14 +
 .../ccr_read_exceptions_alert/index.tsx       |  49 ++++
 .../lib/get_alert_panels_by_category.tsx      |   3 +-
 .../alerts/lib/get_alert_panels_by_node.tsx   |   6 +-
 .../public/alerts/lib/replace_tokens.tsx      |   1 +
 .../cluster/overview/elasticsearch_panel.js   |   7 +-
 x-pack/plugins/monitoring/public/plugin.ts    |   2 +
 .../server/alerts/alerts_factory.ts           |   4 +-
 .../monitoring/server/alerts/base_alert.ts    |  13 +-
 .../alerts/ccr_read_exceptions_alert.ts       | 265 ++++++++++++++++++
 .../server/alerts/cpu_usage_alert.test.ts     |   7 +
 .../plugins/monitoring/server/alerts/index.ts |   1 +
 .../missing_monitoring_data_alert.test.ts     |   8 +
 .../lib/alerts/fetch_ccr_read_exceptions.ts   | 126 +++++++++
 15 files changed, 518 insertions(+), 10 deletions(-)
 create mode 100644 x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx
 create mode 100644 x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
 create mode 100644 x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts

diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts
index cf382701ca40f..c8b3ae0be631a 100644
--- a/x-pack/plugins/monitoring/common/constants.ts
+++ b/x-pack/plugins/monitoring/common/constants.ts
@@ -251,6 +251,7 @@ export const ALERT_MEMORY_USAGE = `${ALERT_PREFIX}alert_jvm_memory_usage`;
 export const ALERT_MISSING_MONITORING_DATA = `${ALERT_PREFIX}alert_missing_monitoring_data`;
 export const ALERT_THREAD_POOL_SEARCH_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_search_rejections`;
 export const ALERT_THREAD_POOL_WRITE_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_write_rejections`;
+export const ALERT_CCR_READ_EXCEPTIONS = `${ALERT_PREFIX}ccr_read_exceptions`;
 
 /**
  * Legacy alerts details/label for server and public use
@@ -451,6 +452,25 @@ export const ALERT_DETAILS = {
         'Alert when the number of rejections in the write thread pool exceeds the threshold.',
     }),
   },
+  [ALERT_CCR_READ_EXCEPTIONS]: {
+    paramDetails: {
+      duration: {
+        label: i18n.translate(
+          'xpack.monitoring.alerts.ccrReadExceptions.paramDetails.duration.label',
+          {
+            defaultMessage: `In the last`,
+          }
+        ),
+        type: AlertParamType.Duration,
+      },
+    },
+    label: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.label', {
+      defaultMessage: 'CCR read exceptions',
+    }),
+    description: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.description', {
+      defaultMessage: 'Alert if any CCR read exceptions have been detected.',
+    }),
+  },
 };
 
 export const ALERT_PANEL_MENU = [
@@ -485,6 +505,7 @@ export const ALERT_PANEL_MENU = [
       { alertName: ALERT_LICENSE_EXPIRATION },
       { alertName: ALERT_THREAD_POOL_SEARCH_REJECTIONS },
       { alertName: ALERT_THREAD_POOL_WRITE_REJECTIONS },
+      { alertName: ALERT_CCR_READ_EXCEPTIONS },
     ],
   },
 ];
@@ -505,6 +526,7 @@ export const ALERTS = [
   ALERT_MISSING_MONITORING_DATA,
   ALERT_THREAD_POOL_SEARCH_REJECTIONS,
   ALERT_THREAD_POOL_WRITE_REJECTIONS,
+  ALERT_CCR_READ_EXCEPTIONS,
 ];
 
 /**
diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts
index 0f10e0e48962b..c28db72c7e6a9 100644
--- a/x-pack/plugins/monitoring/common/types/alerts.ts
+++ b/x-pack/plugins/monitoring/common/types/alerts.ts
@@ -165,6 +165,20 @@ export interface AlertMemoryUsageNodeStats extends AlertNodeStats {
 export interface AlertMissingData extends AlertNodeStats {
   gapDuration: number;
 }
+export interface CCRReadExceptionsStats {
+  remoteCluster: string;
+  followerIndex: string;
+  shardId: number;
+  leaderIndex: string;
+  lastReadException: { type: string; reason: string };
+  clusterUuid: string;
+  ccs: string;
+}
+
+export interface CCRReadExceptionsUIMeta extends CCRReadExceptionsStats {
+  instanceId: string;
+  itemLabel: string;
+}
 
 export interface AlertData {
   nodeName?: string;
diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx
new file mode 100644
index 0000000000000..2dafadf272608
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { Expression, Props } from '../components/duration/expression';
+import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public';
+import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants';
+
+interface ValidateOptions {
+  duration: string;
+}
+
+const validate = (inputValues: ValidateOptions): ValidationResult => {
+  const validationResult = { errors: {} };
+  const errors: { [key: string]: string[] } = {
+    duration: [],
+  };
+  if (!inputValues.duration) {
+    errors.duration.push(
+      i18n.translate('xpack.monitoring.alerts.validation.duration', {
+        defaultMessage: 'A valid duration is required.',
+      })
+    );
+  }
+  validationResult.errors = errors;
+  return validationResult;
+};
+
+export function createCCRReadExceptionsAlertType(): AlertTypeModel {
+  return {
+    id: ALERT_CCR_READ_EXCEPTIONS,
+    name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label,
+    description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description,
+    iconClass: 'bell',
+    documentationUrl(docLinks) {
+      return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html`;
+    },
+    alertParamsExpression: (props: Props) => (
+      <Expression {...props} paramDetails={ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].paramDetails} />
+    ),
+    validate,
+    defaultActionMessage: '{{context.internalFullMessage}}',
+    requiresAppContext: true,
+  };
+}
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx
index 82a1a1f841a22..bbea32e4d2d04 100644
--- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx
@@ -171,6 +171,7 @@ export function getAlertPanelsByCategory(
       for (const { alert, states } of category.alerts) {
         const items = [];
         for (const alertState of states.filter(({ state }) => stateFilter(state))) {
+          const { nodeName, itemLabel } = alertState.state;
           items.push({
             name: (
               <Fragment>
@@ -188,7 +189,7 @@ export function getAlertPanelsByCategory(
                     )}
                   </EuiText>
                 </EuiToolTip>
-                <EuiText size="s">{alertState.state.nodeName}</EuiText>
+                <EuiText size="s">{nodeName || itemLabel}</EuiText>
               </Fragment>
             ),
             panel: ++tertiaryPanelIndex,
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx
index c48706f4edcb9..735b9c3637cdd 100644
--- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx
@@ -69,10 +69,11 @@ export function getAlertPanelsByNode(
           const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) =>
             stateFilter(state)
           );
+          const { nodeName, itemLabel } = states[0].state;
           return {
             name: (
               <EuiText>
-                {states[0].state.nodeName} ({states.length})
+                {nodeName || itemLabel} ({states.length})
               </EuiText>
             ),
             panel: index + 1,
@@ -86,7 +87,8 @@ export function getAlertPanelsByNode(
       let title = '';
       for (const { alert, states } of alertsForNode) {
         for (const alertState of states) {
-          title = alertState.state.nodeName;
+          const { nodeName, itemLabel } = alertState.state;
+          title = nodeName || itemLabel;
           panelItems.push({
             name: (
               <Fragment>
diff --git a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx
index b8ac69cbae68a..0ddda96a1100d 100644
--- a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx
@@ -77,6 +77,7 @@ export function replaceTokens(alertMessage: AlertMessage): JSX.Element | string
     }
 
     const url = linkToken.partialUrl
+      .replace('{basePath}', Legacy.shims.getBasePath())
       .replace('{elasticWebsiteUrl}', Legacy.shims.docLinks.ELASTIC_WEBSITE_URL)
       .replace('{docLinkVersion}', Legacy.shims.docLinks.DOC_LINK_VERSION);
     const index = text.indexOf(linkPart[0]);
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js
index ded309ce64e2e..8849fb05fcf3c 100644
--- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js
+++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js
@@ -47,6 +47,7 @@ import {
   ALERT_NODES_CHANGED,
   ALERT_ELASTICSEARCH_VERSION_MISMATCH,
   ALERT_MISSING_MONITORING_DATA,
+  ALERT_CCR_READ_EXCEPTIONS,
 } from '../../../../common/constants';
 import { AlertsBadge } from '../../../alerts/badge';
 import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge';
@@ -159,7 +160,11 @@ function renderLog(log) {
   );
 }
 
-const OVERVIEW_PANEL_ALERTS = [ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION];
+const OVERVIEW_PANEL_ALERTS = [
+  ALERT_CLUSTER_HEALTH,
+  ALERT_LICENSE_EXPIRATION,
+  ALERT_CCR_READ_EXCEPTIONS,
+];
 
 const NODES_PANEL_ALERTS = [
   ALERT_CPU_USAGE,
diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts
index 0439b47569e72..a0de3a7663a12 100644
--- a/x-pack/plugins/monitoring/public/plugin.ts
+++ b/x-pack/plugins/monitoring/public/plugin.ts
@@ -156,6 +156,7 @@ export class MonitoringPlugin
       './alerts/thread_pool_rejections_alert'
     );
     const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert');
+    const { createCCRReadExceptionsAlertType } = await import('./alerts/ccr_read_exceptions_alert');
 
     const {
       triggersActionsUi: { alertTypeRegistry },
@@ -176,6 +177,7 @@ export class MonitoringPlugin
         ALERT_DETAILS[ALERT_THREAD_POOL_WRITE_REJECTIONS]
       )
     );
+    alertTypeRegistry.register(createCCRReadExceptionsAlertType());
     const legacyAlertTypes = createLegacyAlertTypes();
     for (const legacyAlertType of legacyAlertTypes) {
       alertTypeRegistry.register(legacyAlertType);
diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts
index b43a56562a2aa..64b7148d87d9e 100644
--- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts
+++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts
@@ -5,6 +5,7 @@
  */
 
 import {
+  CCRReadExceptionsAlert,
   CpuUsageAlert,
   MissingMonitoringDataAlert,
   DiskUsageAlert,
@@ -32,6 +33,7 @@ import {
   ALERT_LOGSTASH_VERSION_MISMATCH,
   ALERT_KIBANA_VERSION_MISMATCH,
   ALERT_ELASTICSEARCH_VERSION_MISMATCH,
+  ALERT_CCR_READ_EXCEPTIONS,
 } from '../../common/constants';
 import { AlertsClient } from '../../../alerts/server';
 import { Alert } from '../../../alerts/common';
@@ -49,6 +51,7 @@ const BY_TYPE = {
   [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert,
   [ALERT_KIBANA_VERSION_MISMATCH]: KibanaVersionMismatchAlert,
   [ALERT_ELASTICSEARCH_VERSION_MISMATCH]: ElasticsearchVersionMismatchAlert,
+  [ALERT_CCR_READ_EXCEPTIONS]: CCRReadExceptionsAlert,
 };
 
 export class AlertsFactory {
@@ -68,7 +71,6 @@ export class AlertsFactory {
 
     if (!alertClientAlerts.total || !alertClientAlerts.data?.length) {
       return;
-      // return new alertCls() as BaseAlert;
     }
 
     const [rawAlert] = alertClientAlerts.data as [Alert];
diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts
index 4f989b37421ef..d4ce7854640f3 100644
--- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts
@@ -344,7 +344,7 @@ export class BaseAlert {
 
       const firingNodeUuids = nodes
         .filter((node) => node.shouldFire)
-        .map((node) => node.meta.nodeId)
+        .map((node) => node.meta.nodeId || node.meta.instanceId)
         .join(',');
       const instanceId = `${this.alertOptions.id}:${cluster.clusterUuid}:${firingNodeUuids}`;
       const instance = services.alertInstanceFactory(instanceId);
@@ -354,13 +354,16 @@ export class BaseAlert {
         if (!node.shouldFire) {
           continue;
         }
-        const stat = node.meta as AlertNodeState;
+        const { meta } = node;
         const nodeState = this.getDefaultAlertState(cluster, node) as AlertNodeState;
         if (key) {
-          nodeState[key] = stat[key];
+          nodeState[key] = meta[key];
         }
-        nodeState.nodeId = stat.nodeId || node.nodeId!;
-        nodeState.nodeName = stat.nodeName || node.nodeName || nodeState.nodeId;
+        nodeState.nodeId = meta.nodeId || node.nodeId! || meta.instanceId;
+        // TODO: make these functions more generic, so it's node/item agnostic
+        nodeState.nodeName = meta.itemLabel || meta.nodeName || node.nodeName || nodeState.nodeId;
+        nodeState.itemLabel = meta.itemLabel;
+        nodeState.meta = meta;
         nodeState.ui.triggeredMS = currentUTC;
         nodeState.ui.isFiring = true;
         nodeState.ui.severity = node.severity;
diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
new file mode 100644
index 0000000000000..d3b0d69aad319
--- /dev/null
+++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
@@ -0,0 +1,265 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { BaseAlert } from './base_alert';
+import {
+  AlertData,
+  AlertCluster,
+  AlertState,
+  AlertMessage,
+  CCRReadExceptionsUIMeta,
+  AlertMessageTimeToken,
+  AlertMessageLinkToken,
+  AlertInstanceState,
+  CommonAlertParams,
+} from '../../common/types/alerts';
+import { AlertInstance } from '../../../alerts/server';
+import {
+  INDEX_PATTERN_ELASTICSEARCH,
+  ALERT_CCR_READ_EXCEPTIONS,
+  ALERT_DETAILS,
+} from '../../common/constants';
+// @ts-ignore
+import { ROUNDED_FLOAT } from '../../common/formatting';
+import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions';
+import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
+import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
+import { SanitizedAlert } from '../../../alerts/common';
+import { AlertingDefaults, createLink } from './alert_helpers';
+import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index';
+import { Globals } from '../static_globals';
+
+export class CCRReadExceptionsAlert extends BaseAlert {
+  constructor(public rawAlert?: SanitizedAlert) {
+    super(rawAlert, {
+      id: ALERT_CCR_READ_EXCEPTIONS,
+      name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label,
+      throttle: '6h',
+      defaultParams: {
+        duration: '1h',
+      },
+      actionVariables: [
+        {
+          name: 'remoteClusters',
+          description: i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.remoteClusters',
+            {
+              defaultMessage: 'List of remote clusters that are experiencing CCR read exceptions.',
+            }
+          ),
+        },
+        {
+          name: 'followerIndices',
+          description: i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.followerIndices',
+            {
+              defaultMessage: 'List of follower indices reporting CCR read exceptions.',
+            }
+          ),
+        },
+        ...Object.values(AlertingDefaults.ALERT_TYPE.context),
+      ],
+    });
+  }
+
+  protected async fetchData(
+    params: CommonAlertParams,
+    callCluster: any,
+    clusters: AlertCluster[],
+    availableCcs: string[]
+  ): Promise<AlertData[]> {
+    let esIndexPattern = appendMetricbeatIndex(Globals.app.config, INDEX_PATTERN_ELASTICSEARCH);
+    if (availableCcs) {
+      esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs);
+    }
+    const { duration } = params;
+    const stats = await fetchCCRReadExceptions(
+      callCluster,
+      esIndexPattern,
+      duration as string,
+      Globals.app.config.ui.max_bucket_size
+    );
+
+    return stats.map((stat) => {
+      const {
+        remoteCluster,
+        followerIndex,
+        shardId,
+        leaderIndex,
+        lastReadException,
+        clusterUuid,
+        ccs,
+      } = stat;
+      return {
+        shouldFire: true,
+        severity: AlertSeverity.Danger,
+        meta: {
+          remoteCluster,
+          followerIndex,
+          shardId,
+          leaderIndex,
+          lastReadException,
+          instanceId: `${remoteCluster}:${followerIndex}`,
+          itemLabel: followerIndex,
+        },
+        clusterUuid,
+        ccs,
+      };
+    });
+  }
+
+  protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
+    const { remoteCluster, followerIndex, shardId } = item.meta as CCRReadExceptionsUIMeta;
+    return {
+      text: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.firingMessage', {
+        defaultMessage: `Follower index #start_link{followerIndex}#end_link is reporting CCR read exceptions on remote cluster: {remoteCluster} at #absolute`,
+        values: {
+          remoteCluster,
+          followerIndex,
+        },
+      }),
+      nextSteps: [
+        createLink(
+          i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.identifyCCRStats',
+            {
+              defaultMessage: '#start_linkIdentify CCR usage/stats#end_link',
+            }
+          ),
+          'elasticsearch/ccr',
+          AlertMessageTokenType.Link
+        ),
+        createLink(
+          i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentFollow',
+            {
+              defaultMessage: '#start_linkManage CCR follower indices#end_link',
+            }
+          ),
+          `{basePath}management/data/cross_cluster_replication/follower_indices`
+        ),
+        createLink(
+          i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentAutoFollow',
+            {
+              defaultMessage: '#start_linkCreate auto-follow patterns#end_link',
+            }
+          ),
+          `{basePath}management/data/cross_cluster_replication/auto_follow_patterns`
+        ),
+        createLink(
+          i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followerAPIDoc', {
+            defaultMessage: '#start_linkAdd follower index API (Docs)#end_link',
+          }),
+          `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/ccr-put-follow.html`
+        ),
+        createLink(
+          i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.ccrDocs', {
+            defaultMessage: '#start_linkCross-cluster replication (Docs)#end_link',
+          }),
+          `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/xpack-ccr.html`
+        ),
+        createLink(
+          i18n.translate(
+            'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.biDirectionalReplication',
+            {
+              defaultMessage: '#start_linkBi-directional replication (Blog)#end_link',
+            }
+          ),
+          `{elasticWebsiteUrl}blog/bi-directional-replication-with-elasticsearch-cross-cluster-replication-ccr`
+        ),
+        createLink(
+          i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followTheLeader', {
+            defaultMessage: '#start_linkFollow the Leader (Blog)#end_link',
+          }),
+          `{elasticWebsiteUrl}blog/follow-the-leader-an-introduction-to-cross-cluster-replication-in-elasticsearch`
+        ),
+      ],
+      tokens: [
+        {
+          startToken: '#absolute',
+          type: AlertMessageTokenType.Time,
+          isAbsolute: true,
+          isRelative: false,
+          timestamp: alertState.ui.triggeredMS,
+        } as AlertMessageTimeToken,
+        {
+          startToken: '#start_link',
+          endToken: '#end_link',
+          type: AlertMessageTokenType.Link,
+          url: `elasticsearch/ccr/${followerIndex}/shard/${shardId}`,
+        } as AlertMessageLinkToken,
+      ],
+    };
+  }
+
+  protected executeActions(
+    instance: AlertInstance,
+    { alertStates }: AlertInstanceState,
+    item: AlertData | null,
+    cluster: AlertCluster
+  ) {
+    const remoteClustersList = alertStates
+      .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).remoteCluster)
+      .join(', ');
+    const followerIndicesList = alertStates
+      .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).followerIndex)
+      .join(', ');
+
+    const shortActionText = i18n.translate(
+      'xpack.monitoring.alerts.ccrReadExceptions.shortAction',
+      {
+        defaultMessage:
+          'Verify follower/leader index relationships across the affected remote clusters.',
+      }
+    );
+    const fullActionText = i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.fullAction', {
+      defaultMessage: 'View CCR stats',
+    });
+
+    const ccs = alertStates.find((state) => state.ccs)?.ccs;
+    const globalStateLink = this.createGlobalStateLink(
+      'elasticsearch/ccr',
+      cluster.clusterUuid,
+      ccs
+    );
+
+    const action = `[${fullActionText}](${globalStateLink})`;
+    const internalShortMessage = i18n.translate(
+      'xpack.monitoring.alerts.ccrReadExceptions.firing.internalShortMessage',
+      {
+        defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. {shortActionText}`,
+        values: {
+          remoteClustersList,
+          shortActionText,
+        },
+      }
+    );
+    const internalFullMessage = i18n.translate(
+      'xpack.monitoring.alerts.ccrReadExceptions.firing.internalFullMessage',
+      {
+        defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. Current 'follower_index' indices are affected: {followerIndicesList}. {shortActionText}`,
+        values: {
+          remoteClustersList,
+          shortActionText,
+          followerIndicesList,
+        },
+      }
+    );
+
+    instance.scheduleActions('default', {
+      internalShortMessage,
+      internalFullMessage,
+      state: AlertingDefaults.ALERT_STATE.firing,
+      remoteClusters: remoteClustersList,
+      followerIndices: followerIndicesList,
+      clusterName: cluster.clusterName,
+      action,
+      actionPlain: shortActionText,
+    });
+  }
+}
diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts
index 63195621fb9c8..4622f73b9feb0 100644
--- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts
+++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts
@@ -125,6 +125,13 @@ describe('CpuUsageAlert', () => {
             ccs: undefined,
             cluster: { clusterUuid, clusterName },
             cpuUsage,
+            itemLabel: undefined,
+            meta: {
+              clusterUuid,
+              cpuUsage,
+              nodeId,
+              nodeName,
+            },
             nodeId,
             nodeName,
             ui: {
diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts
index 5fa718dfb34cd..b58476a01dc14 100644
--- a/x-pack/plugins/monitoring/server/alerts/index.ts
+++ b/x-pack/plugins/monitoring/server/alerts/index.ts
@@ -4,6 +4,7 @@
  * you may not use this file except in compliance with the Elastic License.
  */
 
+export { CCRReadExceptionsAlert } from './ccr_read_exceptions_alert';
 export { BaseAlert } from './base_alert';
 export { CpuUsageAlert } from './cpu_usage_alert';
 export { MissingMonitoringDataAlert } from './missing_monitoring_data_alert';
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 6ba4333309f00..65205738f82c3 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
@@ -131,6 +131,14 @@ describe('MissingMonitoringDataAlert', () => {
             nodeId,
             nodeName,
             gapDuration,
+            itemLabel: undefined,
+            meta: {
+              clusterUuid,
+              gapDuration,
+              limit: 86400000,
+              nodeId,
+              nodeName,
+            },
             ui: {
               isFiring: true,
               message: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
new file mode 100644
index 0000000000000..5ecb8bce5aad5
--- /dev/null
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { get } from 'lodash';
+import { CCRReadExceptionsStats } from '../../../common/types/alerts';
+
+export async function fetchCCRReadExceptions(
+  callCluster: any,
+  index: string,
+  duration: string,
+  size: number
+): Promise<CCRReadExceptionsStats[]> {
+  const params = {
+    index,
+    filterPath: ['aggregations.remote_clusters.buckets'],
+    body: {
+      size: 0,
+      query: {
+        bool: {
+          filter: [
+            {
+              nested: {
+                path: 'ccr_stats.read_exceptions',
+                query: {
+                  match_all: {},
+                },
+              },
+            },
+            {
+              term: {
+                type: 'ccr_stats',
+              },
+            },
+            {
+              range: {
+                timestamp: {
+                  gte: `now-${duration}`,
+                },
+              },
+            },
+          ],
+        },
+      },
+      aggs: {
+        remote_clusters: {
+          terms: {
+            field: 'ccr_stats.remote_cluster',
+            size,
+          },
+          aggs: {
+            follower_indices: {
+              terms: {
+                field: 'ccr_stats.follower_index',
+                size,
+              },
+              aggs: {
+                hits: {
+                  top_hits: {
+                    sort: [
+                      {
+                        timestamp: {
+                          order: 'desc',
+                          unmapped_type: 'long',
+                        },
+                      },
+                    ],
+                    _source: {
+                      includes: [
+                        'cluster_uuid',
+                        'ccr_stats.read_exceptions',
+                        'ccr_stats.shard_id',
+                        'ccr_stats.leader_index',
+                      ],
+                    },
+                    size: 1,
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const response = await callCluster('search', params);
+  const stats: CCRReadExceptionsStats[] = [];
+  const { buckets: remoteClusterBuckets = [] } = response.aggregations.remote_clusters;
+
+  if (!remoteClusterBuckets.length) {
+    return stats;
+  }
+
+  for (const remoteClusterBucket of remoteClusterBuckets) {
+    const followerIndicesBuckets = remoteClusterBucket.follower_indices.buckets;
+    const remoteCluster = remoteClusterBucket.key;
+
+    for (const followerIndexBucket of followerIndicesBuckets) {
+      const followerIndex = followerIndexBucket.key;
+      const {
+        _index: monitoringIndexName,
+        _source: { ccr_stats: ccrStats, cluster_uuid: clusterUuid },
+      } = get(followerIndexBucket, 'hits.hits.hits[0]');
+      const {
+        read_exceptions: readExceptions,
+        leader_index: leaderIndex,
+        shard_id: shardId,
+      } = ccrStats;
+      const { exception: lastReadException } = readExceptions[readExceptions.length - 1];
+
+      stats.push({
+        clusterUuid,
+        remoteCluster,
+        followerIndex,
+        shardId,
+        leaderIndex,
+        lastReadException,
+        ccs: monitoringIndexName.includes(':') ? monitoringIndexName.split(':')[0] : null,
+      });
+    }
+  }
+  return stats;
+}

From 55a5adc074dd7f2a4aa8f8b91f940de4db29c290 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Tue, 15 Dec 2020 00:13:23 -0500
Subject: [PATCH 2/7] cleanup

---
 .../monitoring/server/alerts/ccr_read_exceptions_alert.ts       | 2 --
 1 file changed, 2 deletions(-)

diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
index d3b0d69aad319..c4a335f293d87 100644
--- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
@@ -23,8 +23,6 @@ import {
   ALERT_CCR_READ_EXCEPTIONS,
   ALERT_DETAILS,
 } from '../../common/constants';
-// @ts-ignore
-import { ROUNDED_FLOAT } from '../../common/formatting';
 import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions';
 import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
 import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';

From ef76de16a2cc2086fe026278dc691415e8499513 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Tue, 15 Dec 2020 16:29:45 -0500
Subject: [PATCH 3/7] CR feedback

---
 .../server/alerts/ccr_read_exceptions_alert.ts    | 15 ++++++++++-----
 .../lib/alerts/fetch_ccr_read_exceptions.ts       | 11 ++++++++---
 2 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
index c4a335f293d87..9e48af9aad5e5 100644
--- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
@@ -26,6 +26,7 @@ import {
 import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions';
 import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
 import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
+import { parseDuration } from '../../../alerts/common/parse_duration';
 import { SanitizedAlert } from '../../../alerts/common';
 import { AlertingDefaults, createLink } from './alert_helpers';
 import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index';
@@ -74,11 +75,15 @@ export class CCRReadExceptionsAlert extends BaseAlert {
     if (availableCcs) {
       esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs);
     }
-    const { duration } = params;
+    const { duration: durationString } = params;
+    const duration = parseDuration(durationString);
+    const endMs = +new Date();
+    const startMs = endMs - duration;
     const stats = await fetchCCRReadExceptions(
       callCluster,
       esIndexPattern,
-      duration as string,
+      startMs,
+      endMs,
       Globals.app.config.ui.max_bucket_size
     );
 
@@ -212,7 +217,7 @@ export class CCRReadExceptionsAlert extends BaseAlert {
       'xpack.monitoring.alerts.ccrReadExceptions.shortAction',
       {
         defaultMessage:
-          'Verify follower/leader index relationships across the affected remote clusters.',
+          'Verify follower and leader index relationships across the affected remote clusters.',
       }
     );
     const fullActionText = i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.fullAction', {
@@ -240,10 +245,10 @@ export class CCRReadExceptionsAlert extends BaseAlert {
     const internalFullMessage = i18n.translate(
       'xpack.monitoring.alerts.ccrReadExceptions.firing.internalFullMessage',
       {
-        defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. Current 'follower_index' indices are affected: {followerIndicesList}. {shortActionText}`,
+        defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. Current 'follower_index' indices are affected: {followerIndicesList}. {action}`,
         values: {
+          action,
           remoteClustersList,
-          shortActionText,
           followerIndicesList,
         },
       }
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
index 5ecb8bce5aad5..c8933a7cd14a9 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
@@ -10,7 +10,8 @@ import { CCRReadExceptionsStats } from '../../../common/types/alerts';
 export async function fetchCCRReadExceptions(
   callCluster: any,
   index: string,
-  duration: string,
+  startMs: number,
+  endMs: number,
   size: number
 ): Promise<CCRReadExceptionsStats[]> {
   const params = {
@@ -25,7 +26,9 @@ export async function fetchCCRReadExceptions(
               nested: {
                 path: 'ccr_stats.read_exceptions',
                 query: {
-                  match_all: {},
+                  exists: {
+                    field: 'ccr_stats.read_exceptions.exception',
+                  },
                 },
               },
             },
@@ -37,7 +40,9 @@ export async function fetchCCRReadExceptions(
             {
               range: {
                 timestamp: {
-                  gte: `now-${duration}`,
+                  format: 'epoch_millis',
+                  gte: startMs,
+                  lte: endMs,
                 },
               },
             },

From f71e92262fdfff9bfdc3ee337687072d29453209 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Wed, 16 Dec 2020 20:56:29 -0500
Subject: [PATCH 4/7] Added UI/UX to ccr/shards listing and details

---
 .../plugins/monitoring/common/types/alerts.ts |   1 +
 .../monitoring/public/alerts/callout.tsx      |  16 ++-
 .../components/elasticsearch/ccr/ccr.js       | 117 +++++++++++-------
 .../elasticsearch/ccr_shard/ccr_shard.js      |  11 +-
 .../public/views/elasticsearch/ccr/index.js   |  10 +-
 .../views/elasticsearch/ccr/shard/index.js    |  19 ++-
 .../alerts/ccr_read_exceptions_alert.ts       |  17 ++-
 7 files changed, 135 insertions(+), 56 deletions(-)

diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts
index c28db72c7e6a9..66b057798074d 100644
--- a/x-pack/plugins/monitoring/common/types/alerts.ts
+++ b/x-pack/plugins/monitoring/common/types/alerts.ts
@@ -22,6 +22,7 @@ export interface CommonAlertState {
 
 export interface CommonAlertFilter {
   nodeUuid?: string;
+  shardId?: string;
 }
 
 export interface CommonAlertParamDetail {
diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx
index d3feb148cf986..52527b6d04097 100644
--- a/x-pack/plugins/monitoring/public/alerts/callout.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx
@@ -84,11 +84,19 @@ export const AlertsCallout: React.FC<Props> = (props: Props) => {
             paddingLeft: `0.5rem`,
           }}
         >
-          {(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => {
-            return <EuiListGroupItem onClick={() => {}} label={replaceTokens(step)} />;
-          })}
+          {(status.state.state.ui.message.nextSteps || []).map(
+            (step: AlertMessage, stepIndex: number) => {
+              return (
+                <EuiListGroupItem
+                  onClick={() => {}}
+                  label={replaceTokens(step)}
+                  key={index + stepIndex}
+                />
+              );
+            }
+          )}
           <EuiListGroupItem
-            label={<AlertConfiguration alert={status.alert.rawAlert} compressed />}
+            label={<AlertConfiguration alert={status.alert.rawAlert} key={index} compressed />}
           />
         </EuiListGroup>
       </EuiAccordion>
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js
index ab26b6a9cc0bb..8b7c386a4dcc6 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js
@@ -4,7 +4,7 @@
  * you may not use this file except in compliance with the Elastic License.
  */
 
-import React, { Fragment, Component } from 'react';
+import React, { Fragment, useState } from 'react';
 import {
   EuiInMemoryTable,
   EuiLink,
@@ -20,27 +20,20 @@ import {
 import { FormattedMessage } from '@kbn/i18n/react';
 import { i18n } from '@kbn/i18n';
 import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
+import { AlertsStatus } from '../../../alerts/status';
 import './ccr.scss';
 
 function toSeconds(ms) {
   return Math.floor(ms / 1000) + 's';
 }
 
-export class Ccr extends Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      itemIdToExpandedRowMap: {},
-    };
-  }
-
-  toggleShards(index, shards) {
-    const itemIdToExpandedRowMap = {
-      ...this.state.itemIdToExpandedRowMap,
-    };
+export const Ccr = (props) => {
+  const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({});
+  const toggleShards = (index, shards) => {
+    const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
 
-    if (itemIdToExpandedRowMap[index]) {
-      delete itemIdToExpandedRowMap[index];
+    if (itemIdToExpandedRowMapValues[index]) {
+      delete itemIdToExpandedRowMapValues[index];
     } else {
       let pagination = {
         initialPageSize: 5,
@@ -51,7 +44,7 @@ export class Ccr extends Component {
         pagination = false;
       }
 
-      itemIdToExpandedRowMap[index] = (
+      itemIdToExpandedRowMapValues[index] = (
         <EuiInMemoryTable
           items={shards}
           columns={[
@@ -76,6 +69,25 @@ export class Ccr extends Component {
             {
               render: () => null,
             },
+            {
+              field: 'alerts',
+              sortable: true,
+              name: i18n.translate(
+                'xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle',
+                {
+                  defaultMessage: 'Alerts',
+                }
+              ),
+              render: (_field, item) => {
+                return (
+                  <AlertsStatus
+                    showBadge={true}
+                    alerts={props.alerts}
+                    stateFilter={(state) => state.meta.shardId === item.shardId}
+                  />
+                );
+              },
+            },
             {
               field: 'syncLagOps',
               name: i18n.translate(
@@ -157,11 +169,11 @@ export class Ccr extends Component {
         />
       );
     }
-    this.setState({ itemIdToExpandedRowMap });
-  }
+    setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
+  };
 
-  renderTable() {
-    const { data } = this.props;
+  const renderTable = () => {
+    const { data, alerts } = props;
     const items = data;
 
     let pagination = {
@@ -194,9 +206,9 @@ export class Ccr extends Component {
             ),
             sortable: true,
             render: (index, { shards }) => {
-              const expanded = !!this.state.itemIdToExpandedRowMap[index];
+              const expanded = !!itemIdToExpandedRowMap[index];
               return (
-                <EuiLink onClick={() => this.toggleShards(index, shards)}>
+                <EuiLink onClick={() => toggleShards(index, shards)}>
                   {index}
                   &nbsp;
                   {expanded ? <EuiIcon type="arrowUp" /> : <EuiIcon type="arrowDown" />}
@@ -214,6 +226,25 @@ export class Ccr extends Component {
               }
             ),
           },
+          {
+            field: 'alerts',
+            sortable: true,
+            name: i18n.translate(
+              'xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle',
+              {
+                defaultMessage: 'Alerts',
+              }
+            ),
+            render: (_field, item) => {
+              return (
+                <AlertsStatus
+                  showBadge={true}
+                  alerts={alerts}
+                  stateFilter={(state) => state.meta.followerIndex === item.index}
+                />
+              );
+            },
+          },
           {
             field: 'syncLagOps',
             sortable: true,
@@ -264,28 +295,26 @@ export class Ccr extends Component {
         }}
         sorting={sorting}
         itemId="id"
-        itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
+        itemIdToExpandedRowMap={itemIdToExpandedRowMap}
       />
     );
-  }
+  };
 
-  render() {
-    return (
-      <EuiPage>
-        <EuiPageBody>
-          <EuiScreenReaderOnly>
-            <h1>
-              <FormattedMessage
-                id="xpack.monitoring.elasticsearch.ccr.heading"
-                defaultMessage="CCR"
-              />
-            </h1>
-          </EuiScreenReaderOnly>
-          <EuiPageContent>
-            <EuiPageContentBody>{this.renderTable()}</EuiPageContentBody>
-          </EuiPageContent>
-        </EuiPageBody>
-      </EuiPage>
-    );
-  }
-}
+  return (
+    <EuiPage>
+      <EuiPageBody>
+        <EuiScreenReaderOnly>
+          <h1>
+            <FormattedMessage
+              id="xpack.monitoring.elasticsearch.ccr.heading"
+              defaultMessage="CCR"
+            />
+          </h1>
+        </EuiScreenReaderOnly>
+        <EuiPageContent>
+          <EuiPageContentBody>{renderTable()}</EuiPageContentBody>
+        </EuiPageContent>
+      </EuiPageBody>
+    </EuiPage>
+  );
+};
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
index a8aa931bad254..e2bf5756c37f3 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
@@ -25,6 +25,7 @@ import { Status } from './status';
 import { formatDateTimeLocal } from '../../../../common/formatting';
 import { FormattedMessage } from '@kbn/i18n/react';
 import { i18n } from '@kbn/i18n';
+import { AlertsCallout } from '../../../alerts/callout';
 
 export class CcrShard extends PureComponent {
   renderCharts() {
@@ -123,13 +124,17 @@ export class CcrShard extends PureComponent {
   }
 
   render() {
-    const { stat, oldestStat, formattedLeader } = this.props;
+    const { stat, oldestStat, formattedLeader, alerts } = this.props;
 
     return (
       <EuiPage style={{ backgroundColor: 'white' }}>
         <EuiPageBody>
-          <Status stat={stat} formattedLeader={formattedLeader} oldestStat={oldestStat} />
-          <EuiSpacer size="s" />
+          <EuiPanel>
+            <Status stat={stat} formattedLeader={formattedLeader} oldestStat={oldestStat} />
+          </EuiPanel>
+          <EuiSpacer size="m" />
+          <AlertsCallout alerts={alerts} />
+          <EuiSpacer size="m" />
           {this.renderErrors()}
           <EuiFlexGroup wrap>{this.renderCharts()}</EuiFlexGroup>
           <EuiHorizontalRule />
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
index 6569340785736..244e382a95335 100644
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
+++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
@@ -12,7 +12,7 @@ import { routeInitProvider } from '../../../lib/route_init';
 import template from './index.html';
 import { Ccr } from '../../../components/elasticsearch/ccr';
 import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants';
+import { CODE_PATH_ELASTICSEARCH, ALERT_CCR_READ_EXCEPTIONS } from '../../../../common/constants';
 
 uiRoutes.when('/elasticsearch/ccr', {
   template,
@@ -37,6 +37,12 @@ uiRoutes.when('/elasticsearch/ccr', {
         getPageData,
         $scope,
         $injector,
+        alerts: {
+          shouldFetch: true,
+          options: {
+            alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS],
+          },
+        },
       });
 
       $scope.$watch(
@@ -45,7 +51,7 @@ uiRoutes.when('/elasticsearch/ccr', {
           if (!data) {
             return;
           }
-          this.renderReact(<Ccr data={data.data} />);
+          this.renderReact(<Ccr data={data.data} alerts={this.alerts} />);
         }
       );
     }
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
index 33a2d27f39856..d42f417d24202 100644
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
+++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
@@ -13,7 +13,10 @@ import { routeInitProvider } from '../../../../lib/route_init';
 import template from './index.html';
 import { MonitoringViewBaseController } from '../../../base_controller';
 import { CcrShard } from '../../../../components/elasticsearch/ccr_shard';
-import { CODE_PATH_ELASTICSEARCH } from '../../../../../common/constants';
+import {
+  CODE_PATH_ELASTICSEARCH,
+  ALERT_CCR_READ_EXCEPTIONS,
+} from '../../../../../common/constants';
 
 uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
   template,
@@ -27,6 +30,7 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
   controllerAs: 'elasticsearchCcr',
   controller: class ElasticsearchCcrController extends MonitoringViewBaseController {
     constructor($injector, $scope, pageData) {
+      const $route = $injector.get('$route');
       super({
         title: i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.routeTitle', {
           defaultMessage: 'Elasticsearch - Ccr - Shard',
@@ -35,6 +39,17 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
         getPageData,
         $scope,
         $injector,
+        alerts: {
+          shouldFetch: true,
+          options: {
+            alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS],
+            filters: [
+              {
+                shardId: $route.current.pathParams.shardId,
+              },
+            ],
+          },
+        },
       });
 
       $scope.instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', {
@@ -62,7 +77,7 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
             })
           );
 
-          this.renderReact(<CcrShard {...data} />);
+          this.renderReact(<CcrShard {...data} alerts={this.alerts} />);
         }
       );
     }
diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
index 9e48af9aad5e5..ccc52b444061e 100644
--- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
@@ -16,6 +16,8 @@ import {
   AlertMessageLinkToken,
   AlertInstanceState,
   CommonAlertParams,
+  CommonAlertFilter,
+  CCRReadExceptionsStats,
 } from '../../common/types/alerts';
 import { AlertInstance } from '../../../alerts/server';
 import {
@@ -27,7 +29,7 @@ import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions'
 import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern';
 import { AlertMessageTokenType, AlertSeverity } from '../../common/enums';
 import { parseDuration } from '../../../alerts/common/parse_duration';
-import { SanitizedAlert } from '../../../alerts/common';
+import { SanitizedAlert, RawAlertInstance } from '../../../alerts/common';
 import { AlertingDefaults, createLink } from './alert_helpers';
 import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index';
 import { Globals } from '../static_globals';
@@ -200,6 +202,19 @@ export class CCRReadExceptionsAlert extends BaseAlert {
     };
   }
 
+  protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) {
+    const alertInstanceStates = alertInstance.state?.alertStates as AlertState[];
+    const alertFilter = filters?.find((filter) => filter.shardId);
+    if (!filters || !filters.length || !alertInstanceStates?.length || !alertFilter?.shardId) {
+      return alertInstance;
+    }
+    const shardIdInt = parseInt(alertFilter.shardId!, 10);
+    const alertStates = alertInstanceStates.filter(
+      ({ meta }) => (meta as CCRReadExceptionsStats).shardId === shardIdInt
+    );
+    return { state: { alertStates } };
+  }
+
   protected executeActions(
     instance: AlertInstance,
     { alertStates }: AlertInstanceState,

From f6455f60ec0bb0c8255b6e3d1b8740b06a729908 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Thu, 17 Dec 2020 10:11:04 -0500
Subject: [PATCH 5/7] Fixed snaps

---
 .../ccr/__snapshots__/ccr.test.js.snap        |   6 +
 .../__snapshots__/ccr_shard.test.js.snap      | 154 +++++++++---------
 2 files changed, 84 insertions(+), 76 deletions(-)

diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap
index d54612b6f4f29..794982a0b6193 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap
@@ -29,6 +29,12 @@ exports[`Ccr that it renders normally 1`] = `
                 "name": "Follows",
                 "sortable": true,
               },
+              Object {
+                "field": "alerts",
+                "name": "Alerts",
+                "render": [Function],
+                "sortable": true,
+              },
               Object {
                 "field": "syncLagOps",
                 "name": "Sync Lag (ops)",
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap
index e35d2ba6108f5..81398c1d8e836 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap
@@ -2,50 +2,46 @@
 
 exports[`CcrShard that is renders an exception properly 1`] = `
 <EuiPanel>
-  <EuiTitle
-    color="danger"
-    size="s"
-  >
-    <h3>
-      <EuiTextColor
-        color="danger"
-      >
-        <FormattedMessage
-          defaultMessage="Errors"
-          id="xpack.monitoring.elasticsearch.ccrShard.errorsTableTitle"
-          values={Object {}}
-        />
-      </EuiTextColor>
-    </h3>
-  </EuiTitle>
-  <EuiSpacer
-    size="s"
-  />
-  <EuiBasicTable
-    columns={
-      Array [
-        Object {
-          "field": "exception.type",
-          "name": "Type",
-        },
-        Object {
-          "field": "exception.reason",
-          "name": "Reason",
-          "width": "75%",
-        },
-      ]
+  <Status
+    formattedLeader="leader on remote"
+    oldestStat={
+      Object {
+        "failed_read_requests": 0,
+        "operations_written": 2976,
+      }
     }
-    items={
-      Array [
-        Object {
-          "reason": "not sure but something happened",
-          "type": "something_is_wrong",
-        },
-      ]
+    stat={
+      Object {
+        "failed_read_requests": 0,
+        "follower_global_checkpoint": 3049,
+        "follower_index": "follower",
+        "follower_max_seq_no": 3049,
+        "last_requested_seq_no": 3049,
+        "leader_global_checkpoint": 3049,
+        "leader_index": "leader",
+        "leader_max_seq_no": 3049,
+        "mapping_version": 2,
+        "number_of_concurrent_reads": 1,
+        "number_of_concurrent_writes": 0,
+        "number_of_failed_bulk_operations": 0,
+        "number_of_queued_writes": 0,
+        "number_of_successful_bulk_operations": 3050,
+        "number_of_successful_fetches": 3050,
+        "operations_received": 3050,
+        "operations_written": 3050,
+        "read_exceptions": Array [
+          Object {
+            "reason": "not sure but something happened",
+            "type": "something_is_wrong",
+          },
+        ],
+        "shard_id": 0,
+        "time_since_last_read_millis": 9402,
+        "total_fetch_time_millis": 44128980,
+        "total_index_time_millis": 41827,
+        "total_transferred_bytes": 234156,
+      }
     }
-    noItemsMessage="No items found"
-    responsive={true}
-    tableLayout="fixed"
   />
 </EuiPanel>
 `;
@@ -59,44 +55,50 @@ exports[`CcrShard that it renders normally 1`] = `
   }
 >
   <EuiPageBody>
-    <Status
-      formattedLeader="leader on remote"
-      oldestStat={
-        Object {
-          "failed_read_requests": 0,
-          "operations_written": 2976,
+    <EuiPanel>
+      <Status
+        formattedLeader="leader on remote"
+        oldestStat={
+          Object {
+            "failed_read_requests": 0,
+            "operations_written": 2976,
+          }
         }
-      }
-      stat={
-        Object {
-          "failed_read_requests": 0,
-          "follower_global_checkpoint": 3049,
-          "follower_index": "follower",
-          "follower_max_seq_no": 3049,
-          "last_requested_seq_no": 3049,
-          "leader_global_checkpoint": 3049,
-          "leader_index": "leader",
-          "leader_max_seq_no": 3049,
-          "mapping_version": 2,
-          "number_of_concurrent_reads": 1,
-          "number_of_concurrent_writes": 0,
-          "number_of_failed_bulk_operations": 0,
-          "number_of_queued_writes": 0,
-          "number_of_successful_bulk_operations": 3050,
-          "number_of_successful_fetches": 3050,
-          "operations_received": 3050,
-          "operations_written": 3050,
-          "read_exceptions": Array [],
-          "shard_id": 0,
-          "time_since_last_read_millis": 9402,
-          "total_fetch_time_millis": 44128980,
-          "total_index_time_millis": 41827,
-          "total_transferred_bytes": 234156,
+        stat={
+          Object {
+            "failed_read_requests": 0,
+            "follower_global_checkpoint": 3049,
+            "follower_index": "follower",
+            "follower_max_seq_no": 3049,
+            "last_requested_seq_no": 3049,
+            "leader_global_checkpoint": 3049,
+            "leader_index": "leader",
+            "leader_max_seq_no": 3049,
+            "mapping_version": 2,
+            "number_of_concurrent_reads": 1,
+            "number_of_concurrent_writes": 0,
+            "number_of_failed_bulk_operations": 0,
+            "number_of_queued_writes": 0,
+            "number_of_successful_bulk_operations": 3050,
+            "number_of_successful_fetches": 3050,
+            "operations_received": 3050,
+            "operations_written": 3050,
+            "read_exceptions": Array [],
+            "shard_id": 0,
+            "time_since_last_read_millis": 9402,
+            "total_fetch_time_millis": 44128980,
+            "total_index_time_millis": 41827,
+            "total_transferred_bytes": 234156,
+          }
         }
-      }
+      />
+    </EuiPanel>
+    <EuiSpacer
+      size="m"
     />
+    <AlertsCallout />
     <EuiSpacer
-      size="s"
+      size="m"
     />
     <EuiFlexGroup
       wrap={true}

From e724db14901bd851fcd0759e79b27c4d2d7907d4 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Fri, 18 Dec 2020 02:05:27 -0500
Subject: [PATCH 6/7] Added reason for the exception

---
 x-pack/plugins/monitoring/common/types/alerts.ts    |  1 +
 x-pack/plugins/monitoring/public/alerts/callout.tsx | 13 +++++++++++++
 x-pack/plugins/monitoring/public/alerts/panel.tsx   | 13 +++++++++++++
 .../server/alerts/ccr_read_exceptions_alert.ts      |  8 +++++++-
 4 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts
index 66b057798074d..93807f9df12b0 100644
--- a/x-pack/plugins/monitoring/common/types/alerts.ts
+++ b/x-pack/plugins/monitoring/common/types/alerts.ts
@@ -104,6 +104,7 @@ export interface AlertUiState {
 
 export interface AlertMessage {
   text: string; // Do this. #link this is a link #link
+  code?: string;
   nextSteps?: AlertMessage[];
   tokens?: AlertMessageToken[];
 }
diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx
index 52527b6d04097..af2d8c3fef60a 100644
--- a/x-pack/plugins/monitoring/public/alerts/callout.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx
@@ -15,6 +15,7 @@ import {
   EuiFlexGroup,
   EuiFlexItem,
   EuiIcon,
+  EuiCodeBlock,
 } from '@elastic/eui';
 import { replaceTokens } from './lib/replace_tokens';
 import { AlertMessage } from '../../common/types/alerts';
@@ -66,12 +67,24 @@ export const AlertsCallout: React.FC<Props> = (props: Props) => {
       </div>
     );
 
+    const { code } = status.state.state.ui.message;
     const accordion = (
       <EuiAccordion
         id={`monitoringAlertCallout_${index}`}
         buttonContent={buttonContent}
         paddingSize="s"
       >
+        {code?.length ? (
+          <EuiCodeBlock
+            fontSize="s"
+            paddingSize="s"
+            language="json"
+            isCopyable={true}
+            overflowHeight={300}
+          >
+            {code}
+          </EuiCodeBlock>
+        ) : null}
         <EuiListGroup
           flush={true}
           bordered={true}
diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx
index 139010a3d2446..2d319a81dd063 100644
--- a/x-pack/plugins/monitoring/public/alerts/panel.tsx
+++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx
@@ -10,6 +10,7 @@ import {
   EuiHorizontalRule,
   EuiListGroup,
   EuiListGroupItem,
+  EuiCodeBlock,
 } from '@elastic/eui';
 
 import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts';
@@ -47,12 +48,24 @@ export const AlertPanel: React.FC<Props> = (props: Props) => {
       </EuiListGroup>
     ) : null;
 
+  const { code } = alertState.state.ui.message;
   return (
     <Fragment>
       <div style={{ padding: '1rem' }}>
         <EuiTitle size="xs">
           <h5>{replaceTokens(alertState.state.ui.message)}</h5>
         </EuiTitle>
+        {code?.length ? (
+          <EuiCodeBlock
+            fontSize="s"
+            paddingSize="s"
+            language="json"
+            isCopyable={true}
+            overflowHeight={150}
+          >
+            {code}
+          </EuiCodeBlock>
+        ) : null}
         {nextStepsUi ? <EuiSpacer size="s" /> : null}
         {nextStepsUi}
       </div>
diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
index ccc52b444061e..6034f32a8c659 100644
--- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
+++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts
@@ -118,7 +118,12 @@ export class CCRReadExceptionsAlert extends BaseAlert {
   }
 
   protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage {
-    const { remoteCluster, followerIndex, shardId } = item.meta as CCRReadExceptionsUIMeta;
+    const {
+      remoteCluster,
+      followerIndex,
+      shardId,
+      lastReadException,
+    } = item.meta as CCRReadExceptionsUIMeta;
     return {
       text: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.firingMessage', {
         defaultMessage: `Follower index #start_link{followerIndex}#end_link is reporting CCR read exceptions on remote cluster: {remoteCluster} at #absolute`,
@@ -127,6 +132,7 @@ export class CCRReadExceptionsAlert extends BaseAlert {
           followerIndex,
         },
       }),
+      code: JSON.stringify(lastReadException, null, 2),
       nextSteps: [
         createLink(
           i18n.translate(

From d708b215b8436a8b87267c8466e571d7f8ab5626 Mon Sep 17 00:00:00 2001
From: Igor Zaytsev <igor.zaytsev.dev@gmail.com>
Date: Fri, 18 Dec 2020 13:10:06 -0500
Subject: [PATCH 7/7] Added setup mode funtionality and alert status

---
 .../elasticsearch/ccr_shard/ccr_shard.js      |  7 +++++-
 .../elasticsearch/ccr_shard/status.js         |  9 +++++++-
 .../public/views/elasticsearch/ccr/index.js   | 23 +++++++++++++++++--
 .../views/elasticsearch/ccr/shard/index.js    | 18 ++++++++++++++-
 4 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
index e2bf5756c37f3..a6ac2d8510a3a 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js
@@ -130,7 +130,12 @@ export class CcrShard extends PureComponent {
       <EuiPage style={{ backgroundColor: 'white' }}>
         <EuiPageBody>
           <EuiPanel>
-            <Status stat={stat} formattedLeader={formattedLeader} oldestStat={oldestStat} />
+            <Status
+              stat={stat}
+              formattedLeader={formattedLeader}
+              oldestStat={oldestStat}
+              alerts={alerts}
+            />
           </EuiPanel>
           <EuiSpacer size="m" />
           <AlertsCallout alerts={alerts} />
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
index 52de0659ed527..657301d6e1cb3 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
@@ -8,8 +8,9 @@ import React from 'react';
 import { SummaryStatus } from '../../summary_status';
 import { formatMetric } from '../../../lib/format_number';
 import { i18n } from '@kbn/i18n';
+import { AlertsStatus } from '../../../alerts/status';
 
-export function Status({ stat, formattedLeader, oldestStat }) {
+export function Status({ stat, formattedLeader, oldestStat, alerts = {} }) {
   const {
     follower_index: followerIndex,
     shard_id: shardId,
@@ -23,6 +24,12 @@ export function Status({ stat, formattedLeader, oldestStat }) {
   } = oldestStat;
 
   const metrics = [
+    {
+      label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.alerts', {
+        defaultMessage: 'Alerts',
+      }),
+      value: <AlertsStatus alerts={alerts} showOnlyCount={true} />,
+    },
     {
       label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.followerIndexLabel', {
         defaultMessage: 'Follower Index',
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
index 244e382a95335..9e26d453d76a3 100644
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
+++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js
@@ -12,7 +12,13 @@ import { routeInitProvider } from '../../../lib/route_init';
 import template from './index.html';
 import { Ccr } from '../../../components/elasticsearch/ccr';
 import { MonitoringViewBaseController } from '../../base_controller';
-import { CODE_PATH_ELASTICSEARCH, ALERT_CCR_READ_EXCEPTIONS } from '../../../../common/constants';
+import {
+  CODE_PATH_ELASTICSEARCH,
+  ALERT_CCR_READ_EXCEPTIONS,
+  ELASTICSEARCH_SYSTEM_ID,
+} from '../../../../common/constants';
+import { SetupModeRenderer } from '../../../components/renderers';
+import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
 
 uiRoutes.when('/elasticsearch/ccr', {
   template,
@@ -51,7 +57,20 @@ uiRoutes.when('/elasticsearch/ccr', {
           if (!data) {
             return;
           }
-          this.renderReact(<Ccr data={data.data} alerts={this.alerts} />);
+          this.renderReact(
+            <SetupModeRenderer
+              scope={$scope}
+              injector={$injector}
+              productName={ELASTICSEARCH_SYSTEM_ID}
+              render={({ flyoutComponent, bottomBarComponent }) => (
+                <SetupModeContext.Provider value={{ setupModeSupported: true }}>
+                  {flyoutComponent}
+                  <Ccr data={data.data} alerts={this.alerts} />
+                  {bottomBarComponent}
+                </SetupModeContext.Provider>
+              )}
+            />
+          );
         }
       );
     }
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
index d42f417d24202..6c1c4218568e3 100644
--- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
+++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js
@@ -16,7 +16,10 @@ import { CcrShard } from '../../../../components/elasticsearch/ccr_shard';
 import {
   CODE_PATH_ELASTICSEARCH,
   ALERT_CCR_READ_EXCEPTIONS,
+  ELASTICSEARCH_SYSTEM_ID,
 } from '../../../../../common/constants';
+import { SetupModeRenderer } from '../../../../components/renderers';
+import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context';
 
 uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
   template,
@@ -77,7 +80,20 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', {
             })
           );
 
-          this.renderReact(<CcrShard {...data} alerts={this.alerts} />);
+          this.renderReact(
+            <SetupModeRenderer
+              scope={$scope}
+              injector={$injector}
+              productName={ELASTICSEARCH_SYSTEM_ID}
+              render={({ flyoutComponent, bottomBarComponent }) => (
+                <SetupModeContext.Provider value={{ setupModeSupported: true }}>
+                  {flyoutComponent}
+                  <CcrShard {...data} alerts={this.alerts} />
+                  {bottomBarComponent}
+                </SetupModeContext.Provider>
+              )}
+            />
+          );
         }
       );
     }