Skip to content

Commit

Permalink
feat: support report and preview feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
xinyao27 committed Aug 3, 2022
1 parent ba4249e commit a8e5114
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 3 deletions.
4 changes: 4 additions & 0 deletions packages/server/src/api/report/report.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export interface CreateMetricParams {
metric: OhbugEventLike
}

export interface CreateFeedbackParams {
feedback: OhbugEventLike
}

export interface GetAlertStatusParams {
event: Event
issue: Issue
Expand Down
51 changes: 49 additions & 2 deletions packages/server/src/api/report/report.processor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { InjectQueue, OnQueueError, Process, Processor } from '@nestjs/bull'
import type { Job, Queue } from 'bull'
import type { Prisma } from '@prisma/client'
import type { CreateEventParams, CreateMetricParams, GetAlertStatusParams } from './report.interface'
import type { CreateEventParams, CreateFeedbackParams, CreateMetricParams, GetAlertStatusParams } from './report.interface'
import { ForbiddenException, PrismaService } from '~/common'

@Processor('document')
Expand Down Expand Up @@ -110,7 +110,32 @@ export class ReportProcessor {
})
}
catch (error) {
throw new ForbiddenException(4001003, error)
throw new ForbiddenException(4001004, error)
}
}

async CreateFeedback({ feedback }: CreateFeedbackParams) {
try {
return this.prisma.feedback.create({
data: {
apiKey: feedback.apiKey,
appVersion: feedback.appVersion,
appType: feedback.appType,
releaseStage: feedback.releaseStage,
timestamp: feedback.timestamp,
category: feedback.category,
type: feedback.type,
sdk: feedback.sdk as unknown as Prisma.InputJsonObject,
detail: feedback.detail as Prisma.InputJsonValue,
device: feedback.device as Prisma.InputJsonObject,
user: feedback.user as Prisma.InputJsonObject,
actions: feedback.actions as unknown as Prisma.InputJsonArray,
metadata: feedback.metadata,
},
})
}
catch (error) {
throw new ForbiddenException(4001005, error)
}
}

Expand Down Expand Up @@ -179,6 +204,28 @@ export class ReportProcessor {
}
}

@Process('feedback')
async handleFeedback(job: Job) {
try {
const data = job.data as CreateFeedbackParams

if (data) {
const apiKey = data.feedback.apiKey
const project = await this.prisma.project.findUniqueOrThrow({ where: { apiKey } })

if (project) {
await this.CreateFeedback(data)
}
else {
throw new Error(`Project not found for apiKey: ${apiKey}`)
}
}
}
catch (error) {
throw new ForbiddenException(4001004, error)
}
}

@OnQueueError()
handleError(error: Error) {
console.error(error)
Expand Down
14 changes: 13 additions & 1 deletion packages/server/src/api/report/report.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getMd5FromAggregationData,
switchErrorDetailAndGetAggregationDataAndMetaData,
} from './report.core'
import type { CreateEventParams, CreateMetricParams } from './report.interface'
import type { CreateEventParams, CreateFeedbackParams, CreateMetricParams } from './report.interface'
import { ForbiddenException } from '~/common'

