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.13.0 #152

Merged
merged 74 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
6fa8a98
Initial work on loading external game data from API.
sei-bstein Nov 3, 2023
b023a97
Finishing initial draft of loading local storage values from the API.
sei-bstein Nov 3, 2023
a51a83b
Remove handling for non-external games from external load page.
sei-bstein Nov 6, 2023
5d8065d
Removed unused model data from GameStartState entity.
sei-bstein Nov 7, 2023
a82c29c
Fix bug that caused 'time remaining' to appear even after the session…
sei-bstein Nov 8, 2023
022fab6
Modify the Game Map Editor to allow for easier dropzone use. Added a …
sei-bstein Nov 13, 2023
bb57817
Merge branch 'next' into issue/289
sei-bstein Nov 13, 2023
f44ad46
Removed automatic redirect from the game page to the external game pa…
sei-bstein Nov 14, 2023
df6ff11
Merge branch 'next' into issue/289
sei-bstein Nov 14, 2023
b361532
Add external game admin screen.
sei-bstein Nov 14, 2023
033afd3
Rough draft of external game admin.
sei-bstein Nov 16, 2023
124152b
Update external game admin every 30 sec. Use predeploy status to disa…
sei-bstein Nov 16, 2023
ebca7be
Restyle card widths in external game admin.
sei-bstein Nov 16, 2023
1ef3931
Refinement of external game admin form.
sei-bstein Nov 16, 2023
d3e08c0
Tweaks to deploy controls on external team admin.
sei-bstein Nov 17, 2023
1d60ad6
Additional features on external game admin
sei-bstein Nov 17, 2023
4abfb0b
invert boolean on deploy button in external game admin.
sei-bstein Nov 17, 2023
3ba0816
Improve date format on session date for external game admin.
sei-bstein Nov 17, 2023
516ddf5
Correct date translation bugs in external game admin
sei-bstein Nov 17, 2023
5caa678
Add pre-deploy per teamId
sei-bstein Nov 17, 2023
df82d95
MIsc bug fixes to external game deploy
sei-bstein Nov 17, 2023
8f23312
Styling to external game launch
sei-bstein Nov 17, 2023
d9b58fb
Add new SyncStartGameStarting event.
sei-bstein Nov 17, 2023
8053ad8
Fix a bug that caused the external game deploy screen to interpret se…
sei-bstein Nov 20, 2023
33f5bdc
Better styling on external admin, fix a date bug.
sei-bstein Nov 20, 2023
d0f192b
Bump refresh interval on external games admin to 15sec for demo.
sei-bstein Nov 20, 2023
a44a1bb
Partial fix for GBAPI#281 - added more explanatory error message when…
sei-bstein Nov 20, 2023
163e439
Relable predeployment 'deploy'
sei-bstein Nov 20, 2023
46a5f29
Fix a weird bug with per-team deploy.
sei-bstein Nov 20, 2023
5bc1261
Added team-wide ready for external game admin.
sei-bstein Nov 21, 2023
a3bcc18
Clean up and bump external game admin to 30 sec.
sei-bstein Nov 21, 2023
64851da
Error div can now redirect on dismiss.
sei-bstein Nov 21, 2023
8b42e2c
Added new component to the game page which links to the active extern…
sei-bstein Nov 22, 2023
7494c7b
Fixed an issue that caused the external game loading page to fail to …
sei-bstein Nov 22, 2023
740a79c
New strategy for url building the external game link
sei-bstein Nov 22, 2023
1607d6b
More router service tree building stuff.
sei-bstein Nov 22, 2023
c01b66b
Move external game link banner
sei-bstein Nov 22, 2023
3ff3d02
Fix bug with external game link thing
sei-bstein Nov 22, 2023
40e40be
Fixed an issue that prevented activation of the external sync start g…
sei-bstein Nov 22, 2023
e14057a
Update to external game admin to manage deploy per team
sei-bstein Nov 27, 2023
5c252f6
Minor cleanup and new field on SyncStartGameStartedState
sei-bstein Nov 27, 2023
5eea08c
Add challenge feedback in practice mode. Addresses GBAPI#242. Also li…
sei-bstein Nov 28, 2023
707bdde
Merge branch 'issue/242' into next
sei-bstein Nov 28, 2023
6b5dce4
Initial draft of enrollment report by game tab.
sei-bstein Nov 28, 2023
c21312a
Added By Game tab to the enrollment report. resolves GBAPI#308.
sei-bstein Nov 29, 2023
0e3ef35
Remove 'no value' indicator from game fields in reports.
sei-bstein Nov 29, 2023
09e5a10
Restyle sponsor player count modal in enrollment report.
sei-bstein Nov 29, 2023
5128c65
Add new date fields to By Game tab of Enrollment report
sei-bstein Nov 29, 2023
e4fc434
Improve layout of sponsor dialog in By Game tab.
sei-bstein Nov 29, 2023
fd8cf35
More styling for By Games modal.
sei-bstein Nov 29, 2023
b1996b4
Added proper tracking of selected tab for enrollment report. Fixed a …
sei-bstein Nov 29, 2023
49918af
Add paging the By Games tab of enrollment report.
sei-bstein Nov 29, 2023
980fdb3
More bug fixes to the enrollment report by game.
sei-bstein Nov 29, 2023
7d17e09
Fixing directory structure of enrollment report
sei-bstein Nov 29, 2023
92eb039
Pull the trend tab of the enrollment report into its own component fo…
sei-bstein Nov 29, 2023
18ca491
Refactor of enrollment report to deal with weird paging issues.
sei-bstein Nov 30, 2023
4eabfbb
Don't reload enrollment report stats on paging/tab change.
sei-bstein Nov 30, 2023
5edfba2
Guard against enrollment report results with no records.
sei-bstein Nov 30, 2023
d206022
Cleanup
sei-bstein Nov 30, 2023
92a4b83
Clarify automatic bonus display in admin
sei-bstein Nov 30, 2023
79dc186
Fixed a bug that caused null reference in the enrollment report
sei-bstein Nov 30, 2023
2ef5667
Fixed an issue that caused SignalR hubs not to send messages in some …
sei-bstein Nov 30, 2023
0d66cd2
Troubleshooting a signalR connection error.
sei-bstein Nov 30, 2023
d86ae0f
Improve logging for gamehub.
sei-bstein Nov 30, 2023
13edcaa
Fixed a bug that caused the client not to join the game hub until the…
sei-bstein Nov 30, 2023
2c74299
Make non-creator responses in support tickets light bg. Resolves GBAP…
sei-bstein Dec 1, 2023
2c5bcc2
Initial work on sys notifications
sei-bstein Dec 1, 2023
cfefa88
More work on sys notifications.
sei-bstein Dec 4, 2023
c01e846
Finishes GPAPI#169.
sei-bstein Dec 4, 2023
f57e166
Additional work on sys notifications.
sei-bstein Dec 4, 2023
c1fd648
Finished GBAPI#299.
sei-bstein Dec 5, 2023
948a762
Show text if there are no notifications in the db.
sei-bstein Dec 5, 2023
0e9855d
Remove teamId from getGamePlaySTate check.
sei-bstein Dec 5, 2023
0a8fc9d
Add better error handling for an in-progress external game.
sei-bstein Dec 5, 2023
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
Original file line number Diff line number Diff line change
@@ -1,8 +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. -->

