Skip to content

Commit cecece7

Browse files
✨ Add Organization projects (#41)
1 parent 3cc9e5a commit cecece7

8 files changed

Lines changed: 246 additions & 15 deletions

File tree

src/main/java/zenika/oss/stats/config/GitHubClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,10 @@ List<GitHubMember> getOrganizationMembers(@PathParam("organizationName") String
4444
@ClientHeaderParam(name = "Authorization", value = "{zenika.oss.stats.config.GitHubClient.prepareToken}")
4545
@Path("/users/{login}/repos")
4646
List<GitHubProject> getReposForAnUser(@PathParam("login") String login);
47+
48+
@GET
49+
@ClientHeaderParam(name = "Authorization", value = "{zenika.oss.stats.config.GitHubClient.prepareToken}")
50+
@Path("/orgs/{organizationName}/repos")
51+
List<GitHubProject> getOrganizationProjects(@PathParam("organizationName") String organizationName,
52+
@QueryParam("per_page") int perPage);
4753
}

src/main/java/zenika/oss/stats/ressources/ProjectsRessources.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ public Response getAllProjectsForAnUser(@PathParam("memberId") String memberId)
2828
return Response.ok("\uD83D\uDEA7 Not implemented yet").build();
2929
}
3030

31+
@GET
32+
@Path("/organization")
33+
public Response getOrganizationProjects() throws DatabaseException {
34+
return Response.ok(firestoreServices.getAllProjects().stream()
35+
.filter(p -> "GitHub Organization".equals(p.getSource()))
36+
.collect(java.util.stream.Collectors.toList())).build();
37+
}
38+
3139
}

src/main/java/zenika/oss/stats/services/FirestoreServices.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ public void deleteAllGitLabProjects() throws DatabaseException {
198198
deleteProjectsBySource("GitLab");
199199
}
200200

201+
/**
202+
* Remove all GitHub Organization projects from the Firestore database.
203+
*
204+
* @throws DatabaseException exception
205+
*/
206+
@CacheInvalidateAll(cacheName = "projects-cache")
207+
public void deleteAllGitHubOrganizationProjects() throws DatabaseException {
208+
deleteProjectsBySource("GitHub Organization");
209+
}
210+
201211
private void deleteProjectsBySource(String source) throws DatabaseException {
202212
CollectionReference zProjects = firestore.collection(FirestoreCollections.PROJECTS.value);
203213
Query query = zProjects.whereEqualTo("source", source);

src/main/java/zenika/oss/stats/services/GitHubServices.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,18 @@ public List<CustomStatsUser> getContributionsForTheCurrentYearAndAllTheOrganizat
243243
public List<GitHubMember> getZenikaOpenSourceMembers() {
244244
return gitHubClient.getOrganizationMembers(organizationName, NB_MEMBERS_PAR_PAGE);
245245
}
246+
247+
/**
248+
* Get projects for the current organization.
249+
*
250+
* @param organizationName The name of the GitHub organization to fetch projects
251+
* from.
252+
* @return a List of GitHubProject
253+
*/
254+
public List<GitHubProject> getOrganizationProjects(String organizationName) {
255+
var repos = gitHubClient.getOrganizationProjects(organizationName, 100);
256+
return repos.stream()
257+
.filter(repo -> !repo.isFork() && !repo.isArchived())
258+
.collect(Collectors.toList());
259+
}
246260
}

src/main/java/zenika/oss/stats/ui/JavelitDashboard.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
import zenika.oss.stats.ui.tabs.ContributionsTab;
1010
import zenika.oss.stats.ui.tabs.MembersTab;
1111
import zenika.oss.stats.ui.tabs.ProjectsTab;
12+
import zenika.oss.stats.ui.tabs.OrganizationProjectsTab;
1213
import zenika.oss.stats.ui.tabs.StatsTab;
1314

1415
import java.util.List;
1516

1617
import org.eclipse.microprofile.config.inject.ConfigProperty;
1718

