diff --git a/README.md b/README.md index b201d3afb..b85ecf116 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager) [![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager) [![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager) -[![Time](https://img.shields.io/badge/development-657h-blueviolet.svg)]() +[![Time](https://img.shields.io/badge/development-657.5h-blueviolet.svg)]() [![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]() [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend) diff --git a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/ParticipantController.java b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/ParticipantController.java index 59a081b53..d440d22d2 100644 --- a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/ParticipantController.java +++ b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/controller/ParticipantController.java @@ -28,7 +28,6 @@ import com.softwaremagico.kt.core.converters.models.ParticipantConverterRequest; import com.softwaremagico.kt.core.exceptions.TokenExpiredException; import com.softwaremagico.kt.core.exceptions.UserNotFoundException; -import com.softwaremagico.kt.core.providers.DuelProvider; import com.softwaremagico.kt.core.providers.ParticipantProvider; import com.softwaremagico.kt.persistence.entities.Participant; import com.softwaremagico.kt.persistence.repositories.ParticipantRepository; @@ -36,18 +35,16 @@ import org.springframework.stereotype.Controller; import java.time.LocalDateTime; +import java.util.List; @Controller public class ParticipantController extends BasicInsertableController { - private final DuelProvider duelProvider; - @Autowired - public ParticipantController(ParticipantProvider provider, ParticipantConverter converter, DuelProvider duelProvider) { + public ParticipantController(ParticipantProvider provider, ParticipantConverter converter) { super(provider, converter); - this.duelProvider = duelProvider; } @Override @@ -94,13 +91,13 @@ public Token generateToken(String temporalToken) { } - public ParticipantDTO getYourWorstNightmare(ParticipantDTO participant) { - return convert(getProvider().getYourWorstNightmare(reverse(participant))); + public List getYourWorstNightmare(ParticipantDTO participant) { + return convertAll(getProvider().getYourWorstNightmare(reverse(participant))); } - public ParticipantDTO getYouAreTheWorstNightmareOf(ParticipantDTO participant) { - return convert(getProvider().getYouAreTheWorstNightmareOf(reverse(participant))); + public List getYouAreTheWorstNightmareOf(ParticipantDTO participant) { + return convertAll(getProvider().getYouAreTheWorstNightmareOf(reverse(participant))); } } diff --git a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/providers/ParticipantProvider.java b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/providers/ParticipantProvider.java index 0d1a48c49..8121dfcca 100644 --- a/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/providers/ParticipantProvider.java +++ b/backend/kendo-tournament-core/src/main/java/com/softwaremagico/kt/core/providers/ParticipantProvider.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -103,10 +104,6 @@ public List get(Club club) { return getRepository().findByClub(club); } - public Participant getByIdCard(String idCard) { - return getRepository().findByIdCard(idCard); - } - public TemporalToken generateTemporalToken(Participant participant) { do { participant.generateTemporalToken(); @@ -141,7 +138,7 @@ public Optional findByTokenUsername(String tokenUsername) { return Optional.empty(); } - public Participant getYourWorstNightmare(Participant sourceParticipant) { + public List getYourWorstNightmare(Participant sourceParticipant) { if (sourceParticipant == null) { return null; } @@ -149,19 +146,27 @@ public Participant getYourWorstNightmare(Participant sourceParticipant) { final Map lostBy = new HashMap<>(); for (Duel duel : duels) { final Participant winner = duel.getCompetitorWinner(); - if (!Objects.equals(winner, sourceParticipant)) { + if (winner != null && !Objects.equals(winner, sourceParticipant)) { lostBy.put(winner, lostBy.getOrDefault(winner, 0) + 1); } } - final List> sortedLostBy = lostBy.entrySet().stream().sorted(Map.Entry.comparingByValue()).toList(); + final List> sortedLostBy = lostBy.entrySet().stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).toList(); + final List selected = new ArrayList<>(); if (!sortedLostBy.isEmpty()) { - return sortedLostBy.get(0).getKey(); + final int maxScore = sortedLostBy.get(0).getValue(); + int count = 0; + while (count < sortedLostBy.size() && sortedLostBy.get(count) != null && sortedLostBy.get(count).getValue() == maxScore) { + selected.add(sortedLostBy.get(count).getKey()); + count++; + } } - return null; + + return selected; } - public Participant getYouAreTheWorstNightmareOf(Participant sourceParticipant) { + public List getYouAreTheWorstNightmareOf(Participant sourceParticipant) { if (sourceParticipant == null) { return null; } @@ -174,10 +179,18 @@ public Participant getYouAreTheWorstNightmareOf(Participant sourceParticipant) { lostBy.put(looser, lostBy.getOrDefault(looser, 0) + 1); } } - final List> sortedLostBy = lostBy.entrySet().stream().sorted(Map.Entry.comparingByValue()).toList(); + final List> sortedLostBy = lostBy.entrySet().stream() + .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).toList(); + final List selected = new ArrayList<>(); if (!sortedLostBy.isEmpty()) { - return sortedLostBy.get(0).getKey(); + final int maxScore = sortedLostBy.get(0).getValue(); + int count = 0; + while (count < sortedLostBy.size() && sortedLostBy.get(count) != null && sortedLostBy.get(count).getValue() == maxScore) { + selected.add(sortedLostBy.get(count).getKey()); + count++; + } } - return null; + + return selected; } } diff --git a/backend/kendo-tournament-core/src/test/java/com/softwaremagico/kt/core/tests/WorstNightmareTest.java b/backend/kendo-tournament-core/src/test/java/com/softwaremagico/kt/core/tests/WorstNightmareTest.java index 8d7318641..c87890dc7 100644 --- a/backend/kendo-tournament-core/src/test/java/com/softwaremagico/kt/core/tests/WorstNightmareTest.java +++ b/backend/kendo-tournament-core/src/test/java/com/softwaremagico/kt/core/tests/WorstNightmareTest.java @@ -25,6 +25,7 @@ import com.softwaremagico.kt.core.controller.FightController; import com.softwaremagico.kt.core.controller.models.FightDTO; import com.softwaremagico.kt.core.controller.models.TournamentDTO; +import com.softwaremagico.kt.core.converters.ParticipantConverter; import com.softwaremagico.kt.core.managers.TeamsOrder; import com.softwaremagico.kt.core.providers.ParticipantProvider; import com.softwaremagico.kt.persistence.entities.Participant; @@ -60,6 +61,9 @@ public class WorstNightmareTest extends TournamentTestUtils { @Autowired private ParticipantProvider participantProvider; + @Autowired + private ParticipantConverter participantConverter; + @BeforeClass public void prepareData() { addParticipants(MEMBERS, TEAMS, REFEREES, ORGANIZER, VOLUNTEER, PRESS, 0); @@ -98,37 +102,37 @@ public void prepareTournament1() { fightDTOs.get(1).getDuels().get(0).addCompetitor1Score(Score.KOTE); fightDTOs.get(1).getDuels().get(0).addCompetitor1Score(Score.MEN); fightDTOs.get(1).getDuels().get(0).setFinished(true); - fightDTOs.set(1, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(1, fightController.update(fightDTOs.get(1), null)); //P7 vs P4 fightDTOs.get(1).getDuels().get(1).addCompetitor1Score(Score.KOTE); fightDTOs.get(1).getDuels().get(1).addCompetitor1Score(Score.MEN); fightDTOs.get(1).getDuels().get(1).setFinished(true); - fightDTOs.set(1, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(1, fightController.update(fightDTOs.get(1), null)); //P8 vs P5 fightDTOs.get(1).getDuels().get(2).addCompetitor1Score(Score.KOTE); fightDTOs.get(1).getDuels().get(2).addCompetitor1Score(Score.MEN); fightDTOs.get(1).getDuels().get(2).setFinished(true); - fightDTOs.set(1, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(1, fightController.update(fightDTOs.get(1), null)); //P6 vs P0 fightDTOs.get(2).getDuels().get(0).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(0).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(0).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); //P7 vs P1 fightDTOs.get(2).getDuels().get(1).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(1).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(1).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); //P8 vs P2 fightDTOs.get(2).getDuels().get(2).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(2).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(2).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); } @BeforeClass(dependsOnMethods = "createTournaments") @@ -160,54 +164,60 @@ public void prepareTournament2() { //P7 vs P4 fightDTOs.get(1).getDuels().get(1).addCompetitor2Score(Score.KOTE); fightDTOs.get(1).getDuels().get(1).setFinished(true); - fightDTOs.set(1, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(1, fightController.update(fightDTOs.get(1), null)); //P8 vs P5 fightDTOs.get(1).getDuels().get(2).addCompetitor1Score(Score.KOTE); fightDTOs.get(1).getDuels().get(2).addCompetitor1Score(Score.MEN); fightDTOs.get(1).getDuels().get(2).setFinished(true); - fightDTOs.set(1, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(1, fightController.update(fightDTOs.get(1), null)); //P6 vs P0 fightDTOs.get(2).getDuels().get(0).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(0).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(0).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); //P7 vs P1 fightDTOs.get(2).getDuels().get(1).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(1).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(1).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); //P8 vs P2 fightDTOs.get(2).getDuels().get(2).addCompetitor1Score(Score.KOTE); fightDTOs.get(2).getDuels().get(2).addCompetitor1Score(Score.MEN); fightDTOs.get(2).getDuels().get(2).setFinished(true); - fightDTOs.set(2, fightController.update(fightDTOs.get(0), null)); + fightDTOs.set(2, fightController.update(fightDTOs.get(2), null)); } @Test public void checkYourWorstNightmare() { - final Participant participant1 = participantProvider.getByIdCard("0001"); - final Participant participant3 = participantProvider.getByIdCard("0003"); - final Participant participant4 = participantProvider.getByIdCard("0004"); - final Participant participant6 = participantProvider.getByIdCard("0006"); - Assert.assertEquals(participantProvider.getYourWorstNightmare(participant3), participant6); - Assert.assertEquals(participantProvider.getYourWorstNightmare(participant4), participant1); + final Participant participant0 = participantConverter.reverse(participantsDTOs.get(0)); + final Participant participant1 = participantConverter.reverse(participantsDTOs.get(1)); + final Participant participant3 = participantConverter.reverse(participantsDTOs.get(3)); + final Participant participant4 = participantConverter.reverse(participantsDTOs.get(4)); + //P0 and P6 + Assert.assertEquals(participantProvider.getYourWorstNightmare(participant3).size(), 2); + Assert.assertEquals(participantProvider.getYourWorstNightmare(participant3).get(0), participant0); + Assert.assertEquals(participantProvider.getYourWorstNightmare(participant4).size(), 1); + Assert.assertEquals(participantProvider.getYourWorstNightmare(participant4).get(0), participant1); } @Test public void checkWorstNightmareOf() { - final Participant participant1 = participantProvider.getByIdCard("0001"); - final Participant participant3 = participantProvider.getByIdCard("0003"); - final Participant participant4 = participantProvider.getByIdCard("0004"); - final Participant participant6 = participantProvider.getByIdCard("0006"); - Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant6), participant3); - Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant1), participant4); + final Participant participant0 = participantConverter.reverse(participantsDTOs.get(0)); + final Participant participant1 = participantConverter.reverse(participantsDTOs.get(1)); + final Participant participant4 = participantConverter.reverse(participantsDTOs.get(4)); + final Participant participant6 = participantConverter.reverse(participantsDTOs.get(6)); + Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant6).size(), 1); + Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant6).get(0), participant0); + Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant1).size(), 1); + Assert.assertEquals(participantProvider.getYouAreTheWorstNightmareOf(participant1).get(0), participant4); } @AfterClass(alwaysRun = true) + @Override public void wipeOut() { super.wipeOut(); } diff --git a/backend/kendo-tournament-persistence/src/main/java/com/softwaremagico/kt/persistence/repositories/ParticipantRepository.java b/backend/kendo-tournament-persistence/src/main/java/com/softwaremagico/kt/persistence/repositories/ParticipantRepository.java index 2178a4147..3412ac219 100644 --- a/backend/kendo-tournament-persistence/src/main/java/com/softwaremagico/kt/persistence/repositories/ParticipantRepository.java +++ b/backend/kendo-tournament-persistence/src/main/java/com/softwaremagico/kt/persistence/repositories/ParticipantRepository.java @@ -81,8 +81,6 @@ List findParticipantsWithRoleNotInTournaments(@Param("tournament") List findByClub(Club club); - Participant findByIdCard(String idCard); - long countByTemporalToken(String temporalToken); Optional findByTemporalToken(String temporalToken); diff --git a/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/StatisticsServices.java b/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/StatisticsServices.java index 70f788bfc..919b599ac 100644 --- a/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/StatisticsServices.java +++ b/backend/kendo-tournament-rest/src/main/java/com/softwaremagico/kt/rest/services/StatisticsServices.java @@ -159,18 +159,16 @@ public ParticipantStatisticsDTO getStatisticsFromParticipant(@Parameter(descript @PreAuthorize("hasAnyRole('ROLE_VIEWER', 'ROLE_EDITOR', 'ROLE_ADMIN', 'ROLE_PARTICIPANT')") @Operation(summary = "Gets participant worst nightmare.", security = @SecurityRequirement(name = "bearerAuth")) @GetMapping(value = "/participants/your-worst-nightmare/{participantId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ParticipantDTO getYourWorstNightmareFromParticipant(@Parameter(description = "Id of an existing participant", required = true) - @PathVariable("participantId") Integer participantId, - Authentication authentication, - HttpServletRequest request) { + public List getYourWorstNightmareFromParticipant(@Parameter(description = "Id of an existing participant", required = true) + @PathVariable("participantId") Integer participantId, + Authentication authentication, + HttpServletRequest request) { //If is a participant guest, only its own statistics can see. if (authentication != null) { final Optional participant = participantProvider.findByTokenUsername(authentication.getName()); - if (participant.isPresent()) { - if (!Objects.equals(participant.get().getId(), participantId)) { - throw new InvalidRequestException(this.getClass(), "User '" + authentication.getName() - + "' is trying to access to statistics from user '" + participantId + "'."); - } + if (participant.isPresent() && !Objects.equals(participant.get().getId(), participantId)) { + throw new InvalidRequestException(this.getClass(), "User '" + authentication.getName() + + "' is trying to access to statistics from user '" + participantId + "'."); } } return participantController.getYourWorstNightmare(participantController.get(participantId)); @@ -180,10 +178,10 @@ public ParticipantDTO getYourWorstNightmareFromParticipant(@Parameter(descriptio @PreAuthorize("hasAnyRole('ROLE_VIEWER', 'ROLE_EDITOR', 'ROLE_ADMIN', 'ROLE_PARTICIPANT')") @Operation(summary = "Gets participant worst nightmare.", security = @SecurityRequirement(name = "bearerAuth")) @GetMapping(value = "/participants/worst-nightmare-of/{participantId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ParticipantDTO getWorstNightmareOf(@Parameter(description = "Id of an existing participant", required = true) - @PathVariable("participantId") Integer participantId, - Authentication authentication, - HttpServletRequest request) { + public List getWorstNightmareOf(@Parameter(description = "Id of an existing participant", required = true) + @PathVariable("participantId") Integer participantId, + Authentication authentication, + HttpServletRequest request) { //If is a participant guest, only its own statistics can see. if (authentication != null) { final Optional participant = participantProvider.findByTokenUsername(authentication.getName()); diff --git a/frontend/src/app/components/tournament-brackets-editor/tournament-brackets-editor.component.ts b/frontend/src/app/components/tournament-brackets-editor/tournament-brackets-editor.component.ts index 0e3da1a7c..519d74b73 100644 --- a/frontend/src/app/components/tournament-brackets-editor/tournament-brackets-editor.component.ts +++ b/frontend/src/app/components/tournament-brackets-editor/tournament-brackets-editor.component.ts @@ -233,7 +233,7 @@ export class TournamentBracketsEditorComponent implements OnInit, OnDestroy { const pdf: jsPDF = new jsPDF(jsPdfOptions); pdf.addImage(result, 'PNG', 25, 25, widthMM * ratio, heightMM * ratio); pdf.save(this.tournament.name + '.pdf'); - }).catch(error => { + }).catch((): void => { }); } diff --git a/frontend/src/app/services/statistics.service.ts b/frontend/src/app/services/statistics.service.ts index e85c2935f..ee4f1db66 100644 --- a/frontend/src/app/services/statistics.service.ts +++ b/frontend/src/app/services/statistics.service.ts @@ -90,26 +90,26 @@ export class StatisticsService { } - getYourWorstNightmare(participantId: number): Observable { + getYourWorstNightmare(participantId: number): Observable { const url: string = `${this.baseUrl}/participants/your-worst-nightmare/${participantId}`; - return this.http.get(url) + return this.http.get(url) .pipe( tap({ next: () => this.loggerService.info(`fetched worst nightmare for participant id=${participantId}`) }), - catchError(this.messageService.logOnlyError(`get id=${participantId}`)) + catchError(this.messageService.logOnlyError(`get id=${participantId}`)) ); } - getWorstNightmareOf(participantId: number): Observable { + getWorstNightmareOf(participantId: number): Observable { const url: string = `${this.baseUrl}/participants/worst-nightmare-of/${participantId}`; - return this.http.get(url) + return this.http.get(url) .pipe( tap({ next: () => this.loggerService.info(`fetched worst nightmare to participant id=${participantId}`), }), - catchError(this.messageService.logOnlyError(`get id=${participantId}`)) + catchError(this.messageService.logOnlyError(`get id=${participantId}`)) ); } } diff --git a/frontend/src/app/services/user.service.ts b/frontend/src/app/services/user.service.ts index 13b75d442..8a790356b 100644 --- a/frontend/src/app/services/user.service.ts +++ b/frontend/src/app/services/user.service.ts @@ -29,7 +29,7 @@ export class UserService { const url: string = `${this.baseUrl}/register`; return this.http.get(url) .pipe( - map(_users => { + map((_users: any) => { for (let user of _users) { user.roles = UserRoles.getByKeys(user.roles); } @@ -53,7 +53,7 @@ export class UserService { this.loggerService.info(`adding user ${_authenticatedUser}`); this.messageService.infoMessage("infoAuthenticatedUserStored"); }, - error: (error) => { + error: (error: { status: any; }): void => { this.systemOverloadService.isBusy.next(false); if (error instanceof HttpErrorResponse) { switch (error.status) { diff --git a/frontend/src/app/views/participant-statistics/participant-statistics.component.html b/frontend/src/app/views/participant-statistics/participant-statistics.component.html index f6414d27e..afbb6c953 100644 --- a/frontend/src/app/views/participant-statistics/participant-statistics.component.html +++ b/frontend/src/app/views/participant-statistics/participant-statistics.component.html @@ -19,34 +19,34 @@ -

{{participant.name}} {{participant.lastname}}

+

{{ participant.name }} {{ participant.lastname }}

-

{{'roles' | translate}}

+

{{ 'roles' | translate }}

- - + +
-

{{'tournaments' | translate}}

+

{{ 'tournaments' | translate }}

- - + + - - + +
@@ -54,42 +54,42 @@

{{'tournaments' | translate}}

timer -

{{'time' | translate}}

+

{{ 'time' | translate }}

- - + + - - + + - - + + - - + +
-

{{'duels' | translate}}

+

{{ 'duels' | translate }}

- - + + - - + + - - + +
@@ -97,46 +97,46 @@

{{'duels' | translate}}

-

{{'hits' | translate}}

+

{{ 'hits' | translate }}

- - + + - - + + - - + + - - + + - - + + - - + + - - + +
flag -

{{'faults' | translate}}

+

{{ 'faults' | translate }}

- @@ -145,46 +145,46 @@

{{'faults' | translate}}

-

{{'receivedHits' | translate}}

+

{{ 'receivedHits' | translate }}

- - + + - - + + - - + + - - + + - - + + - - + + - - + +
flag -

{{'receivedFaults' | translate}}

+

{{ 'receivedFaults' | translate }}

- @@ -193,7 +193,7 @@

{{'receivedFaults' | translate}}

-

{{'competitorsRanking' | translate}}

+

{{ 'competitorsRanking' | translate }}

@@ -205,14 +205,14 @@

{{'competitorsRanking' | translate}}

'ranking-very-bad': competitorRanking.ranking / competitorRanking.total > 0.9} " class="ranking your-ranking"> - {{competitorRanking.ranking + 1}} + {{ competitorRanking.ranking + 1 }} - +
@@ -220,7 +220,7 @@

{{'competitorsRanking' | translate}}

-

{{'performance' | translate}}

+

{{ 'performance' | translate }}

{{'performance' | translate}}
-

{{'yourWorstNightmare' | translate}}

+

{{ 'yourWorstNightmare' | translate }}

- + + + + + + - +
-

{{'worstNightmareOf' | translate}}

+

{{ 'worstNightmareOf' | translate }}

- - -