<div class="mb-4">
<h1 class="admin-header mb-0">Administration</h1>
<div class="mb-4 container">
<h1 class="admin-header mb-0 pl-0">Administration</h1>
</div>

<div class="container mb-4 pb-4">
Expand All @@ -13,6 +13,7 @@ <h1 class="admin-header mb-0">Administration</h1>
<a class="btn btn-link" routerLinkActive="active" [routerLink]="['registrar', 'players']">Players</a>
<a class="btn btn-link" routerLinkActive="active" [routerLink]="['registrar', 'sponsors']">Sponsors</a>
<a class="btn btn-link" routerLinkActive="active" [routerLink]="['support']">Challenges</a>
<a class="btn btn-link" routerLinkActive="active" [routerLink]="['notifications']">Notifications</a>
<a class="btn btn-link" routerLinkActive="active" [routerLink]="['report']">Reports</a>
</nav>
<main>
Expand Down
26 changes: 23 additions & 3 deletions projects/gameboard-ui/src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ import { ChallengeObserverComponent } from './challenge-observer/challenge-obser
import { ChallengeReportComponent } from './challenge-report/challenge-report.component';
import { ChallengeSpecEditorComponent } from './components/challenge-spec-editor/challenge-spec-editor.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { DeploymentAdminTeamContextMenuComponent } from './components/deployment-admin-team-context-menu/deployment-admin-team-context-menu.component';
import { ExternalGameAdminComponent } from './components/external-game-admin/external-game-admin.component';
import { ExternalGameAdminPlayerContextMenuComponent } from './components/external-game-admin-player-context-menu/external-game-admin-player-context-menu.component';
import { ExternalGamePlayerStatusToStatusLightPipe } from './pipes/external-game-player-status-to-status-light.pipe';
import { ExternalTeamToChallengeCreatedPipe } from './pipes/external-team-to-challenge-created.pipe';
import { ExternalTeamChallengesToIsPredeployablePipe } from './pipes/external-team-challenges-to-is-predeployable.pipe';
import { ExternalSpecIdToChallengePipe } from './pipes/external-specid-to-challenge.pipe';
import { ExternalGamePlayerStatusToFriendlyPipe } from './pipes/external-game-player-status-to-friendly.pipe';
import { FeedbackReportComponent } from './feedback-report/feedback-report.component';
import { GameBonusesConfigComponent } from './components/game-bonuses-config/game-bonuses-config.component';
import { GameDesignerComponent } from './game-designer/game-designer.component';
Expand All @@ -37,11 +45,13 @@ import { ReportPageComponent } from './report-page/report-page.component';
import { SpecBrowserComponent } from './spec-browser/spec-browser.component';
import { SponsorBrowserComponent } from './sponsor-browser/sponsor-browser.component';
import { SupportReportLegacyComponent } from './support-report-legacy/support-report-legacy.component';
import { SystemNotificationsModule } from '@/system-notifications/system-notifications.module';
import { TeamAdminContextMenuComponent } from './components/team-admin-context-menu/team-admin-context-menu.component';
import { TeamObserverComponent } from './team-observer/team-observer.component';
import { UserApiKeysComponent } from './user-api-keys/user-api-keys.component';
import { UserRegistrarComponent } from './user-registrar/user-registrar.component';
import { UserReportComponent } from './user-report/user-report.component';
import { AdminSystemNotificationsComponent } from '@/system-notifications/components/admin-system-notifications/admin-system-notifications.component';

