Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple Repositories in Leaderboard #113

Merged
merged 16 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions server/application-server/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ paths:
type: array
items:
$ref: "#/components/schemas/PullRequest"
/meta:
get:
tags:
- meta
operationId: getMetaData
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/MetaDataDTO"
/leaderboard:
get:
tags:
Expand All @@ -107,6 +119,11 @@ paths:
schema:
type: string
format: date
- name: repository
in: query
required: false
schema:
type: string
responses:
"200":
description: OK
Expand Down Expand Up @@ -469,6 +486,13 @@ components:
type: array
items:
$ref: "#/components/schemas/PullRequestReview"
MetaDataDTO:
type: object
properties:
repositoriesToMonitor:
type: array
items:
type: string
LeaderboardEntry:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class SecurityConfig {
interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<GrantedAuthority>> {
}

@SuppressWarnings("unchecked")
@Bean
AuthoritiesConverter realmRolesAuthoritiesConverter() {
return claims -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ SELECT new UserDTO(u.id, u.login, u.email, u.name, u.url)
FROM User u
JOIN FETCH u.reviews re
WHERE re.createdAt BETWEEN :after AND :before
AND (:repository IS NULL OR re.pullRequest.repository.nameWithOwner = :repository)
""")
List<User> findAllInTimeframe(@Param("after") OffsetDateTime after, @Param("before") OffsetDateTime before);
List<User> findAllInTimeframe(@Param("after") OffsetDateTime after, @Param("before") OffsetDateTime before,
@Param("repository") Optional<String> repository);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ public List<User> getAllUsers() {
return userRepository.findAll().stream().toList();
}

public List<User> getAllUsersInTimeframe(OffsetDateTime after, OffsetDateTime before) {
logger.info("Getting all users in timeframe between " + after + " and " + before);
return userRepository.findAllInTimeframe(after, before);
public List<User> getAllUsersInTimeframe(OffsetDateTime after, OffsetDateTime before, Optional<String> repository) {
logger.info("Getting all users in timeframe between " + after + " and " + before + " for repository: "
+ repository.orElse("all"));
return userRepository.findAllInTimeframe(after, before, repository);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public LeaderboardController(LeaderboardService leaderboardService) {
@GetMapping
public ResponseEntity<List<LeaderboardEntry>> getLeaderboard(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Optional<LocalDate> after,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Optional<LocalDate> before) {
return ResponseEntity.ok(leaderboardService.createLeaderboard(after, before));
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Optional<LocalDate> before,
@RequestParam Optional<String> repository) {
return ResponseEntity.ok(leaderboardService.createLeaderboard(after, before, repository));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ public LeaderboardService(UserService userService) {
this.userService = userService;
}

public List<LeaderboardEntry> createLeaderboard(Optional<LocalDate> after, Optional<LocalDate> before) {
public List<LeaderboardEntry> createLeaderboard(Optional<LocalDate> after, Optional<LocalDate> before,
Optional<String> repository) {
logger.info("Creating leaderboard dataset");

LocalDateTime afterCutOff = after.isPresent() ? after.get().atStartOfDay()
: LocalDate.now().minusDays(timeframe).atStartOfDay();
Optional<LocalDateTime> beforeCutOff = before.map(date -> date.plusDays(1).atStartOfDay());

List<User> users = userService.getAllUsersInTimeframe(afterCutOff.atOffset(ZoneOffset.UTC),
beforeCutOff.map(b -> b.atOffset(ZoneOffset.UTC)).orElse(OffsetDateTime.now()));
beforeCutOff.map(b -> b.atOffset(ZoneOffset.UTC)).orElse(OffsetDateTime.now()), repository);

logger.info("Found " + users.size() + " users for the leaderboard");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.tum.in.www1.hephaestus.meta;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/meta")
public class MetaController {

private final MetaService metaService;

public MetaController(MetaService metaService) {
this.metaService = metaService;
}

@GetMapping
public ResponseEntity<MetaDataDTO> getMetaData() {
return ResponseEntity.ok(metaService.getMetaData());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.tum.in.www1.hephaestus.meta;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record MetaDataDTO(String[] repositoriesToMonitor) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.tum.in.www1.hephaestus.meta;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class MetaService {
private static final Logger logger = LoggerFactory.getLogger(MetaService.class);

@Value("${monitoring.repositories}")
private String[] repositoriesToMonitor;

public MetaDataDTO getMetaData() {
logger.info("Getting meta data...");
return new MetaDataDTO(repositoriesToMonitor);
}
}
3 changes: 3 additions & 0 deletions webapp/src/app/core/modules/openapi/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ api/admin.serviceInterface.ts
api/api.ts
api/leaderboard.service.ts
api/leaderboard.serviceInterface.ts
api/meta.service.ts
api/meta.serviceInterface.ts
api/pull-request.service.ts
api/pull-request.serviceInterface.ts
api/user.service.ts
Expand All @@ -18,6 +20,7 @@ index.ts
model/issue-comment-dto.ts
model/issue-comment.ts
model/leaderboard-entry.ts
model/meta-data-dto.ts
model/models.ts
model/pull-request-dto.ts
model/pull-request-label.ts
Expand Down
5 changes: 4 additions & 1 deletion webapp/src/app/core/modules/openapi/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ export * from './admin.serviceInterface';
export * from './leaderboard.service';
import { LeaderboardService } from './leaderboard.service';
export * from './leaderboard.serviceInterface';
export * from './meta.service';
import { MetaService } from './meta.service';
export * from './meta.serviceInterface';
export * from './pull-request.service';
import { PullRequestService } from './pull-request.service';
export * from './pull-request.serviceInterface';
export * from './user.service';
import { UserService } from './user.service';
export * from './user.serviceInterface';
export const APIS = [AdminService, LeaderboardService, PullRequestService, UserService];
export const APIS = [AdminService, LeaderboardService, MetaService, PullRequestService, UserService];
13 changes: 9 additions & 4 deletions webapp/src/app/core/modules/openapi/api/leaderboard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,14 @@ export class LeaderboardService implements LeaderboardServiceInterface {
/**
* @param after
* @param before
* @param repository
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getLeaderboard(after?: string, before?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<Array<LeaderboardEntry>>;
public getLeaderboard(after?: string, before?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<Array<LeaderboardEntry>>>;
public getLeaderboard(after?: string, before?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<Array<LeaderboardEntry>>>;
public getLeaderboard(after?: string, before?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
public getLeaderboard(after?: string, before?: string, repository?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<Array<LeaderboardEntry>>;
public getLeaderboard(after?: string, before?: string, repository?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<Array<LeaderboardEntry>>>;
public getLeaderboard(after?: string, before?: string, repository?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<Array<LeaderboardEntry>>>;
public getLeaderboard(after?: string, before?: string, repository?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {

let localVarQueryParameters = new HttpParams({encoder: this.encoder});
if (after !== undefined && after !== null) {
Expand All @@ -115,6 +116,10 @@ export class LeaderboardService implements LeaderboardServiceInterface {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>before, 'before');
}
if (repository !== undefined && repository !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>repository, 'repository');
}

let localVarHeaders = this.defaultHeaders;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export interface LeaderboardServiceInterface {
*
* @param after
* @param before
* @param repository
*/
getLeaderboard(after?: string, before?: string, extraHttpRequestParams?: any): Observable<Array<LeaderboardEntry>>;
getLeaderboard(after?: string, before?: string, repository?: string, extraHttpRequestParams?: any): Observable<Array<LeaderboardEntry>>;

}
157 changes: 157 additions & 0 deletions webapp/src/app/core/modules/openapi/api/meta.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Hephaestus API
* API documentation for the Hephaestus application server.
*
* The version of the OpenAPI document: 0.0.1
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/* tslint:disable:no-unused-variable member-ordering */

import { Inject, Injectable, Optional } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams,
HttpResponse, HttpEvent, HttpParameterCodec, HttpContext
} from '@angular/common/http';
import { CustomHttpParameterCodec } from '../encoder';
import { Observable } from 'rxjs';

// @ts-ignore
import { MetaDataDTO } from '../model/meta-data-dto';

// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
import { Configuration } from '../configuration';
import {
MetaServiceInterface
} from './meta.serviceInterface';



@Injectable({
providedIn: 'root'
})
export class MetaService implements MetaServiceInterface {

protected basePath = 'http://localhost';
public defaultHeaders = new HttpHeaders();
public configuration = new Configuration();
public encoder: HttpParameterCodec;

constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) {
if (configuration) {
this.configuration = configuration;
}
if (typeof this.configuration.basePath !== 'string') {
const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined;
if (firstBasePath != undefined) {
basePath = firstBasePath;
}

if (typeof basePath !== 'string') {
basePath = this.basePath;
}
this.configuration.basePath = basePath;
}
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
}


// @ts-ignore
private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
if (typeof value === "object" && value instanceof Date === false) {
httpParams = this.addToHttpParamsRecursive(httpParams, value);
} else {
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
}
return httpParams;
}

private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
if (value == null) {
return httpParams;
}

if (typeof value === "object") {
if (Array.isArray(value)) {
(value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
} else if (value instanceof Date) {
if (key != null) {
httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10));
} else {
throw Error("key may not be null if value is Date");
}
} else {
Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
httpParams, value[k], key != null ? `${key}.${k}` : k));
}
} else if (key != null) {
httpParams = httpParams.append(key, value);
} else {
throw Error("key may not be null if value is not object or array");
}
return httpParams;
}

/**
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public getMetaData(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<MetaDataDTO>;
public getMetaData(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<MetaDataDTO>>;
public getMetaData(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<MetaDataDTO>>;
public getMetaData(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {

let localVarHeaders = this.defaultHeaders;

let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}

let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}

let localVarTransferCache: boolean | undefined = options && options.transferCache;
if (localVarTransferCache === undefined) {
localVarTransferCache = true;
}


let responseType_: 'text' | 'json' | 'blob' = 'json';
if (localVarHttpHeaderAcceptSelected) {
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
responseType_ = 'json';
} else {
responseType_ = 'blob';
}
}

let localVarPath = `/meta`;
return this.httpClient.request<MetaDataDTO>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
transferCache: localVarTransferCache,
reportProgress: reportProgress
}
);
}

}
Loading