Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17,981 changes: 8,361 additions & 9,620 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.1.1",
"@nestjs/serve-static": "^4.0.2",
"@nivo/bar": "^0.87.0",
"@nivo/core": "^0.87.0",
"@nivo/line": "^0.87.0",
"@nivo/bar": "^0.87.0",
"@oclif/core": "^4.0.12",
"@oclif/plugin-warn-if-update-available": "^3.1.11",
"@tanstack/react-query": "^5.48.0",
Expand All @@ -70,6 +70,7 @@
"picocolors": "^1.0.1",
"posthog-js": "^1.160.3",
"react": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-router-dom": "^6.24.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
Expand Down
17 changes: 17 additions & 0 deletions packages/app/src/api/platformServer/errorReport.platformApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { platformApiRequest } from '../request'
import { PLATFORM_API_URL } from '..'
import { useMutation } from '@tanstack/react-query'

const useSendPlatformErrorReport = () => {
const mutationFn = (report: Record<string, unknown>) =>
platformApiRequest({
url: `${PLATFORM_API_URL}/error-report/discord`,
method: 'POST',
body: report,
})
return useMutation({
mutationFn,
})
}

export { useSendPlatformErrorReport }
9 changes: 7 additions & 2 deletions packages/app/src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,15 @@ const requestFn = (apiType: apiType) => {
})
.then(async (response) => {
if (!response.ok) {
const responseObject = await response.json()
let responseObject
const responseClone = response.clone()
try {
responseObject = await responseClone.json()
} catch (err) {
responseObject = await response.text()
}
throw new ApiError(responseObject?.message, response.status)
}

switch (responseType) {
case 'blob':
return response.blob()
Expand Down
74 changes: 74 additions & 0 deletions packages/app/src/components/ErrorFallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Box, Typography } from '@mui/material'
import { useQueryClient } from '@tanstack/react-query'
import { useGetCurrentUser } from '../api/browser/user.api'
import { useSendPlatformErrorReport } from '../api/platformServer/errorReport.platformApi'
import { useState } from 'react'
import { CodeClimbersLoadingButton } from './common/CodeClimbersLoadingButton'

export const ErrorFallback = (props: {
error: Error
resetErrorBoundary: () => void
}) => {
const [reportStatus, setReportStatus] = useState<
'idle' | 'success' | 'error'
>('idle')
const queryClient = useQueryClient()
const queryCache = queryClient.getQueryCache().getAll()

const { data: user } = useGetCurrentUser()
const { mutateAsync: sendPlatformErrorReport, isPending } =
useSendPlatformErrorReport()

const sendErrorReport = async () => {
if (!user) return
try {
const userEmail = user?.email
// Get React Query cache

const report = {
error: props.error.toString(),
errorStack: props.error.stack,
queryCache,
userEmail,
timestamp: new Date().toISOString(),
}

await sendPlatformErrorReport(report)

setReportStatus('success')
} catch (e) {
console.error('Failed to send error report:', e)
setReportStatus('error')
}
}
return (
<Box sx={{ p: 2, textAlign: 'center' }}>
<Typography variant="h6">Something went wrong</Typography>
<Typography variant="body2">The component failed to load</Typography>
{reportStatus === 'idle' ? (
<CodeClimbersLoadingButton
eventName="error-boundary-send-report"
variant="contained"
color="primary"
sx={{ mt: 2 }}
onClick={() => sendErrorReport()}
loading={isPending}
>
Send Report
</CodeClimbersLoadingButton>
) : (
<Typography
variant="body1"
sx={{
mt: 2,
color: reportStatus === 'success' ? 'success.main' : 'error.main',
}}
>
{reportStatus === 'success'
? 'Error report sent successfully'
: 'Failed to send error report'}
</Typography>
)}
</Box>
)
}
18 changes: 14 additions & 4 deletions packages/app/src/components/Home/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ExtensionsWidget } from './Extensions/ExtensionsWidget'
import { Sources } from './Source/Sources'
import { DateHeader } from './DateHeader'
import { useSetFeaturePreference } from '../../hooks/useSetFeaturePreference'
import { ErrorBoundary } from 'react-error-boundary'
import { ErrorFallback } from '../ErrorFallback'

const HomePage = () => {
const { data: health, isPending: isHealthPending } = useGetHealth({
Expand All @@ -34,7 +36,9 @@ const HomePage = () => {
mb: 4,
}}
>
<Time selectedDate={selectedDate} />
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Time selectedDate={selectedDate} />
</ErrorBoundary>
<Box
sx={{
display: 'flex',
Expand All @@ -43,11 +47,17 @@ const HomePage = () => {
gap: 4,
}}
>
<Sources selectedDate={selectedDate} />
<ExtensionsWidget />
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Sources selectedDate={selectedDate} />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ExtensionsWidget />
</ErrorBoundary>
</Box>
</Box>
<ExtensionsDashboard />
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ExtensionsDashboard />
</ErrorBoundary>
</div>
)
}
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/components/Home/Source/Sources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const Sources = ({ selectedDate }: SourcesProps) => {
const { exportPulses } = useExportPulses()
const [exportingPulses, setExportingPulses] = useState(false)
const [addSourcesOpen, setAddSourcesOpen] = useState(false)

const theme = useTheme()

const handleExportPulses = async () => {
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/components/Home/Time/CategoryChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const CategoryChart = ({ selectedDate }: Props) => {
}, 0),
)
}, [todayOverview])

