Skip to content

Commit 9c42789

Browse files
committed
Add game session availability warning and enrollment trend by game
1 parent 82c8c12 commit 9c42789

25 files changed

+264
-135
lines changed

projects/gameboard-ui/src/app/admin/admin.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import { ExternalGameAdminTeamContextMenuComponent } from './components/external
3434
import { ExternalGameAdminComponent } from './components/external-game-admin/external-game-admin.component';
3535
import { ExternalGameHostPickerComponent } from './components/external-game-host-picker/external-game-host-picker.component';
3636
import { ExternalHostEditorComponent } from './components/external-host-editor/external-host-editor.component';
37-
import { FeedbackEditorComponent } from './components/feedback-editor/feedback-editor.component';
3837
import { GameBonusesConfigComponent } from './components/game-bonuses-config/game-bonuses-config.component';
3938
import { GameCenterObserveComponent } from './components/game-center/game-center-observe/game-center-observe.component';
4039
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.
141140
SiteOverviewStatsComponent,
142141
AdminOverviewComponent,
143142
SupportSettingsComponent,
144-
FeedbackEditorComponent,
145143
ExtendTeamsModalComponent,
146144
ActiveTeamsModalComponent,
147145
AdminEnrollTeamModalComponent,

projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.html

Lines changed: 0 additions & 14 deletions
This file was deleted.

projects/gameboard-ui/src/app/admin/components/feedback-editor/feedback-editor.component.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.html

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,6 @@
154154
<app-feedback-template-picker labelText="Challenges Feedback Template"
155155
[(templateId)]="game.challengesFeedbackTemplateId"
156156
(select)="handleChallengesFeedbackTemplateChanged($event)"></app-feedback-template-picker>
157-
158-
<app-feedback-editor *ngIf="game.feedbackConfig"
159-
[feedbackConfig]="game.feedbackConfig"></app-feedback-editor>
160157
</div>
161158

162159
<div class="col-12 mt-5">
@@ -303,7 +300,7 @@ <h4>Execution</h4>
303300

304301
</div>
305302

306-
<div *ngIf="game.gameStart > game.gameEnd" class="row form-group">
303+
<div *ngIf="game.gameEnd && game.gameStart > game.gameEnd" class="row form-group">
307304
<alert type="warning" class="col-12">The game's open date must be less than its close date.</alert>
308305
</div>
309306

@@ -322,6 +319,18 @@ <h4>Execution</h4>
322319
<small>total concurrent sessions allowed for game</small>
323320
</div>
324321

322+
<div class="form-group pb-0 pt-1">
323+
<label for="sessionAvailabilityWarningThreshold-input">Session Availability Warning
324+
Threshold</label>
325+
<input type="number" class="form-control" id="sessionLimit-input"
326+
name="sessionAvailabilityWarningThreshold"
327+
[(ngModel)]="game.sessionAvailabilityWarningThreshold">
328+
<small>
329+
If set, shows a warning when the number of currently-available sessions drops below this
330+
number
331+
</small>
332+
</div>
333+
325334
<div class="form-group pb-0 pt-1">
326335
<label for="gamespaceLimit-input">Gamespace Limit</label>
327336
<input type="number" class="form-control" id="gamespaceLimit-input" name="gamespaceLimit"