@NgModule({
declarations: [
Expand All @@ -52,6 +62,14 @@ import { UserReportComponent } from './user-report/user-report.component';
ChallengeReportComponent,
ChallengeSpecEditorComponent,
DashboardComponent,
DeploymentAdminTeamContextMenuComponent,
ExternalGameAdminComponent,
ExternalGameAdminPlayerContextMenuComponent,
ExternalGamePlayerStatusToStatusLightPipe,
ExternalSpecIdToChallengePipe,
ExternalGamePlayerStatusToFriendlyPipe,
ExternalTeamToChallengeCreatedPipe,
ExternalTeamChallengesToIsPredeployablePipe,
FeedbackReportComponent,
GameDesignerComponent,
GameEditorComponent,
Expand All @@ -76,7 +94,6 @@ import { UserReportComponent } from './user-report/user-report.component';
UserApiKeysComponent,
UserRegistrarComponent,
UserReportComponent,
ChallengeSpecEditorComponent,
],
imports: [
CommonModule,
Expand All @@ -87,6 +104,7 @@ import { UserReportComponent } from './user-report/user-report.component';
{ path: '', pathMatch: 'full', redirectTo: 'dashboard' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'designer/:id', component: GameEditorComponent },
{ path: "game/:gameId/external", component: ExternalGameAdminComponent },
{
path: "practice", component: PracticeComponent, children: [
{ path: "", pathMatch: "full", redirectTo: "settings" },
Expand All @@ -97,15 +115,16 @@ import { UserReportComponent } from './user-report/user-report.component';
{ path: 'registrar/users', component: UserRegistrarComponent, title: "Admin | Users" },
{ path: 'registrar/players', component: PlayerNamesComponent },
{ path: 'registrar/:id', component: PlayerRegistrarComponent },
{ path: 'observer/challenges/:id', component: ChallengeObserverComponent },
{ path: 'observer/challenges/:id', component: ChallengeObserverComponent, title: "Admin | Observe" },
{ path: 'observer/teams/:id', component: TeamObserverComponent },
{ path: 'report', component: ReportPageComponent },
{ path: 'report', component: ReportPageComponent, title: "Admin | Reports" },
{ path: 'report/users', component: UserReportComponent },
{ path: 'report/sponsors', component: PlayerSponsorReportComponent },
{ path: 'report/challenges', component: ChallengeReportComponent },
{ path: 'report/feedback', component: FeedbackReportComponent },
{ path: 'report/support', component: SupportReportLegacyComponent },
{ path: 'report/participation', component: ParticipationReportComponent },
{ path: "notifications", component: AdminSystemNotificationsComponent },
{ path: 'support', component: ChallengeBrowserComponent }
]
},
Expand All @@ -114,6 +133,7 @@ import { UserReportComponent } from './user-report/user-report.component';
ApiModule,
UtilityModule,
SponsorsModule,
SystemNotificationsModule,
]
})
export class AdminModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ <h6>Automatic Bonuses</h6>
<div class="bonuses-container mt-4">
<ol>
<li *ngFor="let bonus of scoringConfig.possibleBonuses">
{{bonus.description}} ({{ bonus.pointValue }})
{{bonus.description}} ({{ bonus.pointValue }} points)
</li>
</ol>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="btn-group" *ngIf="team" dropdown>
<button [id]="'external-game-admin-context-menu-' + team.id" dropdownToggle type="button"
class="btn ctx-menu-button rounded-circle" aria-controls="dropdown-basic"
tooltip="Additional actions for this team">
<fa-icon [icon]="fa.ellipsisVertical"></fa-icon>
</button>
<ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
<li role="menuitem">
<button class="dropdown-item btn" (click)="handleTeamReadyStatusToggled(team, isReady)">
{{ (isReady ? "Unready" : "Ready") + " Team \"" + team.name + "\"" }}
</button>
</li>
<li role="menuitem">
<button class="dropdown-item btn" (click)="handleCopyClicked(team.id)">Copy team ID</button>
</li>
</ul>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { fa } from '@/services/font-awesome.service';
import { SimpleEntity } from '@/api/models';
import { SyncStartService } from '@/services/sync-start.service';
import { firstValueFrom } from 'rxjs';
import { ClipboardService } from '@/utility/services/clipboard.service';
import { ToastService } from '@/utility/services/toast.service';

