-
{{
- challengeName || game.name }}
+
+ {{ challengeName || game.name }}
+
diff --git a/projects/gameboard-ui/src/app/reports/components/challenge-or-game-field/challenge-or-game-field.component.ts b/projects/gameboard-ui/src/app/reports/components/challenge-or-game-field/challenge-or-game-field.component.ts
index 298f8b9f..da6d7b21 100644
--- a/projects/gameboard-ui/src/app/reports/components/challenge-or-game-field/challenge-or-game-field.component.ts
+++ b/projects/gameboard-ui/src/app/reports/components/challenge-or-game-field/challenge-or-game-field.component.ts
@@ -10,5 +10,6 @@ export class ChallengeOrGameFieldComponent {
@Input() challengeName?: string;
@Input() game?: ReportGame;
@Input() disableLinks = false;
+ @Input() mainLabelClass = "";
@Input() fontSize: "small" | "large" = "small";
}
diff --git a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html
index b059acc3..c314ba81 100644
--- a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html
+++ b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html
@@ -21,15 +21,16 @@
-
+
+
-
-
- {{ record.challengeSpec.name }}
-
+
-
+ {{record.challengeSpec.name}}
+
{{ record.distinctPlayerCount }}
diff --git a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.ts b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.ts
index 7d49b3c8..0376a27a 100644
--- a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.ts
+++ b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.ts
@@ -7,6 +7,8 @@ import { ChallengesReportService } from '../challenges-report.service';
import { MultiSelectQueryParamModel } from '@/core/models/multi-select-query-param.model';
import { SimpleEntity } from '@/api/models';
import { DateRangeQueryParamModel } from '@/core/models/date-range-query-param.model';
+import { ModalConfirmService } from '@/services/modal-confirm.service';
+import { SpecQuestionPerformanceModalComponent } from '../../spec-question-performance-modal/spec-question-performance-modal.component';
export interface ChallengesReportContext {
isLoading: boolean,
@@ -56,10 +58,20 @@ export class ChallengesReportComponent extends ReportComponentBase({
+ content: SpecQuestionPerformanceModalComponent,
+ context: { specId: spec.id },
+ modalClasses: ["modal-lg", "modal-dialog-centered"]
+ });
+ }
+
protected async updateView(parameters: ChallengesReportFlatParameters): Promise {
if (!this.challengesReportService)
return { metaData: await firstValueFrom(this.reportsService.getReportMetaData(ReportKey.ChallengesReport)) };
diff --git a/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html
new file mode 100644
index 00000000..e14f4ff5
--- /dev/null
+++ b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+ Question
+ Point Value
+ Submissions
+ Correct
+
+
+
+
+
+
+ {{question.prompt}}
+ {{question.hint}}
+
+
+ {{ question.pointValue | number:"1.0-2" }}
+
+
+ {{question.countSubmitted}}
+
+
+ {{question.countCorrect}}
+ 0 && question.countCorrect > 0">
+
+ ({{ ((question.countCorrect / question.countSubmitted) * 100) | number:"1.0-2" }}%)
+
+
+
+
+
+
+
+
+
+ Loading question performance...
+
diff --git a/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.scss b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.scss
new file mode 100644
index 00000000..80536a68
--- /dev/null
+++ b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.scss
@@ -0,0 +1,4 @@
+td {
+ border-bottom: dashed 1px gray;
+ padding: 4px 0;
+}
diff --git a/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.ts b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.ts
new file mode 100644
index 00000000..fc5ea870
--- /dev/null
+++ b/projects/gameboard-ui/src/app/reports/components/spec-question-performance-modal/spec-question-performance-modal.component.ts
@@ -0,0 +1,22 @@
+import { GetChallengeSpecQuestionPerformanceResult } from '@/api/spec-models';
+import { SpecService } from '@/api/spec.service';
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-spec-question-performance-modal',
+ templateUrl: './spec-question-performance-modal.component.html',
+ styleUrls: ['./spec-question-performance-modal.component.scss']
+})
+export class SpecQuestionPerformanceModalComponent implements OnInit {
+ specId?: string;
+ protected context?: GetChallengeSpecQuestionPerformanceResult;
+
+ constructor(private specService: SpecService) { }
+
+ async ngOnInit() {
+ if (!this.specId)
+ throw new Error("Requires a spec.");
+
+ this.context = await this.specService.getQuestionPerformance(this.specId);
+ }
+}
diff --git a/projects/gameboard-ui/src/app/reports/reports.module.ts b/projects/gameboard-ui/src/app/reports/reports.module.ts
index 4c0b4030..beab52dc 100644
--- a/projects/gameboard-ui/src/app/reports/reports.module.ts
+++ b/projects/gameboard-ui/src/app/reports/reports.module.ts
@@ -49,6 +49,7 @@ import { SiteUsagePlayerListComponent } from './components/reports/site-usage-re
import { SiteUsageReportSponsorsModalComponent } from './components/reports/site-usage-report/site-usage-report-sponsors-modal/site-usage-report-sponsors-modal.component';
import { SiteUsageReportChallengesListComponent } from './components/reports/site-usage-report/site-usage-report-challenges-list/site-usage-report-challenges-list.component';
import { SortHeaderComponent } from './components/sort-header/sort-header.component';
+import { SpecQuestionPerformanceModalComponent } from './components/spec-question-performance-modal/spec-question-performance-modal.component';
@NgModule({
declarations: [
@@ -96,7 +97,8 @@ import { SortHeaderComponent } from './components/sort-header/sort-header.compon
SiteUsagePlayerListComponent,
SiteUsageReportSponsorsModalComponent,
SiteUsageReportChallengesListComponent,
- SortHeaderComponent
+ SortHeaderComponent,
+ SpecQuestionPerformanceModalComponent
],
imports: [
CommonModule,
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.html b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.html
similarity index 100%
rename from projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.html
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.html
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss
similarity index 100%
rename from projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.ts b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.ts
similarity index 100%
rename from projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.ts
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.ts
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.html
similarity index 100%
rename from projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.html
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.scss b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.scss
similarity index 100%
rename from projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.scss
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.scss
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.ts b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.ts
similarity index 93%
rename from projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.ts
rename to projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.ts
index 9776d5ec..c9010580 100644
--- a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.ts
+++ b/projects/gameboard-ui/src/app/scoreboard/components/scoreboard/scoreboard.component.ts
@@ -4,13 +4,11 @@ import { ScoringService } from '@/services/scoring/scoring.service';
import { ScoreboardData, ScoreboardDataTeam } from '@/services/scoring/scoring.models';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { ScoreboardTeamDetailModalComponent } from '../scoreboard-team-detail-modal/scoreboard-team-detail-modal.component';
-import { UnsubscriberService } from '@/services/unsubscriber.service';
@Component({
selector: 'app-scoreboard',
templateUrl: './scoreboard.component.html',
styleUrls: ['./scoreboard.component.scss'],
- providers: [UnsubscriberService]
})
export class ScoreboardComponent implements OnInit, OnDestroy {
@Input() gameId?: string;
@@ -29,8 +27,7 @@ export class ScoreboardComponent implements OnInit, OnDestroy {
constructor(
private modalConfirmService: ModalConfirmService,
- private scoreService: ScoringService,
- private unsub: UnsubscriberService) { }
+ private scoreService: ScoringService) { }
async ngOnInit() {
if (!this.gameId)
diff --git a/projects/gameboard-ui/src/app/game/pipes/manual-bonuses-to-tooltip.pipe.ts b/projects/gameboard-ui/src/app/scoreboard/pipes/challenge-bonuses-to-tooltip.ts
similarity index 100%
rename from projects/gameboard-ui/src/app/game/pipes/manual-bonuses-to-tooltip.pipe.ts
rename to projects/gameboard-ui/src/app/scoreboard/pipes/challenge-bonuses-to-tooltip.ts
diff --git a/projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts b/projects/gameboard-ui/src/app/scoreboard/pipes/score-to-tooltip.pipe.ts
similarity index 100%
rename from projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts
rename to projects/gameboard-ui/src/app/scoreboard/pipes/score-to-tooltip.pipe.ts
diff --git a/projects/gameboard-ui/src/app/scoreboard/scoreboard.module.ts b/projects/gameboard-ui/src/app/scoreboard/scoreboard.module.ts
new file mode 100644
index 00000000..4807e92d
--- /dev/null
+++ b/projects/gameboard-ui/src/app/scoreboard/scoreboard.module.ts
@@ -0,0 +1,27 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { CoreModule } from '@/core/core.module';
+
+import { ChallengeBonusesToTooltip } from './pipes/challenge-bonuses-to-tooltip';
+import { ScoreboardComponent } from './components/scoreboard/scoreboard.component';
+import { ScoreboardTeamDetailModalComponent } from './components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component';
+import { ScoreToTooltipPipe } from './pipes/score-to-tooltip.pipe';
+
+const PUBLIC_DECLARATIONS = [
+ ScoreboardComponent,
+ ScoreboardTeamDetailModalComponent
+];
+
+@NgModule({
+ declarations: [
+ ...PUBLIC_DECLARATIONS,
+ ChallengeBonusesToTooltip,
+ ScoreToTooltipPipe
+ ],
+ imports: [
+ CommonModule,
+ CoreModule
+ ],
+ exports: PUBLIC_DECLARATIONS
+})
+export class ScoreboardModule { }
diff --git a/projects/gameboard-ui/src/app/services/router.service.ts b/projects/gameboard-ui/src/app/services/router.service.ts
index 089549c5..6696f48f 100644
--- a/projects/gameboard-ui/src/app/services/router.service.ts
+++ b/projects/gameboard-ui/src/app/services/router.service.ts
@@ -59,6 +59,14 @@ export class RouterService implements OnDestroy {
return `/user/${localUserId}/certificates/${mode}/${challengeSpecOrGameId}`;
}
+ public getObserveChallengeUrl(gameId: string, challengeId: string) {
+ return `/admin/observer/challenges/${gameId}?search=${challengeId}`;
+ }
+
+ public getObserveTeamsUrl(gameId: string, teamId: string) {
+ return `admin/observer/teams/${gameId}?search=${teamId}`;
+ }
+
public getPracticeAreaWithSearchUrl(searchTerm: string) {
return this.router.createUrlTree(["practice"], { queryParams: { term: searchTerm } });
}
@@ -108,11 +116,11 @@ export class RouterService implements OnDestroy {
}
public buildVmConsoleUrl(vm: VmState, isPractice = false) {
- if (!vm || !vm.isolationId || !vm.name) {
- throw new Error(`Can't launch a VM console without an isolationId and a name.`);
+ if (!vm || !vm.isolationId) {
+ throw new Error(`Can't launch a VM console without an isolationId.`);
}
- return `${this.config.mkshost}?f=1&s=${vm.isolationId}&v=${vm.name}${isPractice ? "&l=true" : ""}`;
+ return `${this.config.mkshost}?f=1&s=${vm.isolationId}&v=${vm.name || 'Console'}${isPractice ? "&l=true" : ""}`;
}
public toVmConsole(vm: VmState) {
diff --git a/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts b/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts
index 83e865e0..368380e2 100644
--- a/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts
+++ b/projects/gameboard-ui/src/app/support/components/ticket-support-tools/ticket-support-tools.component.ts
@@ -20,6 +20,21 @@ export interface TicketSupportToolsContext {
styleUrls: ['./ticket-support-tools.component.scss'],
template: `
+
+ Observe
+
+
View the challenge's state
@@ -57,6 +72,8 @@ export class TicketSupportToolsComponent implements OnInit {
protected hasGameContext = false;
protected challengeStateUrl?: string;
protected gameboardUrl?: string;
+ protected observeChallengeUrl?: string;
+ protected observeTeamUrl?: string;
protected playerAdminUrl?: string;
constructor(
@@ -68,6 +85,11 @@ export class TicketSupportToolsComponent implements OnInit {
if (this.context?.challenge?.id) {
this.challengeStateUrl = this.context?.challenge ? this.routerService.getAdminChallengeUrl(this.context?.challenge.id) : undefined;
+
+ if (this.context?.game?.id) {
+ this.observeChallengeUrl = this.routerService.getObserveChallengeUrl(this.context.game.id, this.context.challenge.id);
+ this.observeTeamUrl = this.routerService.getObserveTeamsUrl(this.context.game.id, this.context.team.id);
+ }
}
if (this.context?.game) {
diff --git a/projects/gameboard-ui/src/app/support/ticket-list/ticket-list.component.html b/projects/gameboard-ui/src/app/support/ticket-list/ticket-list.component.html
index 9f95a441..0a8fb245 100644
--- a/projects/gameboard-ui/src/app/support/ticket-list/ticket-list.component.html
+++ b/projects/gameboard-ui/src/app/support/ticket-list/ticket-list.component.html
@@ -39,9 +39,8 @@ {{ctx.canManage ? 'Tickets' : 'My Tickets'}}
-
+
diff --git a/projects/gameboard-ui/src/styles.scss b/projects/gameboard-ui/src/styles.scss
index c17d1702..a3514d99 100644
--- a/projects/gameboard-ui/src/styles.scss
+++ b/projects/gameboard-ui/src/styles.scss
@@ -75,6 +75,16 @@ markdown ol li {
margin-left: 1rem;
}
+select option {
+ .group-parent {
+ font-weight: bold;
+ }
+
+ .group-child {
+ padding-left: 12px;
+ }
+}
+
app-root {
display: block;
min-height: 100vh;
@@ -248,6 +258,10 @@ table.gameboard-table {
margin: 0;
}
+.li-style-type-circle {
+ list-style-type: disc;
+}
+
.form-group {
padding: 1.5rem;
margin-bottom: 0.5rem;
@@ -402,6 +416,10 @@ th[align="left"] {
min-height: 85vh;
}
+.flex-basis-25 {
+ flex-basis: 25%;
+}
+
.flex-basis-50 {
flex-basis: 50%;
}