integrationRow.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import styled from '@emotion/styled';
  2. import startCase from 'lodash/startCase';
  3. import Alert from 'app/components/alert';
  4. import Button from 'app/components/button';
  5. import Link from 'app/components/links/link';
  6. import {PanelItem} from 'app/components/panels';
  7. import {IconWarning} from 'app/icons';
  8. import {t} from 'app/locale';
  9. import PluginIcon from 'app/plugins/components/pluginIcon';
  10. import space from 'app/styles/space';
  11. import {IntegrationInstallationStatus, Organization, SentryApp} from 'app/types';
  12. import {
  13. convertIntegrationTypeToSnakeCase,
  14. trackIntegrationEvent,
  15. } from 'app/utils/integrationUtil';
  16. import IntegrationStatus from './integrationStatus';
  17. type Props = {
  18. organization: Organization;
  19. type: 'plugin' | 'firstParty' | 'sentryApp' | 'documentIntegration';
  20. slug: string;
  21. displayName: string;
  22. status?: IntegrationInstallationStatus;
  23. publishStatus: 'unpublished' | 'published' | 'internal';
  24. configurations: number;
  25. categories: string[];
  26. alertText?: string;
  27. };
  28. const urlMap = {
  29. plugin: 'plugins',
  30. firstParty: 'integrations',
  31. sentryApp: 'sentry-apps',
  32. documentIntegration: 'document-integrations',
  33. };
  34. const IntegrationRow = (props: Props) => {
  35. const {
  36. organization,
  37. type,
  38. slug,
  39. displayName,
  40. status,
  41. publishStatus,
  42. configurations,
  43. categories,
  44. alertText,
  45. } = props;
  46. const baseUrl =
  47. publishStatus === 'internal'
  48. ? `/settings/${organization.slug}/developer-settings/${slug}/`
  49. : `/settings/${organization.slug}/${urlMap[type]}/${slug}/`;
  50. const renderDetails = () => {
  51. if (type === 'sentryApp') {
  52. return publishStatus !== 'published' && <PublishStatus status={publishStatus} />;
  53. }
  54. // TODO: Use proper translations
  55. return configurations > 0 ? (
  56. <StyledLink to={`${baseUrl}?tab=configurations`}>{`${configurations} Configuration${
  57. configurations > 1 ? 's' : ''
  58. }`}</StyledLink>
  59. ) : null;
  60. };
  61. const renderStatus = () => {
  62. // status should be undefined for document integrations
  63. if (status) {
  64. return <IntegrationStatus status={status} />;
  65. }
  66. return <LearnMore to={baseUrl}>{t('Learn More')}</LearnMore>;
  67. };
  68. return (
  69. <PanelRow noPadding data-test-id={slug}>
  70. <FlexContainer>
  71. <PluginIcon size={36} pluginId={slug} />
  72. <Container>
  73. <IntegrationName to={baseUrl}>{displayName}</IntegrationName>
  74. <IntegrationDetails>
  75. {renderStatus()}
  76. {renderDetails()}
  77. </IntegrationDetails>
  78. </Container>
  79. <InternalContainer>
  80. {categories?.map(category => (
  81. <CategoryTag
  82. key={category}
  83. category={startCase(category)}
  84. priority={category === publishStatus}
  85. />
  86. ))}
  87. </InternalContainer>
  88. </FlexContainer>
  89. {alertText && (
  90. <AlertContainer>
  91. <Alert type="warning" icon={<IconWarning size="sm" />}>
  92. <span>{alertText}</span>
  93. <ResolveNowButton
  94. href={`${baseUrl}?tab=configurations&referrer=directory_resolve_now`}
  95. size="xsmall"
  96. onClick={() =>
  97. trackIntegrationEvent(
  98. 'integrations.resolve_now_clicked',
  99. {
  100. integration_type: convertIntegrationTypeToSnakeCase(type),
  101. integration: slug,
  102. },
  103. organization
  104. )
  105. }
  106. >
  107. {t('Resolve Now')}
  108. </ResolveNowButton>
  109. </Alert>
  110. </AlertContainer>
  111. )}
  112. </PanelRow>
  113. );
  114. };
  115. const PanelRow = styled(PanelItem)`
  116. flex-direction: column;
  117. `;
  118. const FlexContainer = styled('div')`
  119. display: flex;
  120. align-items: center;
  121. padding: ${space(2)};
  122. `;
  123. const InternalContainer = styled(FlexContainer)`
  124. padding: 0 ${space(2)};
  125. `;
  126. const Container = styled('div')`
  127. flex: 1;
  128. padding: 0 16px;
  129. `;
  130. const IntegrationName = styled(Link)`
  131. font-weight: bold;
  132. `;
  133. const IntegrationDetails = styled('div')`
  134. display: flex;
  135. align-items: center;
  136. margin-top: 6px;
  137. font-size: 0.8em;
  138. `;
  139. const StyledLink = styled(Link)`
  140. color: ${p => p.theme.gray300};
  141. &:before {
  142. content: '|';
  143. color: ${p => p.theme.gray200};
  144. margin-right: ${space(0.75)};
  145. font-weight: normal;
  146. }
  147. `;
  148. const LearnMore = styled(Link)`
  149. color: ${p => p.theme.gray300};
  150. `;
  151. type PublishStatusProps = {status: SentryApp['status']; theme?: any};
  152. const PublishStatus = styled(({status, ...props}: PublishStatusProps) => (
  153. <div {...props}>{t(`${status}`)}</div>
  154. ))`
  155. color: ${(props: PublishStatusProps) =>
  156. props.status === 'published' ? props.theme.success : props.theme.gray300};
  157. font-weight: light;
  158. margin-right: ${space(0.75)};
  159. text-transform: capitalize;
  160. &:before {
  161. content: '|';
  162. color: ${p => p.theme.gray200};
  163. margin-right: ${space(0.75)};
  164. font-weight: normal;
  165. }
  166. `;
  167. // TODO(Priscila): Replace this component with the Tag component
  168. const CategoryTag = styled(
  169. ({
  170. priority: _priority,
  171. category,
  172. ...p
  173. }: {
  174. category: string;
  175. priority: boolean;
  176. theme?: any;
  177. }) => <div {...p}>{category}</div>
  178. )`
  179. display: flex;
  180. flex-direction: row;
  181. padding: 1px 10px;
  182. background: ${p => (p.priority ? p.theme.purple200 : p.theme.gray100)};
  183. border-radius: 20px;
  184. font-size: ${space(1.5)};
  185. margin-right: ${space(1)};
  186. line-height: ${space(3)};
  187. text-align: center;
  188. color: ${p => (p.priority ? p.theme.white : p.theme.gray500)};
  189. `;
  190. const ResolveNowButton = styled(Button)`
  191. color: ${p => p.theme.subText};
  192. float: right;
  193. `;
  194. const AlertContainer = styled('div')`
  195. padding: 0px ${space(3)} 0px 68px;
  196. `;
  197. export default IntegrationRow;