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

[OSS101] Task 5: Display Community OpenRank #843

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
14df77c
init colorful-calendar
Fiveneves Apr 30, 2024
873b31c
change calendar's color
Fiveneves Apr 30, 2024
a763ef5
add ColorPicker
Fiveneves Apr 30, 2024
f9d5ea0
restore colorful-calendar
Fiveneves May 3, 2024
99674fb
Merge pull request #1 from hypertrons/master
Fiveneves Jul 19, 2024
0a97d64
Merge branch 'hypertrons:master' into fiveneves
Fiveneves Jul 21, 2024
957347a
Merge pull request #3 from Fiveneves/fiveneves
Fiveneves Jul 21, 2024
4631e33
add community openrank api
Fiveneves Jul 22, 2024
5f28e97
Merge remote-tracking branch 'origin/fiveneves' into fiveneves
Fiveneves Jul 22, 2024
0a7b9d0
add community openrank feature container to perceptor
Fiveneves Jul 22, 2024
a69ce7a
Merge pull request #4 from Fiveneves/fiveneves
Fiveneves Jul 22, 2024
17f03b8
add Community OpenRank Detail Network
Fiveneves Jul 22, 2024
3961e60
add DatePicker to Community OpenRank Detail Network
Fiveneves Jul 24, 2024
b81fc98
Merge branch 'hypertrons:master' into master
Fiveneves Jul 24, 2024
9678532
Merge branch 'master' into fiveneves
Fiveneves Jul 24, 2024
93cb3d6
Merge pull request #6 from Fiveneves/fiveneves
Fiveneves Jul 24, 2024
f1effd5
add Community OpenRank Racing Bar
Fiveneves Jul 24, 2024
28c658f
Merge remote-tracking branch 'origin/fiveneves' into fiveneves
Fiveneves Jul 24, 2024
abcb34b
Merge pull request #7 from Fiveneves/fiveneves
Fiveneves Jul 24, 2024
cf334c3
Delete src/pages/ContentScripts/features/colorful-calendar directory
Fiveneves Jul 24, 2024
968fd6e
remove colorful calendar from index.ts
Fiveneves Jul 24, 2024
a7a58a2
Merge pull request #8 from Fiveneves/master
Fiveneves Jul 25, 2024
124ff58
Fix a Bug in sorting data
Fiveneves Jul 25, 2024
0e01d64
Update index.tsx
reset0514 Jul 25, 2024
25f7b19
Merge pull request #10 from Fiveneves/fiveneves
Fiveneves Jul 25, 2024
fe54aa1
Merge branch 'hypertrons:master' into master
Fiveneves Jul 25, 2024
99543c4
delete Chinese comments
Fiveneves Jul 25, 2024
b8b220f
delete code that has been commented out
Fiveneves Jul 25, 2024
3c5113e
delete imported items are not used
Fiveneves Jul 25, 2024
c69e4d2
add rsuite dependency
Fiveneves Jul 25, 2024
1a4e558
add a selectPicker to the CommunityOpenRankRacingBar feature to choos…
Fiveneves Jul 25, 2024
f20fd91
Merge pull request #11 from Fiveneves/fiveneves
Fiveneves Jul 25, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"react-hot-loader": "^4.13.0",
"react-i18next": "^14.1.2",
"react-modal": "3.15.1",
"rsuite": "^5.67.0",
"strip-indent": "^4.0.0"
},
"devDependencies": {
Expand Down
24 changes: 24 additions & 0 deletions src/api/community.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import request from '../helpers/request';
import { ErrorCode, OSS_XLAB_ENDPOINT } from '../constant';

export const getMetricByDate = async (repoName: string, date: string) => {
let response;
try {
response = await request(
`${OSS_XLAB_ENDPOINT}/open_digger/github/${repoName}/project_openrank_detail/${date}.json`
);
} catch (error) {
// the catched error being "404" means the metric file is not available so return a null
if (error === ErrorCode.NOT_FOUND) {
return null;
} else {
// other errors should be throwed
throw error;
}
}
return response;
};

export const getOpenRank = async (repo: string, date: string) => {
return getMetricByDate(repo, date);
};
4 changes: 4 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"global_day_one": "day",
"global_day_other": "days",
"global_clickToshow": "Click to show",
"component_communityOpenRankNetwork_title": "Community OpenRank Detail Network",
"component_communityOpenRankNetwork_description": "Community OpenRank Detail Network shows the OpenRank about the project by month. Double-click a node in this network to view the details of OpenRank for that node in the table on the right. ",
"component_communityOpenRankRacingBar_title": "Community OpenRank Racing Bar",
"component_communityOpenRankRacingBar_description": "This chart shows how the OpenRank in this community evolve. ",
"component_developerCollaborationNetwork_title": "Developer Collaboration Network",
"component_developerCollaborationNetwork_description": "Developer Collaboration Network shows the collaboration between developers for a given time period. From this graph you can find other developers who are closet to a given developer.",
"component_developerCollaborationNetwork_description_node": "Node: Developer, node size and shades of color indicate developer activity.",
Expand Down
4 changes: 4 additions & 0 deletions src/locales/zh_CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"global_period": "周期",
"global_day": "天",
"global_clickToshow": "点击查看",
"component_communityOpenRankNetwork_title": "社区OpenRank网络图",
"component_communityOpenRankNetwork_description": "社区OpenRank网络图按月显示有关该项目的OpenRank。双击此网络中的一个节点,可以在右侧表格中查看该节点OpenRank详细信息。 ",
"component_communityOpenRankRacingBar_title": "社区OpenRank滚榜",
"component_communityOpenRankRacingBar_description": "社区OpenRank滚榜展示了社区中OpenRank的演化过程。",
"component_developerCollaborationNetwork_title": "开发者协作网络图",
"component_developerCollaborationNetwork_description": "开发者协作网络图展示了在给定的时间段内,开发者与开发者之间的协作关系, 用于开发者关系的追踪与挖掘。从该网络图中,可以找出与该开发者联系较为紧密的其他开发者。",
"component_developerCollaborationNetwork_description_node": "节点:一个节点表示开发者,节点大小与颜色的深浅表示开发者活跃度的大小。",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import React, { CSSProperties, forwardRef, useEffect, useRef, ForwardedRef, useImperativeHandle } from 'react';
import * as echarts from 'echarts';

import { debounce } from 'lodash-es';
import getGithubTheme from '../../../../helpers/get-github-theme';
import { getOpenRank } from '../../../../api/community';

export interface DateControllers {
update: (newDate: string) => void;
}

interface NetworkProps {
/**
* data
*/
readonly data: any;
/**
* `style` for graph container
*/
readonly style?: CSSProperties;

readonly focusedNodeID: string;

date?: string;
}

const typeMap = new Map([
['r', 'repo'],
['i', 'issue'],
['p', 'pull'],
['u', 'user'],
]);

const genName = (node: { c: string; n: { toString: () => any } }) =>
node.c == 'i' || node.c == 'p' ? `#${node.n.toString()}` : node.n.toString();

const categories = Array.from(typeMap.values());

const theme = getGithubTheme();
const DARK_TEXT_COLOR = 'rgba(230, 237, 243, 0.9)';

const generateEchartsData = (data: any, focusedNodeID: string | undefined): any => {
const generateNodes = (nodes: any[]): any => {
return nodes.map((n: any) => {
return {
id: n.id,
name: genName(n),
value: n.v,
symbolSize: Math.log(n.v + 1) * 6,
category: typeMap.get(n.c),
};
});
};
const generateEdges = (edges: any[]): any => {
if (edges.length === 0) {
return [];
}
return edges.map((e: any) => {
return {
source: e.s,
target: e.t,
value: e.w,
};
});
};
return {
nodes: generateNodes(data.nodes),
edges: generateEdges(data.links),
};
};

const getOption = (data: any, date: string | undefined) => {
return {
tooltip: {
trigger: 'item',
},
animation: true,
animationDuration: 2000,

legend: [
{
data: categories,
},
],
series: [
{
name: 'Collaborative graph',
type: 'graph',
layout: 'force',
nodes: data.nodes,
edges: data.edges,
categories: categories.map((c) => {
return { name: c };
}),
// Enable mouse zooming and translating
roam: true,
label: {
position: 'right',
show: true,
},
force: {
repulsion: 300,
// Disable the iteration animation of layout
layoutAnimation: false,
},
lineStyle: {
curveness: 0.3,
opacity: 0.2,
},
emphasis: {
focus: 'adjacency',
label: {
position: 'right',
show: true,
},
},
},
],
graphic: {
elements: [
{
type: 'text',
right: 60,
bottom: 60,
style: {
text: date,
font: 'bolder 60px monospace',
fill: theme === 'light' ? 'rgba(100, 100, 100, 0.3)' : DARK_TEXT_COLOR,
},
z: 100,
},
],
},
};
};

const Network = forwardRef(
(
{ data, style = {}, focusedNodeID, date }: NetworkProps,
forwardedRef: ForwardedRef<DateControllers>
): JSX.Element => {
const divEL = useRef(null);
let graphData = generateEchartsData(data, focusedNodeID);
let option = getOption(graphData, date);

const clearDiv = (id: string) => {
var div = document.getElementById(id);
if (div && div.hasChildNodes()) {
var children = div.childNodes;
for (var child of children) {
div.removeChild(child);
}
}
};

const addRow = (table: HTMLElement | null, texts: any[]) => {
// @ts-ignore
var tr = table.insertRow();
for (var t of texts) {
var td = tr.insertCell();
td.appendChild(document.createTextNode(t));
}
};

const update = (newDate: string) => {
getOpenRank(focusedNodeID, newDate).then((openRank) => {
let chartDOM = divEL.current;
const instance = echarts.getInstanceByDom(chartDOM as any);
if (instance) {
if (openRank == null) {
instance.setOption(
{
title: {
text: `OpenRank for ${focusedNodeID} in ${newDate} is has not been generated`,
top: 'middle',
left: 'center',
},
},
{ notMerge: true }
);
} else {
graphData = generateEchartsData(openRank, focusedNodeID);
option = getOption(graphData, newDate);
instance.setOption(option, { notMerge: true });
}
}
});
};

const setDetails = (graph: { links: any[]; nodes: any[] }, node: { r: number; i: number; id: any }) => {
clearDiv('details_table');
var table = document.getElementById('details_table');
addRow(table, ['From', 'Ratio', 'Value', 'OpenRank']);
addRow(table, ['Self', node.r, node.i, (node.r * node.i).toFixed(3)]);
var other = graph.links
.filter((l) => l.t == node.id)
.map((l) => {
var source = graph.nodes.find((n) => n.id == l.s);
return [
genName(source),
parseFloat(((1 - node.r) * l.w).toFixed(3)),
source.v,
parseFloat(((1 - node.r) * l.w * source.v).toFixed(3)),
];
})
.sort((a, b) => b[3] - a[3]);
for (var r of other) {
addRow(table, r);
}
};

useImperativeHandle(forwardedRef, () => ({
update,
}));

useEffect(() => {
let chartDOM = divEL.current;
const instance = echarts.init(chartDOM as any);

return () => {
instance.dispose();
};
}, []);

useEffect(() => {
let chartDOM = divEL.current;
const instance = echarts.getInstanceByDom(chartDOM as any);
if (instance) {
instance.setOption(option);
instance.on('dblclick', function (params) {
setDetails(
data,
// @ts-ignore
data.nodes.find((i: { id: any }) => i.id === params.data.id)
);
});
const debouncedResize = debounce(() => {
instance.resize();
}, 1000);
window.addEventListener('resize', debouncedResize);
}
}, []);

return (
<div className="hypertrons-crx-border">
<div ref={divEL} style={style}></div>
</div>
);
}
);

export default Network;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#details_table {
width: 95%;
margin: 10px;
tr:nth-child(even) {
background-color: #d6eeee;
}
th,
td {
border: 1px solid black;
text-align: center;
vertical-align: middle;
}
}

