|
| 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 | +} |
0 commit comments