Territory analytics (#1926)
* add territory to analytics selectors * implement territory analytics, revert user satistics header * fix linting errors * disallow some territory names * fix linting error * minor adjustments to header * escape input * 404 on non-existant sub * exclude unused queries depending on sub select --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
		
							parent
							
								
									5de9d92af2
								
							
						
					
					
						commit
						73170ba8a2
					
				@ -121,6 +121,39 @@ export default {
 | 
			
		||||
        FROM ${viewGroup(range, 'stacking_growth')}
 | 
			
		||||
        GROUP BY time
 | 
			
		||||
        ORDER BY time ASC`, ...range)
 | 
			
		||||
    },
 | 
			
		||||
    itemGrowthSubs: async (parent, { when, to, from, sub }, { models }) => {
 | 
			
		||||
      const range = whenRange(when, from, to)
 | 
			
		||||
 | 
			
		||||
      const subExists = await models.sub.findUnique({ where: { name: sub } })
 | 
			
		||||
      if (!subExists) throw new Error('Sub not found')
 | 
			
		||||
 | 
			
		||||
      return await models.$queryRawUnsafe(`
 | 
			
		||||
        SELECT date_trunc('${timeUnitForRange(range)}', t) at time zone 'America/Chicago' as time, json_build_array(
 | 
			
		||||
          json_build_object('name', 'posts', 'value', coalesce(sum(posts),0)),
 | 
			
		||||
          json_build_object('name', 'comments', 'value', coalesce(sum(comments),0))
 | 
			
		||||
        ) AS data
 | 
			
		||||
        FROM ${viewGroup(range, 'sub_stats')}
 | 
			
		||||
        WHERE sub_name = $3
 | 
			
		||||
        GROUP BY time
 | 
			
		||||
        ORDER BY time ASC`, ...range, sub)
 | 
			
		||||
    },
 | 
			
		||||
    revenueGrowthSubs: async (parent, { when, to, from, sub }, { models }) => {
 | 
			
		||||
      const range = whenRange(when, from, to)
 | 
			
		||||
 | 
			
		||||
      const subExists = await models.sub.findUnique({ where: { name: sub } })
 | 
			
		||||
      if (!subExists) throw new Error('Sub not found')
 | 
			
		||||
 | 
			
		||||
      return await models.$queryRawUnsafe(`
 | 
			
		||||
        SELECT date_trunc('${timeUnitForRange(range)}', t) at time zone 'America/Chicago' as time, json_build_array(
 | 
			
		||||
          json_build_object('name', 'revenue', 'value', coalesce(sum(msats_revenue/1000),0)),
 | 
			
		||||
          json_build_object('name', 'stacking', 'value', coalesce(sum(msats_stacked/1000),0)),
 | 
			
		||||
          json_build_object('name', 'spending', 'value', coalesce(sum(msats_spent/1000),0))
 | 
			
		||||
        ) AS data
 | 
			
		||||
        FROM ${viewGroup(range, 'sub_stats')}
 | 
			
		||||
        WHERE sub_name = $3
 | 
			
		||||
        GROUP BY time
 | 
			
		||||
        ORDER BY time ASC`, ...range, sub)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,8 @@ export default gql`
 | 
			
		||||
    spenderGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
			
		||||
    stackingGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
			
		||||
    stackerGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
			
		||||
    itemGrowthSubs(when: String, from: String, to: String, sub: String): [TimeData!]!
 | 
			
		||||
    revenueGrowthSubs(when: String, from: String, to: String, sub: String): [TimeData!]!
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type TimeData {
 | 
			
		||||
 | 
			
		||||
@ -173,7 +173,7 @@ export default function Footer ({ links = true }) {
 | 
			
		||||
              <Rewards />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className='mb-0' style={{ fontWeight: 500 }}>
 | 
			
		||||
              <Link href='/stackers/day' className='nav-link p-0 p-0 d-inline-flex'>
 | 
			
		||||
              <Link href='/stackers/all/day' className='nav-link p-0 p-0 d-inline-flex'>
 | 
			
		||||
                analytics
 | 
			
		||||
              </Link>
 | 
			
		||||
              <span className='mx-2 text-muted'> \ </span>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										79
									
								
								components/sub-analytics-header.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								components/sub-analytics-header.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
import { Select, DatePicker } from './form'
 | 
			
		||||
import { useSubs } from './sub-select'
 | 
			
		||||
import { WHENS } from '@/lib/constants'
 | 
			
		||||
import { whenToFrom } from '@/lib/time'
 | 
			
		||||
import styles from './sub-select.module.css'
 | 
			
		||||
import classNames from 'classnames'
 | 
			
		||||
 | 
			
		||||
export function SubAnalyticsHeader ({ pathname = null }) {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
 | 
			
		||||
  const path = pathname || 'stackers'
 | 
			
		||||
 | 
			
		||||
  const select = async values => {
 | 
			
		||||
    const { sub, when, ...query } = values
 | 
			
		||||
 | 
			
		||||
    if (when !== 'custom') { delete query.from; delete query.to }
 | 
			
		||||
    if (query.from && !query.to) return
 | 
			
		||||
 | 
			
		||||
    await router.push({
 | 
			
		||||
 | 
			
		||||
      pathname: `/${path}/${sub}/${when}`,
 | 
			
		||||
      query
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const when = router.query.when || 'day'
 | 
			
		||||
  const sub = router.query.sub || 'all'
 | 
			
		||||
 | 
			
		||||
  const subs = useSubs({ prependSubs: ['all'], sub, appendSubs: [], filterSubs: () => true })
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className='text-muted fw-bold my-0 d-flex align-items-center flex-wrap'>
 | 
			
		||||
      <div className='text-muted fw-bold mb-2 d-flex align-items-center'>
 | 
			
		||||
        stacker analytics in
 | 
			
		||||
        <Select
 | 
			
		||||
          groupClassName='mb-0 mx-2'
 | 
			
		||||
          className={classNames(styles.subSelect, styles.subSelectSmall)}
 | 
			
		||||
          name='sub'
 | 
			
		||||
          size='sm'
 | 
			
		||||
          items={subs}
 | 
			
		||||
          value={sub}
 | 
			
		||||
          noForm
 | 
			
		||||
          onChange={(formik, e) => {
 | 
			
		||||
            const range = when === 'custom' ? { from: router.query.from, to: router.query.to } : {}
 | 
			
		||||
            select({ sub: e.target.value, when, ...range })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        for
 | 
			
		||||
        <Select
 | 
			
		||||
          groupClassName='mb-0 mx-2'
 | 
			
		||||
          className='w-auto'
 | 
			
		||||
          name='when'
 | 
			
		||||
          size='sm'
 | 
			
		||||
          items={WHENS}
 | 
			
		||||
          value={when}
 | 
			
		||||
          noForm
 | 
			
		||||
          onChange={(formik, e) => {
 | 
			
		||||
            const range = e.target.value === 'custom' ? { from: whenToFrom(when), to: Date.now() } : {}
 | 
			
		||||
            select({ sub, when: e.target.value, ...range })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      {when === 'custom' &&
 | 
			
		||||
        <DatePicker
 | 
			
		||||
          noForm
 | 
			
		||||
          fromName='from'
 | 
			
		||||
          toName='to'
 | 
			
		||||
          className='p-0 px-2 mb-0'
 | 
			
		||||
          onChange={(formik, [from, to], e) => {
 | 
			
		||||
            select({ sub, when, from: from.getTime(), to: to.getTime() })
 | 
			
		||||
          }}
 | 
			
		||||
          from={router.query.from}
 | 
			
		||||
          to={router.query.to}
 | 
			
		||||
          when={when}
 | 
			
		||||
        />}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -3,10 +3,10 @@ import { Select, DatePicker } from './form'
 | 
			
		||||
import { WHENS } from '@/lib/constants'
 | 
			
		||||
import { whenToFrom } from '@/lib/time'
 | 
			
		||||
 | 
			
		||||
export function UsageHeader ({ pathname = null }) {
 | 
			
		||||
export function UserAnalyticsHeader ({ pathname = null }) {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
 | 
			
		||||
  const path = pathname || 'stackers'
 | 
			
		||||
  const path = pathname || 'satistics/graph'
 | 
			
		||||
 | 
			
		||||
  const select = async values => {
 | 
			
		||||
    const { when, ...query } = values
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
// to be loaded from the server
 | 
			
		||||
export const DEFAULT_SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
 | 
			
		||||
export const DEFAULT_SUBS_NO_JOBS = DEFAULT_SUBS.filter(s => s !== 'jobs')
 | 
			
		||||
export const RESERVED_SUB_NAMES = ['all', 'home']
 | 
			
		||||
 | 
			
		||||
export const PAID_ACTION_PAYMENT_METHODS = {
 | 
			
		||||
  FEE_CREDIT: 'FEE_CREDIT',
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,8 @@ import { string, ValidationError, number, object, array, boolean, date } from '.
 | 
			
		||||
import {
 | 
			
		||||
  BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES,
 | 
			
		||||
  MIN_POLL_NUM_CHOICES, MAX_FORWARDS, BOOST_MULT, MAX_TERRITORY_DESC_LENGTH, POST_TYPES,
 | 
			
		||||
  TERRITORY_BILLING_TYPES, MAX_COMMENT_TEXT_LENGTH, MAX_POST_TEXT_LENGTH, MIN_TITLE_LENGTH, BOUNTY_MIN, BOUNTY_MAX
 | 
			
		||||
  TERRITORY_BILLING_TYPES, MAX_COMMENT_TEXT_LENGTH, MAX_POST_TEXT_LENGTH, MIN_TITLE_LENGTH, BOUNTY_MIN, BOUNTY_MAX,
 | 
			
		||||
  RESERVED_SUB_NAMES
 | 
			
		||||
} from './constants'
 | 
			
		||||
import { SUPPORTED_CURRENCIES } from './currency'
 | 
			
		||||
import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
 | 
			
		||||
@ -306,7 +307,7 @@ export function territorySchema (args) {
 | 
			
		||||
          const isArchived = sub => sub.status === 'STOPPED'
 | 
			
		||||
          const filter = sub => editing ? !isEdit(sub) : !isArchived(sub)
 | 
			
		||||
          const exists = await subExists(name, { ...args, filter })
 | 
			
		||||
          return !exists
 | 
			
		||||
          return !exists & !RESERVED_SUB_NAMES.includes(name)
 | 
			
		||||
        },
 | 
			
		||||
        message: 'taken'
 | 
			
		||||
      }),
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import { useRouter } from 'next/router'
 | 
			
		||||
import PageLoading from '@/components/page-loading'
 | 
			
		||||
import dynamic from 'next/dynamic'
 | 
			
		||||
import { numWithUnits } from '@/lib/format'
 | 
			
		||||
import { UsageHeader } from '@/components/usage-header'
 | 
			
		||||
import { UserAnalyticsHeader } from '@/components/user-analytics-header'
 | 
			
		||||
import { SatisticsHeader } from '..'
 | 
			
		||||
import { WhenComposedChartSkeleton, WhenAreaChartSkeleton } from '@/components/charts-skeletons'
 | 
			
		||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
 | 
			
		||||
@ -55,7 +55,7 @@ export default function Satistics ({ ssrData }) {
 | 
			
		||||
        <SatisticsHeader />
 | 
			
		||||
        <div className='tab-content' id='myTabContent'>
 | 
			
		||||
          <div className='tab-pane fade show active text-muted' id='statistics' role='tabpanel' aria-labelledby='statistics-tab'>
 | 
			
		||||
            <UsageHeader pathname='satistics/graphs' />
 | 
			
		||||
            <UserAnalyticsHeader pathname='satistics/graphs' />
 | 
			
		||||
            <div className='mt-3'>
 | 
			
		||||
              <div className='d-flex row justify-content-between'>
 | 
			
		||||
                <div className='col-md-6 mb-2'>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										157
									
								
								pages/stackers/[sub]/[when].js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								pages/stackers/[sub]/[when].js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
import { gql, useQuery } from '@apollo/client'
 | 
			
		||||
import { getGetServerSideProps } from '@/api/ssrApollo'
 | 
			
		||||
import Layout from '@/components/layout'
 | 
			
		||||
import Col from 'react-bootstrap/Col'
 | 
			
		||||
import Row from 'react-bootstrap/Row'
 | 
			
		||||
import { SubAnalyticsHeader } from '@/components/sub-analytics-header'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
import dynamic from 'next/dynamic'
 | 
			
		||||
import PageLoading from '@/components/page-loading'
 | 
			
		||||
import { WhenAreaChartSkeleton, WhenComposedChartSkeleton, WhenLineChartSkeleton } from '@/components/charts-skeletons'
 | 
			
		||||
 | 
			
		||||
const WhenAreaChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenAreaChart), {
 | 
			
		||||
  loading: () => <WhenAreaChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
const WhenLineChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenLineChart), {
 | 
			
		||||
  loading: () => <WhenLineChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
const WhenComposedChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenComposedChart), {
 | 
			
		||||
  loading: () => <WhenComposedChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const GROWTH_QUERY = gql`
 | 
			
		||||
  query Growth($when: String!, $from: String, $to: String, $sub: String, $subSelect: Boolean = false)
 | 
			
		||||
  {
 | 
			
		||||
    registrationGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    itemGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    spendingGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    spenderGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stackingGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stackerGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    itemGrowthSubs(when: $when, from: $from, to: $to, sub: $sub) @include(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    revenueGrowthSubs(when: $when, from: $from, to: $to, sub: $sub) @include(if: $subSelect) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }`
 | 
			
		||||
 | 
			
		||||
const variablesFunc = vars => ({ ...vars, subSelect: vars.sub !== 'all' })
 | 
			
		||||
export const getServerSideProps = getGetServerSideProps({ query: GROWTH_QUERY, variables: variablesFunc })
 | 
			
		||||
 | 
			
		||||
export default function Growth ({ ssrData }) {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
  const { when, from, to, sub } = router.query
 | 
			
		||||
 | 
			
		||||
  const { data } = useQuery(GROWTH_QUERY, { variables: { when, from, to, sub, subSelect: sub !== 'all' } })
 | 
			
		||||
  if (!data && !ssrData) return <PageLoading />
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    registrationGrowth,
 | 
			
		||||
    itemGrowth,
 | 
			
		||||
    spendingGrowth,
 | 
			
		||||
    spenderGrowth,
 | 
			
		||||
    stackingGrowth,
 | 
			
		||||
    stackerGrowth,
 | 
			
		||||
    itemGrowthSubs,
 | 
			
		||||
    revenueGrowthSubs
 | 
			
		||||
  } = data || ssrData
 | 
			
		||||
 | 
			
		||||
  if (sub === 'all') {
 | 
			
		||||
    return (
 | 
			
		||||
      <Layout>
 | 
			
		||||
        <SubAnalyticsHeader />
 | 
			
		||||
        <Row>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>stackers</div>
 | 
			
		||||
            <WhenLineChart data={stackerGrowth} />
 | 
			
		||||
          </Col>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>stacking</div>
 | 
			
		||||
            <WhenAreaChart data={stackingGrowth} />
 | 
			
		||||
          </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
        <Row>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>spenders</div>
 | 
			
		||||
            <WhenLineChart data={spenderGrowth} />
 | 
			
		||||
          </Col>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>spending</div>
 | 
			
		||||
            <WhenAreaChart data={spendingGrowth} />
 | 
			
		||||
          </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
        <Row>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>registrations</div>
 | 
			
		||||
            <WhenAreaChart data={registrationGrowth} />
 | 
			
		||||
          </Col>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>items</div>
 | 
			
		||||
            <WhenComposedChart data={itemGrowth} areaNames={['posts', 'comments', 'jobs']} areaAxis='left' lineNames={['comments/posts', 'territories']} lineAxis='right' barNames={['zaps']} />
 | 
			
		||||
          </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
      </Layout>
 | 
			
		||||
    )
 | 
			
		||||
  } else {
 | 
			
		||||
    return (
 | 
			
		||||
      <Layout>
 | 
			
		||||
        <SubAnalyticsHeader />
 | 
			
		||||
        <Row>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>items</div>
 | 
			
		||||
            <WhenLineChart data={itemGrowthSubs} />
 | 
			
		||||
          </Col>
 | 
			
		||||
          <Col className='mt-3'>
 | 
			
		||||
            <div className='text-center text-muted fw-bold'>sats</div>
 | 
			
		||||
            <WhenLineChart data={revenueGrowthSubs} />
 | 
			
		||||
          </Col>
 | 
			
		||||
        </Row>
 | 
			
		||||
      </Layout>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,115 +0,0 @@
 | 
			
		||||
import { gql, useQuery } from '@apollo/client'
 | 
			
		||||
import { getGetServerSideProps } from '@/api/ssrApollo'
 | 
			
		||||
import Layout from '@/components/layout'
 | 
			
		||||
import Col from 'react-bootstrap/Col'
 | 
			
		||||
import Row from 'react-bootstrap/Row'
 | 
			
		||||
import { UsageHeader } from '@/components/usage-header'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
import dynamic from 'next/dynamic'
 | 
			
		||||
import PageLoading from '@/components/page-loading'
 | 
			
		||||
import { WhenAreaChartSkeleton, WhenComposedChartSkeleton, WhenLineChartSkeleton } from '@/components/charts-skeletons'
 | 
			
		||||
 | 
			
		||||
const WhenAreaChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenAreaChart), {
 | 
			
		||||
  loading: () => <WhenAreaChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
const WhenLineChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenLineChart), {
 | 
			
		||||
  loading: () => <WhenLineChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
const WhenComposedChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenComposedChart), {
 | 
			
		||||
  loading: () => <WhenComposedChartSkeleton />
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const GROWTH_QUERY = gql`
 | 
			
		||||
  query Growth($when: String!, $from: String, $to: String)
 | 
			
		||||
  {
 | 
			
		||||
    registrationGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    itemGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    spendingGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    spenderGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stackingGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stackerGrowth(when: $when, from: $from, to: $to) {
 | 
			
		||||
      time
 | 
			
		||||
      data {
 | 
			
		||||
        name
 | 
			
		||||
        value
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }`
 | 
			
		||||
 | 
			
		||||
export const getServerSideProps = getGetServerSideProps({ query: GROWTH_QUERY })
 | 
			
		||||
 | 
			
		||||
export default function Growth ({ ssrData }) {
 | 
			
		||||
  const router = useRouter()
 | 
			
		||||
  const { when, from, to } = router.query
 | 
			
		||||
 | 
			
		||||
  const { data } = useQuery(GROWTH_QUERY, { variables: { when, from, to } })
 | 
			
		||||
  if (!data && !ssrData) return <PageLoading />
 | 
			
		||||
 | 
			
		||||
  const { registrationGrowth, itemGrowth, spendingGrowth, spenderGrowth, stackingGrowth, stackerGrowth } = data || ssrData
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Layout>
 | 
			
		||||
      <UsageHeader />
 | 
			
		||||
      <Row>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>stackers</div>
 | 
			
		||||
          <WhenLineChart data={stackerGrowth} />
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>stacking</div>
 | 
			
		||||
          <WhenAreaChart data={stackingGrowth} />
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Row>
 | 
			
		||||
      <Row>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>spenders</div>
 | 
			
		||||
          <WhenLineChart data={spenderGrowth} />
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>spending</div>
 | 
			
		||||
          <WhenAreaChart data={spendingGrowth} />
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Row>
 | 
			
		||||
      <Row>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>registrations</div>
 | 
			
		||||
          <WhenAreaChart data={registrationGrowth} />
 | 
			
		||||
        </Col>
 | 
			
		||||
        <Col className='mt-3'>
 | 
			
		||||
          <div className='text-center text-muted fw-bold'>items</div>
 | 
			
		||||
          <WhenComposedChart data={itemGrowth} areaNames={['posts', 'comments', 'jobs']} areaAxis='left' lineNames={['comments/posts', 'territories']} lineAxis='right' barNames={['zaps']} />
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Row>
 | 
			
		||||
    </Layout>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user