From be822f0c0b6ea688166c3effd3799cacb12e37dc Mon Sep 17 00:00:00 2001 From: Ben Stein <115497763+sei-bstein@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:35:40 -0500 Subject: [PATCH] v3.25.1 (#209) * Misc cleanup, fix enrollment trend, fix game level feedback alignment * Improve visibility of incorrect questions. Explain no performance if none is available in the challenges report. * Fix a reloading bug on competitive play page * Make support notifications closeable and open in new tab * Style toastify close button a bit --- projects/gameboard-ui/src/app/app.module.ts | 4 +- .../cumulative-time-clock.component.html | 2 +- .../line-chart/line-chart.component.ts | 2 +- .../gamespace-quiz.component.html | 1 - .../gamespace-quiz.component.scss | 1 + .../pages/game-page/game-page.component.html | 7 +- .../gameboard-page.component.html | 223 +++++++++--------- .../gameboard-page.component.scss | 19 -- .../gameboard-page.component.ts | 3 +- .../src/app/guards/game-is-started.guard.ts | 16 +- .../src/app/guards/user-is-playing.guard.ts | 10 +- .../enrollment-report.models.ts | 12 +- .../enrollment-report.service.ts | 6 +- ...-question-performance-modal.component.html | 8 +- .../app/services/app-notifications.service.ts | 7 +- .../app/services/signalR/signalr.service.ts | 1 - .../src/app/services/window.service.ts | 5 +- .../src/app/utility/auth.guard.ts | 9 +- projects/gameboard-ui/src/scss/_toastify.scss | 3 +- projects/gameboard-ui/src/styles.scss | 12 + 20 files changed, 177 insertions(+), 174 deletions(-) diff --git a/projects/gameboard-ui/src/app/app.module.ts b/projects/gameboard-ui/src/app/app.module.ts index c1ddd8060..958f6998a 100644 --- a/projects/gameboard-ui/src/app/app.module.ts +++ b/projects/gameboard-ui/src/app/app.module.ts @@ -100,8 +100,6 @@ import { UserNavItemComponent } from './standalone/user/components/user-nav-item }) export class AppModule { } -export function loadSettings( - config: ConfigService, -): (() => Observable) { +export function loadSettings(config: ConfigService): (() => Observable) { return (): Observable => config.load(); } diff --git a/projects/gameboard-ui/src/app/core/components/cumulative-time-clock/cumulative-time-clock.component.html b/projects/gameboard-ui/src/app/core/components/cumulative-time-clock/cumulative-time-clock.component.html index afba27bb7..91bd0a673 100644 --- a/projects/gameboard-ui/src/app/core/components/cumulative-time-clock/cumulative-time-clock.component.html +++ b/projects/gameboard-ui/src/app/core/components/cumulative-time-clock/cumulative-time-clock.component.html @@ -1,5 +1,5 @@
- + {{ ((time$ | async) || 0 | clock) || "--" }} -- diff --git a/projects/gameboard-ui/src/app/core/components/line-chart/line-chart.component.ts b/projects/gameboard-ui/src/app/core/components/line-chart/line-chart.component.ts index cc91ae140..996d5c5ca 100644 --- a/projects/gameboard-ui/src/app/core/components/line-chart/line-chart.component.ts +++ b/projects/gameboard-ui/src/app/core/components/line-chart/line-chart.component.ts @@ -1,5 +1,5 @@ -import { LogService } from '@/services/log.service'; import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; +import { LogService } from '@/services/log.service'; import { Chart, ChartConfiguration, ChartConfigurationCustomTypesPerDataset } from 'chart.js'; export type LineChartConfig = ChartConfiguration<"line", number[], string> | ChartConfigurationCustomTypesPerDataset<"line", number[], string>; diff --git a/projects/gameboard-ui/src/app/game/gamespace-quiz/gamespace-quiz.component.html b/projects/gameboard-ui/src/app/game/gamespace-quiz/gamespace-quiz.component.html index 85f7fd28b..091f625bf 100644 --- a/projects/gameboard-ui/src/app/game/gamespace-quiz/gamespace-quiz.component.html +++ b/projects/gameboard-ui/src/app/game/gamespace-quiz/gamespace-quiz.component.html @@ -3,7 +3,6 @@ -
Info
-
-
+
+

Scoreboard   @@ -95,8 +95,7 @@

-
+
diff --git a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.html b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.html index a98d4da80..e70a52578 100644 --- a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.html +++ b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.html @@ -18,133 +18,132 @@
-
-
-

To start a challenge, click a circle on the below gameboard:

-
-
-
-
- {{this.hoveredItem?.lockedText || this.hoveredItem?.name}}
- Points: {{this.hoveredItem?.points}} -
- - - - - - - - +
+
+

+ Select a circle on the gameboard below to start playing: +

+ +
+ {{this.hoveredItem?.lockedText || this.hoveredItem?.name}}
+ Points: {{this.hoveredItem?.points}}
+ + + + + + + + +
-
- - - -
- - -
- - -
-
- - Start Challenge - -
- This will start the challenge timer. The time it takes to - earn points is a factor in tie-breaking. +
+ + + +
+ + +
+ +
-
- - -
- - Estimated time remaining: - - {{etd}}s -
-
+ + Start Challenge + +
+ This will start the challenge timer. The time it takes to + earn points is a factor in tie-breaking.
+ +
+ + +
+ + Estimated time remaining: + + {{etd}}s +
+
+
- - - - - - - - -

Gamespace Resources

-
-
-
    -
  • - -
  • -
-
+ -
- - - Destroy - - - - Deploy - -
-
-
- -
-
+ + -

Challenge Questions

- - -
diff --git a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.scss b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.scss index 4e0766bc4..ee6bbeacc 100644 --- a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.scss +++ b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.scss @@ -37,22 +37,3 @@ max-width: 300px; max-height: 300px; } - -.countdown-green { - color: $success; -} - -.countdown-yellow { - color: $warning; -} - -.countdown-red { - color: $danger; -} - -// Uses a 16 : 10 ratio, but can be changed here -iframe { - // aspect-ratio: 16 / 10; - margin: auto; - display: block; -} diff --git a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.ts b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.ts index 1a030d65c..8e8fb9399 100644 --- a/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.ts +++ b/projects/gameboard-ui/src/app/game/pages/gameboard-page/gameboard-page.component.ts @@ -7,7 +7,6 @@ import { ActivatedRoute } from '@angular/router'; import { firstValueFrom, merge, Observable, of, Subject, timer } from 'rxjs'; import { catchError, debounceTime, filter, map, switchMap, tap } from 'rxjs/operators'; import { faArrowLeft, faBolt, faExclamationTriangle, faTrash, faTv } from '@fortawesome/free-solid-svg-icons'; - import { BoardPlayer, BoardSpec, Challenge, NewChallenge, VmState } from '@/api/board-models'; import { BoardService } from '@/api/board.service'; import { ApiUser } from '@/api/user-models'; @@ -52,12 +51,12 @@ export class GameboardPageComponent { constructor( challengeService: ChallengesService, - route: ActivatedRoute, title: Title, usersvc: UserService, private api: BoardService, private config: ConfigService, private hub: NotificationService, + protected route: ActivatedRoute, private routerService: RouterService, private unsub: UnsubscriberService, private windowService: WindowService diff --git a/projects/gameboard-ui/src/app/guards/game-is-started.guard.ts b/projects/gameboard-ui/src/app/guards/game-is-started.guard.ts index 362cc0994..d819b5b48 100644 --- a/projects/gameboard-ui/src/app/guards/game-is-started.guard.ts +++ b/projects/gameboard-ui/src/app/guards/game-is-started.guard.ts @@ -1,10 +1,8 @@ import { GamePlayState } from '@/api/game-models'; -import { GameService } from '@/api/game.service'; import { PlayerService } from '@/api/player.service'; import { TeamService } from '@/api/team.service'; import { UserRolePermissionsService } from '@/api/user-role-permissions.service'; import { LogService } from '@/services/log.service'; -import { UserService } from '@/utility/user.service'; import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable, firstValueFrom, map } from 'rxjs'; @@ -12,7 +10,6 @@ import { Observable, firstValueFrom, map } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class GameIsStarted implements CanActivate, CanActivateChild { constructor( - private localUserService: UserService, private log: LogService, private permissionsService: UserRolePermissionsService, private playerService: PlayerService, @@ -31,14 +28,11 @@ export class GameIsStarted implements CanActivate, CanActivateChild { private async _canActivate(route: ActivatedRouteSnapshot): Promise { // if the user is admin/tester, they can ignore start phase restrictions - const localUser = this.localUserService.user$.getValue(); - if (await firstValueFrom(this.permissionsService.can$("Play_IgnoreExecutionWindow"))) { return true; } const playerId = route.paramMap.get("playerId") || ''; - const gameId = route.paramMap.get("gameId") || ''; // can't make a decision without a playerId (because we need their team and game) if (!playerId) { @@ -46,13 +40,11 @@ export class GameIsStarted implements CanActivate, CanActivateChild { return false; } - let resolvedGameId = gameId; const player = await firstValueFrom(this.playerService.retrieve(playerId)); - if (!resolvedGameId) { - resolvedGameId = player.gameId; - } - this.log.logInfo("Resolved gameId, teamId for GameIsStartedGuard", gameId, player.teamId); + this.log.logInfo("Resolved teamId for GameIsStartedGuard", player.teamId || ""); - return await firstValueFrom(this.teamService.getGamePlayState(player.teamId).pipe(map(phase => phase == GamePlayState.Started))); + const playState = await firstValueFrom(this.teamService.getGamePlayState(player.teamId)); + this.log.logInfo("Guard: GameIsStartedGuard", playState, playState == GamePlayState.Started); + return playState == GamePlayState.Started; } } diff --git a/projects/gameboard-ui/src/app/guards/user-is-playing.guard.ts b/projects/gameboard-ui/src/app/guards/user-is-playing.guard.ts index 2da10441f..132f0b936 100644 --- a/projects/gameboard-ui/src/app/guards/user-is-playing.guard.ts +++ b/projects/gameboard-ui/src/app/guards/user-is-playing.guard.ts @@ -1,6 +1,6 @@ import { PlayerService } from '@/api/player.service'; import { LogService } from '@/services/log.service'; -import { UserService } from '@/utility/user.service'; +import { AuthService } from '@/utility/auth.service'; import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable, firstValueFrom } from 'rxjs'; @@ -8,9 +8,9 @@ import { Observable, firstValueFrom } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UserIsPlayingGuard implements CanActivate, CanActivateChild { constructor( + private auth: AuthService, private log: LogService, - private playerService: PlayerService, - private localUserService: UserService + private playerService: PlayerService ) { } canActivate( route: ActivatedRouteSnapshot, @@ -34,7 +34,7 @@ export class UserIsPlayingGuard implements CanActivate, CanActivateChild { } // need to know if the current user is the specified player - const localUserId = this.localUserService.user$.value?.id; + const localUserId = this.auth.oidcUser?.profile?.sub; if (!localUserId) { return false; } @@ -59,7 +59,7 @@ export class UserIsPlayingGuard implements CanActivate, CanActivateChild { } const result = await firstValueFrom(this.playerService.list({ gid: resolvedGameId, uid: localUserId })); - this.log.logInfo("Result from UserIsPlayingGuard", result.length > 0); + this.log.logInfo("Guard: Result from UserIsPlayingGuard", result.length > 0); return result.length > 0; } } diff --git a/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.models.ts b/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.models.ts index 6fe6e0d1b..9e125ce27 100644 --- a/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.models.ts +++ b/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.models.ts @@ -1,6 +1,7 @@ +import { DateTime } from "luxon"; import { ChallengeResult } from "@/api/board-models"; -import { PagingArgs, SimpleEntity } from "@/api/models"; -import { ReportDateRange, ReportGame, ReportSponsor } from "@/reports/reports-models"; +import { SimpleEntity } from "@/api/models"; +import { ReportGame, ReportSponsor } from "@/reports/reports-models"; export type EnrollmentReportTab = "summary" | "trend" | "game"; @@ -86,6 +87,13 @@ export interface EnrollmentReportStatSummary { teamsWithNoStartedChallengeCount: number; } +export interface EnrollmentReportLineChartResponse { + playerGroups: { [dateString: string]: EnrollmentReportLineChartGroup }; + periodType: string; + periodStart: DateTime; + periodEnd: DateTime; +} + export interface EnrollmentReportLineChartGroup { totalCount: number; players: { diff --git a/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.service.ts b/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.service.ts index cfae1156f..89d4f0aee 100644 --- a/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.service.ts +++ b/projects/gameboard-ui/src/app/reports/components/reports/enrollment-report/enrollment-report.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { EnrollmentReportByGameRecord, EnrollmentReportFlatParameters, EnrollmentReportLineChartGroup, EnrollmentReportRecord, EnrollmentReportStatSummary } from './enrollment-report.models'; +import { EnrollmentReportByGameRecord, EnrollmentReportFlatParameters, EnrollmentReportLineChartGroup, EnrollmentReportLineChartResponse, EnrollmentReportRecord, EnrollmentReportStatSummary } from './enrollment-report.models'; import { Observable, firstValueFrom, map, } from 'rxjs'; import { ReportResults } from '@/reports/reports-models'; import { ReportsService } from '@/reports/reports.service'; @@ -47,12 +47,12 @@ export class EnrollmentReportService { return await firstValueFrom(this .http - .get<{ [dateString: string]: EnrollmentReportLineChartGroup }>(this.apiUrl.build("reports/enrollment/trend", trendParams)) + .get(this.apiUrl.build("reports/enrollment/trend", trendParams)) .pipe( map(results => { const mapped = new Map(); - for (const entry of Object.entries(results)) { + for (const entry of Object.entries(results.playerGroups)) { mapped.set(DateTime.fromISO(entry[0]), entry[1]); } diff --git a/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html index e14f4ff54..958ce21a7 100644 --- a/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html +++ b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html @@ -1,7 +1,7 @@ - +
@@ -44,3 +44,9 @@ Loading question performance... + + +
+ No one has attempted this challenge yet. +
+
diff --git a/projects/gameboard-ui/src/app/services/app-notifications.service.ts b/projects/gameboard-ui/src/app/services/app-notifications.service.ts index b71afb6dc..b52a9b21b 100644 --- a/projects/gameboard-ui/src/app/services/app-notifications.service.ts +++ b/projects/gameboard-ui/src/app/services/app-notifications.service.ts @@ -71,7 +71,12 @@ export class AppNotificationsService implements OnDestroy { this.log.logWarning(`Can't send browser notification (${this._canShowBrowserNotifications$.value}) - falling back to toast.`); this.toastService.show({ text: `${sendNotification.title}: ${sendNotification.body}`, - onClick: sendNotification.appUrl ? () => this.router.navigateByUrl(sendNotification.appUrl!) : undefined, + onClick: sendNotification.appUrl ? () => new Promise(resolve => { + this.windowService.open(sendNotification.appUrl!); + return true; + }) : + undefined, + showCloseIcon: true }); return; } diff --git a/projects/gameboard-ui/src/app/services/signalR/signalr.service.ts b/projects/gameboard-ui/src/app/services/signalR/signalr.service.ts index ea62b4008..80d1ab592 100644 --- a/projects/gameboard-ui/src/app/services/signalR/signalr.service.ts +++ b/projects/gameboard-ui/src/app/services/signalR/signalr.service.ts @@ -140,7 +140,6 @@ export class SignalRService { for (let handler of newEventHandlers) { this._eventHandlers[handler.eventType.toString()] = handler; connection.on(handler.eventType.toString(), ev => handler.handler(ev)); - this.logger.logInfo(`Bound event ${handler.eventType.toString()} to`, handler.handler); } } diff --git a/projects/gameboard-ui/src/app/services/window.service.ts b/projects/gameboard-ui/src/app/services/window.service.ts index b4b2964b4..07bf13125 100644 --- a/projects/gameboard-ui/src/app/services/window.service.ts +++ b/projects/gameboard-ui/src/app/services/window.service.ts @@ -1,5 +1,6 @@ import { DOCUMENT } from '@angular/common'; import { Inject, Injectable, OnDestroy } from '@angular/core'; +import { UrlTree } from '@angular/router'; import { BehaviorSubject, fromEvent, Subscription } from 'rxjs'; @Injectable({ providedIn: 'root' }) @@ -23,8 +24,8 @@ export class WindowService implements OnDestroy { return this.document.defaultView!; } - open(url: string) { - this.document.defaultView?.open(url, "_blank"); + open(url: string | UrlTree) { + this.document.defaultView?.open(url.toString(), "_blank"); } print() { diff --git a/projects/gameboard-ui/src/app/utility/auth.guard.ts b/projects/gameboard-ui/src/app/utility/auth.guard.ts index 961f3a11d..39f28d60c 100644 --- a/projects/gameboard-ui/src/app/utility/auth.guard.ts +++ b/projects/gameboard-ui/src/app/utility/auth.guard.ts @@ -1,14 +1,17 @@ // Copyright 2021 Carnegie Mellon University. All Rights Reserved. // Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router'; import { Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { AuthService, AuthTokenState } from './auth.service'; +import { LogService } from '@/services/log.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate, CanActivateChild { + private _log = inject(LogService); + constructor( private auth: AuthService, private router: Router @@ -28,8 +31,8 @@ export class AuthGuard implements CanActivate, CanActivateChild { private validateAuth(url: string): Observable { return this.auth.tokenState$.pipe( - map(t => t === AuthTokenState.valid), - map(v => v ? v : this.router.parseUrl(`/login?redirectTo=${url}`)) + map(authTokenState => authTokenState === AuthTokenState.valid ? true : this.router.parseUrl(`/login?redirectTo=${url}`)), + tap(result => this._log.logInfo("Guard: results from auth guard", result)) ); } } diff --git a/projects/gameboard-ui/src/scss/_toastify.scss b/projects/gameboard-ui/src/scss/_toastify.scss index 72bebd8a8..48198f325 100644 --- a/projects/gameboard-ui/src/scss/_toastify.scss +++ b/projects/gameboard-ui/src/scss/_toastify.scss @@ -33,7 +33,8 @@ } button.toast-close { - color: $gray-100 !important; + color: white !important; + fill: white !important; font-size: 0.8rem; padding: 0; margin-left: 0.75rem; diff --git a/projects/gameboard-ui/src/styles.scss b/projects/gameboard-ui/src/styles.scss index 3e708e7df..d5930c4cd 100644 --- a/projects/gameboard-ui/src/styles.scss +++ b/projects/gameboard-ui/src/styles.scss @@ -167,6 +167,18 @@ app-root { color: lighten($danger, 20%); } +.countdown-green { + color: $success !important; +} + +.countdown-yellow { + color: $warning !important; +} + +.countdown-red { + color: $danger !important; +} + fa-icon + span { margin-left: 4px; }