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

v3.32.0 #231

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f9b8b94
Fix vite dependency in @angular-devkit/build-angular
sei-bstein Feb 13, 2025
d125f2a
Resolve https://github.com/cmu-sei/Gameboard/issues/489
sei-bstein Feb 13, 2025
288be7e
Clarify GBAPI #282
sei-bstein Feb 14, 2025
269b5d1
Fix a bug where redirect to the report page would cause selection box…
sei-bstein Feb 14, 2025
6fa078c
Oops
sei-bstein Feb 14, 2025
026162e
Fix https://github.com/cmu-sei/Gameboard/issues/550
sei-bstein Feb 14, 2025
ed279f4
Better visibility on last submission for #604.
sei-bstein Feb 17, 2025
9014e5a
Hide last attempt message after session end
sei-bstein Feb 17, 2025
7c7c29c
Add markdown help to Support Page greeting box
sei-bstein Feb 17, 2025
cdc3176
Add markdown help to announce box
sei-bstein Feb 17, 2025
a8198df
Refactor markdown helper text into a pipe
sei-bstein Feb 17, 2025
d6b6679
the landing page now calls-to-action for the practice area if there a…
sei-bstein Feb 17, 2025
a494998
Resolves https://github.com/cmu-sei/Gameboard/issues/286
sei-bstein Feb 18, 2025
3576ada
Resolve GBAPI#415, GBAPI#424
sei-bstein Feb 18, 2025
0731c4d
Resolve https://github.com/cmu-sei/Gameboard/issues/439
sei-bstein Feb 18, 2025
3eef7b2
Cleanup and add auto-refresh on https://github.com/cmu-sei/Gameboard/…
sei-bstein Feb 19, 2025
e539774
Cleanup
sei-bstein Feb 19, 2025
f334092
Fix broken link in game admin table view
sei-bstein Feb 19, 2025
f1041d8
Misc cleanup and resolve https://github.com/cmu-sei/Gameboard/issues/434
sei-bstein Feb 19, 2025
b24ba33
Fix package-lock?
sei-bstein Feb 19, 2025
4b3f5c9
Update node version for build
sei-bstein Feb 19, 2025
3092982
Rollback to node 18, dedupe packages
sei-bstein Feb 19, 2025
b9208a8
Maybe actually fix package.json? Related to this bug in NPM CLI https…
sei-bstein Feb 19, 2025
2ea2822
WIP #290
sei-bstein Feb 20, 2025
2072d63
WIP practice summary
sei-bstein Feb 24, 2025
bf6c374
Styling
sei-bstein Feb 25, 2025
0831530
VIP of #613.
sei-bstein Feb 26, 2025
70ea0be
style tweak
sei-bstein Feb 27, 2025
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
5,718 changes: 3,614 additions & 2,104 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion projects/gameboard-ui/src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { ActiveTeamsModalComponent } from './components/active-teams-modal/activ
import { AdminEnrollTeamModalComponent } from './components/admin-enroll-team-modal/admin-enroll-team-modal.component';
import { AdminOverviewComponent } from './components/admin-overview/admin-overview.component';
import { AdminPageComponent } from './admin-page/admin-page.component';
import { AdminRolesComponent } from './components/admin-roles/admin-roles.component';
import { AnnounceComponent } from './announce/announce.component';
import { ChallengeBrowserComponent } from './challenge-browser/challenge-browser.component';
import { ChallengeObserverComponent } from './challenge-observer/challenge-observer.component';
Expand Down Expand Up @@ -91,6 +90,8 @@ import { CertificateTemplatePickerComponent } from '@/certificates/components/ce
import { CertificatePreviewerComponent } from '@/certificates/components/certificate-previewer/certificate-previewer.component';
import { GameCenterPracticeTeamContextMenuComponent } from './components/game-center/game-center-practice-team-context-menu/game-center-practice-team-context-menu.component';
import { SystemAdminComponent } from './components/system-admin/system-admin.component';
import { MarkdownPlaceholderPipe } from '@/core/pipes/markdown-placeholder.pipe';
import { SessionExtensionGameEndWarningComponent } from "./components/session-extension-game-end-warning/session-extension-game-end-warning.component";

