Skip to content

Commit

Permalink
v3.17.3-beta0 (#177)
Browse files Browse the repository at this point in the history
* Start on 170, resolve 248. Styling for ticket list.

* Fixed an issue that caused the player context menu to hide the 'start session' and 'ready player' options in some cases.

* More work on ticket label picker

* Wrap active teams at 3 cards (up from 2).

* Finish draft of #170. Improvements to support ticket list styling.

* Remove unused button

* Slightly clarified labeling on 'import automatic bonuses' component.

* Fix bug that prevented refresh of challenge list in game editor until the spec was manipulated on the board. Specs now have semi-random x/y values to prevent overlapping new specs. Resolves #341.

* Initial draft of #400 (reload external game host on API recover).

* NPM audit

* Pull logging. Resolve #400.

* Migrate #400 fix from test

* Fix iframe refresh check bug

* Fix iframe load handler bug

* Minor cleanup

* Fix iframe scroll bug

* Throttle app notifications by title over 2 seconds.

* Refactor of how app announcements work. Resolves #210.
  • Loading branch information
sei-bstein committed Mar 21, 2024
1 parent 19345d1 commit bcf112f
Show file tree
Hide file tree
Showing 57 changed files with 668 additions and 209 deletions.
26 changes: 13 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@microsoft/signalr": "^7.0.5",
"@microsoft/signalr": "^7.0.14",
"@ngneat/elf": "^2.3.1",
"@ngneat/elf-cli-ng": "^1.0.0",
"@ngneat/elf-devtools": "^1.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { UserService } from '../../api/user.service';
import { firstValueFrom } from 'rxjs';
import { LogService } from '@/services/log.service';
import { ToastService } from '@/utility/services/toast.service';
import { AdminService } from '@/api/admin.service';

@Component({
selector: 'app-announce',
Expand All @@ -24,7 +25,7 @@ export class AnnounceComponent {
protected isLoading = false;

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

Expand All @@ -34,9 +35,9 @@ export class AnnounceComponent {
}

try {
await firstValueFrom(this.api.announce({
teamId: this.teamId,
message: this.message
await firstValueFrom(this.adminService.sendAnnouncement({
contentMarkdown: this.message,
teamId: this.teamId
}));

this.message = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.team-card {
flex-basis: 32%;
flex-basis: 30%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
</div>

<div class="d-flex full-width mt-2">
<button type="button" class="btn btn-info d-block flex-grow-1" (disabled)="!textPlaceholder"
<button type="button" class="btn btn-info d-block flex-grow-1 mr-1" (disabled)="!textPlaceholder"
(click)="handlePasteExampleConfigClick()">
Paste example configuration
Paste this example configuration
</button>
<app-confirm-button [disabled]="!hasBonusesConfigured" class="d-block mx-2 flex-grow-1"
componentContainerClass="full-width" btnClass="btn btn-danger"
<app-confirm-button *ngIf="configuredBonusCount > 0" [disabled]="configuredBonusCount == 0"
class="d-block mx-1 flex-grow-1" componentContainerClass="full-width" btnClass="btn btn-danger"
(confirm)="handleDeleteClick(gameId)">
Delete bonuses configuration
</app-confirm-button>
<button type="button" class="btn btn-success d-block flex-grow-1" [disabled]="!yamlIn"
<button type="button" class="btn btn-success d-block ml-1 flex-grow-1" [disabled]="!yamlIn"
(click)="handleImportClick()">
Import bonus configuration
Import this configuration
</button>
</div>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class GameBonusesConfigComponent implements OnInit, OnChanges {
@Output() update = new EventEmitter<string>();

protected config?: GameScoringConfig;
protected configuredBonusCount = 0;
protected errors: any[] = [];
protected hasBonusesConfigured = false;
protected isLoading = false;
protected textPlaceholder: string | null = null;
protected textPlaceholderRows = 8;
Expand Down Expand Up @@ -51,7 +51,6 @@ export class GameBonusesConfigComponent implements OnInit, OnChanges {

async ngOnChanges(changes: SimpleChanges): Promise<void> {
if (this.gameId) {

await this.loadConfig(this.gameId);
}
}
Expand Down Expand Up @@ -99,6 +98,7 @@ export class GameBonusesConfigComponent implements OnInit, OnChanges {
this.isLoading = true;
await this.scoringService.deleteGameAutoChallengeBonuses(gameId);
this.yamlIn = undefined;
this.toastsService.showMessage("Deleted all automatic bonuses for this game.");

//reload
await this.loadConfig(gameId);
Expand All @@ -108,8 +108,8 @@ export class GameBonusesConfigComponent implements OnInit, OnChanges {

private bindConfig(config: GameScoringConfig) {
this.config = config;
this.hasBonusesConfigured = config.specs.some(c => c.possibleBonuses.length);
if (this.hasBonusesConfigured) {
this.configuredBonusCount = config.specs.filter(s => s.possibleBonuses.length).length;
if (this.configuredBonusCount) {
this.yamlIn = this.renderConfigAsYaml(config);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,17 @@ export class GameMapperComponent implements OnInit, AfterViewInit {

this.created$ = api.selected$.pipe(
filter(s => !this.list.find(i => i.externalId === s.externalId)),
map(s => ({ ...s, gameId: this.game.id, points: 0, x: .5, y: .5, r: .015 } as NewSpec)),
map(s => {
// compute semi-random coordinates for the new spec so it doesn't sit on top of other
// added specs
const randomX = Math.random() * 0.5 + 0.25;
const randomY = Math.random() * 0.5 + 0.25;
return { ...s, gameId: this.game.id, points: 1, x: randomX, y: randomY, r: .015 } as NewSpec;
}),
switchMap(s => api.create(s)),
tap(r => this.list.push(r)),
tap(r => this.addedCount += 1)
tap(r => this.addedCount += 1),
tap(s => this.refresh())
);

this.updated$ = this.updating$.pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="text-content">
<h6>You've got a new API key!</h6>
<p>The key is: <em class="visible-key">{{visibleKey}}</em></p>
<p>Be sure to record this somewhere; you'll never be able to view it again.</p>
<p>Be sure to record this somewhere safe; you'll never be able to view it again.</p>
</div>
<div class="buttons">
<button type="button" class="btn btn-secondary" (click)="copyKey(visibleKey)">
Expand All @@ -27,7 +27,9 @@ <h6>You've got a new API key!</h6>
<table>
<tr>
<th>Key</th>
<th>Issued on</th>
<th>
<ng-container *ngIf="apiKeys.length">Issued on</ng-container>
</th>
<th>Expires on</th>
<th></th>
</tr>
Expand Down Expand Up @@ -58,7 +60,7 @@ <h6>You've got a new API key!</h6>
[(ngModel)]="newApiKey.name">
</td>
<td>
<span>{{dummyGeneratedOn | shortdate}} @ {{dummyGeneratedOn | friendlyTime}}</span>
<span *ngIf="apiKeys.length" class="text-muted">--</span>
</td>
<td>
<div class="input-group">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export class UserApiKeysComponent implements OnInit, OnDestroy {

protected errors: string[] = [];
protected apiKeys$?: Observable<ApiKeyViewModel[]>;
protected dummyGeneratedOn = new Date();
protected minDate = new Date();
protected newApiKey!: NewApiKey;
protected visibleKey: string = '';
Expand Down
29 changes: 29 additions & 0 deletions projects/gameboard-ui/src/app/api-status.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, Subject, finalize, tap } from 'rxjs';
import { ApiStatus } from './api/api-status.service';

// not sure how much sense this makes, but I'm trying it. Basically, it feels funky
// to ask consumers to inject an HttpInterceptor rather than a service (even though they're
// closely related and both injectable), so I just gave this thing an observable. It
// can be read by any consumer if they want, but this lets them ignore the implementation detail of
// HttpInterceptor and just consume the service, independent of what's powering it.
@Injectable()
export class ApiStatusInterceptor implements HttpInterceptor {
private _apiStatus$ = new Subject<ApiStatus>();
public apiStatus$ = this._apiStatus$.asObservable();

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let finalResponse: HttpEvent<any> | HttpErrorResponse;

return next.handle(req).pipe(
tap({
next: successResponse => finalResponse = successResponse,
error: err => finalResponse = err
}),
finalize(() => {
this._apiStatus$.next(finalResponse instanceof HttpErrorResponse ? "down" : "up");
})
);
}
}
6 changes: 6 additions & 0 deletions projects/gameboard-ui/src/app/api/admin.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,9 @@ export interface GetSiteOverviewStatsResponse {
activeCompetitiveTeams: number;
registeredUsers: number;
}

export interface SendAnnouncement {
contentMarkdown: string;
title?: string;
teamId?: string;
}
6 changes: 5 additions & 1 deletion projects/gameboard-ui/src/app/api/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map, tap } from 'rxjs';
import { ApiUrlService } from '@/services/api-url.service';
import { GetAppActiveChallengesResponse, GetAppActiveTeamsResponse, GetSiteOverviewStatsResponse } from './admin.models';
import { GetAppActiveChallengesResponse, GetAppActiveTeamsResponse, GetSiteOverviewStatsResponse, SendAnnouncement } from './admin.models';
import { PlayerMode } from './player-models';
import { DateTime } from 'luxon';

Expand Down Expand Up @@ -42,4 +42,8 @@ export class AdminService {
getOverallSiteStats(): Observable<GetSiteOverviewStatsResponse> {
return this.http.get<GetSiteOverviewStatsResponse>(this.apiUrl.build("admin/stats"));
}

sendAnnouncement(announcement: SendAnnouncement): Observable<void> {
return this.http.post<void>(this.apiUrl.build("admin/announce"), announcement);
}
}
27 changes: 27 additions & 0 deletions projects/gameboard-ui/src/app/api/api-status.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Inject, Injectable } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpInterceptor } from '@angular/common/http';
import { Observable, debounceTime, distinctUntilChanged } from 'rxjs';
import { ApiStatusInterceptor } from '@/api-status.interceptor';

export type ApiStatus = "up" | "down";

@Injectable({ providedIn: 'root' })
export class ApiStatusService {
private _apiStatusInterceptor: ApiStatusInterceptor;
public status$: Observable<ApiStatus>;

constructor(@Inject(HTTP_INTERCEPTORS) allHttpInterceptors: HttpInterceptor[]) {
const apiStatusInterceptor = allHttpInterceptors.find(i => i instanceof ApiStatusInterceptor);
if (!apiStatusInterceptor)
throw new Error("Couldn't resolve the api status interceptor.");

this._apiStatusInterceptor = apiStatusInterceptor as ApiStatusInterceptor;
this.status$ = this
._apiStatusInterceptor
.apiStatus$
.pipe(
distinctUntilChanged(),
debounceTime(5000)
);
}
}
5 changes: 2 additions & 3 deletions projects/gameboard-ui/src/app/api/report.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import { UserReport, PlayerReport, SponsorReport, GameSponsorReport, ChallengeRe

@Injectable({ providedIn: 'root' })
export class ReportService {

url = '';

constructor(
private http: HttpClient,
private config: ConfigService
private config: ConfigService,
private http: HttpClient
) {
this.url = config.apphost + 'api';
}
Expand Down
8 changes: 0 additions & 8 deletions projects/gameboard-ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// 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 { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
Expand All @@ -17,7 +16,6 @@ import { ApiModule } from './api/api.module';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './utility/auth.interceptor';
import { ConfigService } from './utility/config.service';
import { UserService as CurrentUserService } from './utility/user.service';
import { UtilityModule } from './utility/utility.module';
Expand Down Expand Up @@ -49,7 +47,6 @@ import { SystemNotificationsModule } from './system-notifications/system-notific
BrowserModule,
BrowserAnimationsModule,
FormsModule,
HttpClientModule,
AppRoutingModule,
ApiModule,
FontAwesomeModule,
Expand All @@ -64,11 +61,6 @@ import { SystemNotificationsModule } from './system-notifications/system-notific
ProgressbarModule.forRoot(),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: loadSettings,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<ng-container *ngIf="isDevMode">
<app-status-light [state]="gameHubStatusLightState" [tooltip]="tooltip"></app-status-light>
<app-status-light [state]="supportHubStatusLightState"></app-status-light>
<app-status-light [state]="userHubStatusLightState" [tooltip]="userHubTooltip"></app-status-light>
</ng-container>

0 comments on commit bcf112f

Please sign in to comment.