@Injectable()
Expand Down Expand Up @@ -97,6 +97,18 @@ export class ReportService {
},
)
}
else if (eventLike.category === 'feedback') {
const createFeedbackParams: CreateFeedbackParams = { feedback: eventLike }
await this.documentQueue.add(
'feedback',
createFeedbackParams,
{
delay: 3000,
removeOnComplete: true,
removeOnFail: true,
},
)
}
else {
const aggregationEvent = this.aggregation(eventLike)
const createEventParams: CreateEventParams = {
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/common/constants/status.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"4001002": "event an error occurred during task scheduling",
"4001003": "failed to create event",
"4001004": "failed to create metrics",
"4001005": "failed to create feedback",

"4001100": "alert task execution failed",

Expand Down
50 changes: 50 additions & 0 deletions packages/web/src/components/feedbacksList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'
import dayjs from 'dayjs'
import type { FC } from 'react'
import type { Feedback } from '@prisma/client'
import { renderStringOrJson } from '~/libs/utils'

interface Props {
feedbacks?: Feedback[]
}

const FeedbacksList: FC<Props> = ({ feedbacks }) => {
return (
<TableContainer>
<Table className="w-full table table-compact">
<Thead>
<Tr>
<Th>createdAt</Th>
<Th>feedback</Th>
<Th>user</Th>
<Th>selected element</Th>
</Tr>
</Thead>
<Tbody>
{
feedbacks?.map((feedback) => {
const detail = feedback.detail as any
return (
<Tr key={feedback.id}>
<Td>
{dayjs(feedback.createdAt).format('YYYY-MM-DD HH:mm:ss')}
</Td>
<Td>{detail.feedback}</Td>
<Td>{renderStringOrJson(feedback.user)}</Td>
<Td>
<img
alt="selected element"
src={detail.dataURL}
/>
</Td>
</Tr>
)
})
}
</Tbody>
</Table>
</TableContainer>
)
}

export default FeedbacksList
5 changes: 5 additions & 0 deletions packages/web/src/components/navMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const NavMenu: FC = () => {
link: '/[projectId]/metrics',
as: `/${projectId}/metrics`,
},
{
label: 'Feedback',
link: '/[projectId]/feedbacks',
as: `/${projectId}/feedbacks`,
},
{
label: 'Releases',
link: '/[projectId]/releases',
Expand Down
46 changes: 46 additions & 0 deletions packages/web/src/pages/[projectId]/feedbacks/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Button } from '@chakra-ui/react'
import type { Feedback } from '@prisma/client'
import type { NextPage } from 'next'
import { useTranslations } from 'next-intl'
import FeedbacksList from '~/components/feedbacksList'
import ThemeBox from '~/components/themeBox'
import Title from '~/components/title'
import Wrapper from '~/components/wrapper'
import useCurrentProject from '~/hooks/useCurrentProject'
import { useInfinite } from '~/hooks/useInfinite'

const Feedbacks: NextPage = () => {
const ct = useTranslations('Common')
const { projectId } = useCurrentProject()
const { data: feedbacks, isLoading, size, setSize, isReachingEnd } = useInfinite<Feedback>(index => `/api/feedbacks?projectId=${projectId}&page=${index + 1}`)

return (
<ThemeBox bg="current">
<Title>
Feedbacks
</Title>

<Wrapper>
<FeedbacksList feedbacks={feedbacks} />
<Button
disabled={isLoading || isReachingEnd}
mt="6"
onClick={() => setSize(size + 1)}
size="sm"
variant="outline"
w="full"
>
{
isLoading
? `${ct('loading')}...`
: isReachingEnd
? ct('noMoreData')
: ct('loadMore')
}
</Button>
</Wrapper>
</ThemeBox>
)
}

export default Feedbacks
16 changes: 16 additions & 0 deletions packages/web/src/pages/api/feedbacks/[id].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import type { Feedback } from '@prisma/client'
import { getAuth } from '~/libs/middleware'
import { serviceGetFeedback } from '~/services/feedbacks'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Feedback>,
) {
const auth = await getAuth(req, res)
if (!auth) return

const feedback = await serviceGetFeedback({ id: req.query.id as string })
if (feedback) { res.status(200).json(feedback) }
else { res.end(`Feedback ${req.query.id} not found`) }
}
25 changes: 25 additions & 0 deletions packages/web/src/pages/api/feedbacks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import type { Feedback } from '@prisma/client'
import { PAGE_SIZE } from 'common'
import { serviceGetFeedbacks } from '~/services/feedbacks'
import { getAuth } from '~/libs/middleware'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Feedback[]>,
) {
const auth = await getAuth(req, res)
if (!auth) return

const page = parseInt(req.query.page as string) || 1
const pageSize = parseInt(req.query.pageSize as string) || PAGE_SIZE
const projectId = parseInt(req.query.projectId as string)
const query = req.query.query as string
const feedbacks = await serviceGetFeedbacks({
page,
pageSize,
projectId,
query,
})
res.status(200).json(feedbacks)
}
32 changes: 32 additions & 0 deletions packages/web/src/services/feedbacks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Pagination } from 'common'
import { PAGE_SIZE, pagination } from 'common'
import { serviceGetProject } from './projects'
import { getPrisma } from '~/db'

interface ServiceGetFeedbacksParams extends Pagination {
projectId: number
query?: string
}
export async function serviceGetFeedbacks({
projectId,
query,
page = 0,
pageSize = PAGE_SIZE,
}: ServiceGetFeedbacksParams) {
const project = await serviceGetProject(projectId)
const options: any = {
where: { apiKey: project.apiKey },
...pagination({ page, pageSize }),
}
if (query) {
options.where.metadata = { search: query }
}
return getPrisma().feedback.findMany(options)
}

interface ServiceGetFeedbackParams {
id: string
}
export function serviceGetFeedback({ id }: ServiceGetFeedbackParams) {
return getPrisma().feedback.findUniqueOrThrow({ where: { id } })
}
26 changes: 26 additions & 0 deletions prisma/migrations/20220803074940_add_feedback_table/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- CreateTable
CREATE TABLE "Feedback" (
"id" TEXT NOT NULL,
"apiKey" TEXT NOT NULL,
"appVersion" TEXT,
"appType" TEXT,
"releaseStage" TEXT,
"timestamp" TIMESTAMP(3) NOT NULL,
"category" TEXT,
"type" TEXT NOT NULL,
"sdk" JSONB NOT NULL,
"detail" JSONB NOT NULL,
"device" JSONB NOT NULL,
"user" JSONB,
"actions" JSONB,
"metadata" JSONB,
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Feedback_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Feedback_apiKey_idx" ON "Feedback"("apiKey");

-- CreateIndex
CREATE INDEX "Feedback_createdAt_idx" ON "Feedback" USING BRIN ("createdAt" timestamp_bloom_ops);
21 changes: 21 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ model Metric {
@@index([createdAt(ops: TimestampBloomOps)], type: Brin)
}

model Feedback {
id String @id @default(uuid())
apiKey String
appVersion String?
appType String?
releaseStage String?
timestamp DateTime
category String?
type String
sdk Json
detail Json
device Json
user Json?
actions Json?
metadata Json?
createdAt DateTime @default(now()) @db.Timestamp
@@index([apiKey])
@@index([createdAt(ops: TimestampBloomOps)], type: Brin)
}

model Issue {
id String @id
apiKey String
Expand Down

0 comments on commit a8e5114

Please sign in to comment.