const getCategoryMinutes = (
overview: CodeClimbers.TimeOverview[],
category = '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,37 @@ export const HourlyCategoryReportChart = () => {
const [chartData, setChartData] = useState<ChartData[]>([])
const [yIntervals, setYIntervals] = useState<number[]>([2, 4, 6, 8, 10])

const { mutateAsync: getCodingData, isPending } = useGetCodingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { mutateAsync: getBrowsingData } = useGetBrowsingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { mutateAsync: getCommunicatingData } = useGetCommunicatingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { mutateAsync: getDesigningData } = useGetDesigningData(
const { data: codingData, isPending: isCodingPending } = useGetCodingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { data: browsingData, isPending: isBrowsingPending } =
useGetBrowsingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { data: communicatingData, isPending: isCommunicatingPending } =
useGetCommunicatingData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)
const { data: designingData, isPending: isDesigningPending } =
useGetDesigningData(
selectedDate.startOf('day').toISOString(),
selectedDate.endOf('day').toISOString(),
)

const isPending =
isCodingPending ||
isBrowsingPending ||
isCommunicatingPending ||
isDesigningPending
useEffect(() => {
const getData = async () => {
const coding: HourlyResponse[] = await getCodingData()
const browsing: HourlyResponse[] = await getBrowsingData()
const communicating: HourlyResponse[] = await getCommunicatingData()
const designing: HourlyResponse[] = await getDesigningData()
const coding: HourlyResponse[] = codingData || []
const browsing: HourlyResponse[] = browsingData || []
const communicating: HourlyResponse[] = communicatingData || []
const designing: HourlyResponse[] = designingData || []

const formattedData = formatData(
[...coding, ...browsing, ...communicating, ...designing],
Expand All @@ -57,8 +65,10 @@ export const HourlyCategoryReportChart = () => {
setYIntervals(getYIntervals([...coding, ...browsing]))
setChartData(formattedData)
}
getData()
}, [])
if (!isPending) {
getData()
}
}, [isPending])

if (isPending)
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { sqlQueryFn } from '../../api/browser/services/query.service'
import { useMutation } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'

const getQuery = (startDate: string, endDate: string, category: string) =>
`WITH getMinutes (category, time) AS (
Expand All @@ -20,31 +20,35 @@ export const useGetCodingData = (startDate: string, endDate: string) => {
"(category is 'coding' or category is 'debugging')",
)

return useMutation({
mutationFn: () => sqlQueryFn(query, 'hourlyCategoryReport-coding'),
return useQuery({
queryKey: ['hourlyCategoryReport-coding', startDate, endDate],
queryFn: () => sqlQueryFn(query, 'hourlyCategoryReport-coding'),
})
}

export const useGetBrowsingData = (startDate: string, endDate: string) => {
const query = getQuery(startDate, endDate, "category is 'browsing'")

return useMutation({
mutationFn: () => sqlQueryFn(query, 'hourlyCategoryReport-browsing'),
return useQuery({
queryKey: ['hourlyCategoryReport-browsing', startDate, endDate],
queryFn: () => sqlQueryFn(query, 'hourlyCategoryReport-browsing'),
})
}

export const useGetCommunicatingData = (startDate: string, endDate: string) => {
const query = getQuery(startDate, endDate, "category is 'communicating'")

return useMutation({
mutationFn: () => sqlQueryFn(query, 'hourlyCategoryReport-communicating'),
return useQuery({
queryKey: ['hourlyCategoryReport-communicating', startDate, endDate],
queryFn: () => sqlQueryFn(query, 'hourlyCategoryReport-communicating'),
})
}

export const useGetDesigningData = (startDate: string, endDate: string) => {
const query = getQuery(startDate, endDate, "category is 'designing'")

return useMutation({
mutationFn: () => sqlQueryFn(query, 'hourlyCategoryReport-designing'),
return useQuery({
queryKey: ['hourlyCategoryReport-designing', startDate, endDate],
queryFn: () => sqlQueryFn(query, 'hourlyCategoryReport-designing'),
})
}
14 changes: 11 additions & 3 deletions packages/server/src/common/scheduleTask.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export class ScheduledTaskService {
this.reportService = reportService
this.userService = userService
}
@Cron('0 8 * * 1')
// every hour on monday
@Cron('0 * * * 1')
handleWeeklyReport() {
this.sendWeeklyReport()
// Add your actual function logic here
// if it's after 8am, send the report
if (dayjs().hour() < 8) {
return
}
// wait a random amount of seconds between 0 and 600 to help spread out the load
const waitTime = Math.floor(Math.random() * 600)
setTimeout(() => {
this.sendWeeklyReport()
}, waitTime * 1000)
}

@Cron(CronExpression.EVERY_10_SECONDS)
Expand Down
Loading