18-
1919
@ApplicationScoped
2020
public class JavelitDashboard {
2121

@@ -28,6 +28,9 @@ public class JavelitDashboard {
2828
@Inject
2929
ProjectsTab projectsTab;
3030

31+
@Inject
32+
OrganizationProjectsTab organizationProjectsTab;
33+
3134
@Inject
3235
ContributionsTab contributionsTab;
3336

@@ -42,10 +45,12 @@ void onStart(@Observes StartupEvent ev) {
4245
"This dashboard get publics datas from GitHub (and GitLab as soon)")
4346
.use();
4447

45-
var tabs = Jt.tabs(List.of("🙋 Members", "🚀 Projects", "📊 Contributions", "📈 Stats")).use();
48+
var tabs = Jt.tabs(List.of("🙋 Members", "🚀 Members Projects", "🏢 Zenika Open Source Projects",
49+
"📊 Contributions", "📈 Stats")).use();
4650

4751
membersTab.render(tabs.tab("🙋 Members"));
48-
projectsTab.render(tabs.tab("🚀 Projects"));
52+
projectsTab.render(tabs.tab("🚀 Members Projects"));
53+
organizationProjectsTab.render(tabs.tab("🏢 Zenika Open Source Projects"));
4954
contributionsTab.render(tabs.tab("📊 Contributions"));
5055
statsTab.render(tabs.tab("📈 Stats"));
5156

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package zenika.oss.stats.ui.tabs;
2+
3+
import io.javelit.core.Jt;
4+
import io.javelit.core.JtContainer;
5+
import jakarta.enterprise.context.ApplicationScoped;
6+
import jakarta.inject.Inject;
7+
import org.jboss.logging.Logger;
8+
import zenika.oss.stats.beans.Project;
9+
import zenika.oss.stats.beans.github.GitHubProject;
10+
import zenika.oss.stats.exception.DatabaseException;
11+
import zenika.oss.stats.services.FirestoreServices;
12+
import zenika.oss.stats.services.GitHubServices;
13+
import org.eclipse.microprofile.config.inject.ConfigProperty;
14+
15+
import java.util.Comparator;
16+
import java.util.List;
17+
import java.util.stream.Collectors;
18+
19+
@ApplicationScoped
20+
public class OrganizationProjectsTab {
21+
22+
private static final Logger LOG = Logger.getLogger(OrganizationProjectsTab.class);
23+
24+
@Inject
25+
GitHubServices gitHubServices;
26+
27+
@Inject
28+
FirestoreServices firestoreServices;
29+
30+
@ConfigProperty(name = "organization.name")
31+
String organizationName;
32+
33+
private String projectSearchTerm = "";
34+
private String projectSortColumn = "Stars";
35+
private boolean projectSortAscending = false;
36+
37+
public void render(JtContainer projectsTab) {
38+
try {
39+
List<Project> allProjects = firestoreServices.getAllProjects();
40+
List<Project> organizationProjects = allProjects.stream()
41+
.filter(p -> "GitHub Organization".equals(p.getSource()))
42+
.collect(Collectors.toList());
43+
44+
var columns = Jt.columns(2).key("org_projects_columns").use(projectsTab);
45+
Jt.subheader("Organization Projects (" + organizationProjects.size() + ")").use(columns.col(0));
46+
47+
if (Jt.button("🚀 Sync " + organizationName + " Organization Projects").use(columns.col(1))) {
48+
try {
49+
firestoreServices.deleteAllGitHubOrganizationProjects();
50+
List<GitHubProject> gitHubProjects = gitHubServices.getOrganizationProjects(organizationName);
51+
int totalProjects = 0;
52+
for (GitHubProject project : gitHubProjects) {
53+
project.setSource("GitHub Organization");
54+
firestoreServices.createProject(project);
55+
totalProjects++;
56+
}
57+
Jt.success("Successfully synced " + totalProjects + " projects for " + organizationName
58+
+ " organization!")
59+
.use(projectsTab);
60+
} catch (DatabaseException e) {
61+
Jt.error("Error syncing organization projects: " + e.getMessage()).use(projectsTab);
62+
}
63+
}
64+
65+
if (!organizationProjects.isEmpty()) {
66+
// Search Bar
67+
var searchRow = Jt.columns(2).key("org_projects_search").use(projectsTab);
68+
projectSearchTerm = Jt.textInput("Search Projects").key("org_projects_search_input")
69+
.value(projectSearchTerm).use(searchRow.col(0));
70+
71+
List<Project> filteredProjects = organizationProjects.stream()
72+
.filter(p -> matchesProject(p, projectSearchTerm))
73+
.collect(Collectors.toList());
74+
75+
// Sort
76+
sortProjects(filteredProjects);
77+
78+
record ProjectDisplay(String id, String name, String fullName, String url, Long stars, Long forks,
79+
String source) {
80+
}
81+
List<ProjectDisplay> rows = filteredProjects.stream()
82+
.map(p -> new ProjectDisplay(p.getId(), p.getName(), p.getFull_name(), p.getHtml_url(),
83+
p.getWatchers_count(), p.getForks(), p.getSource()))
84+
.collect(Collectors.toList());
85+
86+
// Custom Header with Sort Buttons
87+
var header = Jt.columns(6).key("org_projects_header").use(projectsTab);
88+
89+
if (Jt.button(getSortLabel("Name")).key("org_sort_name").use(header.col(0))) {
90+
toggleSort("Name");
91+
}
92+
Jt.text("Full Name").use(header.col(1));
93+
Jt.text("URL").use(header.col(2));
94+
if (Jt.button(getSortLabel("Stars")).key("org_sort_stars").use(header.col(3))) {
95+
toggleSort("Stars");
96+
}
97+
if (Jt.button(getSortLabel("Forks")).key("org_sort_forks").use(header.col(4))) {
98+
toggleSort("Forks");
99+
}
100+
Jt.text("Source").use(header.col(5));
101+
102+
// Custom Table Rows
103+
for (ProjectDisplay p : rows) {
104+
var row = Jt.columns(6).key("org_project_row_" + p.id()).use(projectsTab);
105+
Jt.text(p.name()).use(row.col(0));
106+
Jt.text(p.fullName()).use(row.col(1));
107+
108+
String linkMarkdown = "<a href=\"" + p.url() + "\" target=\"_blank\" rel=\"noopener noreferrer\">"
109+
+ p.url() + "</a>";
110+
Jt.markdown(linkMarkdown).use(row.col(2));
111+
112+
Jt.text(String.valueOf(p.stars())).use(row.col(3));
113+
Jt.text(String.valueOf(p.forks())).use(row.col(4));
114+
Jt.text(p.source() != null ? p.source() : "").use(row.col(5));
115+
}
116+
117+
} else {
118+
Jt.text("no data available").use(projectsTab);
119+
}
120+
121+
} catch (Exception e) {
122+
Jt.warning("Could not load current projects: " + e.getMessage()).use(projectsTab);
123+
LOG.error("Could not load current projects", e);
124+
}
125+
}
126+
127+
private boolean matchesProject(Project p, String term) {
128+
if (term == null || term.isBlank())
129+
return true;
130+
String lowerTerm = term.toLowerCase();
131+
return (p.getName() != null && p.getName().toLowerCase().contains(lowerTerm)) ||
132+
(p.getFull_name() != null && p.getFull_name().toLowerCase().contains(lowerTerm)) ||
133+
(p.getHtml_url() != null && p.getHtml_url().toLowerCase().contains(lowerTerm)) ||
134+
(p.getSource() != null && p.getSource().toLowerCase().contains(lowerTerm));
135+
}
136+
137+
private void toggleSort(String column) {
138+
if (projectSortColumn.equals(column)) {
139+
projectSortAscending = !projectSortAscending;
140+
} else {
141+
projectSortColumn = column;
142+
projectSortAscending = true;
143+
}
144+
}
145+
146+
private String getSortLabel(String column) {
147+
if (projectSortColumn.equals(column)) {
148+
return column + (projectSortAscending ? " ▲" : " ▼");
149+
}
150+
return column;
151+
}
152+
153+
private void sortProjects(List<Project> projects) {
154+
Comparator<Project> comparator = switch (projectSortColumn) {
155+
case "Name" -> Comparator.comparing(p -> p.getName() != null ? p.getName().toLowerCase() : "");
156+
case "Stars" -> Comparator.comparing(p -> p.getWatchers_count() != null ? p.getWatchers_count() : 0L);
157+
case "Forks" -> Comparator.comparing(p -> p.getForks() != null ? p.getForks() : 0L);
158+
default -> Comparator.comparing(p -> p.getName() != null ? p.getName().toLowerCase() : "");
159+
};
160+
161+
if (!projectSortAscending) {
162+
comparator = comparator.reversed();
163+
}
164+
projects.sort(comparator);
165+
}
166+
}

src/main/java/zenika/oss/stats/ui/tabs/ProjectsTab.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ public class ProjectsTab {
3939
public void render(JtContainer projectsTab) {
4040
try {
4141
List<Project> allProjects = firestoreServices.getAllProjects();
42+
List<Project> memberProjects = allProjects.stream()
43+
.filter(p -> !"GitHub Organization".equals(p.getSource()))
44+
.collect(Collectors.toList());
4245

4346
var columns = Jt.columns(2).key("projects_columns").use(projectsTab);
44-
Jt.subheader("User Projects (" + allProjects.size() + ")").use(columns.col(0));
47+
Jt.subheader("Members Projects (" + memberProjects.size() + ")").use(columns.col(0));
4548

4649
if (Jt.button("🚀 Sync GitHub Personal Projects").use(columns.col(1))) {
4750
try {
@@ -91,12 +94,13 @@ public void render(JtContainer projectsTab) {
9194
}
9295
}
9396

94-
if (!allProjects.isEmpty()) {
97+
if (!memberProjects.isEmpty()) {
9598
// Search Bar
96-
var searchRow = Jt.columns(2).use(projectsTab);
97-
projectSearchTerm = Jt.textInput("Search Projects").value(projectSearchTerm).use(searchRow.col(0));
99+
var searchRow = Jt.columns(2).key("members_projects_search").use(projectsTab);
100+
projectSearchTerm = Jt.textInput("Search Projects").key("members_projects_search_input")
101+
.value(projectSearchTerm).use(searchRow.col(0));
98102

99-
List<Project> filteredProjects = allProjects.stream()
103+
List<Project> filteredProjects = memberProjects.stream()
100104
.filter(p -> matchesProject(p, projectSearchTerm))
101105
.collect(Collectors.toList());
102106

@@ -114,15 +118,15 @@ record ProjectDisplay(String id, String name, String fullName, String url, Long
114118
// Custom Header with Sort Buttons
115119
var header = Jt.columns(6).key("projects_header").use(projectsTab);
116120

117-
if (Jt.button(getSortLabel("Name")).use(header.col(0))) {
121+
if (Jt.button(getSortLabel("Name")).key("members_sort_name").use(header.col(0))) {
118122
toggleSort("Name");
119123
}
120124
Jt.text("Full Name").use(header.col(1));
121125
Jt.text("URL").use(header.col(2));
122-
if (Jt.button(getSortLabel("Stars")).use(header.col(3))) {
126+
if (Jt.button(getSortLabel("Stars")).key("members_sort_stars").use(header.col(3))) {
123127
toggleSort("Stars");
124128
}
125-
if (Jt.button(getSortLabel("Forks")).use(header.col(4))) {
129+
if (Jt.button(getSortLabel("Forks")).key("members_sort_forks").use(header.col(4))) {
126130
toggleSort("Forks");
127131
}
128132
Jt.text("Source").use(header.col(5));

src/main/java/zenika/oss/stats/ui/tabs/StatsTab.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,37 @@ public void render(JtContainer statsTab) {
5959
List<zenika.oss.stats.beans.Project> allProjects = firestoreServices.getAllProjects();
6060

6161
if (!allProjects.isEmpty()) {
62-
Jt.subheader("\uD83C\uDFC6 Top 3 Projects by Stars").use(statsTab);
62+
Jt.subheader("\uD83C\uDFC6 Top 3 Community Projects by Stars").use(statsTab);
6363

6464
record ProjectDisplay(String name, String fullName, String url, Long stars, Long forks) {
6565
}
6666

67-
List<ProjectDisplay> topProjects = allProjects.stream()
68-
.sorted((p1, p2) -> Long.compare(p2.getWatchers_count(), p1.getWatchers_count()))
67+
List<ProjectDisplay> topCommunityProjects = allProjects.stream()
68+
.filter(p -> !"GitHub Organization".equals(p.getSource()))
69+
.sorted((p1, p2) -> Long.compare(
70+
p2.getWatchers_count() != null ? p2.getWatchers_count() : 0L,
71+
p1.getWatchers_count() != null ? p1.getWatchers_count() : 0L))
6972
.limit(3)
7073
.map(p -> new ProjectDisplay(p.getName(), p.getFull_name(), p.getHtml_url(),
7174
p.getWatchers_count(), p.getForks()))
7275
.collect(Collectors.toList());
7376

74-
Jt.table(topProjects).use(statsTab);
77+
Jt.table(topCommunityProjects).key("top_community_projects_table").use(statsTab);
78+
79+
List<ProjectDisplay> topOrgProjects = allProjects.stream()
80+
.filter(p -> "GitHub Organization".equals(p.getSource()))
81+
.sorted((p1, p2) -> Long.compare(
82+
p2.getWatchers_count() != null ? p2.getWatchers_count() : 0L,
83+
p1.getWatchers_count() != null ? p1.getWatchers_count() : 0L))
84+
.limit(3)
85+
.map(p -> new ProjectDisplay(p.getName(), p.getFull_name(), p.getHtml_url(),
86+
p.getWatchers_count(), p.getForks()))
87+
.collect(Collectors.toList());
88+
89+
if (!topOrgProjects.isEmpty()) {
90+
Jt.subheader("\uD83C\uDFC6 Top 3 Zenika Open Source Projects by Stars").use(statsTab);
91+
Jt.table(topOrgProjects).key("top_org_projects_table").use(statsTab);
92+
}
7593
}
7694
} catch (Exception e) {
7795
Jt.warning("Could not load top projects: " + e.getMessage()).use(statsTab);

0 commit comments

Comments
 (0)