NaisConsoleClient.java

  1. package no.nav.data.team.naisteam;

  2. import com.github.benmanes.caffeine.cache.Caffeine;
  3. import com.github.benmanes.caffeine.cache.LoadingCache;
  4. import lombok.extern.slf4j.Slf4j;
  5. import no.nav.data.common.utils.MetricUtils;
  6. import no.nav.data.common.utils.StreamUtils;
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  9. import org.springframework.core.ParameterizedTypeReference;
  10. import org.springframework.graphql.client.GraphQlClient;
  11. import org.springframework.graphql.client.HttpGraphQlClient;
  12. import org.springframework.http.HttpHeaders;
  13. import org.springframework.stereotype.Service;
  14. import org.springframework.web.reactive.function.client.WebClient;

  15. import java.time.Duration;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. import java.util.Optional;

  19. import static java.time.temporal.ChronoUnit.MINUTES;
  20. import static java.util.Collections.singletonList;
  21. import static java.util.Comparator.comparing;
  22. import static no.nav.data.common.utils.StartsWithComparator.startsWith;
  23. import static no.nav.data.common.utils.StreamUtils.safeStream;
  24. import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;

  25. @Slf4j
  26. @Service
  27. @EnableConfigurationProperties(NaisConsoleProperties.class)
  28. public class NaisConsoleClient {

  29.     private final GraphQlClient client;

  30.     private final LoadingCache<String, List<NaisTeam>> allTeamsCache;
  31.     private final LoadingCache<String, NaisTeam> teamCache;

  32.     public NaisConsoleClient(WebClient.Builder builder, NaisConsoleProperties consoleProperties) {
  33.         client = HttpGraphQlClient.builder(builder)
  34.                 .webClient(client -> {
  35.                     client.baseUrl(consoleProperties.baseUrl());
  36.                     client.defaultHeaders(headers -> {
  37.                         headers.put(HttpHeaders.AUTHORIZATION, singletonList("Bearer " + consoleProperties.auth().token()));
  38.                         headers.put(HttpHeaders.CONTENT_TYPE, singletonList("application/json"));

  39.                     });
  40.                 })
  41.                 .build();

  42.         this.allTeamsCache = Caffeine.newBuilder().recordStats()
  43.                 .expireAfterWrite(Duration.of(10, MINUTES))
  44.                 .maximumSize(1)
  45.                 .build(k -> fetchAllNaisTeams());

  46.         this.teamCache = Caffeine.newBuilder()
  47.                 .recordStats()
  48.                 .expireAfterWrite(Duration.of(10, MINUTES))
  49.                 .maximumSize(100)
  50.                 .build(this::fetchNaisTeam);

  51.         MetricUtils.register("NaisConsoleTeamsCache", allTeamsCache);
  52.         MetricUtils.register("NaisConsoleTeamCache", teamCache);
  53.     }

  54.     public List<NaisTeam> getAllNaisTeams() {
  55.         List<NaisTeam> naisTeams = allTeamsCache.get("singleton");
  56.         return safeStream(naisTeams)
  57.                 .distinct()
  58.                 .filter(team -> StringUtils.isNotBlank(team.slug()))
  59.                 .sorted(comparing(NaisTeam::slug))
  60.                 .toList();
  61.     }

  62.     public Optional<NaisTeam> getNaisteam(String teamId) {
  63.         return Optional.ofNullable(teamCache.get(teamId));
  64.     }

  65.     public List<NaisTeam> searchForNaisTeams(String name) {
  66.         var teams = StreamUtils.filter(getAllNaisTeams(), team -> containsIgnoreCase(team.slug(), name));
  67.         teams.sort(comparing(NaisTeam::slug, startsWith(name)));
  68.         return teams;
  69.     }

  70.     public boolean naisTeamExists(String teamId) {
  71.         return getAllNaisTeams().stream().anyMatch(team -> team.slug().equals(teamId));
  72.     }

  73.     private List<NaisTeam> fetchAllNaisTeams() {
  74.         List<NaisTeam> allTeams = new ArrayList<>();
  75.         String cursor = "";
  76.         boolean hasNextPage = true;

  77.         while (hasNextPage) {
  78.             var response = client.document(NaisTeam.TEAMS_QUERY)
  79.                 .variable("first", 100)
  80.                 .variable("after", cursor)
  81.                 .execute()
  82.                 .block();

  83.             if (response != null && response.isValid()) {
  84.                 var teams = response
  85.                     .field("teams.nodes")
  86.                     .toEntity(new ParameterizedTypeReference<List<NaisTeam>>() {});

  87.                 if (teams != null) {
  88.                     allTeams.addAll(teams);
  89.                 }

  90.                 cursor = response.field("teams.pageInfo.endCursor").toEntity(String.class);
  91.                 hasNextPage = Boolean.TRUE.equals(response.field("teams.pageInfo.hasNextPage").toEntity(Boolean.class));
  92.             } else {
  93.                 log.error("fetchAllNaisTeams: Received a null response from the GraphQL query.");
  94.                 break;
  95.             }
  96.         }

  97.         log.info("fetchAllNaisTeams: Fetched {} nais-teams in total.", allTeams.size());
  98.         return allTeams;
  99.     }

  100.     private NaisTeam fetchNaisTeam(String slug) {
  101.         var response = client.document(NaisTeam.TEAM_QUERY)
  102.                 .variable("slug", slug)
  103.                 .execute()
  104.                 .block();

  105.         return response != null && response.isValid() ? response.field("team").toEntity(NaisTeam.class) : null;
  106.     }
  107. }