import { css } from '@emotion/css'; import { groupBy, size, upperFirst } from 'lodash'; import { Fragment, ReactNode } from 'react'; import { GrafanaTheme2, dateTime } from '@grafana/data'; import { Icon, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; import { PrimaryText } from 'app/features/alerting/unified/components/common/TextVariants'; import { ContactPointHeader } from 'app/features/alerting/unified/components/contact-points/ContactPointHeader'; import { useDeleteContactPointModal } from 'app/features/alerting/unified/components/contact-points/components/Modals'; import { useDeleteContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { receiverTypeNames } from 'app/plugins/datasource/alertmanager/consts'; import { GrafanaNotifierType, NotifierStatus } from 'app/types/alerting'; import { INTEGRATION_ICONS } from '../../types/contact-points'; import { MetaText } from '../MetaText'; import { ReceiverMetadataBadge } from '../receivers/grafanaAppReceivers/ReceiverMetadataBadge'; import { ReceiverPluginMetadata } from '../receivers/grafanaAppReceivers/useReceiversMetadata'; import { RECEIVER_META_KEY, RECEIVER_PLUGIN_META_KEY, RECEIVER_STATUS_KEY } from './constants'; import { ContactPointWithMetadata, ReceiverConfigWithMetadata, getReceiverDescription } from './utils'; interface ContactPointProps { contactPoint: ContactPointWithMetadata; } export const ContactPoint = ({ contactPoint }: ContactPointProps) => { const { grafana_managed_receiver_configs: receivers } = contactPoint; const styles = useStyles2(getStyles); const { selectedAlertmanager } = useAlertmanager(); const [deleteTrigger] = useDeleteContactPoint({ alertmanager: selectedAlertmanager! }); const [DeleteModal, showDeleteModal] = useDeleteContactPointModal(deleteTrigger.execute); // TODO probably not the best way to figure out if we want to show either only the summary or full metadata for the receivers? const showFullMetadata = receivers.some((receiver) => Boolean(receiver[RECEIVER_META_KEY])); return (
showDeleteModal({ name: contactPointToDelete.id || contactPointToDelete.name, resourceVersion: contactPointToDelete.metadata?.resourceVersion, }) } /> {showFullMetadata ? (
{receivers.map((receiver, index) => { const diagnostics = receiver[RECEIVER_STATUS_KEY]; const metadata = receiver[RECEIVER_META_KEY]; const sendingResolved = !Boolean(receiver.disableResolveMessage); const pluginMetadata = receiver[RECEIVER_PLUGIN_META_KEY]; const key = metadata.name + index; return ( ); })}
) : (
)}
{DeleteModal}
); }; interface ContactPointReceiverProps { name: string; type: GrafanaNotifierType | string; description?: ReactNode; sendingResolved?: boolean; diagnostics?: NotifierStatus; pluginMetadata?: ReceiverPluginMetadata; } const ContactPointReceiver = (props: ContactPointReceiverProps) => { const { name, type, description, diagnostics, pluginMetadata, sendingResolved = true } = props; const styles = useStyles2(getStyles); const hasMetadata = diagnostics !== undefined; return (
{hasMetadata && }
); }; export interface ContactPointReceiverTitleRowProps { name: string; type: GrafanaNotifierType | string; description?: ReactNode; pluginMetadata?: ReceiverPluginMetadata; } export function ContactPointReceiverTitleRow(props: ContactPointReceiverTitleRowProps) { const { name, type, description, pluginMetadata } = props; const iconName = INTEGRATION_ICONS[type]; return ( {iconName && } {pluginMetadata ? ( ) : ( {name} )} {description && ( {description} )} ); } interface ContactPointReceiverMetadata { sendingResolved: boolean; diagnostics: NotifierStatus; } type ContactPointReceiverSummaryProps = { receivers: ReceiverConfigWithMetadata[]; limit?: number; }; /** * This summary is used when we're dealing with non-Grafana managed alertmanager since they * don't have any metadata worth showing other than a summary of what types are configured for the contact point */ export const ContactPointReceiverSummary = ({ receivers, limit }: ContactPointReceiverSummaryProps) => { // limit for how many integrations are rendered const INTEGRATIONS_LIMIT = limit ?? Number.MAX_VALUE; const countByType = groupBy(receivers, (receiver) => receiver.type); const numberOfUniqueIntegrations = size(countByType); const integrationsShown = Object.entries(countByType).slice(0, INTEGRATIONS_LIMIT); const numberOfIntegrationsNotShown = numberOfUniqueIntegrations - INTEGRATIONS_LIMIT; return ( {integrationsShown.length === 0 && ( No integrations configured )} {integrationsShown.map(([type, receivers], index) => { const iconName = INTEGRATION_ICONS[type]; const receiverName = receiverTypeNames[type] ?? upperFirst(type); const isLastItem = size(countByType) - 1 === index; // Pick the first integration of the grouped receivers, since they should all be the same type // e.g. if we have multiple Oncall, they _should_ all have the same plugin metadata, // so we can just use the first one for additional display purposes const receiver = receivers[0]; return ( {receiver[RECEIVER_PLUGIN_META_KEY]?.icon && ( {receiver[RECEIVER_PLUGIN_META_KEY]?.title} )} {iconName && } {receiverName} {receivers.length > 1 && ` (${receivers.length})`} {!isLastItem && '⋅'} ); })} {numberOfIntegrationsNotShown > 0 && {`+${numberOfIntegrationsNotShown} more`}} ); }; const ContactPointReceiverMetadataRow = ({ diagnostics, sendingResolved }: ContactPointReceiverMetadata) => { const styles = useStyles2(getStyles); const failedToSend = Boolean(diagnostics.lastNotifyAttemptError); const lastDeliveryAttempt = dateTime(diagnostics.lastNotifyAttempt); const lastDeliveryAttemptDuration = diagnostics.lastNotifyAttemptDuration; const hasDeliveryAttempt = lastDeliveryAttempt.isValid(); return (
{/* this is shown when the last delivery failed – we don't show any additional metadata */} {failedToSend ? ( <> Last delivery attempt failed ) : ( <> {/* this is shown when we have a last delivery attempt */} {hasDeliveryAttempt && ( <> Last delivery attempt {lastDeliveryAttempt.locale('en').fromNow()} Last delivery took )} {/* when we have no last delivery attempt */} {!hasDeliveryAttempt && ( No delivery attempts )} {/* this is only shown for contact points that only want "firing" updates */} {!sendingResolved && ( Delivering only firing notifications )} )}
); }; const getStyles = (theme: GrafanaTheme2) => ({ contactPointWrapper: css({ borderRadius: `${theme.shape.radius.default}`, border: `solid 1px ${theme.colors.border.weak}`, borderBottom: 'none', }), integrationWrapper: css({ position: 'relative', background: `${theme.colors.background.primary}`, padding: `${theme.spacing(1)} ${theme.spacing(1.5)}`, borderBottom: `solid 1px ${theme.colors.border.weak}`, }), metadataRow: css({ borderBottomLeftRadius: `${theme.shape.radius.default}`, borderBottomRightRadius: `${theme.shape.radius.default}`, }), noIntegrationsContainer: css({ paddingTop: `${theme.spacing(1.5)}`, paddingLeft: `${theme.spacing(1.5)}`, }), });