Skip to content

Commit

Permalink
chore: Merge remote-tracking branch 'origin/summit-2023'
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Czeladka committed Nov 27, 2023
2 parents e1b333f + d689d42 commit f142a48
Show file tree
Hide file tree
Showing 33 changed files with 1,449 additions and 512 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/publish-summit-2023.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,51 @@ jobs:
${{ env.PRIVATE_DOCKER_REGISTRY_URL }}/${{ env.APP_NAME }}:${{ env.ARTIFACT_VERSION }}

publish-voting-ledger-follower-app:
runs-on: self-hosted
env:
APP_NAME: voting-ledger-follower-app
needs: build-version
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Execute Gradle build
working-directory: backend-services/${{ env.APP_NAME }}
run: ./gradlew bootJar

- name: Private Docker Hub Login
uses: docker/login-action@v2
with:
registry: ${{ env.PRIVATE_DOCKER_REGISTRY_URL }}
username: ${{ env.PRIVATE_DOCKER_REGISTRY_USER }}
password: ${{ env.PRIVATE_DOCKER_REGISTRY_PASS }}

- name: Public Docker Hub Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and Push docker image
uses: docker/build-push-action@v4
env:
ARTIFACT_VERSION: ${{needs.build-version.outputs.ARTIFACT_VERSION}}
with:
context: backend-services/${{ env.APP_NAME }}
push: true
tags: |
${{ env.PRIVATE_DOCKER_REGISTRY_URL }}/${{ env.APP_NAME }}:${{ env.ARTIFACT_VERSION }}

publish-ui-summit-2023:
runs-on: self-hosted
env:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ jpb-settings.xml

