| package build.bazel.dashboard.github.teamtable; |
| |
| import static build.bazel.dashboard.github.teamtable.GithubTeamTable.Cell; |
| |
| import build.bazel.dashboard.github.GithubUtils; |
| import build.bazel.dashboard.github.issuequery.GithubIssueQueryExecutor; |
| import build.bazel.dashboard.github.repo.GithubRepoService; |
| import build.bazel.dashboard.github.team.GithubTeam; |
| import build.bazel.dashboard.github.team.GithubTeamService; |
| import build.bazel.dashboard.github.teamtable.GithubTeamTable.Row; |
| import build.bazel.dashboard.github.teamtable.GithubTeamTableRepo.GithubTeamTableData; |
| import com.github.benmanes.caffeine.cache.AsyncCache; |
| import com.github.benmanes.caffeine.cache.Caffeine; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import java.time.Duration; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.StructuredTaskScope.Subtask; |
| import java.util.stream.Collectors; |
| import java.util.concurrent.StructuredTaskScope; |
| import lombok.Builder; |
| import lombok.RequiredArgsConstructor; |
| import lombok.Value; |
| import lombok.extern.slf4j.Slf4j; |
| import org.springframework.stereotype.Service; |
| |
| @Service |
| @Slf4j |
| @RequiredArgsConstructor |
| public class GithubTeamTableService { |
| private final GithubTeamTableRepo githubTeamTableRepo; |
| private final GithubRepoService githubRepoService; |
| private final GithubTeamService githubTeamService; |
| private final GithubIssueQueryExecutor githubIssueQueryExecutor; |
| |
| @Builder |
| @Value |
| static class GithubTeamTableCacheKey { |
| String owner; |
| String repo; |
| String tableId; |
| } |
| |
| private final AsyncCache<GithubTeamTableCacheKey, GithubTeamTable> tableCache = |
| Caffeine.newBuilder() |
| .expireAfterWrite(Duration.ofSeconds(1)) |
| .executor(Executors.newVirtualThreadPerTaskExecutor()) |
| .buildAsync(); |
| |
| public GithubTeamTable findOne(String owner, String repo, String tableId) { |
| try { |
| return tableCache |
| .get( |
| GithubTeamTableCacheKey.builder().owner(owner).repo(repo).tableId(tableId).build(), |
| (key) -> loadOne(key.getOwner(), key.getRepo(), key.getTableId())) |
| .get(); |
| } catch (InterruptedException | ExecutionException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| GithubTeamTable loadOne(String owner, String repo, String tableId) { |
| return githubRepoService |
| .findOne(owner, repo) |
| .flatMap( |
| githubRepo -> |
| githubTeamTableRepo |
| .findOne(githubRepo.getOwner(), githubRepo.getRepo(), tableId) |
| .map( |
| table -> { |
| var noneTeamOwner = ""; |
| if (githubRepo.getActionOwner() != null) { |
| noneTeamOwner = githubRepo.getActionOwner(); |
| } |
| var teams = findAllTeams(owner, repo, noneTeamOwner); |
| return fetchTable(teams, table); |
| })) |
| .orElseGet(() -> GithubTeamTable.buildNone(owner, repo, tableId, "")); |
| } |
| |
| private GithubTeamTable fetchTable(List<GithubTeam> teams, GithubTeamTableData table) { |
| var rows = Lists.<Row>newArrayListWithExpectedSize(teams.size()); |
| try (var scope = new StructuredTaskScope<>()) { |
| var subtasks = ImmutableList.<Subtask<Row>>builderWithExpectedSize(teams.size()); |
| for (var team : teams) { |
| var subtask = scope.fork(() -> fetchRow(teams, team, table)); |
| subtasks.add(subtask); |
| } |
| |
| try { |
| scope.join(); |
| |
| for (var subtask : subtasks.build()) { |
| rows.add(subtask.get()); |
| } |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| rows.sort(Comparator.comparing(row -> row.getTeam().getName())); |
| return GithubTeamTable.builder() |
| .owner(table.getOwner()) |
| .repo(table.getRepo()) |
| .id(table.getId()) |
| .name(table.getName()) |
| .headers( |
| table.getHeaders().stream() |
| .map( |
| header -> |
| GithubTeamTable.Header.builder() |
| .id(header.getId()) |
| .name(header.getName()) |
| .build()) |
| .collect(Collectors.toList())) |
| .rows(rows) |
| .build(); |
| } |
| |
| record CellEntry(String headerId, Cell cell) {} |
| |
| private Row fetchRow(List<GithubTeam> teams, GithubTeam team, GithubTeamTableData table) { |
| var cells = new HashMap<String, Cell>(); |
| try (var scope = new StructuredTaskScope<>()) { |
| var subtasks = |
| ImmutableList.<Subtask<CellEntry>>builderWithExpectedSize(table.getHeaders().size()); |
| for (var header : table.getHeaders()) { |
| String query = interceptQuery(teams, team, header.getQuery()); |
| var subtask = |
| scope.fork( |
| () -> { |
| var count = |
| githubIssueQueryExecutor.fetchQueryResultCount( |
| team.getOwner(), team.getRepo(), query); |
| return new CellEntry( |
| header.getId(), |
| Cell.builder() |
| .url( |
| GithubUtils.buildIssueQueryUrl( |
| team.getOwner(), team.getRepo(), query)) |
| .count(count) |
| .build()); |
| }); |
| subtasks.add(subtask); |
| } |
| try { |
| scope.join(); |
| |
| for (var future : subtasks.build()) { |
| var entry = future.get(); |
| cells.put(entry.headerId, entry.cell); |
| } |
| } catch (InterruptedException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| return Row.builder().team(GithubTeamTable.Team.create(team)).cells(cells).build(); |
| } |
| |
| private String interceptQuery(List<GithubTeam> teams, GithubTeam team, String query) { |
| String teamQuery; |
| if (team.isNone()) { |
| teamQuery = |
| teams.stream() |
| .map(GithubTeam::getLabel) |
| .filter(label -> !label.equals("")) |
| .map(label -> "-label:" + label) |
| .collect(Collectors.joining(" ")); |
| } else { |
| teamQuery = "label:" + team.getLabel(); |
| } |
| |
| return String.format("%s %s", query, teamQuery); |
| } |
| |
| private ImmutableList<GithubTeam> findAllTeams(String owner, String repo, String noneTeamOwner) { |
| var teams = githubTeamService.findAll(owner, repo); |
| return ImmutableList.<GithubTeam>builderWithExpectedSize(teams.size() + 1) |
| .addAll(teams) |
| .add(GithubTeam.buildNone(owner, repo, noneTeamOwner)) |
| .build(); |
| } |
| } |