customerGrid.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import moment from 'moment-timezone';
  2. import {OrganizationAvatar} from 'sentry/components/core/avatar/organizationAvatar';
  3. import {Tag} from 'sentry/components/core/badge/tag';
  4. import Link from 'sentry/components/links/link';
  5. import {Tooltip} from 'sentry/components/tooltip';
  6. import CustomerContact from 'admin/components/customerContact';
  7. import CustomerName from 'admin/components/customerName';
  8. import CustomerStatus from 'admin/components/customerStatus';
  9. import PercentChange from 'admin/components/percentChange';
  10. import ResultGrid from 'admin/components/resultGrid';
  11. import type {Subscription} from 'getsentry/types';
  12. import {displayPrice} from 'getsentry/views/amCheckout/utils';
  13. type ResultGridProps = React.ComponentProps<typeof ResultGrid>;
  14. type Props = Omit<Partial<ResultGridProps>, 'endpoint'> &
  15. Pick<ResultGridProps, 'endpoint'> & {
  16. modifyOrgMembershipbutton?: React.ReactNode;
  17. };
  18. const getRow = (row: Subscription) => [
  19. <td key="customer">
  20. <CustomerName>
  21. <OrganizationAvatar size={36} organization={row as any} />
  22. <div>
  23. <strong>
  24. <Link to={`/_admin/customers/${row.slug}/`}>{row.name}</Link>
  25. </strong>
  26. <small> — {row.slug}</small>
  27. </div>
  28. <div>
  29. <small>
  30. {row.owner && (
  31. <span>
  32. <CustomerContact owner={row.owner} />
  33. </span>
  34. )}
  35. </small>
  36. {row.isGracePeriod && <Tag type="warning">Grace Period</Tag>}
  37. {row.usageExceeded && <Tag type="warning">Capacity Limit</Tag>}
  38. {row.isSuspended && (
  39. <Tooltip title={row.suspensionReason}>
  40. <Tag type="error">Suspended</Tag>
  41. </Tooltip>
  42. )}
  43. </div>
  44. </CustomerName>
  45. </td>,
  46. <td key="events" style={{textAlign: 'center'}}>
  47. {row.stats?.events30d.toLocaleString()}
  48. <br />
  49. <small>
  50. {row.stats ? (
  51. <PercentChange current={row.stats.events30d} prev={row.stats.eventsPrev30d} />
  52. ) : (
  53. 'Unknown'
  54. )}
  55. </small>
  56. </td>,
  57. <td key="members" style={{textAlign: 'center'}}>
  58. {row.totalMembers?.toLocaleString()}
  59. </td>,
  60. <td key="status" style={{textAlign: 'center'}}>
  61. <CustomerStatus customer={row} />
  62. </td>,
  63. <td key="ondemand" style={{textAlign: 'center'}}>
  64. {displayPrice({cents: row.onDemandSpendUsed || 0})}
  65. </td>,
  66. <td key="acv" style={{textAlign: 'center'}}>
  67. {row.acv ? displayPrice({cents: row.acv}) : 'unknown'}
  68. </td>,
  69. <td key="joined" style={{textAlign: 'right'}}>
  70. {moment(row.dateJoined).format('MMMM YYYY')}
  71. <br />
  72. <small>{moment(row.dateJoined).fromNow()}</small>
  73. </td>,
  74. ];
  75. function CustomerGrid(props: Props) {
  76. return (
  77. <ResultGrid
  78. inPanel
  79. isRegional
  80. path="/_admin/customers/"
  81. method="GET"
  82. columns={[
  83. <th key="customer">Customer</th>,
  84. <th key="events" style={{width: 130, textAlign: 'center'}}>
  85. Events (30d)
  86. </th>,
  87. <th key="members" style={{width: 85, textAlign: 'center'}}>
  88. Members
  89. </th>,
  90. <th key="status" style={{width: 150, textAlign: 'center'}}>
  91. Status
  92. </th>,
  93. <th key="ondemand" style={{width: 100, textAlign: 'center'}}>
  94. OnDemand
  95. </th>,
  96. <th key="acv" style={{width: 100, textAlign: 'center'}}>
  97. ACV
  98. </th>,
  99. <th key="joined" style={{width: 150, textAlign: 'right'}}>
  100. Joined
  101. </th>,
  102. ]}
  103. columnsForRow={getRow}
  104. hasSearch
  105. filters={{
  106. planType: {
  107. name: 'Plan Type',
  108. options: [
  109. ['team', 'Team'],
  110. ['business', 'Business'],
  111. ['enterprise', 'Enterprise'],
  112. ['enterprise_trial', 'Enterprise Trial'],
  113. ['trial', 'Trial'],
  114. ['small', 'Small'],
  115. ['medium', 'Medium'],
  116. ['large', 'Large'],
  117. ['sponsored', 'Sponsored'],
  118. ['free', 'Free'],
  119. ],
  120. },
  121. status: {
  122. name: 'Status',
  123. options: [
  124. ['active', 'Active'],
  125. ['trialing', 'Trialing'],
  126. ['trialing_enterprise', 'Trialing (enterprise)'],
  127. ['past_due', 'Past Due'],
  128. ['free', 'Free'],
  129. ],
  130. },
  131. paymentMethod: {
  132. name: 'Payment Method',
  133. options: [
  134. ['credit_card', 'Credit Card'],
  135. ['invoiced', 'Invoiced'],
  136. ['third_party', 'Third Party'],
  137. ],
  138. },
  139. managed: {
  140. name: 'Managed',
  141. options: [
  142. ['0', 'No'],
  143. ['1', 'Yes'],
  144. ],
  145. },
  146. gracePeriod: {
  147. name: 'Grace Period',
  148. options: [
  149. ['0', 'No'],
  150. ['1', 'Yes'],
  151. ],
  152. },
  153. suspended: {
  154. name: 'Suspended',
  155. options: [
  156. ['0', 'No'],
  157. ['1', 'Yes'],
  158. ],
  159. },
  160. usageExceeded: {
  161. name: 'Usage Exceeded',
  162. options: [
  163. ['0', 'No'],
  164. ['1', 'Yes'],
  165. ],
  166. },
  167. softCap: {
  168. name: 'Soft Cap',
  169. options: [
  170. ['0', 'No'],
  171. ['1', 'Yes'],
  172. ],
  173. },
  174. overageNotifications: {
  175. name: 'Overage Notifications',
  176. options: [
  177. ['0', 'No'],
  178. ['1', 'Yes'],
  179. ],
  180. },
  181. dataRetention: {
  182. name: 'Data Retention',
  183. options: [
  184. ['0', '30d'],
  185. ['1', '60d'],
  186. ['2', '90d'],
  187. ],
  188. },
  189. }}
  190. sortOptions={[
  191. ['date', 'Date Joined'],
  192. ['members', 'Members'],
  193. ['events.30d', 'Events (30d)'],
  194. ['events.30d.growth', 'Events (30d) - Growth'],
  195. ['events.24h', 'Events (24h)'],
  196. ['events.24h.growth', 'Events (24h) - Growth'],
  197. ['projects', 'Projects'],
  198. // TODO(mark) Re-enable this when subscription and billinghistory
  199. // are in the same database again.
  200. // ['ondemand.spend', 'OnDemand (Spend)'],
  201. ]}
  202. defaultSort="members"
  203. buttonGroup={props.modifyOrgMembershipbutton}
  204. {...props}
  205. />
  206. );
  207. }
  208. export default CustomerGrid;