projects/gameboard-ui/src/app/admin/components/game-center/game-center-settings/game-center-settings.component.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import { KeyValue } from '@angular/common';
22
import { AfterViewInit, Component, ViewChild } from '@angular/core';
33
import { FormGroup, NgForm } from '@angular/forms';
44
import { debounceTime, filter, firstValueFrom, switchMap, tap } from 'rxjs';
5-
import { FeedbackTemplate } from '@/api/feedback-models';
65
import { ExternalGameHost, Game, GameEngineMode, GameRegistrationType } from '@/api/game-models';
76
import { GameService } from '@/api/game.service';
87
import { fa } from '@/services/font-awesome.service';
98
import { PracticeService } from '@/services/practice.service';
109
import { UnsubscriberService } from '@/services/unsubscriber.service';
11-
import { YamlService } from '@/services/yaml.service';
1210
import { ToastService } from '@/utility/services/toast.service';
1311
import { ActivatedRoute } from '@angular/router';
1412
import { FeedbackTemplateView } from '@/feedback/feedback.models';
@@ -167,7 +165,7 @@ export class GameCenterSettingsComponent implements AfterViewInit {
167165
if (game.minTeamSize > game.maxTeamSize)
168166
return false;
169167

170-
if (game.gameStart > game.gameEnd)
168+
if (game.gameEnd && new Date(game.gameEnd).getFullYear() > 1 && game.gameStart > game.gameEnd)
171169
return false;
172170

173171
if (game.registrationType == GameRegistrationType.open && game.registrationOpen > game.registrationClose)

projects/gameboard-ui/src/app/api/feedback.service.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export class FeedbackService {
1919
private url = '';
2020
private yamlService = inject(YamlService);
2121

22-
private _deleted$ = new Subject<string>();
23-
public deleted$ = this._deleted$.asObservable();
22+
private _templatesDeleted$ = new Subject<string>();
23+
public templatesDeleted$ = this._templatesDeleted$.asObservable();
24+
25+
private _templatesUpdated$ = new Subject<void>();
26+
public templatesUpdated$ = this._templatesUpdated$.asObservable();
2427

2528
constructor(
2629
config: ConfigService,
@@ -47,7 +50,7 @@ export class FeedbackService {
4750

4851
public async deleteTemplate(template: FeedbackTemplateView) {
4952
await firstValueFrom(this.http.delete(`${this.url}/feedback/template/${template.id}`));
50-
this._deleted$.next(template.id);
53+
this._templatesDeleted$.next(template.id);
5154
}
5255

5356
public async getTemplate(templateId: string): Promise<FeedbackTemplateView> {

projects/gameboard-ui/src/app/api/game-models.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
22
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
33

4+
import { DateTime } from "luxon";
45
import { FeedbackTemplate } from "./feedback-models";
56
import { SimpleEntity } from "./models";
67
import { Player, PlayerMode, TimeWindow } from "./player-models";
@@ -36,6 +37,7 @@ export interface GameDetail {
3637
maxTeamSize: number;
3738
sessionMinutes: number;
3839
sessionLimit: number;
40+
sessionAvailabilityWarningThreshold?: number;
3941
gamespaceLimitPerSession: number;
4042
isPublished: boolean;
4143
requireSponsoredTeam: boolean;
@@ -169,6 +171,12 @@ export interface UpsertExternalGameHost {
169171
teamExtendedEndpoint?: string;
170172
}
171173

174+
export interface GameSessionAvailibilityResponse {
175+
nextSessionEnd?: DateTime
176+
sessionsAvailable: number
177+
}
178+
179+
172180
export interface GetExternalTeamDataResponse {
173181
teamId: string;
174182
externalUrl: string;

projects/gameboard-ui/src/app/api/game.service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { map, tap } from 'rxjs/operators';
88
import { SyncStartGameState } from '../game/game.models';
99
import { ConfigService } from '../utility/config.service';
1010
import { ChallengeGate } from './board-models';
11-
import { ChangedGame, Game, GameGroup, NewGame, SessionForecast, UploadedFile } from './game-models';
11+
import { ChangedGame, Game, GameGroup, GameSessionAvailibilityResponse, NewGame, SessionForecast, UploadedFile } from './game-models';
1212
import { TimeWindow } from './player-models';
1313
import { Spec } from './spec-models';
1414
import { YamlService } from '@/services/yaml.service';
@@ -83,6 +83,10 @@ export class GameService {
8383
return firstValueFrom(this.http.post(`${this.url}/game/${gameId}/resources`, { teamIds }));
8484
}
8585

86+
public getSessionAvailability(gameId: string): Promise<GameSessionAvailibilityResponse> {
87+
return firstValueFrom(this.http.get<GameSessionAvailibilityResponse>(`${this.url}/game/${gameId}/session-availability`));
88+
}
89+
8690
public getSyncStartState(gameId: string): Observable<SyncStartGameState> {
8791
return this.http.get<SyncStartGameState>(`${this.url}/game/${gameId}/ready`);
8892
}

projects/gameboard-ui/src/app/app.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import { SignalRService } from './services/signalR/signalr.service';
3535
import { LogService } from './services/log.service';
3636
import { SystemNotificationsModule } from './system-notifications/system-notifications.module';
3737
import { UserNavItemComponent } from './standalone/user/components/user-nav-item/user-nav-item.component';
38+
import { markedOptionsFactory } from './core/config/marked.config';
39+
import { MarkdownModule, MarkedOptions } from 'ngx-markdown';
3840

3941
@NgModule({
4042
declarations: [
@@ -54,6 +56,7 @@ import { UserNavItemComponent } from './standalone/user/components/user-nav-item
5456
SystemNotificationsModule,
5557
CoreModule,
5658
UtilityModule,
59+
MarkdownModule.forRoot(),
5760
TooltipModule.forRoot(),
5861
TypeaheadModule.forRoot(),
5962
ButtonsModule.forRoot(),
@@ -76,6 +79,10 @@ import { UserNavItemComponent } from './standalone/user/components/user-nav-item
7679
useFactory: (config: ConfigService, log: LogService, userService: UserService) => new SignalRService(config, log, userService),
7780
deps: [ConfigService, LogService, UserService],
7881
},
82+
{
83+
provide: MarkedOptions,
84+
useFactory: markedOptionsFactory
85+
},
7986
{
8087
provide: [NotificationService],
8188
useFactory: () => NotificationService,

projects/gameboard-ui/src/app/core/config/marked.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export function markedOptionsFactory(): MarkedOptions {
77
return `<div class="text-center"><img class="img-fluid rounded" src=${href} alt="${text}" /></div>`;
88
};
99

10+
renderer.link = (href, title, text) => {
11+
return `<a role="link" target="_blank" href="${href}" rel="nofollow noopener noreferrer">{{ ${text} }}</a>`;
12+
};
13+
1014
renderer.blockquote = (quote) => {
1115
return `<blockquote class="blockquote">${quote}</blockquote>`;
1216
};

projects/gameboard-ui/src/app/core/core.module.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NgModule } from '@angular/core';
1+
import { ModuleWithProviders, NgModule } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
44
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -19,7 +19,6 @@ import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
1919

2020
// other 3rd party modules
2121
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
22-
import { MarkdownModule, MarkedOptions } from 'ngx-markdown';
2322
import { NgChartsModule } from 'ng2-charts';
2423
// import luxon adapter for chartjs
2524
import 'chartjs-adapter-luxon';
@@ -120,9 +119,9 @@ import { ObserverConsoleComponent } from './components/observer-console/observer
120119
import { TicketLabelPickerComponent } from './components/ticket-label-picker/ticket-label-picker.component';
121120
import { GameMapImageUrlPipe } from './pipes/game-map-image-url.pipe';
122121
import { SpinnerComponent } from '@/standalone/core/components/spinner/spinner.component';
123-
import { ErrorDivComponent } from '@/standalone/core/components/error-div/error-div.component';
124122
import { IfHasPermissionDirective } from '@/standalone/directives/if-has-permission.directive';
125123
import { ToSupportCodePipe } from '@/standalone/core/pipes/to-support-code.pipe';
124+
import { MarkdownModule } from 'ngx-markdown';
126125

127126
const PUBLIC_DECLARATIONS = [
128127
AbsoluteValuePipe,
@@ -260,19 +259,11 @@ const RELAYED_MODULES = [
260259
HttpClientModule,
261260
ProgressbarModule,
262261
TooltipModule,
263-
MarkdownModule.forRoot({
264-
loader: HttpClient,
265-
markedOptions: {
266-
provide: MarkedOptions,
267-
useFactory: markedOptionsFactory,
268-
},
269-
}),
270262
PopoverModule.forRoot(),
271263
TypeaheadModule.forRoot(),
272264
...RELAYED_MODULES,
273265

274266
// standalones
275-
ErrorDivComponent,
276267
IfHasPermissionDirective,
277268
SpinnerComponent,
278269
ToSupportCodePipe
@@ -281,7 +272,7 @@ const RELAYED_MODULES = [
281272
CommonModule,
282273
...RELAYED_MODULES,
283274
...PUBLIC_DECLARATIONS,
284-
IfHasPermissionDirective,
275+
IfHasPermissionDirective
285276
]
286277
})
287278
export class CoreModule { }

projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.html

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
[disabled]="feedbackForm.submitted! || isPreview" id="check-{{q.id}}-{{option}}"
6464
[name]="q.id" [value]="option">
6565

66-
{{option}}{{q.specify && q.specify.key ==
66+
<span [innerHTML]="option | markdown"></span>
67+
{{q.specify && q.specify.key ==
6768
option ? ": " +
6869
(q.specify.prompt ? q.specify.prompt : "") : "" }}
6970
</label>
@@ -87,10 +88,10 @@
8788
[checked]="q.answer && q.answer!.indexOf(option) > -1"
8889
[disabled]="feedbackForm.submitted!" id="check-{{q.id}}-{{option}}" [name]="option"
8990
[value]="option">
90-
<label class="form-check-label" for="{{option}}">{{option}}{{q.specify && q.specify.key
91-
==
92-
option ? ": " +
93-
(q.specify.prompt ? q.specify.prompt : "") : "" }}</label>
91+
<label class="form-check-label" for="{{option}}">
92+
<span [innerHTML]="option | markdown"></span>
93+
{{q.specify && q.specify.key == option ? ": " + (q.specify.prompt ?
94+
q.specify.prompt : "") : "" }}</label>
9495
<br>
9596
<textarea *ngIf="q.specify && q.specify.key == option" rows="1" type="text"
9697
[disabled]="feedbackForm.submitted!"
@@ -146,3 +147,7 @@
146147
<ng-template #noResponse>
147148
<em>(no response)</em>
148149
</ng-template>
150+
151+
<ng-template #markdownOption let-content>
152+
<span [innerHTML]="content | markdown"></span>
153+
</ng-template>

projects/gameboard-ui/src/app/feedback/components/feedback-submission-form/feedback-submission-form.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component, inject, Input, OnDestroy, OnInit, ViewChild } from '@angular
22
import { CommonModule } from '@angular/common';
33
import { FormGroup, NgForm } from '@angular/forms';
44
import { DateTime } from 'luxon';
5-
import { catchError, debounceTime, delay, filter, Observable, of, Subscription, tap } from 'rxjs';
5+
import { catchError, debounceTime, delay, filter, of, Subscription, tap } from 'rxjs';
66
import { fa } from '@/services/font-awesome.service';
77
import { UserService as LocalUserService } from '@/utility/user.service';
88
import { FeedbackSubmissionAttachedEntity, FeedbackSubmissionView, FeedbackTemplateView } from '@/feedback/feedback.models';
@@ -12,6 +12,8 @@ import { ErrorDivComponent } from '@/standalone/core/components/error-div/error-
1212
import { CoreModule } from '@/core/core.module';
1313
import { FeedbackQuestion } from '@/api/feedback-models';
1414
import { SpinnerComponent } from '@/standalone/core/components/spinner/spinner.component';
15+
import { MarkedOptions } from 'ngx-markdown';
16+
import { markedOptionsFactory } from '@/core/config/marked.config';
1517

1618
@Component({
1719
selector: 'app-feedback-submission-form',
@@ -25,6 +27,9 @@ import { SpinnerComponent } from '@/standalone/core/components/spinner/spinner.c
2527
ErrorDivComponent,
2628
SpinnerComponent
2729
],
30+
providers: [
31+
{ provide: MarkedOptions, useFactory: markedOptionsFactory }
32+
],
2833
templateUrl: './feedback-submission-form.component.html',
2934
styleUrls: ['./feedback-submission-form.component.scss']
3035
})

projects/gameboard-ui/src/app/feedback/components/feedback-template-picker/feedback-template-picker.component.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@
2323
tooltip="Preview this feedback template">
2424
<fa-icon [icon]="fa.eye"></fa-icon>
2525
</button>
26-
<button type="button" class="btn btn-success btn-sm" (click)="handleEdit(selectedTemplate)">
26+
<button *ngIf="selectedTemplate.responseCount == 0" type="button" class="btn btn-success btn-sm"
27+
(click)="handleEdit(selectedTemplate)">
2728
<fa-icon [icon]="fa.edit"></fa-icon>
2829
</button>
30+
<button type="button" class="btn btn-success btn-sm" [appCopyOnClick]="selectedTemplate.content"
31+
tooltip="Copy this template's YAML configuration"
32+
appCopyOnClickMessage="Copied this template's YAML to your clipboard.">
33+
<fa-icon [icon]="fa.copy"></fa-icon>
34+
</button>
2935
<button type="button" class="btn btn-danger btn-sm" (click)="handleDelete(selectedTemplate)">
3036
<fa-icon [icon]="fa.trash"></fa-icon>
3137
</button>

0 commit comments

Comments
 (0)