You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
4.7 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import api from 'mastodon/api';
  4. import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
  5. import classNames from 'classnames';
  6. import { roundTo10 } from 'mastodon/utils/numbers';
  7. const dateForCohort = cohort => {
  8. switch(cohort.frequency) {
  9. case 'day':
  10. return <FormattedDate value={cohort.period} month='long' day='2-digit' />;
  11. default:
  12. return <FormattedDate value={cohort.period} month='long' year='numeric' />;
  13. }
  14. };
  15. export default class Retention extends React.PureComponent {
  16. static propTypes = {
  17. start_at: PropTypes.string,
  18. end_at: PropTypes.string,
  19. frequency: PropTypes.string,
  20. };
  21. state = {
  22. loading: true,
  23. data: null,
  24. };
  25. componentDidMount () {
  26. const { start_at, end_at, frequency } = this.props;
  27. api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
  28. this.setState({
  29. loading: false,
  30. data: res.data,
  31. });
  32. }).catch(err => {
  33. console.error(err);
  34. });
  35. }
  36. render () {
  37. const { loading, data } = this.state;
  38. const { frequency } = this.props;
  39. let content;
  40. if (loading) {
  41. content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />;
  42. } else {
  43. content = (
  44. <table className='retention__table'>
  45. <thead>
  46. <tr>
  47. <th>
  48. <div className='retention__table__date retention__table__label'>
  49. <FormattedMessage id='admin.dashboard.retention.cohort' defaultMessage='Sign-up month' />
  50. </div>
  51. </th>
  52. <th>
  53. <div className='retention__table__number retention__table__label'>
  54. <FormattedMessage id='admin.dashboard.retention.cohort_size' defaultMessage='New users' />
  55. </div>
  56. </th>
  57. {data[0].data.slice(1).map((retention, i) => (
  58. <th key={retention.date}>
  59. <div className='retention__table__number retention__table__label'>
  60. {i + 1}
  61. </div>
  62. </th>
  63. ))}
  64. </tr>
  65. <tr>
  66. <td>
  67. <div className='retention__table__date retention__table__average'>
  68. <FormattedMessage id='admin.dashboard.retention.average' defaultMessage='Average' />
  69. </div>
  70. </td>
  71. <td>
  72. <div className='retention__table__size'>
  73. <FormattedNumber value={data.reduce((sum, cohort, i) => sum + ((cohort.data[0].value * 1) - sum) / (i + 1), 0)} maximumFractionDigits={0} />
  74. </div>
  75. </td>
  76. {data[0].data.slice(1).map((retention, i) => {
  77. const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].rate - sum)/(k + 1) : sum, 0);
  78. return (
  79. <td key={retention.date}>
  80. <div className={classNames('retention__table__box', 'retention__table__average', `retention__table__box--${roundTo10(average * 100)}`)}>
  81. <FormattedNumber value={average} style='percent' />
  82. </div>
  83. </td>
  84. );
  85. })}
  86. </tr>
  87. </thead>
  88. <tbody>
  89. {data.slice(0, -1).map(cohort => (
  90. <tr key={cohort.period}>
  91. <td>
  92. <div className='retention__table__date'>
  93. {dateForCohort(cohort)}
  94. </div>
  95. </td>
  96. <td>
  97. <div className='retention__table__size'>
  98. <FormattedNumber value={cohort.data[0].value} />
  99. </div>
  100. </td>
  101. {cohort.data.slice(1).map(retention => (
  102. <td key={retention.date}>
  103. <div className={classNames('retention__table__box', `retention__table__box--${roundTo10(retention.rate * 100)}`)}>
  104. <FormattedNumber value={retention.rate} style='percent' />
  105. </div>
  106. </td>
  107. ))}
  108. </tr>
  109. ))}
  110. </tbody>
  111. </table>
  112. );
  113. }
  114. let title = null;
  115. switch(frequency) {
  116. case 'day':
  117. title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />;
  118. break;
  119. default:
  120. title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
  121. }
  122. return (
  123. <div className='retention'>
  124. <h4>{title}</h4>
  125. {content}
  126. </div>
  127. );
  128. }
  129. }