import PropTypes from 'prop-types'; import { PureComponent } from 'react'; import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl'; import classNames from 'classnames'; import api from 'mastodon/api'; import { roundTo10 } from 'mastodon/utils/numbers'; const dateForCohort = cohort => { const timeZone = 'UTC'; switch(cohort.frequency) { case 'day': return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />; default: return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />; } }; export default class Retention extends PureComponent { static propTypes = { start_at: PropTypes.string, end_at: PropTypes.string, frequency: PropTypes.string, }; state = { loading: true, data: null, }; componentDidMount () { const { start_at, end_at, frequency } = this.props; api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => { this.setState({ loading: false, data: res.data, }); }).catch(err => { console.error(err); }); } render () { const { loading, data } = this.state; const { frequency } = this.props; let content; if (loading) { content = <FormattedMessage id='loading_indicator.label' defaultMessage='Loading…' />; } else { content = ( <table className='retention__table'> <thead> <tr> <th> <div className='retention__table__date retention__table__label'> <FormattedMessage id='admin.dashboard.retention.cohort' defaultMessage='Sign-up month' /> </div> </th> <th> <div className='retention__table__number retention__table__label'> <FormattedMessage id='admin.dashboard.retention.cohort_size' defaultMessage='New users' /> </div> </th> {data[0].data.slice(1).map((retention, i) => ( <th key={retention.date}> <div className='retention__table__number retention__table__label'> {i + 1} </div> </th> ))} </tr> <tr> <td> <div className='retention__table__date retention__table__average'> <FormattedMessage id='admin.dashboard.retention.average' defaultMessage='Average' /> </div> </td> <td> <div className='retention__table__size'> <FormattedNumber value={data.reduce((sum, cohort, i) => sum + ((cohort.data[0].value * 1) - sum) / (i + 1), 0)} maximumFractionDigits={0} /> </div> </td> {data[0].data.slice(1).map((retention, i) => { const average = data.reduce((sum, cohort, k) => cohort.data[i + 1] ? sum + (cohort.data[i + 1].rate - sum)/(k + 1) : sum, 0); return ( <td key={retention.date}> <div className={classNames('retention__table__box', 'retention__table__average', `retention__table__box--${roundTo10(average * 100)}`)}> <FormattedNumber value={average} style='percent' /> </div> </td> ); })} </tr> </thead> <tbody> {data.slice(0, -1).map(cohort => ( <tr key={cohort.period}> <td> <div className='retention__table__date'> {dateForCohort(cohort)} </div> </td> <td> <div className='retention__table__size'> <FormattedNumber value={cohort.data[0].value} /> </div> </td> {cohort.data.slice(1).map(retention => ( <td key={retention.date}> <div className={classNames('retention__table__box', `retention__table__box--${roundTo10(retention.rate * 100)}`)}> <FormattedNumber value={retention.rate} style='percent' /> </div> </td> ))} </tr> ))} </tbody> </table> ); } let title = null; switch(frequency) { case 'day': title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />; break; default: title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />; } return ( <div className='retention'> <h4>{title}</h4> {content} </div> ); } }