import {Fragment, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import color from 'color'; import sortBy from 'lodash/sortBy'; import startCase from 'lodash/startCase'; import {loadIncidents} from 'sentry/actionCreators/serviceIncidents'; import Button from 'sentry/components/button'; import List from 'sentry/components/list'; import ListItem from 'sentry/components/list/listItem'; import Text from 'sentry/components/text'; import Tooltip from 'sentry/components/tooltip'; import {IS_ACCEPTANCE_TEST} from 'sentry/constants'; import { IconCheckmark, IconFatal, IconFire, IconInfo, IconOpen, IconWarning, } from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import space from 'sentry/styles/space'; import {SentryServiceStatus} from 'sentry/types'; import marked from 'sentry/utils/marked'; import TimeSince from '../timeSince'; import SidebarItem from './sidebarItem'; import SidebarPanel from './sidebarPanel'; import SidebarPanelEmpty from './sidebarPanelEmpty'; import SidebarPanelItem from './sidebarPanelItem'; import {CommonSidebarProps, SidebarPanelKey} from './types'; type Props = CommonSidebarProps; type Status = SentryServiceStatus['incidents'][number]['affectedComponents'][number]['status']; const COMPONENT_STATUS_SORT: Status[] = [ 'operational', 'degraded_performance', 'partial_outage', 'major_outage', ]; function ServiceIncidents({ currentPanel, onShowPanel, hidePanel, collapsed, orientation, }: Props) { const [serviceStatus, setServiceStatus] = useState(null); async function fetchData() { try { setServiceStatus(await loadIncidents()); } catch (e) { Sentry.withScope(scope => { scope.setLevel('warning'); scope.setFingerprint(['ServiceIncidents-fetchData']); Sentry.captureException(e); }); } } useEffect(() => void fetchData(), []); // Never render incidents in acceptance tests if (IS_ACCEPTANCE_TEST) { return null; } if (!serviceStatus) { return null; } const active = currentPanel === SidebarPanelKey.ServiceIncidents; const isEmpty = !serviceStatus.incidents || serviceStatus.incidents.length === 0; if (isEmpty) { return null; } return ( } label={t('Service status')} onClick={onShowPanel} /> {active && serviceStatus && ( {isEmpty && ( {t('There are no incidents to report')} )} {serviceStatus.incidents.map(incident => ( } priority="link" href={incident.url} external > {t('Full Incident Details')} } > {tct( "This incident started [timeAgo]. We're experiencing the following problems with our services", { timeAgo: ( ), } )} {sortBy(incident.affectedComponents, i => COMPONENT_STATUS_SORT.indexOf(i.status) ).map(({name, status}, key) => ( {name} ))} {incident.updates.map(({status, body, updatedAt}, key) => ( {startCase(status)} {tct('([time])', {time: })} ))} ))} )} ); } function getStatusSymbol(status: Status) { return ( {status === 'operational' ? ( ) : status === 'major_outage' ? ( ) : status === 'degraded_performance' ? ( ) : status === 'partial_outage' ? ( ) : ( )} ); } const AffectedServices = styled('div')` margin: ${space(2)} 0; `; const UpdatesList = styled(List)` gap: ${space(3)}; margin-left: ${space(1.5)}; position: relative; &::before { content: ''; display: block; position: absolute; height: 100%; width: 2px; margin: ${space(1)} 0 ${space(1)} -${space(1.5)}; background: ${p => p.theme.gray100}; } &::after { content: ''; display: block; position: absolute; bottom: -${space(1)}; margin-left: -${space(1.5)}; height: 30px; width: 2px; background: linear-gradient( 0deg, ${p => p.theme.background}, ${p => color(p.theme.background).alpha(0).string()} ); } `; const UpdateHeading = styled('div')` margin-bottom: ${space(0.5)}; display: flex; align-items: center; gap: ${space(1)}; position: relative; &::before { content: ''; display: block; position: absolute; height: 8px; width: 8px; margin-left: -15px; border-radius: 50%; background: ${p => p.theme.purple300}; } `; const StatusTitle = styled('div')` color: ${p => p.theme.headingColor}; font-weight: bold; `; const StatusDate = styled('div')` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeRelativeSmall}; `; const ComponentList = styled(List)` margin-top: ${space(1)}; display: block; column-count: 2; `; const ComponentStatus = styled(ListItem)` font-size: ${p => p.theme.fontSizeSmall}; line-height: 2; `; export default ServiceIncidents;