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.22.1 #195

Merged
merged 2 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions projects/gameboard-mks/src/app/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { PlatformLocation } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { interval, Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';
import { catchError, map, switchMap } from 'rxjs/operators';
import { environment } from '../environments/environment';
import { ConsoleActionResponse, ConsoleActor, ConsoleRequest, ConsoleSummary, KeyValuePair, VmAnswer, VmOperation, VmOptions } from './api.models';
import { ConsoleActionResponse, ConsoleActor, ConsoleRequest, ConsoleSummary, KeyValuePair, VmAnswer, VmOptions } from './api.models';
import { UserActivityListenerEventType } from './components/user-activity-listener/user-activity-listener.component';

@Injectable({ providedIn: 'root' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ClipboardService } from '../../clipboard.service';
import { HubService } from '../../hub.service';
import { UserActivityListenerEventType } from '../user-activity-listener/user-activity-listener.component';
import { ConfigService } from '@/utility/config.service';
import { LogService } from '@/services/log.service';

@Component({
selector: 'app-console',
Expand Down Expand Up @@ -63,6 +64,7 @@ export class ConsoleComponent implements AfterViewInit, OnDestroy {
private config: ConfigService,
private injector: Injector,
private api: ApiService,
private logService: LogService,
private titleSvc: Title,
private clipSvc: ClipboardService,
private renderer: Renderer2
Expand Down Expand Up @@ -134,6 +136,7 @@ export class ConsoleComponent implements AfterViewInit, OnDestroy {
}

this.state = state;
this.logService.logInfo("State to", state);
this.shadowState(state);
this.isConnected = state === 'connected';

Expand Down Expand Up @@ -181,6 +184,8 @@ export class ConsoleComponent implements AfterViewInit, OnDestroy {
(info: ConsoleSummary) => this.create(info),
(err) => {
const msg = err?.error?.message || err?.message || err;
this.logService.logError(`Console reload error: ${msg || "[no error resolved]"}`);

this.changeState(
msg.match(/forbidden/i) ? 'forbidden' : 'failed'
);
Expand Down Expand Up @@ -338,12 +343,6 @@ export class ConsoleComponent implements AfterViewInit, OnDestroy {
}
}

@HostListener('window:blur', ['$event'])
onBlur(): void {
// don't set actor map on blur
// this.api.blur(this.request);
}

@HostListener('document:mouseup', ['$event'])
dragged(): void {
this.audiencePos = null;
Expand Down
6 changes: 3 additions & 3 deletions projects/gameboard-ui/src/app/api/challenges.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PlayerMode } from './player-models';

@Injectable({ providedIn: 'root' })
export class ChallengesService {
private _challengeDeployStateChanged$ = new Subject<string>();
private _challengeDeployStateChanged$ = new Subject<Challenge>();
public readonly challengeDeployStateChanged$ = this._challengeDeployStateChanged$.asObservable();

private _challengeGraded$ = new Subject<Challenge>();
Expand Down Expand Up @@ -67,13 +67,13 @@ export class ChallengesService {

public deploy(challenge: { id: string }): Observable<Challenge> {
return this.http.put<Challenge>(this.apiUrl.build("challenge/start"), challenge).pipe(tap(challenge => {
this._challengeDeployStateChanged$.next(challenge.id);
this._challengeDeployStateChanged$.next(challenge);
}));
}

public undeploy(challenge: { id: string }): Observable<Challenge> {
return this.http.put<Challenge>(this.apiUrl.build("challenge/stop"), challenge).pipe(tap(challenge => {
this._challengeDeployStateChanged$.next(challenge.id);
this._challengeDeployStateChanged$.next(challenge);
}));
}

Expand Down
14 changes: 6 additions & 8 deletions projects/gameboard-ui/src/app/api/team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ export class TeamService {
this._teamSessionEndedManually$.next(request.teamId);
}

public extendSession(model: SessionExtendRequest): Observable<void> {
return from(this.updateSession(model));
public async extendSession(model: SessionExtendRequest): Promise<void> {
await this.updateSession(model);
this._teamSessionExtended$.next([model.teamId]);
}

public resetSession(request: { teamId: string, resetType?: TeamSessionResetType }): Observable<void> {
Expand All @@ -97,11 +98,8 @@ export class TeamService {
}

private async updateSession(request: SessionExtendRequest | SessionEndRequest): Promise<void> {
await firstValueFrom(
this.http.put<any>(this.apiUrl.build("/team/session"), request).pipe(
tap(_ => this._teamSessionsChanged$.next([request.teamId])),
tap(_ => this._playerSessionChanged$.next(request.teamId)),
)
);
await firstValueFrom(this.http.put<any>(this.apiUrl.build("/team/session"), request));
this._playerSessionChanged$.next(request.teamId);
this._teamSessionsChanged$.next([request.teamId]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ <h3>Challenge Questions</h3>
</ng-container>
</div>

<div *ngIf="isMiniPlayerAvailable && isMiniPlayerSelected && challenge?.challengeDeployment"
<div *ngIf="isMiniPlayerAvailable && isMiniPlayerSelected && !isDeploying && !isUndeploying && challenge?.challengeDeployment"
class="mini-player-container">
<ng-container *ngTemplateOutlet="miniPlayer"></ng-container>
</div>
Expand All @@ -100,13 +100,13 @@ <h3>Challenge Questions</h3>
<ng-container *ngIf="challenge && !isDeploying && !isUndeploying">
<h5><ng-container *ngTemplateOutlet="challengeConsolesText"></ng-container></h5>

<div class="vms-container my-4">
<div *ngIf="challenge?.challengeDeployment?.vms?.length" class="vms-container my-4">
<ul class="d-flex flex-wrap">
<li *ngFor="let vm of challenge.challengeDeployment.vms">
<a class="btn btn-sm btn-dark mr-2 d-block" [href]="vmUrls[vm.id] | safeurl" target="_blank"
role="button">
<fa-icon [icon]="fa.computer"></fa-icon>
<span>{{vm.name}}</span>
<span> {{vm.name}}</span>
</a>
</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class PracticeChallengeStateSummaryComponent {

// update timers to accurately reflect the active challenge
this.msElapsed$ = this._timer$.pipe(
map(tick =>
map(() =>
this.userActivePracticeChallenge?.session.start ?
DateTime.now().diff(this.userActivePracticeChallenge.session.start).toMillis() :
undefined
Expand All @@ -82,10 +82,10 @@ export class PracticeChallengeStateSummaryComponent {
async extendSession(practiceChallenge: LocalActiveChallenge): Promise<void> {
this.isChangingSessionEnd = true;
const teamId = practiceChallenge.teamId;
await firstValueFrom(this.teamService.extendSession({
await this.teamService.extendSession({
teamId,
sessionEnd: new Date()
}));
});

this.isChangingSessionEnd = false;
this.showExtensionToast(DateTime.now().plus({ minutes: 60 }));
Expand Down
70 changes: 48 additions & 22 deletions projects/gameboard-ui/src/app/stores/active-challenges.store.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Injectable, OnDestroy } from "@angular/core";
import { createStore, withProps } from "@ngneat/elf";
import { DateTime } from "luxon";
import { Observable, Subject, Subscription, combineLatest, firstValueFrom, interval, map, merge, of, startWith, switchMap } from "rxjs";
import { Observable, Subject, Subscription, firstValueFrom, interval, merge, of, startWith, switchMap } from "rxjs";
import { LocalActiveChallenge } from "@/api/challenges.models";
import { SimpleEntity } from "@/api/models";
import { ChallengesService } from "@/api/challenges.service";
import { PlayerService } from "@/api/player.service";
import { Challenge } from "@/api/board-models";
import { TeamService } from "@/api/team.service";
import { UserService as LocalUserService } from "@/utility/user.service";
Expand Down Expand Up @@ -44,28 +43,23 @@ export class ActiveChallengesRepo implements OnDestroy {

constructor(
challengesService: ChallengesService,
playerService: PlayerService,
localUser: LocalUserService,
teamService: TeamService) {

this._subs.push(
combineLatest([
merge([
of(challengesService),
localUser.user$,
merge([
challengesService.challengeGraded$,
challengesService.challengeDeployStateChanged$,
playerService.playerSessionReset$,
teamService.playerSessionChanged$,
teamService.teamSessionsChanged$
challengesService.challengeDeployStateChanged$
])
]).pipe(
map(([challengesService]) => ({ challengesService })),
).subscribe(ctx => this._initState(ctx.challengesService, localUser.user$.value?.id || null)),
]).subscribe(() => this._initState(challengesService, localUser.user$.value?.id || null)),
interval(1000).subscribe(() => this.checkActiveChallengesForEnd()),
challengesService.challengeGraded$.subscribe(challenge => this.handleChallengeGraded(challenge)),
teamService.teamSessionEndedManually$.subscribe(tId => this.handleTeamSessionEndedManually(tId))

challengesService.challengeDeployStateChanged$.subscribe(challenge => this.handleChallengeDeployStateChanged(challenge)),
teamService.teamSessionEndedManually$.subscribe(tId => this.handleTeamSessionEndedManually(tId)),
teamService.teamSessionsChanged$.subscribe(tId => this._initState(challengesService, localUser.user$.value?.id || null))
);

this._initState(challengesService, localUser.user$.value?.id || null);
Expand All @@ -81,6 +75,15 @@ export class ActiveChallengesRepo implements OnDestroy {
return this.resolveActivePracticeChallenge(activeChallengesStore.value);
}

private buildChallengeDeployment(challenge: Challenge) {
return {
challengeId: challenge.id,
isDeployed: challenge.hasDeployedGamespace,
markdown: challenge.state.markdown || "",
vms: challenge.state.vms,
};
}

private checkActiveChallengesForEnd() {
const challenges = [...activeChallengesStore.state.practice];

Expand All @@ -96,14 +99,44 @@ export class ActiveChallengesRepo implements OnDestroy {
}
}

private handleChallengeDeployStateChanged(challengeDeployStateChange: Challenge) {
activeChallengesStore.update(state => {
const competitive = [...state.competition];
const practice = [...state.practice];

for (const challenge of competitive) {
if (challenge.id == challengeDeployStateChange.id) {
challenge.challengeDeployment = this.buildChallengeDeployment(challengeDeployStateChange);
}
}

for (const challenge of practice) {
if (challenge.id === challengeDeployStateChange.id) {
challenge.challengeDeployment = this.buildChallengeDeployment(challengeDeployStateChange);
}
}

return {
...state,
competition: [...competitive],
practice: [...practice]
};
});
}

private handleChallengeGraded(challenge: Challenge) {
// check if the graded challenge is the active practice challenge, and if it is, evaluate it for completeness
// and grading attempts, and notify appropriate subjects
const activePracticeChallenge = this.resolveActivePracticeChallenge(activeChallengesStore.state);
if (activePracticeChallenge?.challengeDeployment.challengeId === challenge.id) {
// no matter what, update the activeChallenge thing in state with the deploy state of the
// challenge's gamespace
activePracticeChallenge.challengeDeployment.isDeployed = challenge.state.isActive;
activePracticeChallenge.challengeDeployment = {
challengeId: challenge.id,
isDeployed: challenge.hasDeployedGamespace,
markdown: challenge.state.markdown || "",
vms: challenge.state.vms,
};
let removeChallengeFromState = false;

if (challenge.score >= challenge.points) {
Expand All @@ -128,13 +161,6 @@ export class ActiveChallengesRepo implements OnDestroy {
this.removeFromActiveWithPredicate(c => c.teamId == teamId);
}

private getAllChallenges() {
return [
...activeChallengesStore.state.competition,
...activeChallengesStore.state.practice
];
}

private removeFromActive(endedChallenge: Challenge | LocalActiveChallenge) {
this.removeFromActiveWithPredicate(c => c.id === endedChallenge.id);
}
Expand All @@ -151,7 +177,7 @@ export class ActiveChallengesRepo implements OnDestroy {

private async _initState(challengesService: ChallengesService, localUserId: string | null) {
if (!localUserId) {
activeChallengesStore.update(state => DEFAULT_STATE);
activeChallengesStore.update(() => DEFAULT_STATE);
return;
}

Expand Down
Loading