@Component({
selector: 'app-deployment-admin-team-context-menu',
templateUrl: './deployment-admin-team-context-menu.component.html',
styleUrls: ['./deployment-admin-team-context-menu.component.scss']
})
export class DeploymentAdminTeamContextMenuComponent {
@Input() team?: SimpleEntity;
@Input() isReady = false;
@Output() teamReadyStateToggled = new EventEmitter<{ teamId: string, isReady: boolean }>();

protected fa = fa;

constructor(
private clipboardService: ClipboardService,
private syncStartService: SyncStartService,
private toastService: ToastService) { }

protected handleCopyClicked(text: string) {
this.clipboardService.copy(text);
this.toastService.showMessage(`Copied team ID "${text}" to your clipboard.`);
}

protected async handleTeamReadyStatusToggled(team: SimpleEntity, isReady: boolean) {
await firstValueFrom(this.syncStartService.updateTeamReadyState(team.id, { isReady: !isReady }));
this.teamReadyStateToggled.emit({ teamId: team.id, isReady: !isReady });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div class="btn-group" *ngIf="player" dropdown>
<button [id]="'external-game-admin-context-menu-' + player.id" dropdownToggle type="button"
class="btn ctx-menu-button rounded-circle" aria-controls="dropdown-basic"
tooltip="{{ 'Additional actions for ' + player.name }}">
<fa-icon [icon]="fa.ellipsisVertical"></fa-icon>
</button>
<ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
<li role="menuitem">
<button class="dropdown-item btn"
(click)="togglePlayerReadyStatusClicked(player.id, player.isSyncStartReady)">
{{ (player.isSyncStartReady ? "Unready " : "Ready ") + player.name }}
</button>
</li>
<li role="menuitem">
<button class="dropdown-item btn" (click)="copy(player.id, 'player ID')">Copy player ID</button>
</li>
</ul>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { fa } from "@/services/font-awesome.service";
import { PlayerService } from '@/api/player.service';
import { firstValueFrom } from 'rxjs';
import { ClipboardService } from '@/utility/services/clipboard.service';
import { ToastService } from '@/utility/services/toast.service';
import { SyncStartService } from '@/services/sync-start.service';

export interface ExternalGameAdminPlayerContextMenuData {
id: string;
name: string;
isSyncStartReady: boolean;
}

@Component({
selector: 'app-external-game-admin-player-context-menu',
templateUrl: './external-game-admin-player-context-menu.component.html',
styleUrls: ['./external-game-admin-player-context-menu.component.scss']
})
export class ExternalGameAdminPlayerContextMenuComponent implements OnChanges {
@Input() player?: ExternalGameAdminPlayerContextMenuData;
@Output() playerReadyStateChanged = new EventEmitter<{ playerId: string, isReady: boolean }>();

protected fa = fa;

constructor(
private clipboardService: ClipboardService,
private syncStartService: SyncStartService,
private toastService: ToastService) { }

ngOnChanges(changes: SimpleChanges): void {
if (!this.player)
return;
}

protected copy(text: string, description: string) {
this.clipboardService.copy(text);
this.toastService.showMessage(`Copied ${description} ${text} to your clipboard.`);
}

protected async togglePlayerReadyStatusClicked(playerId: string, currentIsReady: boolean) {
const isReady = !currentIsReady;
await firstValueFrom(this.syncStartService.updatePlayerReadyState(playerId, { isReady }));
this.playerReadyStateChanged.emit({ playerId, isReady });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<div class="external-game-admin-component py-2" *ngIf="ctx$ | async as context; else loading">
<h2 class="text-upper fs-10 m-0 p-0">Deployment</h2>
<h1>{{context.game.name}}</h1>
<h3 class="sync-session-date fs-12" *ngIf="sessionDateDescription">
{{sessionDateDescription}}
</h3>
<hr class="light" />

<div class="w-100">
<app-error-div [errors]="errors"></app-error-div>

<div *ngIf="context.teams.length"
class="global-controls-container my-2 d-flex align-items-center justify-content-end">
<app-confirm-button btnClass="btn btn-lg btn-info" [disabled]="!canDeploy"
(confirm)="handlePreDeployAllClick(context.game.id)"
[tooltip]="context.overallDeployStatus == 'deploying' ? 'Resources are being deployed for this game. Hang tight...' : ''">
Deploy All
</app-confirm-button>
</div>

<alert *ngIf="context.hasNonStandardSessionWindow" type="warning" class="my-4">
<h3>Warning</h3>

<p>
This game has been deployed and started, but there are multiple "start" and "end" dates
among challenges and player sessions. You may need to use the session align tool to ensure
these line up (coming soon™).
</p>
</alert>

<div *ngIf="context.teams.length; else noTeams"
class="teams-data-container my-4 d-flex flex-wrap justify-content-start">
<div *ngFor="let team of context.teams" class="card team-card mr-2 mb-2">
<div class="team-avatars-container">
<app-player-avatar-list [players]="team.players"></app-player-avatar-list>
</div>
<div class="card-body">
<div class="d-flex align-items-center">
<h4 class="card-title flex-grow w-100">{{team.name}}</h4>
<app-deployment-admin-team-context-menu class="d-block" [team]="team" [isReady]="team.isReady"
(teamReadyStateToggled)="handleTeamReadyStateChanged(team.id)"></app-deployment-admin-team-context-menu>
</div>

<div class="card-text mb-4">
<div class="card-section mb-4">
<h5>Players</h5>

<ul class="player-list">
<li *ngFor="let player of team.players" class="d-flex align-items-center w-100">
<app-player-avatar [player]="player" size="tiny"></app-player-avatar>
<app-status-light [state]="player.status | externalGamePlayerStatusToStatusLight"
[tooltip]="player.status | externalGamePlayerStatusToFriendly"></app-status-light>
<div class="flex-grow-1">{{player.name}}</div>
<app-external-game-admin-player-context-menu
(playerReadyStateChanged)="handlePlayerReadyStateChanged($event.playerId)"
[player]="{ id: player.id, name: player.name, isSyncStartReady: player.status == 'ready'}"></app-external-game-admin-player-context-menu>
</li>
</ul>
</div>

<div class="card-section">
<h5>Challenges</h5>

<table class="w-100">
<col>
<col>
<col>

<thead class="thead-light">
<tr>
<th></th>
<th>Created</th>
<th>Deployed</th>
</tr>
</thead>

<tbody>
<tr *ngFor="let spec of context.specs">
<td>
<span placement="top"
[tooltip]="(spec.id | externalSpecIdToChallenge:team.challenges)?.id">
{{spec.name}}
</span>
</td>
<td>
<span
[class.not-deployed]="!(spec.id | externalSpecIdToChallenge:team.challenges)!.challengeCreated"
[class.text-success]="(spec.id | externalSpecIdToChallenge:team.challenges)!.challengeCreated">
{{((spec.id |
externalSpecIdToChallenge:team.challenges)!.challengeCreated ? "Yes" :
"No")}}
</span>
</td>
<td>
<span
[class.not-deployed]="!(spec.id | externalSpecIdToChallenge:team.challenges)!.gamespaceDeployed"
[class.text-success]="(spec.id | externalSpecIdToChallenge:team.challenges)!.gamespaceDeployed">
{{((spec.id |
externalSpecIdToChallenge:team.challenges)!.gamespaceDeployed
? "Yes" : "No")}}
</span>
</td>
</tr>
</tbody>
</table>

</div>

</div>
<button type="button" href="#" class="btn btn-info"
(click)="handlePreDeployTeamClick(context.game.id, team.id)"
[class.disabled]="team.deployStatus != 'notStarted' || !canDeploy">Deploy</button>
</div>
</div>
</div>
</div>
</div>

<ng-template #noChallenges>
<em>No challenges deployed</em>
</ng-template>

<ng-template #noTeams>
<div class="text-center my-4">
<em>No players or teams are currently enrolled in this game.</em>
</div>
</ng-template>

<ng-template #loading>
<div class="w-100 d-flex align-items-center justify-content-center">
<app-spinner>
<h1>Loading game data...</h1>
</app-spinner>
</div>
</ng-template>
Loading
Loading