Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: init the insight page #731

Merged
merged 7 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
10 changes: 3 additions & 7 deletions assistant/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
{
"name": "@petercatai/assistant",
"version": "2.1.0-alpha.0",
"version": "2.1.0-alpha.1",
"description": "PeterCat Assistant Application",
"repository": "https://github.com/petercat-ai/petercat.git",
"license": "MIT",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"import": "./dist/esm/index.js"
},
"./style": {
"import": "./dist/esm/style/global.css"
}
".": "./dist/esm/index.js",
"./style": "./dist/esm/style/global.css"
},
"module": "dist/esm/index.js",
"browser": "dist/umd/assistant.min.js",
Expand Down
3 changes: 2 additions & 1 deletion assistant/src/SegmentedTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const SegmentedTabs: React.FC<SegmentedTabsProps> = ({
const getTabClass = (value: string) => {
const isActive = active === value;

const base = 'flex-1 flex items-center justify-center cursor-pointer';
const base =
'flex-1 flex items-center justify-center cursor-pointer text-[14px]';

const normal = 'bg-white text-black border-none';

Expand Down
26 changes: 25 additions & 1 deletion client/app/factory/list/components/BotCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,31 @@ const BotCard = (props: { bot: BotInfo; userId: string }) => {
{bot.name}
</span>
<div className="flex items-center gap-2 shrink-0">
<div className="w-[32px] h-[32px] p-[7px] flex items-center rounded-[16px] bg-[#F4F4F5]">
<Tooltip
content="Insights"
classNames={{
base: [
// arrow color
'before:bg-[#3F3F46] dark:before:bg-white',
],
content: [
'py-2 px-4 rounded-lg shadow-xl text-white',
'bg-[#3F3F46]',
],
}}
>
<img
onClick={() =>
router.push(
`/insight?repo=${bot.repo_name}&name=${bot.name}`,
)
}
className="w-[20px] h-[20px] cursor-pointer"
src="/images/statistic.svg"
/>
</Tooltip>

<div className="w-[20px] h-[20px] flex items-center">
{renderTaskStatusIcon(taskInfo ?? [])}
</div>
</div>
Expand Down
6 changes: 1 addition & 5 deletions client/app/factory/list/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import { Tables } from '@/types/database.types';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { isEmpty, map } from 'lodash';
import BotCard from './components/BotCard';

Expand Down Expand Up @@ -34,10 +34,6 @@ export default function List() {
[isComplete, userLoading, isLoading, user],
);

useEffect(() => {
console.log(' opening', isOpening);
}, [isOpening]);

if (isOpening) {
return (
<FullPageSkeleton
Expand Down
66 changes: 66 additions & 0 deletions client/app/hooks/useInsight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useQuery } from '@tanstack/react-query';
import {
getIssueStatistics,
getIssueResolutionDuration,
getPrStatistics,
getCodeFrequency,
getActivityStatistics,
getActivityDatesAndTimes,
getContributorStatistics,
} from '../services/InsightController';

export function useIssueStatistics(repoName: string) {
return useQuery({
queryKey: [`insight.issue.statistics`, repoName],
queryFn: async () => getIssueStatistics(repoName),
enabled: repoName !== undefined,
});
}

export function useContributorStatistics(repoName: string) {
return useQuery({
queryKey: [`insight.contributor.statistics`, repoName],
queryFn: async () => getContributorStatistics(repoName),
enabled: repoName !== undefined,
});
}

export function useIssueResolutionDuration(repoName: string) {
return useQuery({
queryKey: [`insight.issue.resolution_duration`, repoName],
queryFn: async () => getIssueResolutionDuration(repoName),
enabled: repoName !== undefined,
});
}

export function usePrStatistics(repoName: string) {
return useQuery({
queryKey: [`insight.pr.statistics`, repoName],
queryFn: async () => getPrStatistics(repoName),
enabled: repoName !== undefined,
});
}

export function useCodeFrequency(repoName: string) {
return useQuery({
queryKey: [`insight.pr.code_frequency`, repoName],
queryFn: async () => getCodeFrequency(repoName),
enabled: repoName !== undefined,
});
}

export function useActivityStatistics(repoName: string) {
return useQuery({
queryKey: [`insight.activity.statistics`, repoName],
queryFn: async () => getActivityStatistics(repoName),
enabled: repoName !== undefined,
});
}

export function useActivityDatesAndTimes(repoName: string) {
return useQuery({
queryKey: [`insight.activity.dates_and_times`, repoName],
queryFn: async () => getActivityDatesAndTimes(repoName),
enabled: repoName !== undefined,
});
}
143 changes: 143 additions & 0 deletions client/app/insight/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use client';
import React, { Suspense } from 'react';
import { Tables } from '@/types/database.types';
import HomeIcon from '@/public/icons/HomeIcon';
import {
AreaChart,
LineChart,
Heatmap,
BoxChart,
RankChart,
} from '@petercatai/assistant';
import { useSearchParams } from 'next/navigation';
import { Skeleton } from '@nextui-org/react';
import {
useIssueStatistics,
useIssueResolutionDuration,
usePrStatistics,
useCodeFrequency,
useActivityStatistics,
useActivityDatesAndTimes,
useContributorStatistics,
} from '../hooks/useInsight';

export default function Insight() {
const searchParams = useSearchParams();
const repoName = searchParams.get('repo') || '';
const botName = searchParams.get('name') || '';
const { data: issueStatistic } = useIssueStatistics(repoName);
const { data: issueResolutionDuration } =
useIssueResolutionDuration(repoName);
const { data: prStatistic } = usePrStatistics(repoName);
const { data: codeFrequency } = useCodeFrequency(repoName);
const { data: activityStatistics } = useActivityStatistics(repoName);
const { data: activityDatesAndTimes } = useActivityDatesAndTimes(repoName);
const { data: contributors } = useContributorStatistics(repoName);
return (
<div className="flex w-full h-full flex-col bg-[#F3F4F6] min-h-screen">
<div className="relative flex h-[72px] w-full items-center justify-between gap-2 px-6 flex-shrink-0">
<div className="flex items-center gap-2">
<span
className="flex items-center gap-2 cursor-pointer"
onClick={(e) => {
e.preventDefault();
window.history.back();
}}
>
<HomeIcon />
<span className="text-gray-400">{botName}</span>
</span>
<span className="text-gray-400">/</span>
<span>Insight</span>
</div>
<div className="flex items-center gap-2 text-gray-400 text-sm">
Powered by
<a
className="text-gray-500"
href="https://open-digger.cn/docs/user_docs/intro"
>
HyperCRX
</a>
</div>
</div>
<div className="pb-[42px] px-[40px] overflow-y-auto">
<div className="grid grid-cols-2 gap-2">
<div className="bg-white rounded h-[607px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{contributors && (
<AreaChart
data={contributors}
height={500}
title="Contributors"
/>
)}
</Suspense>
</div>
<div className="bg-white rounded h-[607px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{codeFrequency && (
<AreaChart
data={codeFrequency}
height={500}
title="Code Frequency"
/>
)}
</Suspense>
</div>
<div className="bg-white rounded h-[579px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{prStatistic && (
<LineChart data={prStatistic} height={450} title="PR History" />
)}
</Suspense>
</div>
<div className="bg-white rounded h-[579px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{issueStatistic && (
<LineChart
data={issueStatistic}
height={450}
title="Issue History"
/>
)}
</Suspense>
</div>
<div className="bg-white rounded h-[400px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{issueResolutionDuration && (
<BoxChart
data={issueResolutionDuration}
height={300}
title="Average Time To Issue Close"
/>
)}
</Suspense>
</div>
<div className="bg-white rounded h-[400px] p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{activityDatesAndTimes && (
<Heatmap
data={activityDatesAndTimes}
height={300}
title="Repository Activity"
/>
)}
</Suspense>
</div>