application-prod.properties
bin
votes.csv
*.csv
.env
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public class ChainFollowerClient {
@Value("${ledger.follower.app.base.url}")
private String ledgerFollowerBaseUrl;

public Either<Problem, L1CategoryResults> getVotingResults(String eventId,
String categoryId,
String tallyName
public Either<Problem, L1CategoryResults> getVotingResultsPerCategory(String eventId,
String categoryId,
String tallyName
) {
var url = String.format("%s/api/tally/voting-results/{eventId}/{categoryId}/{tallyName}", ledgerFollowerBaseUrl);

Expand All @@ -48,6 +48,22 @@ public Either<Problem, L1CategoryResults> getVotingResults(String eventId,
}
}

public Either<Problem, List<L1CategoryResults>> getVotingResultsForAllCategories(String eventId,
String tallyName
) {
var url = String.format("%s/api/tally/voting-results/{eventId}/{tallyName}", ledgerFollowerBaseUrl);

try {
return Either.right(Arrays.asList(restTemplate.getForObject(url, L1CategoryResults[].class, eventId, tallyName)));
} catch (HttpClientErrorException e) {
return Either.left(Problem.builder()
.withTitle("CATEGORY_RESULTS_ERROR")
.withDetail("Unable to get category results from chain-tip follower service, reason:" + e.getMessage())
.withStatus(new HttpStatusAdapter(e.getStatusCode()))
.build());
}
}

public Either<Problem, ChainTipResponse> getChainTip() {
var url = String.format("%s/api/blockchain/tip", ledgerFollowerBaseUrl);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ public ResponseEntity<?> getEventLeaderBoard(
);
}

@Deprecated
@RequestMapping(value = "/{eventId}/{categoryId}", method = GET, produces = "application/json")
@Timed(value = "resource.leaderboard.category", histogram = true)
@Operation(
Expand Down Expand Up @@ -300,4 +301,81 @@ public ResponseEntity<?> getCategoryLeaderBoard(@Parameter(name = "eventId", des
);
}

@RequestMapping(value = "/{eventId}/{categoryId}/results", method = GET, produces = "application/json")
@Timed(value = "resource.leaderboard.category", histogram = true)
public ResponseEntity<?> getCategoryLeaderBoardPerCategoryResults(@PathVariable("eventId") String eventId,
@PathVariable("categoryId") String categoryId,
@RequestHeader(value = XForceLeaderBoardResults, required = false, defaultValue = "false") boolean forceLeaderboardResults,
@Valid @RequestParam(name = "source") Optional<WinnerLeaderboardSource> winnerLeaderboardSourceM) {
var winnerLeaderboardSource = winnerLeaderboardSourceM.orElse(db);

var cacheControl = CacheControl.maxAge(5, MINUTES)
.noTransform()
.mustRevalidate();

var forceLeaderboard = forceLeaderboardResults && forceLeaderboardResultsAvailability;

var categoryLeaderboardE = leaderboardWinnersProvider
.getWinnerLeaderboardSource(winnerLeaderboardSource)
.getCategoryLeaderboard(eventId, categoryId, forceLeaderboard);

return categoryLeaderboardE
.fold(problem -> {
return ResponseEntity
.status(problem.getStatus().getStatusCode())
.body(problem);
},
proposalsInCategoryStatsM -> {
if (proposalsInCategoryStatsM.isEmpty()) {
var problem = Problem.builder()
.withTitle("VOTING_RESULTS_NOT_YET_AVAILABLE")
.withDetail("Leaderboard not yet available for event: " + eventId)
.withStatus(NOT_FOUND)
.build();

return ResponseEntity
.status(problem.getStatus().getStatusCode())
.body(problem);
}

return ResponseEntity
.ok()
.cacheControl(cacheControl)
.body(proposalsInCategoryStatsM.orElseThrow());
}
);
}

@RequestMapping(value = "/{eventId}/results", method = GET, produces = "application/json")
@Timed(value = "resource.leaderboard.category.all", histogram = true)
public ResponseEntity<?> getCategoryLeaderBoardForAllCategoriesResults(@PathVariable("eventId") String eventId,
@RequestHeader(value = XForceLeaderBoardResults, required = false, defaultValue = "false") boolean forceLeaderboardResults,
@Valid @RequestParam(name = "source") Optional<WinnerLeaderboardSource> winnerLeaderboardSourceM) {
var winnerLeaderboardSource = winnerLeaderboardSourceM.orElse(db);

var cacheControl = CacheControl.maxAge(5, MINUTES)
.noTransform()
.mustRevalidate();

var forceLeaderboard = forceLeaderboardResults && forceLeaderboardResultsAvailability;

var categoryLeaderboardE = leaderboardWinnersProvider
.getWinnerLeaderboardSource(winnerLeaderboardSource)
.getAllCategoriesLeaderboard(eventId, forceLeaderboard);

return categoryLeaderboardE
.fold(problem -> {
return ResponseEntity
.status(problem.getStatus().getStatusCode())
.body(problem);
},
allCategoryResults -> {
return ResponseEntity
.ok()
.cacheControl(cacheControl)
.body(allCategoryResults);
}
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,37 @@ public class AbstractWinnersService {
@Autowired
protected ChainFollowerClient chainFollowerClient;


@Transactional(readOnly = true)
public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
.withTitle("ERROR_GETTING_EVENT_DETAILS")
.withDetail("Unable to get event details from chain-tip follower service, event:" + event)
.withStatus(INTERNAL_SERVER_ERROR)
.build()
);
}
var maybeEventDetails = eventDetailsE.get();
if (maybeEventDetails.isEmpty()) {
return Either.left(Problem.builder()
.withTitle("UNRECOGNISED_EVENT")
.withDetail("Unrecognised event, event:" + event)
.withStatus(BAD_REQUEST)
.build()
);
}
var eventDetails = maybeEventDetails.orElseThrow();

return isCategoryLeaderboardAvailable(eventDetails, forceLeaderboard);
}

@Transactional(readOnly = true)
public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event, String category, boolean forceLeaderboard) {
public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event,
String category,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cardano.foundation.voting.service.leader_board;

import io.vavr.control.Either;
import org.cardano.foundation.voting.client.ChainFollowerClient;
import org.cardano.foundation.voting.domain.Leaderboard;
import org.cardano.foundation.voting.repository.VoteRepository;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -9,6 +10,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.zalando.problem.Problem;

import java.util.List;
import java.util.Optional;

import static java.util.stream.Collectors.toMap;
Expand All @@ -23,7 +25,9 @@ public class DBLeaderboardWinnersService extends AbstractWinnersService implemen

@Override
@Transactional(readOnly = true)
public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCategoryLeaderboard(String event, String category, boolean forceLeaderboard) {
public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCategoryLeaderboard(String event,
String category,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand Down Expand Up @@ -56,6 +60,39 @@ public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCate
}
var categoryDetails = maybeCategory.orElseThrow();

return getCategoryLeaderboard(eventDetails, categoryDetails, forceLeaderboard);
}

@Override
public Either<Problem, List<Leaderboard.ByProposalsInCategoryStats>> getAllCategoriesLeaderboard(String event,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
.withTitle("ERROR_GETTING_EVENT_DETAILS")
.withDetail("Unable to get event details from chain-tip follower service, event:" + event)
.withStatus(INTERNAL_SERVER_ERROR)
.build()
);
}
var maybeEventDetails = eventDetailsE.get();
if (maybeEventDetails.isEmpty()) {
return Either.left(Problem.builder()
.withTitle("UNRECOGNISED_EVENT")
.withDetail("Unrecognised event, event:" + event)
.withStatus(BAD_REQUEST)
.build()
);
}
var eventDetails = maybeEventDetails.orElseThrow();

return getCategoryLeaderboardForAllCategories(eventDetails, forceLeaderboard);
}

@Override
public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCategoryLeaderboard(ChainFollowerClient.EventDetailsResponse eventDetails,
ChainFollowerClient.CategoryDetailsResponse categoryDetails,
boolean forceLeaderboard) {
var categoryLeaderboardAvailableE = isCategoryLeaderboardAvailable(eventDetails, forceLeaderboard);
if (categoryLeaderboardAvailableE.isEmpty()) {
return Either.left(categoryLeaderboardAvailableE.getLeft());
Expand All @@ -71,7 +108,7 @@ public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCate
);
}

var votes = voteRepository.getCategoryLevelStats(event, categoryDetails.id());
var votes = voteRepository.getCategoryLevelStats(eventDetails.id(), categoryDetails.id());

var proposalResultsMap = votes.stream()
.collect(toMap(VoteRepository.CategoryLevelStats::getProposalId, v -> {
Expand All @@ -91,8 +128,54 @@ public Either<Problem, Optional<Leaderboard.ByProposalsInCategoryStats>> getCate
return Either.right(Optional.of(Leaderboard.ByProposalsInCategoryStats.builder()
.category(categoryDetails.id())
.proposals(reInitialiseResultsToEmptyIfMissing(categoryDetails, proposalResultsMap, eventDetails))
.build())
);
.build()));
}

public Either<Problem, List<Leaderboard.ByProposalsInCategoryStats>> getCategoryLeaderboardForAllCategories(ChainFollowerClient.EventDetailsResponse eventDetails,
boolean forceLeaderboard) {
var categoryLeaderboardAvailableE = isCategoryLeaderboardAvailable(eventDetails, forceLeaderboard);
if (categoryLeaderboardAvailableE.isEmpty()) {
return Either.left(categoryLeaderboardAvailableE.getLeft());
}

var isCategoryLeaderBoardAvailable = categoryLeaderboardAvailableE.get();
if (!isCategoryLeaderBoardAvailable) {
return Either.left(Problem.builder()
.withTitle("VOTING_RESULTS_NOT_AVAILABLE")
.withDetail("Category level voting results not available until results can be revealed!")
.withStatus(FORBIDDEN)
.build()
);
}

var allResultsForAllCategories = eventDetails.categories()
.stream()
.map(categoryDetails -> {
var categoryStats = voteRepository.getCategoryLevelStats(eventDetails.id(), categoryDetails.id());

var results = categoryStats.stream()
.collect(toMap(VoteRepository.CategoryLevelStats::getProposalId, v -> {
var totalVotesCount = Optional.ofNullable(v.getTotalVoteCount()).orElse(0L);
var totalVotingPower = Optional.ofNullable(v.getTotalVotingPower()).map(String::valueOf).orElse("0");

var b = Leaderboard.Votes.builder();
b.votes(totalVotesCount);

switch (eventDetails.votingEventType()) {
case BALANCE_BASED, STAKE_BASED -> b.votingPower(totalVotingPower);
}

return b.build();
}));

return Leaderboard.ByProposalsInCategoryStats.builder()
.category(categoryDetails.id())
.proposals(reInitialiseResultsToEmptyIfMissing(categoryDetails, results, eventDetails))
.build();
})
.toList();

return Either.right(allResultsForAllCategories);
}

}
Loading

0 comments on commit f142a48

Please sign in to comment.