#details_title {
text-align: center;
font-size: 12px;
}

.bordered {
border: 2px solid grey;
}

#details_div {
height: 250px;
}

.scrollit {
overflow-x: hidden;
overflow-y: auto;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { render, Container } from 'react-dom';
import $ from 'jquery';

import features from '../../../../feature-manager';
import isPerceptor from '../../../../helpers/is-perceptor';
import { getRepoName, isPublicRepoWithMeta } from '../../../../helpers/get-repo-info';
import { getOpenRank } from '../../../../api/community';
import { RepoMeta, metaStore } from '../../../../api/common';
import View from './view';
import './index.scss';
import DataNotFound from '../repo-networks/DataNotFound';

const featureId = features.getFeatureID(import.meta.url);
let repoName: string;
let openRank: any;
let meta: RepoMeta;

const getData = async () => {
meta = (await metaStore.get(repoName)) as RepoMeta;
openRank = await getOpenRank(repoName, '2023-09');
};

const renderTo = (container: Container) => {
if (!openRank) {
render(<DataNotFound />, container);
return;
}
render(<View repoName={repoName} openrank={openRank} meta={meta} />, container);
};

const init = async (): Promise<void> => {
repoName = getRepoName();
await getData();
// create container
const container = document.createElement('div');
container.id = featureId;

$('#hypercrx-perceptor-slot-community-openrank-network').append(container);
renderTo(container);
};

const restore = async () => {
// Clicking another repo link in one repo will trigger a turbo:visit,
// so in a restoration visit we should be careful of the current repo.
if (repoName !== getRepoName()) {
repoName = getRepoName();
await getData();
}
// rerender the chart or it will be empty
renderTo($(`#${featureId}`)[0]);
};

features.add(featureId, {
asLongAs: [isPerceptor, isPublicRepoWithMeta],
awaitDomReady: true,
init,
restore,
});
Loading
Loading