Skip to content

Commit

Permalink
feat: 准备 issue 趋势图数据
Browse files Browse the repository at this point in the history
  • Loading branch information
xinyao27 committed Jun 7, 2022
1 parent 0bfd469 commit d0f8c35
Show file tree
Hide file tree
Showing 12 changed files with 431 additions and 25 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@prisma/client": "^3.11.1",
"dayjs": "^1.11.2",
"dotenv": "^16.0.1",
"ramda": "^0.28.0",
"source-map-trace": "^0.1.3"
},
"devDependencies": {
Expand All @@ -50,6 +51,7 @@
"@chenyueban/tsconfig": "^2.1.0",
"@commitlint/cli": "^17.0.2",
"@types/node": "^17.0.38",
"@types/ramda": "^0.28.13",
"commitizen": "^4.2.4",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
"dependencies": {
"@heroicons/react": "^1.0.6",
"daisyui": "^2.15.2",
"highcharts": "^10.1.0",
"highcharts-react-official": "^3.1.0",
"jotai": "^1.7.1",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-use": "^17.4.0",
"swr": "^1.3.0",
"types": "workspace:*",
"zustand": "4.0.0-rc.1"
},
Expand Down
38 changes: 34 additions & 4 deletions packages/web/src/components/issueList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { FC } from 'react'
import { useMemo } from 'react'
import type { Issue } from 'types'
import { ClockIcon } from '@heroicons/react/outline'
import dayjs from 'dayjs'
import { groupBy } from 'ramda'
import MiniChart from './miniChart'
import type { serviceGetIssuesTrendsReturn } from '~/services/issues'

function renderStringOrJson(value: any) {
return typeof value === 'string'
Expand All @@ -10,13 +14,36 @@ function renderStringOrJson(value: any) {
}

interface Props {
data: Issue[]
issues: Issue[]
trends?: serviceGetIssuesTrendsReturn
}
const IssueList: FC<Props> = ({ data }) => {
const IssueList: FC<Props> = ({ issues, trends }) => {
// const buckets = Array.from(
// // @ts-expect-error
// new Array(dayjs(max).diff(dayjs(min), interval) + 1)).map((_, index) => {
// const timestamp = dayjs(min)
// // @ts-expect-error
// .add(index, interval)
// .format(trend?.format?.replace(/\d+/, ''))
// const match = result.find((v: { timestamp: string | number; count: string | number }) =>
// v.timestamp === timestamp)
// if (match) {
// return {
// timestamp,
// count: parseInt(match.count, 10),
// }
// }
// return {
// timestamp,
// count: trend?.min_doc_count,
// }
// })
const groupedTrends = useMemo(() => groupBy(issue => issue.issueId, trends ?? []), [trends])

return (
<div className="border rounded">
{
data.map(issue => (
issues.map(issue => (
<div
aria-label="issue item"
className="opacity-60 py-3 px-2 flex hover:bg-gray-400 hover:bg-opacity-10 hover:opacity-100"
Expand Down Expand Up @@ -74,7 +101,10 @@ const IssueList: FC<Props> = ({ data }) => {
</div>

<div className="w-48">
chart
<MiniChart
data={groupedTrends[issue.id]}
type="24h"
/>
</div>

<div className="w-20 flex items-center justify-center">
Expand Down
83 changes: 83 additions & 0 deletions packages/web/src/components/miniChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { FC } from 'react'
import { memo, useMemo } from 'react'
import type { Options } from 'highcharts'
import Highcharts from 'highcharts'
import HighchartsExporting from 'highcharts/modules/exporting'
import HighchartsReact from 'highcharts-react-official'
import dayjs from 'dayjs'
import { useMount } from 'react-use'
import { theme } from '~/styles/chart.theme'

if (typeof Highcharts === 'object')
HighchartsExporting(Highcharts)

interface MiniChartProps {
type: '24h' | '14d'
data?: {
time: string
count: number
}[]
title?: string
}

const MiniChart: FC<MiniChartProps> = memo(({ type, data, title }) => {
useMount(() => {
if (typeof Highcharts === 'object')
Highcharts.setOptions(theme)
})

const options = useMemo<Options>(
() => ({
chart: {
height: 60,
spacingTop: 5,
spacingBottom: 5,
},
xAxis: { labels: { enabled: false } },
series: [
{
type: 'areaspline',
data: data?.map(v => ({
name: v.time,
y: v.count,
})),
lineWidth: 4,
marker: { enabled: false },
tooltip: {
headerFormat: '',
pointFormatter() {
const { name, y } = this
if (type === '24h')
return `<div style="text-align: center"><span>${dayjs(name).format('YYYY-MM-DD')}<br />${dayjs(name).format('h:00 A → h:59 A')}</span><br /><b>${y} events</b></div>`

if (type === '14d')
return `<div style="text-align: center"><span>${dayjs(name).format('YYYY-MM-DD')}</span><br /><b>${y} events</b></div>`

return ''
},
},
},
],
}),
[data, type],
)

return (
<div>
{title && (
<div className="font-semibold">
<i />
{title}
</div>
)}
<HighchartsReact
highcharts={Highcharts}
options={options}
/>
</div>
)
})

MiniChart.displayName = 'MiniChart'

export default MiniChart
11 changes: 8 additions & 3 deletions packages/web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AppProps } from 'next/app'
import { SWRConfig } from 'swr'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import Layout from '../components/layout'
Expand All @@ -8,9 +9,13 @@ dayjs.extend(relativeTime)

function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
<SWRConfig
value={{ fetcher: (resource, init) => fetch(resource, init).then(res => res.json()) }}
>
<Layout>
<Component {...pageProps} />
</Layout>
</SWRConfig>
)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/pages/api/issues/[issueId].ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { Issue } from '@prisma/client'
import type { Issue } from '@prisma/client'
import { serviceGetIssue } from '~/services/issues'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Issue>,
) {
const issue = await serviceGetIssue({ id: Number(req.query.issueId as string) })
const issue = await serviceGetIssue({ id: req.query.issueId as string })
if (issue)
res.status(200).json(issue)
else
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/pages/api/issues/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { Issue } from '@prisma/client'
import type { Issue } from '@prisma/client'
import { serviceGetIssues } from '~/services/issues'

export default async function handler(
Expand Down
16 changes: 16 additions & 0 deletions packages/web/src/pages/api/trends/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import type { serviceGetIssuesTrendsReturn } from '~/services/issues'
import { serviceGetIssuesTrends } from '~/services/issues'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<serviceGetIssuesTrendsReturn[]>,
) {
const ids = req.query.ids as string
const type = (req.query.type || '24h') as '24h' | '14d'
const trends = await serviceGetIssuesTrends({
ids,
type,
})
res.status(200).json(trends)
}
18 changes: 12 additions & 6 deletions packages/web/src/pages/issues.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import type { GetServerSideProps, NextPage } from 'next'
import type { Issue } from 'types'
import useSWR from 'swr'
import IssueList from '~/components/issueList'
import type { serviceGetIssuesTrendsReturn } from '~/services/issues'
import { serviceGetIssues } from '~/services/issues'

interface Props {
data: Issue[]
issues: Issue[]
}

export const getServerSideProps: GetServerSideProps<Props> = async() => {
const data = await serviceGetIssues({
const issues = await serviceGetIssues({
skip: 0,
take: 100,
}) as unknown as Issue[]

return { props: { data } }
return { props: { issues } }
}

const Issues: NextPage<Props> = ({ data }) => {
const Issues: NextPage<Props> = ({ issues }) => {
const { data: trends } = useSWR<serviceGetIssuesTrendsReturn>(`/api/trends?ids=${issues.map(issue => `'${issue.id}'`)}&type=24h`)

return (
<div className="p-4">
<IssueList data={data} />
<IssueList
issues={issues}
trends={trends}
/>
</div>
)
}
Expand Down
30 changes: 26 additions & 4 deletions packages/web/src/services/issues.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { prisma } from '~/db'

interface getIssuesParams {
interface serviceGetIssuesParams {
skip: number
take: number
}
export function serviceGetIssues({ skip = 0, take = 100 }: getIssuesParams) {
export function serviceGetIssues({ skip = 0, take = 100 }: serviceGetIssuesParams) {
return prisma.issue.findMany({
skip,
take,
Expand All @@ -19,9 +19,31 @@ export function serviceGetIssues({ skip = 0, take = 100 }: getIssuesParams) {
})
}

interface getIssueParams {
interface serviceGetIssuesTrendsParams {
ids: string
type: '24h' | '14d'
}
export type serviceGetIssuesTrendsReturn = {
issueId: string
time: string
count: number
}[]
export function serviceGetIssuesTrends({ ids, type }: serviceGetIssuesTrendsParams) {
const format = type === '14d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH24'
const list = `(${ids})`

return prisma.$queryRawUnsafe<serviceGetIssuesTrendsReturn[]>(`
SELECT "issueId", to_char("Event"."createdAt", '${format}') AS time, count("Event".*)
FROM "Event"
WHERE "Event"."issueId" IN ${list}
GROUP BY time, "issueId"
order by "issueId"
`)
}

interface serviceGetIssueParams {
id: string
}
export function serviceGetIssue({ id }: getIssueParams) {
export function serviceGetIssue({ id }: serviceGetIssueParams) {
return prisma.issue.findUnique({ where: { id } })
}
30 changes: 30 additions & 0 deletions packages/web/src/styles/chart.theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const theme = {
credits: { enabled: false },
colors: ['black'],
title: { text: undefined },
chart: {
spacingTop: 0,
spacingBottom: 0,
spacingLeft: 0,
spacingRight: 0,
backgroundColor: 'transparent',
},
legend: { enabled: false },
xAxis: {
title: { text: null },
tickWidth: 0,
lineWidth: 0,
},
yAxis: {
title: { text: null },
labels: { enabled: false },
gridLineWidth: 0,
},
tooltip: {
useHTML: true,
outside: true,
animation: false,
backgroundColor: 'white',
borderWidth: 0,
},
}
Loading

0 comments on commit d0f8c35

Please sign in to comment.