@NgModule({
declarations: [
Expand Down Expand Up @@ -219,13 +220,16 @@ import { SystemAdminComponent } from './components/system-admin/system-admin.com
GameCenterPracticeTeamContextMenuComponent,
GameInfoBubblesComponent,
IfHasPermissionDirective,
MarkdownPlaceholderPipe,
SafeUrlPipe,
SessionExtensionGameEndWarningComponent,
SpinnerComponent,
ToSupportCodePipe,
CertificatePreviewerComponent,
CertificateTemplatePickerComponent,
FeedbackTemplatePickerComponent,
UserPickerComponent,
SessionExtensionGameEndWarningComponent
]
})
export class AdminModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<div class="form-group" *ngIf="!isLoading; else loading">
<label class="mb-0" for="input-announce">Announcement</label>
<div class="input-group">
<textarea name="input-announce" id="input-announce" rows="7" [placeholder]="placeholderText" class="form-control"
[(ngModel)]="message">
<textarea name="input-announce" id="input-announce" rows="7" [placeholder]="placeholderText | markdownPlaceholder"
class="form-control" [(ngModel)]="message">
</textarea>
<div class="input-group-append">
<button class="btn btn-success" (click)="announce()" [disabled]="!message">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// 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 { Component, Input } from '@angular/core';
import { Component, inject, Input } from '@angular/core';
import { faPaperPlane } from '@fortawesome/free-solid-svg-icons';
import { UserService } from '../../api/user.service';
import { firstValueFrom } from 'rxjs';
import { LogService } from '@/services/log.service';
import { ToastService } from '@/utility/services/toast.service';
Expand All @@ -18,17 +17,16 @@ export class AnnounceComponent {
@Input() teamId = '';
@Input() placeholderText = "We'd like to inform everyone playing that...";

private adminService = inject(AdminService);
private logService = inject(LogService);
private toastService = inject(ToastService);

message = '';
faSend = faPaperPlane;
errors: any[] = [];

protected finalPlaceholder = "";
protected isLoading = false;

constructor(
private adminService: AdminService,
private logService: LogService,
private toastService: ToastService) { }

async announce(): Promise<void> {
if (!this.message) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ <h2 class="m-0 p-0 fs-11">{{ spec.name }}</h2>
<h3 class="m-0 p-0 text-success fs-09 fw-bold cursor-pointer" [appCopyOnClick]="spec.id"
appCopyOnClickMessage="Copied **{{spec.name}}**'s ID to your clipboard"
class="text-success fs-09 fw-bold cursor-pointer">
{{ spec.id | slice:0:6 }}
<span tooltip="Copy this challenge's ID to your clipboard">{{ spec.id | slice:0:6 }}</span>
</h3>
<hr class="my-2" />
<div class="spec-controls-container d-flex flex-wrap align-items-center">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,70 @@
<div class="modal-content" *ngIf="apiTeams; else loading">
<div class="modal-header">
<h5 class="modal-title">{{ modalTitle }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-modal-content *ngIf="apiTeams; else loading" title="Extend Session" [subtitle]="modalTitle"
[cancelDisabled]="isWorking" [confirmDisabled]="isWorking || !extensionInMinutes"
[confirmButtonText]="extensionInMinutes > 0 ? 'Extend' : 'Shorten'" [isDangerConfirm]="extensionInMinutes < 0"
(confirm)="extend(extensionInMinutes)">
<app-error-div [errors]="errors"></app-error-div>
<ng-container *ngIf="!isWorking; else loading">
<alert *ngIf="ineligibleTeamIds.length" type="warning">
The following teamIds are ineligible for extension. They may not have started their session yet:

<div class="modal-body">
<app-error-div [errors]="errors"></app-error-div>
<ng-container *ngIf="!isWorking; else loading">
<alert *ngIf="ineligibleTeamIds.length" type="warning">
The following teamIds are ineligible for extension. They may not have started their session yet:
<ul class="mt-2 mb-0">
<li *ngFor="let ineligibleTeamId of ineligibleTeamIds">{{ ineligibleTeamId }}</li>
</ul>
</alert>

<ul class="mt-2 mb-0">
<li *ngFor="let ineligibleTeamId of ineligibleTeamIds">{{ ineligibleTeamId }}</li>
</ul>
</alert>
<div class="form-group px-0">
<label for="extensionInMinutes">Extension Length</label>
<input type="number" class="form-control" id="extensionInMinutes" [(ngModel)]="extensionInMinutes" required
placeholder="Extension length (in minutes)">
</div>

<div class="form-group px-0">
<label for="extensionInMinutes">Extension Length</label>
<input type="number" class="form-control" id="extensionInMinutes" [(ngModel)]="extensionInMinutes"
required placeholder="Extension length (in minutes)">
</div>
<alert type="warning" *ngIf="extensionInMinutes < 0">
If you use a negative value for the extension length, you'll <em>decrease</em> the amount of time
available to the {{ (game?.maxTeamSize || 1) > 1 ? "teams" : "players" }} shown here. Ensure the session
times below are what you expect.
</alert>

<alert type="warning" *ngIf="extensionInMinutes < 0">
If you use a negative value for the extension length, you'll <em>decrease</em> the amount of time
available to the {{ (game?.maxTeamSize || 1) > 1 ? "teams" : "players" }} shown here. Ensure the session
times below are what you expect.
</alert>
<app-session-extension-game-end-warning *ngIf="maxCurrentSessionEnd && game"
[extensionInMinutes]="extensionInMinutes" [gameEnd]="game.endsOn" [sessionEnd]="maxCurrentSessionEnd">
</app-session-extension-game-end-warning>

<ng-container *ngIf="extensionInMinutes">
<div class="mb-4" *ngIf="extensionInMinutes">
{{ extensionInMinutes > 0 ? "Extend" : "Shorten" }} {{ apiTeams.length === 1 ? "this session" : "the
following sessions" }} by
<strong>{{extensionInMinutes | absoluteValue}}</strong> minutes?
</div>
<div>
<ng-container *ngIf="extensionInMinutes">
<div class="mb-4" *ngIf="extensionInMinutes">
{{ extensionInMinutes > 0 ? "Extend" : "Shorten" }}
{{ apiTeams.length === 1 ? "this session" : "the following sessions" }}
by <strong>{{extensionInMinutes | absoluteValue}}</strong> minutes?
</div>
<div>

<ul *ngIf="apiTeams">
<li *ngFor="let team of apiTeams" class="session-list-item d-flex align-items-center">
<ng-container *ngIf="(team.sessionEnd | dateToDateTime) as sessionEndDateTime">
<div class="flex-grow-1">{{team.approvedName}}</div>
<ul *ngIf="apiTeams">
<li *ngFor="let team of apiTeams" class="session-list-item d-flex align-items-center">
<ng-container *ngIf="(team.sessionEnd | dateToDateTime) as sessionEndDateTime">
<div class="flex-grow-1">{{team.approvedName}}</div>

<div *ngIf="((team.sessionEnd | dateToDateTime) != null) && (sessionEndDateTime | addDuration: { minutes: extensionInMinutes } | dateTimeIsPast)"
class="badge badge-danger mr-2">Session will end</div>
<div>
(from
<div *ngIf="((team.sessionEnd | dateToDateTime) != null) && (sessionEndDateTime | addDuration: { minutes: extensionInMinutes } | dateTimeIsPast)"
class="badge badge-danger mr-2">Session will end</div>
<div>
(from

<strong class="fw-bold old-session-time">
{{team.sessionEnd | friendlyDateAndTime}}
</strong>
to
<ng-container
*ngIf="sessionEndDateTime | addDuration: { minutes: extensionInMinutes } as newSessionEnd">
<strong class="fw-bold" [class.text-danger]="newSessionEnd | dateTimeIsPast"
[class.text-success]="!(newSessionEnd | dateTimeIsPast)">
{{ newSessionEnd | datetimeToDate | friendlyDateAndTime }}
</strong>)
</ng-container>
</div>
</ng-container>
</li>
</ul>
</div>
</ng-container>
<strong class="fw-bold old-session-time">
{{team.sessionEnd | friendlyDateAndTime}}
</strong>
to
<ng-container
*ngIf="sessionEndDateTime | addDuration: { minutes: extensionInMinutes } as newSessionEnd">
<strong class="fw-bold" [class.text-danger]="newSessionEnd | dateTimeIsPast"
[class.text-success]="!(newSessionEnd | dateTimeIsPast)">
{{ newSessionEnd | datetimeToDate | friendlyDateAndTime }}
</strong>)
</ng-container>
</div>
</ng-container>
</li>
</ul>
</div>
</ng-container>
</div>

<div class="modal-footer">
<button type="button" class="btn" (click)="close()" [disabled]="isWorking">Cancel</button>
<button type="button" [class.btn-success]="extensionInMinutes > 0 || !!extensionInMinutes"
[class.btn-warning]="extensionInMinutes < 0" class="btn btn-success" (click)="extend(extensionInMinutes)"
[disabled]="isWorking || !extensionInMinutes">{{
extensionInMinutes > 0 ? "Extend" : "Shorten" }} </button>
</div>
</div>
</ng-container>
</app-modal-content>

<ng-template #loading>
<app-spinner></app-spinner>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { firstValueFrom, map, Observable } from 'rxjs';
import { firstValueFrom, map, Observable, tap } from 'rxjs';
import { TeamService } from '@/api/team.service';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { ToastService } from '@/utility/services/toast.service';
import { Team } from '@/api/player-models';
import { DateTime } from 'luxon';

@Component({
selector: 'app-extend-teams-modal',
Expand All @@ -13,6 +14,7 @@ import { Team } from '@/api/player-models';
export class ExtendTeamsModalComponent implements OnInit {
game?: {
id: string;
endsOn?: DateTime,
maxTeamSize: number;
name: string;
};
Expand All @@ -23,6 +25,7 @@ export class ExtendTeamsModalComponent implements OnInit {
protected errors: any[] = [];
protected ineligibleTeamIds: string[] = [];
protected isWorking = false;
protected maxCurrentSessionEnd?: DateTime;
protected modalTitle = "Extend Sessions";
protected apiTeams: Team[] = [];

Expand All @@ -41,10 +44,18 @@ export class ExtendTeamsModalComponent implements OnInit {
}

this.apiTeams = await firstValueFrom(this.teamService.search(this.teamIds).pipe(
map(teams => teams.filter(t => t.sessionEnd.getFullYear() !== 0))
map(teams => teams.filter(t => t.sessionEnd.getFullYear() !== 0)),
tap(teams => {
for (const team of teams) {
const sessionEnd = DateTime.fromJSDate(team.sessionEnd)
if (!this.maxCurrentSessionEnd || sessionEnd > this.maxCurrentSessionEnd) {
this.maxCurrentSessionEnd = sessionEnd;
}
}
})
));
this.ineligibleTeamIds = this.teamIds.filter(tId => this.apiTeams.map(t => t.teamId).indexOf(tId) < 0);
this.modalTitle = this.apiTeams.length === 1 ? `Extend Session: ${this.apiTeams[0].approvedName}` : "Extend Sessions";
this.modalTitle = this.apiTeams.length === 1 ? this.apiTeams[0].approvedName : "Multiple Sessions";
}

async extend(extensionDurationInMinutes: number) {
Expand All @@ -58,7 +69,7 @@ export class ExtendTeamsModalComponent implements OnInit {
await this.onExtend();

this.close();
this.toastService.showMessage(`Extended sessions by **${extensionDurationInMinutes}** minutes for **${result.teams.length}** teams.`);
this.toastService.showMessage(`Extended **${result.teams.length}** session(s) by **${extensionDurationInMinutes}** minutes.`);
}
catch (err: any) {
this.errors.push(err);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { DateTime } from 'luxon';
import { TeamSessionResetType } from '@/api/teams.models';
import { ApiError, SimpleEntity } from '@/api/models';
import { fa } from '@/services/font-awesome.service';
Expand Down Expand Up @@ -31,7 +32,7 @@ export interface TeamSessionResetRequest {
styleUrls: ['./game-center-team-context-menu.component.scss']
})
export class GameCenterTeamContextMenuComponent {
@Input() game?: { id: string; name: string; isSyncStart: boolean; maxTeamSize: number };
@Input() game?: { id: string; name: string; endsOn?: DateTime, isSyncStart: boolean; maxTeamSize: number };
@Input() team?: GameCenterTeamsResultsTeam;
@Output() error = new EventEmitter<string[]>();
@Output() teamUpdated = new EventEmitter<SimpleEntity>();
Expand Down Expand Up @@ -145,7 +146,8 @@ export class GameCenterTeamContextMenuComponent {
context: {
game: this.game,
teamIds: [team.id]
}
},
modalClasses: ["modal-lg"]
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ <h4>Team Management</h4>
<ng-container *ngIf="team.session.start">
<h4 class="mt-4 px-3">Session</h4>
<div class="form-group" *ngIf="!isExtending; else loading">
<label class="mb-0" for="duration-input">Extend</label>
<label class="mb-0" for="duration-input">Extension</label>
<div class="input-group">
<input id="duration-input" class="form-control" type="number"
placeholder="Extension duration (in minutes)" [(ngModel)]="durationExtensionInMinutes">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { DateTime } from 'luxon';
import { ExtendTeamsModalComponent } from '../../extend-teams-modal/extend-teams-modal.component';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { firstValueFrom } from 'rxjs';
import { TeamChallenge } from '@/api/player-models';
import { PlayerService } from '@/api/player.service';
import { fa } from '@/services/font-awesome.service';
import { GameCenterTeamsResultsTeam } from '../game-center.models';
import { AdminService } from '@/api/admin.service';
Expand All @@ -20,6 +19,7 @@ import { UpdatePlayerNameChangeRequest } from '@/api/admin.models';
export class GameCenterTeamDetailComponent implements OnInit {
game!: {
id: string;
endsOn?: DateTime;
maxTeamSize: number;
name: string;
};
Expand Down Expand Up @@ -47,7 +47,6 @@ export class GameCenterTeamDetailComponent implements OnInit {
constructor(
private adminService: AdminService,
private modalService: ModalConfirmService,
private playerService: PlayerService,
private teamService: TeamService,
private toastService: ToastService) { }

Expand Down Expand Up @@ -75,21 +74,6 @@ export class GameCenterTeamDetailComponent implements OnInit {
});
}

protected openExtendModal(gameId: string) {
this.modalService.openComponent({
content: ExtendTeamsModalComponent,
context: {
extensionInMinutes: 30,
game: {
id: gameId,
maxTeamSize: this.game.maxTeamSize,
name: this.game.name,
},
teamIds: [this.team.id]
},
modalClasses: ["modal-lg"]
});
}
protected async updateNameChangeRequest(playerId: string, overrideName: string, args: UpdatePlayerNameChangeRequest) {
if (!args.status) {
args.approvedName = overrideName || args.requestedName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
<div>Playing now</div>
<strong class="text-success d-block"
[tooltip]="'Ends ' + (team.session.end | epochMsToDateTime | friendlyDateAndTime) || ''">{{
team.session.end | epochMsToDateTime | datetimeToDate | dateToCountdown | async
team.session.end | epochMsToDateTime | datetimeToDate | dateToCountdown:120000 | async
}}</strong>
<div class="fs-08 text-muted">remaining</div>
</ng-container>
Expand Down Expand Up @@ -164,7 +164,7 @@
<div contextMenu>
<app-game-center-team-context-menu *ngIf="game" (error)="handleContextMenuError($event)"
[team]="team"
[game]="{ id: game.id, name: game.name, isSyncStart: game.requireSynchronizedStart, maxTeamSize: game.maxTeamSize }"
[game]="{ id: game.id, name: game.name, endsOn: ((game.gameEnd | dateToDateTime) || undefined), isSyncStart: game.requireSynchronizedStart, maxTeamSize: game.maxTeamSize }"
(teamUpdated)="load()"></app-game-center-team-context-menu>
</div>
</app-team-list-card>
Expand Down
Loading