<div className="bg-white rounded h-[500px] col-span-2 p-[24px]">
<Suspense fallback={<Skeleton className="rounded-lg" />}>
{activityStatistics && (
<RankChart
data={activityStatistics}
height={408}
title="Contributor Rankings Top 10"
/>
)}
</Suspense>
</div>
</div>
</div>
</div>
);
}
9 changes: 3 additions & 6 deletions client/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,13 @@ export default function RootLayout({
<NextUIProvider>
<GlobalProvider>
<BotProvider>
{pathname === '/' ||
pathname === '/policy' ||
pathname === '/agreement' ||
pathname === '/release' ? (
children
) : (
{pathname === '/factory/list' || pathname === '/market' ? (
<div className="flex flex-col bg-[#F3F4F6] min-h-screen">
<Navbar />
<div className="pb-[40px]">{children}</div>
</div>
) : (
children
)}
</BotProvider>
</GlobalProvider>
Expand Down
5 changes: 2 additions & 3 deletions client/app/market/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use client';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useState } from 'react';
import { Tables } from '@/types/database.types';
import { isEmpty, map } from 'lodash';
import BotCard from '@/components/BotCard';
import { useBotList } from '@/app/hooks/useBot';
import FullPageSkeleton from '@/components/FullPageSkeleton';
import { useGlobal } from '@/app/contexts/GlobalContext';
import { Assistant, useUser } from '@petercatai/assistant';
import { useFingerprint } from '../hooks/useFingerprint';
import { Assistant } from '@petercatai/assistant';
import Crash from '@/components/Crash';

declare type Bot = Tables<'bots'>;
Expand Down
53 changes: 53 additions & 0 deletions client/app/services/InsightController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import axios from 'axios';

const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that NEXT_PUBLIC_API_DOMAIN is defined in the environment variables to avoid runtime errors when making API requests.

axios.defaults.withCredentials = true;

export async function getIssueStatistics(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/issue/statistics?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getIssueResolutionDuration(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/issue/resolution_duration?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getContributorStatistics(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/contributor/statistics?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getPrStatistics(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/pr/statistics?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getCodeFrequency(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/pr/code_frequency?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getActivityStatistics(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/activity/statistics?repo_name=${repoName}`,
);
return response.data.data;
}

export async function getActivityDatesAndTimes(repoName: string) {
const response = await axios.get(
`${apiDomain}/api/insight/activity/dates_and_times?repo_name=${repoName}`,
);
return response.data.data;
}
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@fullpage/react-fullpage": "^0.1.42",
"@next/bundle-analyzer": "^13.4.19",
"@nextui-org/react": "^2.2.9",
"@petercatai/assistant": "^2.0.23",
"@petercatai/assistant": "2.1.0-alpha.1",
"@sentry/nextjs": "^8.28.0",
"@supabase/supabase-js": "^2.32.0",
"@tanstack/react-query": "^5.17.19",
Expand Down
Loading
Loading