Skip to content

Commit d8be9bd

Browse files
authored
✨ 대시보드 웹 ui 추가 (#112)
1 parent 2ba2d46 commit d8be9bd

6 files changed

Lines changed: 128 additions & 4 deletions

File tree

src/entities/Member.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type Member = {
22
githubUsername: string;
33
slackUserId: string;
4+
name: string;
45
};

src/infrastructures/implementMemberWaffleDotComRepository.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import type { Member } from '../entities/Member';
66
*/
77
export const implementMemberWaffleDotComRepository = ({
88
wadotClient,
9-
}: { wadotClient: { listUsers: () => Promise<{ github_id: string; slack_id: string }[]> } }): {
9+
}: {
10+
wadotClient: {
11+
listUsers: () => Promise<{ github_id: string; slack_id: string; first_name: string }[]>;
12+
};
13+
}): {
1014
getAllMembers: () => Promise<{ members: Member[] }>;
1115
} => {
1216
return {
@@ -17,6 +21,7 @@ export const implementMemberWaffleDotComRepository = ({
1721
members: users.map((user) => ({
1822
slackUserId: user.slack_id,
1923
githubUsername: user.github_id,
24+
name: user.first_name,
2025
})),
2126
};
2227
},

src/main.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ import { implementWaffleService } from './services/WaffleService';
1010

1111
import type { MongoClient } from 'mongodb';
1212
import { z } from 'zod';
13+
import {
14+
type WaffleGraphDashboardService,
15+
implementWaffleGraphDashboardService,
16+
} from './services/WaffleGraphDashboardService';
1317

1418
export const handle = async (
1519
dependencies: {
1620
slackClient: Pick<WebClient['chat'], 'postMessage' | 'getPermalink'>;
1721
mongoClient: Pick<MongoClient, 'db'>;
18-
wadotClient: { listUsers: () => Promise<{ github_id: string; slack_id: string }[]> };
22+
wadotClient: {
23+
listUsers: () => Promise<{ github_id: string; slack_id: string; first_name: string }[]>;
24+
};
1925
},
2026
env: {
2127
slackBotToken: string;
@@ -50,6 +56,10 @@ export const handle = async (
5056
},
5157
},
5258
});
59+
const waffleGraphDashboardService = implementWaffleGraphDashboardService({
60+
waffleRepository: implementMongoAtlasWaffleRepository(dependencies),
61+
memberRepository: implementMemberWaffleDotComRepository(dependencies),
62+
});
5363
const deployWebhookController = implementGitHubDeployWebhookController({
5464
deploymentService: implementDeploymentService({
5565
messengerPresenter: implementSlackPresenter({
@@ -66,6 +76,14 @@ export const handle = async (
6676
if (request.method === 'GET' && url.pathname === '/health-check')
6777
return new Response('ok', { status: 200 });
6878

79+
if (request.method === 'GET' && url.pathname === '/dashboard') {
80+
const data = await waffleGraphDashboardService.getGraphData();
81+
return new Response(html(data), {
82+
status: 200,
83+
headers: { 'content-type': 'text/html;charset=utf-8' },
84+
});
85+
}
86+
6987
if (request.method === 'POST' && url.pathname === '/slack/action-endpoint') {
7088
const body = (await request.json()) as {
7189
token: unknown;
@@ -135,3 +153,32 @@ export const handle = async (
135153
return new Response(null, { status: 500 });
136154
}
137155
};
156+
157+
const html = (data: Awaited<ReturnType<WaffleGraphDashboardService['getGraphData']>>) => {
158+
return `
159+
<!DOCTYPE html>
160+
<html lang="ko">
161+
<head>
162+
<title>Waffle Dashboard</title>
163+
<script src="//cdn.jsdelivr.net/npm/3d-force-graph"></script>
164+
</head>
165+
<body style="margin: 0">
166+
<div id="graph"></div>
167+
168+
<script>
169+
const gData = {
170+
nodes: ${JSON.stringify(data.vertexes)},
171+
links: ${JSON.stringify(data.edges.map((e) => ({ source: e.from, target: e.to, count: e.count })))}
172+
};
173+
174+
const Graph = ForceGraph3D()
175+
(document.getElementById('graph'))
176+
.nodeVal(node => node.count / 10) // use val for node size
177+
.nodeLabel(node => node.title)
178+
.linkWidth(link => link.count * 0.03) // use weight for link thickness
179+
.graphData(gData);
180+
</script>
181+
</body>
182+
</html>
183+
`;
184+
};

src/server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ Bun.serve({
3434
wadotClient: {
3535
listUsers: () =>
3636
fetch('https://wadot-api.wafflestudio.com/api/v1/users').then(
37-
(res) => res.json() as Promise<{ github_id: string; slack_id: string }[]>,
37+
(res) =>
38+
res.json() as Promise<
39+
{ github_id: string; slack_id: string; first_name: string }[]
40+
>,
3841
),
3942
},
4043
},
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { Log } from '../entities/Waffle';
2+
3+
export type WaffleGraphDashboardService = {
4+
getGraphData: () => Promise<{
5+
vertexes: { id: string; count: number; title: string }[];
6+
edges: { from: string; to: string; count: number }[];
7+
}>;
8+
};
9+
10+
export const implementWaffleGraphDashboardService = ({
11+
waffleRepository,
12+
memberRepository,
13+
}: {
14+
waffleRepository: {
15+
listAllLogs: () => Promise<{ logs: Log[] }>;
16+
};
17+
memberRepository: {
18+
getAllMembers: () => Promise<{ members: { slackUserId: string; name: string }[] }>;
19+
};
20+
}): WaffleGraphDashboardService => {
21+
return {
22+
getGraphData: async () => {
23+
const { logs } = await waffleRepository.listAllLogs();
24+
const { members } = await memberRepository.getAllMembers();
25+
26+
const vertexes = logs
27+
.reduce<{ user: string; given: number; taken: number }[]>((a, c) => {
28+
const foundGiver = a.find((it) => it.user === c.from);
29+
const foundReceiver = a.find((it) => it.user === c.to);
30+
31+
if (foundGiver) foundGiver.given += c.count;
32+
else a.push({ user: c.from, given: c.count, taken: 0 });
33+
34+
if (foundReceiver) foundReceiver.taken += c.count;
35+
else a.push({ user: c.to, given: 0, taken: c.count });
36+
37+
return a;
38+
}, [])
39+
.map((d) => ({
40+
id: d.user,
41+
title: [
42+
members.find((m) => m.slackUserId === d.user)?.name ?? '-',
43+
`(${d.given + d.taken})`,
44+
].join(' '),
45+
count: d.given + d.taken,
46+
}));
47+
48+
const edges = logs.reduce(
49+
(
50+
acc: { from: string; to: string; count: number }[],
51+
cur,
52+
): { from: string; to: string; count: number }[] => {
53+
const found = acc.find(
54+
(a) =>
55+
(a.from === cur.from && a.to === cur.to) || (a.from === cur.to && a.to === cur.from),
56+
);
57+
if (found) found.count += cur.count;
58+
else acc.push({ from: cur.from, to: cur.to, count: cur.count });
59+
return acc;
60+
},
61+
[],
62+
);
63+
64+
return { vertexes, edges };
65+
},
66+
};
67+
};

src/weekly-dashboard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const dashboardService = implementDashboardService({
5151
wadotClient: {
5252
listUsers: () =>
5353
fetch('https://wadot-api.wafflestudio.com/api/v1/users').then(
54-
(res) => res.json() as Promise<{ github_id: string; slack_id: string }[]>,
54+
(res) =>
55+
res.json() as Promise<{ github_id: string; slack_id: string; first_name: string }[]>,
5556
),
5657
},
5758
}),

0 commit comments

Comments
 (0)