From a717b1256a7d93652dc759a79c41b911127c1dd4 Mon Sep 17 00:00:00 2001 From: Ben Stein <115497763+sei-bstein@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:39:16 -0500 Subject: [PATCH] v3.26.3 (#213) * Add game session availability warning and enrollment trend by game * Allow viewing of the game screen for practice mode games * Improve performance of enrollment trend line * Fix link rendering in markdown * Fix onn-dismissible notification creation --- .../src/app/admin/admin.module.ts | 2 - .../feedback-editor.component.html | 14 ---- .../feedback-editor.component.ts | 62 ----------------- .../game-center-settings.component.html | 17 +++-- .../game-center-settings.component.ts | 4 +- .../src/app/api/feedback.service.ts | 9 ++- .../gameboard-ui/src/app/api/game-models.ts | 8 +++ .../gameboard-ui/src/app/api/game.service.ts | 6 +- projects/gameboard-ui/src/app/app.module.ts | 7 ++ .../src/app/core/config/marked.config.ts | 4 ++ .../gameboard-ui/src/app/core/core.module.ts | 15 +---- .../feedback-submission-form.component.html | 15 +++-- .../feedback-submission-form.component.ts | 7 +- .../feedback-template-picker.component.html | 8 ++- .../feedback-template-picker.component.ts | 3 +- ...ession-availability-warning.component.html | 18 +++++ ...ssion-availability-warning.component.scss} | 0 ...-session-availability-warning.component.ts | 41 ++++++++++++ .../gameboard-ui/src/app/game/game.module.ts | 4 +- .../pages/game-page/game-page.component.html | 54 ++++++++++----- .../pages/game-page/game-page.component.ts | 2 + .../app/home/landing/landing.component.html | 2 +- .../src/app/home/landing/landing.component.ts | 6 +- .../enrollment-report-trend.component.html | 7 ++ .../enrollment-report-trend.component.ts | 67 ++++++++++++++++--- .../enrollment-report.models.ts | 34 +++++++--- .../enrollment-report.service.ts | 27 ++++++-- .../error-div/error-div.component.ts | 2 - ...dit-system-notification-modal.component.ts | 1 + 29 files changed, 288 insertions(+), 158 deletions(-) delete mode 100644 projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.html delete mode 100644 projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.ts create mode 100644 projects/gameboard-ui/src/app/game/components/game-session-availability-warning/game-session-availability-warning.component.html rename projects/gameboard-ui/src/app/{admin/components/feedback-editor/feedback-editor.component.scss => game/components/game-session-availability-warning/game-session-availability-warning.component.scss} (100%) create mode 100644 projects/gameboard-ui/src/app/game/components/game-session-availability-warning/game-session-availability-warning.component.ts diff --git a/projects/gameboard-ui/src/app/admin/admin.module.ts b/projects/gameboard-ui/src/app/admin/admin.module.ts index 635365db0..912dbcba3 100644 --- a/projects/gameboard-ui/src/app/admin/admin.module.ts +++ b/projects/gameboard-ui/src/app/admin/admin.module.ts @@ -34,7 +34,6 @@ import { ExternalGameAdminTeamContextMenuComponent } from './components/external import { ExternalGameAdminComponent } from './components/external-game-admin/external-game-admin.component'; import { ExternalGameHostPickerComponent } from './components/external-game-host-picker/external-game-host-picker.component'; import { ExternalHostEditorComponent } from './components/external-host-editor/external-host-editor.component'; -import { FeedbackEditorComponent } from './components/feedback-editor/feedback-editor.component'; import { GameBonusesConfigComponent } from './components/game-bonuses-config/game-bonuses-config.component'; import { GameCenterObserveComponent } from './components/game-center/game-center-observe/game-center-observe.component'; import { GameCenterPracticePlayerDetailComponent } from './components/game-center/game-center-practice-player-detail/game-center-practice-player-detail.component'; @@ -141,7 +140,6 @@ import { UserPickerComponent } from '@/standalone/users/user-picker/user-picker. SiteOverviewStatsComponent, AdminOverviewComponent, SupportSettingsComponent, - FeedbackEditorComponent, ExtendTeamsModalComponent, ActiveTeamsModalComponent, AdminEnrollTeamModalComponent, diff --git a/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.html b/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.html deleted file mode 100644 index fa727cf1b..000000000 --- a/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
- -
DEPRECATED
- - -
- -
-
diff --git a/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.ts b/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.ts deleted file mode 100644 index 09513d9fa..000000000 --- a/projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { BehaviorSubject, debounceTime, tap } from 'rxjs'; -import { FeedbackTemplate } from '@/api/feedback-models'; -import { UnsubscriberService } from '@/services/unsubscriber.service'; -import { ModalConfirmService } from '@/services/modal-confirm.service'; - -@Component({ - selector: 'app-feedback-editor', - templateUrl: './feedback-editor.component.html', - styleUrls: ['./feedback-editor.component.scss'], - providers: [UnsubscriberService] -}) -export class FeedbackEditorComponent implements OnInit { - @Input() feedbackConfig = ""; - @Output() templateChange = new EventEmitter(); - - protected boundYaml = ""; - protected sampleConfig?: string; - protected validationMessages: string[] = []; - - private templateChangeSubject$ = new BehaviorSubject(null); - - public constructor( - private modalService: ModalConfirmService, - private unsub: UnsubscriberService) { - this.unsub.add( - this.templateChangeSubject$ - .pipe( - debounceTime(1000), - tap(t => this.templateChange.emit(t)) - ) - .subscribe() - ); - } - - async ngOnInit() { - this.boundYaml = this.feedbackConfig; - } - - protected handleAboutFeedbackClick() { - this.modalService.openConfirm({ - bodyContent: `You can use this box to configure questions that will automatically be presented to players upon conclusion of a challenge or game. Enter valid YAML to set these up. - -Each question requires these properties at minumum: - -- **id:** A unique identifying string for the question. -- **prompt:** The question that players will answer (e.g. "If you could change one thing about this challenge, what would it be?") -- **type:** The type of the response you want to collect from players. Valid options are **likert**, **text**, **selectOne**, and **selectMany**. - -Depending on the value of **type**, additional configuration may be required. For non-text questions, you'll also need: - -- **min:** The minimum numeric value of the question's scale (e.g. 1 on a scale from 1-10) -- **minLabel:** The label for the lowest value of the question's scale (e.g. "Very Easy") -- **max:** The maximum numeric value of the question's scale (e.g. 10 on a scale from 10) -- **maxLabel:** The label for the lowest value of the question's scale (e.g. "Very Hard")`, - hideCancel: true, - renderBodyAsMarkdown: true, - modalClasses: ["modal-xl"], - title: "About feedback templates" - }); - } -} diff --git a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.html b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.html index 7462a1fa3..5589c676b 100644 --- a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.html +++ b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.html @@ -154,9 +154,6 @@ - -
@@ -303,7 +300,7 @@

Execution

-
+
The game's open date must be less than its close date.
@@ -322,6 +319,18 @@

Execution

total concurrent sessions allowed for game
+
+ + + + If set, shows a warning when the number of currently-available sessions drops below this + number + +
+
game.maxTeamSize) return false; - if (game.gameStart > game.gameEnd) + if (game.gameEnd && new Date(game.gameEnd).getFullYear() > 1 && game.gameStart > game.gameEnd) return false; if (game.registrationType == GameRegistrationType.open && game.registrationOpen > game.registrationClose) diff --git a/projects/gameboard-ui/src/app/api/feedback.service.ts b/projects/gameboard-ui/src/app/api/feedback.service.ts index 363694dd3..790606971 100644 --- a/projects/gameboard-ui/src/app/api/feedback.service.ts +++ b/projects/gameboard-ui/src/app/api/feedback.service.ts @@ -19,8 +19,11 @@ export class FeedbackService { private url = ''; private yamlService = inject(YamlService); - private _deleted$ = new Subject(); - public deleted$ = this._deleted$.asObservable(); + private _templatesDeleted$ = new Subject(); + public templatesDeleted$ = this._templatesDeleted$.asObservable(); + + private _templatesUpdated$ = new Subject(); + public templatesUpdated$ = this._templatesUpdated$.asObservable(); constructor( config: ConfigService, @@ -47,7 +50,7 @@ export class FeedbackService { public async deleteTemplate(template: FeedbackTemplateView) { await firstValueFrom(this.http.delete(`${this.url}/feedback/template/${template.id}`)); - this._deleted$.next(template.id); + this._templatesDeleted$.next(template.id); } public async getTemplate(templateId: string): Promise { diff --git a/projects/gameboard-ui/src/app/api/game-models.ts b/projects/gameboard-ui/src/app/api/game-models.ts index 8129cd4eb..c401e17fd 100644 --- a/projects/gameboard-ui/src/app/api/game-models.ts +++ b/projects/gameboard-ui/src/app/api/game-models.ts @@ -1,6 +1,7 @@ // 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 { DateTime } from "luxon"; import { FeedbackTemplate } from "./feedback-models"; import { SimpleEntity } from "./models"; import { Player, PlayerMode, TimeWindow } from "./player-models"; @@ -36,6 +37,7 @@ export interface GameDetail { maxTeamSize: number; sessionMinutes: number; sessionLimit: number; + sessionAvailabilityWarningThreshold?: number; gamespaceLimitPerSession: number; isPublished: boolean; requireSponsoredTeam: boolean; @@ -169,6 +171,12 @@ export interface UpsertExternalGameHost { teamExtendedEndpoint?: string; } +export interface GameSessionAvailibilityResponse { + nextSessionEnd?: DateTime + sessionsAvailable: number +} + + export interface GetExternalTeamDataResponse { teamId: string; externalUrl: string; diff --git a/projects/gameboard-ui/src/app/api/game.service.ts b/projects/gameboard-ui/src/app/api/game.service.ts index d560ca7e6..fda7bbb53 100644 --- a/projects/gameboard-ui/src/app/api/game.service.ts +++ b/projects/gameboard-ui/src/app/api/game.service.ts @@ -8,7 +8,7 @@ import { map, tap } from 'rxjs/operators'; import { SyncStartGameState } from '../game/game.models'; import { ConfigService } from '../utility/config.service'; import { ChallengeGate } from './board-models'; -import { ChangedGame, Game, GameGroup, NewGame, SessionForecast, UploadedFile } from './game-models'; +import { ChangedGame, Game, GameGroup, GameSessionAvailibilityResponse, NewGame, SessionForecast, UploadedFile } from './game-models'; import { TimeWindow } from './player-models'; import { Spec } from './spec-models'; import { YamlService } from '@/services/yaml.service'; @@ -83,6 +83,10 @@ export class GameService { return firstValueFrom(this.http.post(`${this.url}/game/${gameId}/resources`, { teamIds })); } + public getSessionAvailability(gameId: string): Promise { + return firstValueFrom(this.http.get(`${this.url}/game/${gameId}/session-availability`)); + } + public getSyncStartState(gameId: string): Observable { return this.http.get(`${this.url}/game/${gameId}/ready`); } diff --git a/projects/gameboard-ui/src/app/app.module.ts b/projects/gameboard-ui/src/app/app.module.ts index 958f6998a..b70149f8f 100644 --- a/projects/gameboard-ui/src/app/app.module.ts +++ b/projects/gameboard-ui/src/app/app.module.ts @@ -35,6 +35,8 @@ import { SignalRService } from './services/signalR/signalr.service'; import { LogService } from './services/log.service'; import { SystemNotificationsModule } from './system-notifications/system-notifications.module'; import { UserNavItemComponent } from './standalone/user/components/user-nav-item/user-nav-item.component'; +import { markedOptionsFactory } from './core/config/marked.config'; +import { MarkdownModule, MarkedOptions } from 'ngx-markdown'; @NgModule({ declarations: [ @@ -54,6 +56,7 @@ import { UserNavItemComponent } from './standalone/user/components/user-nav-item SystemNotificationsModule, CoreModule, UtilityModule, + MarkdownModule.forRoot(), TooltipModule.forRoot(), TypeaheadModule.forRoot(), ButtonsModule.forRoot(), @@ -76,6 +79,10 @@ import { UserNavItemComponent } from './standalone/user/components/user-nav-item useFactory: (config: ConfigService, log: LogService, userService: UserService) => new SignalRService(config, log, userService), deps: [ConfigService, LogService, UserService], }, + { + provide: MarkedOptions, + useFactory: markedOptionsFactory + }, { provide: [NotificationService], useFactory: () => NotificationService, diff --git a/projects/gameboard-ui/src/app/core/config/marked.config.ts b/projects/gameboard-ui/src/app/core/config/marked.config.ts index 2e42c6948..56091d151 100644 --- a/projects/gameboard-ui/src/app/core/config/marked.config.ts +++ b/projects/gameboard-ui/src/app/core/config/marked.config.ts @@ -7,6 +7,10 @@ export function markedOptionsFactory(): MarkedOptions { return `
${text}
`; }; + renderer.link = (href, title, text) => { + return `${text}`; + }; + renderer.blockquote = (quote) => { return `
${quote}
`; }; diff --git a/projects/gameboard-ui/src/app/core/core.module.ts b/projects/gameboard-ui/src/app/core/core.module.ts index 1fc22fb16..ffe38ee76 100644 --- a/projects/gameboard-ui/src/app/core/core.module.ts +++ b/projects/gameboard-ui/src/app/core/core.module.ts @@ -1,4 +1,4 @@ -import { NgModule } from '@angular/core'; +import { ModuleWithProviders, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -19,7 +19,6 @@ import { TypeaheadModule } from 'ngx-bootstrap/typeahead'; // other 3rd party modules import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { MarkdownModule, MarkedOptions } from 'ngx-markdown'; import { NgChartsModule } from 'ng2-charts'; // import luxon adapter for chartjs import 'chartjs-adapter-luxon'; @@ -120,9 +119,9 @@ import { ObserverConsoleComponent } from './components/observer-console/observer import { TicketLabelPickerComponent } from './components/ticket-label-picker/ticket-label-picker.component'; import { GameMapImageUrlPipe } from './pipes/game-map-image-url.pipe'; import { SpinnerComponent } from '@/standalone/core/components/spinner/spinner.component'; -import { ErrorDivComponent } from '@/standalone/core/components/error-div/error-div.component'; import { IfHasPermissionDirective } from '@/standalone/directives/if-has-permission.directive'; import { ToSupportCodePipe } from '@/standalone/core/pipes/to-support-code.pipe'; +import { MarkdownModule } from 'ngx-markdown'; const PUBLIC_DECLARATIONS = [ AbsoluteValuePipe, @@ -260,19 +259,11 @@ const RELAYED_MODULES = [ HttpClientModule, ProgressbarModule, TooltipModule, - MarkdownModule.forRoot({ - loader: HttpClient, - markedOptions: { - provide: MarkedOptions, - useFactory: markedOptionsFactory, - }, - }), PopoverModule.forRoot(), TypeaheadModule.forRoot(), ...RELAYED_MODULES, // standalones - ErrorDivComponent, IfHasPermissionDirective, SpinnerComponent, ToSupportCodePipe @@ -281,7 +272,7 @@ const RELAYED_MODULES = [ CommonModule, ...RELAYED_MODULES, ...PUBLIC_DECLARATIONS, - IfHasPermissionDirective, + IfHasPermissionDirective ] }) export class CoreModule { } diff --git a/projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.html b/projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.html index 3edfde1fe..0952a5a8c 100644 --- a/projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.html +++ b/projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.html @@ -63,7 +63,8 @@ [disabled]="feedbackForm.submitted! || isPreview" id="check-{{q.id}}-{{option}}" [name]="q.id" [value]="option"> - {{option}}{{q.specify && q.specify.key == + + {{q.specify && q.specify.key == option ? ": " + (q.specify.prompt ? q.specify.prompt : "") : "" }} @@ -87,10 +88,10 @@ [checked]="q.answer && q.answer!.indexOf(option) > -1" [disabled]="feedbackForm.submitted!" id="check-{{q.id}}-{{option}}" [name]="option" [value]="option"> - +