Skip to content

Commit

Permalink
Update dashboard (bazelbuild#1672)
Browse files Browse the repository at this point in the history
Check in changes I made months before for the dashboard. The code are
already running in production for months.

Changes includes:
- Upgrade to Spring Boot 3.
- Upgrade to JDK19.
- Use VirtualThread for web requests and replace almost all the async
code with sync code.
  • Loading branch information
coeuvre authored May 16, 2023
1 parent 151e0a7 commit 035d4e2
Show file tree
Hide file tree
Showing 53 changed files with 1,193 additions and 1,216 deletions.
4 changes: 2 additions & 2 deletions dashboard/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM azul/zulu-openjdk-alpine:11
FROM azul/zulu-openjdk-alpine:19
ARG DEPENDENCY=dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java", "-cp", "/app:/app/lib/*", "build.bazel.dashboard.Application"]
ENTRYPOINT ["java", "--enable-preview", "--add-modules=jdk.incubator.concurrent", "-Djdk.tracePinnedThreads", "-cp", "/app:/app/lib/*", "build.bazel.dashboard.Application"]
32 changes: 28 additions & 4 deletions dashboard/server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>build.bazel</groupId>
Expand All @@ -15,7 +15,7 @@
<description>The server for the dashboard</description>

<properties>
<java.version>11</java.version>
<java.version>19</java.version>
<project.version>DEV</project.version>
</properties>

Expand Down Expand Up @@ -46,13 +46,12 @@
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<groupId>org.postgresql</groupId>
<artifactId>r2dbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.0.13</version>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
Expand All @@ -62,6 +61,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -98,9 +102,29 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>--enable-preview</arg>
<arg>--add-modules=jdk.incubator.concurrent</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-preview --add-modules=jdk.incubator.concurrent</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>--enable-preview --add-modules=jdk.incubator.concurrent -Djdk.tracePinnedThreads</jvmArguments>
</configuration>
</plugin>
</plugins>
</build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package build.bazel.dashboard.github.api;

import io.reactivex.rxjava3.core.Single;

public interface GithubApi {
Single<GithubApiResponse> listRepositoryIssues(ListRepositoryIssuesRequest request);
GithubApiResponse listRepositoryIssues(ListRepositoryIssuesRequest request);

Single<GithubApiResponse> listRepositoryEvents(ListRepositoryEventsRequest request);
GithubApiResponse listRepositoryEvents(ListRepositoryEventsRequest request);

Single<GithubApiResponse> listRepositoryIssueEvents(ListRepositoryIssueEventsRequest request);
GithubApiResponse listRepositoryIssueEvents(ListRepositoryIssueEventsRequest request);

Single<GithubApiResponse> fetchIssue(FetchIssueRequest request);
GithubApiResponse fetchIssue(FetchIssueRequest request);

Single<GithubApiResponse> listIssueComments(ListIssueCommentsRequest request);
GithubApiResponse listIssueComments(ListIssueCommentsRequest request);

Single<GithubApiResponse> searchIssues(SearchIssuesRequest request);
GithubApiResponse searchIssues(SearchIssuesRequest request);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package build.bazel.dashboard.github.api;

import static build.bazel.dashboard.utils.HttpHeadersUtils.getAsIntOrZero;
import static build.bazel.dashboard.utils.HttpHeadersUtils.getOrEmpty;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.Builder;
import lombok.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.reactive.function.client.ClientResponse;
import reactor.core.publisher.Mono;

import static build.bazel.dashboard.utils.HttpHeadersUtils.getAsIntOrZero;
import static build.bazel.dashboard.utils.HttpHeadersUtils.getOrEmpty;

@Builder
@Value
public class GithubApiResponse {
HttpStatus status;
HttpStatusCode status;
String etag;
RateLimit rateLimit;
JsonNode body;
Expand All @@ -37,7 +37,7 @@ public static RateLimit fromHeaders(ClientResponse.Headers headers) {
}

public static Mono<GithubApiResponse> fromClientResponse(ClientResponse clientResponse) {
HttpStatus status = clientResponse.statusCode();
var status = clientResponse.statusCode();
ClientResponse.Headers headers = clientResponse.headers();
GithubApiResponseBuilder builder =
GithubApiResponse.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package build.bazel.dashboard.github.api;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Strings;
import io.reactivex.rxjava3.core.Single;
import java.net.URI;
import java.util.Optional;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriBuilder;
import reactor.adapter.rxjava.RxJava3Adapter;

import java.net.URI;
import java.util.Optional;
import java.util.function.Function;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;

@Component
@Slf4j
Expand All @@ -32,7 +28,7 @@ public WebClientGithubApi(
}

@Override
public Single<GithubApiResponse> listRepositoryIssues(ListRepositoryIssuesRequest request) {
public GithubApiResponse listRepositoryIssues(ListRepositoryIssuesRequest request) {
log.debug("{}", request);

checkNotNull(request.getOwner());
Expand All @@ -45,13 +41,13 @@ public Single<GithubApiResponse> listRepositoryIssues(ListRepositoryIssuesReques
.pathSegment("repos", request.getOwner(), request.getRepo(), "issues")
.queryParamIfPresent("per_page", Optional.ofNullable(request.getPerPage()))
.queryParamIfPresent("page", Optional.ofNullable(request.getPage()))
.build());
.build(), "");

return exchange(spec);
}

@Override
public Single<GithubApiResponse> listRepositoryEvents(ListRepositoryEventsRequest request) {
public GithubApiResponse listRepositoryEvents(ListRepositoryEventsRequest request) {
log.debug("{}", request);

checkNotNull(request.getOwner());
Expand All @@ -64,16 +60,13 @@ public Single<GithubApiResponse> listRepositoryEvents(ListRepositoryEventsReques
.pathSegment("repos", request.getOwner(), request.getRepo(), "events")
.queryParamIfPresent("per_page", Optional.ofNullable(request.getPerPage()))
.queryParamIfPresent("page", Optional.ofNullable(request.getPage()))
.build());
if (!Strings.isNullOrEmpty(request.getEtag())) {
spec.ifNoneMatch(request.getEtag());
}
.build(), request.getEtag());

return exchange(spec);
}

@Override
public Single<GithubApiResponse> listRepositoryIssueEvents(ListRepositoryIssueEventsRequest request) {
public GithubApiResponse listRepositoryIssueEvents(ListRepositoryIssueEventsRequest request) {
log.debug("{}", request);

checkNotNull(request.getOwner());
Expand All @@ -86,16 +79,13 @@ public Single<GithubApiResponse> listRepositoryIssueEvents(ListRepositoryIssueEv
.pathSegment("repos", request.getOwner(), request.getRepo(), "issues", "events")
.queryParamIfPresent("per_page", Optional.ofNullable(request.getPerPage()))
.queryParamIfPresent("page", Optional.ofNullable(request.getPage()))
.build());
if (!Strings.isNullOrEmpty(request.getEtag())) {
spec.ifNoneMatch(request.getEtag());
}
.build(), request.getEtag());

return exchange(spec);
}

@Override
public Single<GithubApiResponse> fetchIssue(FetchIssueRequest request) {
public GithubApiResponse fetchIssue(FetchIssueRequest request) {
log.debug("{}", request);

checkNotNull(request.getOwner());
Expand All @@ -111,16 +101,13 @@ public Single<GithubApiResponse> fetchIssue(FetchIssueRequest request) {
request.getRepo(),
"issues",
String.valueOf(request.getIssueNumber()))
.build());
if (!Strings.isNullOrEmpty(request.getEtag())) {
spec.ifNoneMatch(request.getEtag());
}
.build(), request.getEtag());

return exchange(spec);
}

@Override
public Single<GithubApiResponse> listIssueComments(ListIssueCommentsRequest request) {
public GithubApiResponse listIssueComments(ListIssueCommentsRequest request) {
log.debug("{}", request);

checkNotNull(request.getOwner());
Expand All @@ -130,19 +117,22 @@ public Single<GithubApiResponse> listIssueComments(ListIssueCommentsRequest requ
get(
uriBuilder ->
uriBuilder
.pathSegment("repos", request.getOwner(), request.getRepo(), "issues", Integer.toString(request.getIssueNumber()), "comments")
.pathSegment(
"repos",
request.getOwner(),
request.getRepo(),
"issues",
Integer.toString(request.getIssueNumber()),
"comments")
.queryParamIfPresent("per_page", Optional.ofNullable(request.getPerPage()))
.queryParamIfPresent("page", Optional.ofNullable(request.getPage()))
.build());
if (!Strings.isNullOrEmpty(request.getEtag())) {
spec.ifNoneMatch(request.getEtag());
}
.build(), request.getEtag());

return exchange(spec);
}

@Override
public Single<GithubApiResponse> searchIssues(SearchIssuesRequest request) {
public GithubApiResponse searchIssues(SearchIssuesRequest request) {
log.debug("{}", request);

checkNotNull(request.getQ());
Expand All @@ -157,26 +147,33 @@ public Single<GithubApiResponse> searchIssues(SearchIssuesRequest request) {
.queryParamIfPresent("order", Optional.ofNullable(request.getOrder()))
.queryParamIfPresent("per_page", Optional.ofNullable(request.getPerPage()))
.queryParamIfPresent("page", Optional.ofNullable(request.getPage()))
.build(request.getQ()));
.build(request.getQ()), "");

return exchange(spec);
}

private WebClient.RequestHeadersSpec<?> get(Function<UriBuilder, URI> uriFunction) {
private WebClient.RequestHeadersSpec<?> get(Function<UriBuilder, URI> uriFunction, String etag) {
WebClient.RequestHeadersSpec<?> spec =
webClient
.get()
.uri(uriBuilder -> uriFunction.apply(uriBuilder.scheme(SCHEME).host(HOST)))
.header("Accept", "application/vnd.github.v3+json");

if (!Strings.isNullOrEmpty(etag)) {
spec.ifNoneMatch(etag);
}

if (!Strings.isNullOrEmpty(accessToken)) {
spec = spec.header("Authorization", "token " + accessToken);
}

return spec;
}

private Single<GithubApiResponse> exchange(WebClient.RequestHeadersSpec<?> spec) {
return RxJava3Adapter.monoToSingle(spec.exchangeToMono(GithubApiResponse::fromClientResponse));
private GithubApiResponse exchange(WebClient.RequestHeadersSpec<?> spec) {
return spec.exchangeToMono(response -> {
log.debug("{} {}", response.statusCode(), response.headers().asHttpHeaders().toSingleValueMap());
return GithubApiResponse.fromClientResponse(response);
}).block();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Builder;
import lombok.Value;

import javax.annotation.Nullable;
import java.time.Instant;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Value;

@Builder
@Value
Expand Down Expand Up @@ -42,7 +41,7 @@ public Data parseData(ObjectMapper objectMapper) throws JsonProcessingException

@Builder
@Value
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class Data {
int number;
User user;
Expand All @@ -56,15 +55,15 @@ public static class Data {

@Builder
@Value
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class User {
String login;
String avatarUrl;
}

@Builder
@Value
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class Label {
String name;
String description;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package build.bazel.dashboard.github.issue;

import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import java.util.Optional;

public interface GithubIssueRepo {

Completable save(GithubIssue githubIssue);
void save(GithubIssue githubIssue);

Completable delete(String owner, String repo, int issueNumber);
void delete(String owner, String repo, int issueNumber);

Maybe<GithubIssue> findOne(String owner, String repo, int issueNumber);
Optional<GithubIssue> findOne(String owner, String repo, int issueNumber);

Observable<GithubIssue> list();

Single<Integer> findMaxIssueNumber(String owner, String repo);
Integer findMaxIssueNumber(String owner, String repo);
}
Loading

0 comments on commit 035d4e2

Please sign in to comment.