hostDetails.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {useQuery} from '@tanstack/react-query';
  4. import CircleIndicator from 'sentry/components/circleIndicator';
  5. import {IconOpen} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import usePageFilters from 'sentry/utils/usePageFilters';
  9. import {
  10. ERROR_CODE_DESCRIPTIONS,
  11. EXTERNAL_APIS,
  12. } from 'sentry/views/starfish/modules/APIModule/constants';
  13. import {MeterBar} from 'sentry/views/starfish/modules/APIModule/hostTable';
  14. import {
  15. getHostStatusBreakdownEventView,
  16. getHostStatusBreakdownQuery,
  17. } from 'sentry/views/starfish/modules/APIModule/queries';
  18. import {useSpansQuery} from 'sentry/views/starfish/utils/useSpansQuery';
  19. type Props = {
  20. host: string;
  21. };
  22. export function HostDetails({host}: Props) {
  23. const theme = useTheme();
  24. const pageFilter = usePageFilters();
  25. const {isLoading: isStatusBreakdownLoading, data: statusBreakdown} = useSpansQuery({
  26. queryString: getHostStatusBreakdownQuery({
  27. domain: host,
  28. datetime: pageFilter.selection.datetime,
  29. }),
  30. eventView: getHostStatusBreakdownEventView({
  31. domain: host,
  32. datetime: pageFilter.selection.datetime,
  33. }),
  34. initialData: [],
  35. });
  36. const hostMarketingName = Object.keys(EXTERNAL_APIS).find(key => host.includes(key));
  37. const failures = statusBreakdown?.filter((item: any) => item.status > 299);
  38. const successes = statusBreakdown?.filter((item: any) => item.status < 300);
  39. const totalCount = statusBreakdown?.reduce(
  40. (acc: number, item: any) => acc + item.count,
  41. 0
  42. );
  43. const externalApi = hostMarketingName && EXTERNAL_APIS[hostMarketingName];
  44. const {isLoading: isStatusLoading, data: statusData} = useQuery({
  45. queryKey: ['domain-status', host],
  46. queryFn: () =>
  47. fetch(`${externalApi?.statusPage}?format=json`).then(res => res.json()),
  48. retry: false,
  49. refetchOnWindowFocus: false,
  50. initialData: {},
  51. enabled: !!externalApi,
  52. });
  53. return (
  54. <DetailsContainer>
  55. <FlexContainer>
  56. {externalApi?.faviconLink && (
  57. <img
  58. src={externalApi.faviconLink}
  59. width="16"
  60. height="16"
  61. style={{marginRight: space(1)}}
  62. />
  63. )}
  64. {hostMarketingName ? (
  65. <span>
  66. <Host>{hostMarketingName}</Host>
  67. <span>{` (${host})`}</span>
  68. </span>
  69. ) : (
  70. <Host>{host}</Host>
  71. )}
  72. {!isStatusLoading && statusData.status ? (
  73. <StatusText>
  74. <CircleIndicator size={8} enabled={statusData.status.indicator === 'none'} />{' '}
  75. {statusData.status.description}
  76. </StatusText>
  77. ) : null}
  78. <LinkContainer>
  79. {externalApi?.statusPage && (
  80. <a href={externalApi.statusPage} target="_blank" rel="noreferrer">
  81. {t('Status')}
  82. <StyledIconOpen size="xs" />
  83. </a>
  84. )}
  85. </LinkContainer>
  86. </FlexContainer>
  87. <ExternalApiDescription>{externalApi?.description}</ExternalApiDescription>
  88. <StatusContainer>
  89. {isStatusBreakdownLoading
  90. ? null
  91. : failures?.map((item: any) => {
  92. const errorCodeDescription = ERROR_CODE_DESCRIPTIONS[item.status];
  93. return (
  94. <MeterBarContainer key={item.status}>
  95. <MeterBar
  96. color={theme.red300}
  97. meterItems={['count']}
  98. minWidth={0.1}
  99. row={item}
  100. total={totalCount}
  101. meterText={
  102. <Failure>{`${item.status}${
  103. errorCodeDescription ? ` ${errorCodeDescription}` : ''
  104. } (${item.count})`}</Failure>
  105. }
  106. />
  107. </MeterBarContainer>
  108. );
  109. })}
  110. {isStatusBreakdownLoading
  111. ? null
  112. : successes?.map((item: any) => (
  113. <MeterBarContainer key={item.status}>
  114. <MeterBar
  115. color={theme.green300}
  116. meterItems={['count']}
  117. minWidth={0.1}
  118. row={item}
  119. total={totalCount}
  120. meterText={`${item.status} (${item.count})`}
  121. />
  122. </MeterBarContainer>
  123. ))}
  124. </StatusContainer>
  125. </DetailsContainer>
  126. );
  127. }
  128. const DetailsContainer = styled('div')`
  129. padding: ${space(2)};
  130. border-radius: ${p => p.theme.borderRadius};
  131. border: 1px solid ${p => p.theme.border};
  132. margin-bottom: ${space(2)};
  133. `;
  134. const FlexContainer = styled('div')`
  135. display: flex;
  136. flex-direction: row;
  137. `;
  138. const Host = styled('span')`
  139. font-weight: bold;
  140. `;
  141. const StatusText = styled('span')`
  142. margin-left: ${space(2)};
  143. `;
  144. const StyledIconOpen = styled(IconOpen)`
  145. flex: 0;
  146. top: 2px;
  147. position: relative;
  148. margin-left: ${space(0.5)};
  149. `;
  150. const LinkContainer = styled('span')`
  151. flex: 1;
  152. text-align: right;
  153. `;
  154. const StatusContainer = styled('span')`
  155. margin-top: ${space(1)};
  156. flex: 1;
  157. height: 20px;
  158. display: flex;
  159. flex-direction: row;
  160. gap: ${space(1)};
  161. `;
  162. const MeterBarContainer = styled('div')`
  163. width: 150px;
  164. top: -6px;
  165. position: relative;
  166. `;
  167. const Failure = styled('span')`
  168. font-weight: bold;
  169. color: ${p => p.theme.red300};
  170. `;
  171. const ExternalApiDescription = styled('span')`
  172. font-size: ${p => p.theme.fontSizeSmall};
  173. color: ${p => p.theme.gray300};
  174. `;