Skip to content

Commit 3939f5b

Browse files
authored
SF-3593 Show build executionData on project Serval admin page (#3503)
1 parent 1f65cc9 commit 3939f5b

19 files changed

+781
-70
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/event-metrics/event-metric-dialog.component.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@ <h3>{{ t("event_type") }}</h3>
99
<pre>{{ data.eventType }}</pre>
1010
</div>
1111
<h3>{{ t("parameters") }}</h3>
12-
<div>
13-
<pre>{{ data.payload | json }}</pre>
14-
</div>
12+
<app-json-viewer [data]="data.payload"></app-json-viewer>
1513
@if (data.result != null) {
1614
<h3>{{ t("result") }}</h3>
17-
<div>
18-
<pre>{{ data.result }}</pre>
19-
</div>
15+
<app-json-viewer [data]="data.result"></app-json-viewer>
2016
}
2117
@if (data.exception != null) {
2218
<h3>{{ t("exception") }}</h3>

src/SIL.XForge.Scripture/ClientApp/src/app/event-metrics/event-metric-dialog.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
44
import { TranslocoModule } from '@ngneat/transloco';
55
import { I18nService } from 'xforge-common/i18n.service';
66
import { UICommonModule } from 'xforge-common/ui-common.module';
7+
import { JsonViewerComponent } from '../shared/json-viewer/json-viewer.component';
78
import { EventMetric } from './event-metric';
89

910
@Component({
1011
selector: 'app-event-metric-dialog',
1112
templateUrl: './event-metric-dialog.component.html',
1213
styleUrls: ['./event-metric-dialog.component.scss'],
13-
imports: [CommonModule, MatDialogModule, TranslocoModule, UICommonModule]
14+
imports: [CommonModule, MatDialogModule, TranslocoModule, UICommonModule, JsonViewerComponent]
1415
})
1516
export class EventMetricDialogComponent {
1617
constructor(

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/job-events-dialog.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ <h4>{{ getEventTypeLabel(event.eventType) }}</h4>
2929
@if (hasPayload(event.payload)) {
3030
<div class="event-payload">
3131
<strong>Parameters:</strong>
32-
<pre>{{ event.payload | json }}</pre>
32+
<app-json-viewer [data]="event.payload"></app-json-viewer>
3333
</div>
3434
}
3535

3636
@if (event.result != null) {
3737
<div class="event-result">
3838
<strong>Result:</strong>
39-
<pre>{{ event.result }}</pre>
39+
<app-json-viewer [data]="event.result"></app-json-viewer>
4040
</div>
4141
}
4242
</div>

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/job-events-dialog.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
44
import { I18nService } from 'xforge-common/i18n.service';
55
import { UICommonModule } from 'xforge-common/ui-common.module';
66
import { EventMetric } from '../event-metrics/event-metric';
7+
import { JsonViewerComponent } from '../shared/json-viewer/json-viewer.component';
78

89
interface JobEventsDialogData {
910
projectId: string;
@@ -28,7 +29,7 @@ const EVENT_TYPE_LABELS: {
2829
selector: 'app-job-events-dialog',
2930
templateUrl: './job-events-dialog.component.html',
3031
styleUrls: ['./job-events-dialog.component.scss'],
31-
imports: [CommonModule, MatDialogModule, UICommonModule]
32+
imports: [CommonModule, MatDialogModule, UICommonModule, JsonViewerComponent]
3233
})
3334
export class JobEventsDialogComponent {
3435
constructor(

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.html

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -111,37 +111,56 @@ <h2>Downloads</h2>
111111
</table>
112112
</div>
113113

114-
<h2>Last Draft Settings</h2>
115-
<h3>Training books</h3>
116-
@for (book of trainingBooksByProject; track book.source) {
117-
<div class="training">
118-
<p>{{ book.source }}</p>
119-
<p class="training-source-range">{{ book.scriptureRange }}</p>
120-
</div>
121-
} @empty {
122-
<p class="training">None</p>
123-
}
124-
<h3>Training data files</h3>
125-
<p>{{ trainingFiles.join(", ") || "None" }}</p>
126-
<h3>Translation books</h3>
127-
@for (book of translationBooksByProject; track book.source) {
128-
<div class="translation">
129-
<p>{{ book.source }}</p>
130-
<p class="translation-range">{{ book.scriptureRange }}</p>
131-
</div>
132-
} @empty {
133-
<p class="translation">None</p>
114+
@if (rawLastCompletedBuild?.executionData?.warnings?.length > 0) {
115+
<h2>Serval warnings from last build</h2>
116+
@for (warning of rawLastCompletedBuild.executionData.warnings; track $index) {
117+
<p>
118+
<app-notice icon="warning" mode="fill-dark" type="warning">{{ warning }}</app-notice>
119+
</p>
120+
}
134121
}
135122

136-
@if (draftJob$ | async; as draftJob) {
137-
<h3>Diagnostic Information</h3>
138-
<app-draft-information [draftJob]="draftJob"></app-draft-information>
139-
}
123+
<h2>Additional Information</h2>
140124

141-
<h2>Raw Draft Configuration</h2>
142-
<pre class="raw-draft-config">
143-
@for (key of keys(draftConfig ?? {}); track key) {{{ key }}: <strong>{{
144-
key === 'servalConfig' ? draftConfig![key] : stringify(draftConfig![key])
145-
}}</strong>
146-
}
147-
</pre>
125+
<mat-accordion>
126+
<mat-expansion-panel>
127+
<mat-expansion-panel-header>Last Draft Settings</mat-expansion-panel-header>
128+
<h3>Training books</h3>
129+
@for (book of trainingBooksByProject; track book.source) {
130+
<div class="training">
131+
<p>{{ book.source }}</p>
132+
<p class="training-source-range">{{ book.scriptureRange }}</p>
133+
</div>
134+
} @empty {
135+
<p class="training">None</p>
136+
}
137+
<h3>Training data files</h3>
138+
<p>{{ trainingFiles.join(", ") || "None" }}</p>
139+
<h3>Translation books</h3>
140+
@for (book of translationBooksByProject; track book.source) {
141+
<div class="translation">
142+
<p>{{ book.source }}</p>
143+
<p class="translation-range">{{ book.scriptureRange }}</p>
144+
</div>
145+
} @empty {
146+
<p class="translation">None</p>
147+
}
148+
</mat-expansion-panel>
149+
150+
@if (draftJob$ | async; as draftJob) {
151+
<mat-expansion-panel>
152+
<mat-expansion-panel-header>Diagnostic Information</mat-expansion-panel-header>
153+
<app-draft-information [draftJob]="draftJob"></app-draft-information>
154+
</mat-expansion-panel>
155+
}
156+
157+
<mat-expansion-panel>
158+
<mat-expansion-panel-header>Raw Scripture Forge Draft Configuration</mat-expansion-panel-header>
159+
<app-json-viewer [data]="draftConfig ?? {}"></app-json-viewer>
160+
</mat-expansion-panel>
161+
162+
<mat-expansion-panel>
163+
<mat-expansion-panel-header>Raw Serval Build Info</mat-expansion-panel-header>
164+
<app-json-viewer [data]="rawLastCompletedBuild ?? {}"></app-json-viewer>
165+
</mat-expansion-panel>
166+
</mat-accordion>

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.scss

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,7 @@
1414
}
1515
}
1616

17-
.raw-draft-config {
18-
font-family: monospace;
19-
}
20-
2117
.zip-progress {
2218
display: flex;
2319
align-items: center;
2420
}
25-
26-
pre {
27-
white-space: pre-wrap;
28-
}

src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-project.component.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Router } from '@angular/router';
55
import { saveAs } from 'file-saver';
66
import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
77
import { DraftConfig, TranslateSource } from 'realtime-server/lib/esm/scriptureforge/models/translate-config';
8-
import { catchError, lastValueFrom, Observable, of, Subscription, switchMap, throwError } from 'rxjs';
8+
import { catchError, firstValueFrom, lastValueFrom, Observable, of, Subscription, switchMap, throwError } from 'rxjs';
99
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
1010
import { DataLoadingComponent } from 'xforge-common/data-loading-component';
1111
import { I18nService } from 'xforge-common/i18n.service';
@@ -18,6 +18,7 @@ import { WriteStatusComponent } from 'xforge-common/write-status/write-status.co
1818
import { ParatextService } from '../core/paratext.service';
1919
import { SFProjectService } from '../core/sf-project.service';
2020
import { BuildDto } from '../machine-api/build-dto';
21+
import { JsonViewerComponent } from '../shared/json-viewer/json-viewer.component';
2122
import { MobileNotSupportedComponent } from '../shared/mobile-not-supported/mobile-not-supported.component';
2223
import { NoticeComponent } from '../shared/notice/notice.component';
2324
import { SharedModule } from '../shared/shared.module';
@@ -56,7 +57,8 @@ function projectType(project: TranslateSource | SFProjectProfile): string {
5657
UICommonModule,
5758
DraftInformationComponent,
5859
MobileNotSupportedComponent,
59-
WriteStatusComponent
60+
WriteStatusComponent,
61+
JsonViewerComponent
6062
]
6163
})
6264
export class ServalProjectComponent extends DataLoadingComponent implements OnInit {
@@ -84,6 +86,7 @@ export class ServalProjectComponent extends DataLoadingComponent implements OnIn
8486
draftConfig: Object | undefined;
8587
draftJob$: Observable<BuildDto | undefined> = new Observable<BuildDto | undefined>();
8688
lastCompletedBuild: BuildDto | undefined;
89+
rawLastCompletedBuild: any;
8790
zipSubscription: Subscription | undefined;
8891

8992
constructor(
@@ -204,8 +207,11 @@ export class ServalProjectComponent extends DataLoadingComponent implements OnIn
204207
}),
205208
quietTakeUntilDestroyed(this.destroyRef)
206209
)
207-
.subscribe((build: BuildDto | undefined) => {
210+
.subscribe(async (build: BuildDto | undefined) => {
208211
this.lastCompletedBuild = build;
212+
if (build?.id != null) {
213+
this.rawLastCompletedBuild = await firstValueFrom(this.draftGenerationService.getRawBuild(build.id));
214+
}
209215
});
210216
}
211217

@@ -288,23 +294,6 @@ export class ServalProjectComponent extends DataLoadingComponent implements OnIn
288294
this.checkUpdateStatus(updateTaskPromise);
289295
}
290296

291-
keys(obj: Object): string[] {
292-
return Object.keys(obj);
293-
}
294-
295-
stringify(value: any): string {
296-
if (Array.isArray(value)) {
297-
return this.arrayToString(value);
298-
}
299-
return JSON.stringify(value, (_key, value) => (Array.isArray(value) ? this.arrayToString(value) : value), 2);
300-
}
301-
302-
arrayToString(value: any): string {
303-
const isObject = typeof value[0] === 'object';
304-
const contents = isObject ? value.map(x => this.stringify(x)).join(', ') : value.join(', ');
305-
return '[' + contents + ']';
306-
}
307-
308297
private checkUpdateStatus(updatePromise: Promise<void>): void {
309298
this.updateState = ElementState.Submitting;
310299
updatePromise
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@use '@angular/material' as mat;
2+
3+
@mixin color($theme) {
4+
$is-dark: mat.get-theme-type($theme) == dark;
5+
6+
@if $is-dark {
7+
--json-viewer-key-color: #9cdcfe; // Light blue for property names
8+
--json-viewer-string-color: #ce9178; // Light orange for strings
9+
--json-viewer-number-color: #b5cea8; // Light green for numbers
10+
--json-viewer-boolean-color: #569cd6; // Medium blue for booleans
11+
--json-viewer-null-color: #808080; // Gray for null
12+
--json-viewer-brace-color: #d4d4d4; // Light gray for braces/brackets
13+
--json-viewer-punctuation-color: #cccccc; // Light gray for colons/commas
14+
} @else {
15+
--json-viewer-key-color: #0451a5; // Blue for property names
16+
--json-viewer-string-color: #a31515; // Red for strings
17+
--json-viewer-number-color: #098658; // Green for numbers
18+
--json-viewer-boolean-color: #0000ff; // Blue for booleans
19+
--json-viewer-null-color: #808080; // Gray for null
20+
--json-viewer-brace-color: #000000; // Black for braces/brackets
21+
--json-viewer-punctuation-color: #808080; // Gray for colons/commas
22+
}
23+
}
24+
25+
@mixin theme($theme) {
26+
@if mat.theme-has($theme, color) {
27+
@include color($theme);
28+
}
29+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@for (token of tokens; track $index) {
2+
@switch (token.type) {
3+
@case ("indent") {
4+
<span class="json-indent">{{ token.value }}</span>
5+
}
6+
@case ("key") {
7+
<span class="json-key">{{ token.value }}</span>
8+
}
9+
@case ("colon") {
10+
<span class="json-colon">{{ token.value }} </span>
11+
}
12+
@case ("string") {
13+
<span class="json-string">{{ token.value }}</span>
14+
}
15+
@case ("number") {
16+
<span class="json-number">{{ token.value }}</span>
17+
}
18+
@case ("boolean") {
19+
<span class="json-boolean">{{ token.value }}</span>
20+
}
21+
@case ("null") {
22+
<span class="json-null">{{ token.value }}</span>
23+
}
24+
@case ("brace-open") {
25+
<span class="json-brace">{{ token.value }}</span>
26+
}
27+
@case ("brace-close") {
28+
<span class="json-brace">{{ token.value }}</span>
29+
}
30+
@case ("bracket-open") {
31+
<span class="json-bracket">{{ token.value }}</span>
32+
}
33+
@case ("bracket-close") {
34+
<span class="json-bracket">{{ token.value }}</span>
35+
}
36+
@case ("comma") {
37+
<span class="json-comma">{{ token.value }}</span>
38+
}
39+
@case ("newline") {
40+
<br />
41+
}
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
:host {
2+
display: block;
3+
border-radius: 8px;
4+
overflow-x: auto;
5+
padding: 16px;
6+
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
7+
font-size: 14px;
8+
line-height: 1.6;
9+
white-space: nowrap;
10+
11+
// JSON syntax highlighting using CSS custom properties
12+
.json-key {
13+
color: var(--json-viewer-key-color);
14+
font-weight: 500;
15+
}
16+
17+
.json-string {
18+
color: var(--json-viewer-string-color);
19+
}
20+
21+
.json-number {
22+
color: var(--json-viewer-number-color);
23+
}
24+
25+
.json-boolean {
26+
color: var(--json-viewer-boolean-color);
27+
}
28+
29+
.json-null {
30+
color: var(--json-viewer-null-color);
31+
font-style: italic;
32+
}
33+
34+
.json-brace,
35+
.json-bracket {
36+
color: var(--json-viewer-brace-color);
37+
font-weight: 600;
38+
}
39+
40+
.json-colon,
41+
.json-comma {
42+
color: var(--json-viewer-punctuation-color);
43+
}
44+
45+
.json-indent {
46+
white-space: pre;
47+
}
48+
}

0 commit comments

Comments
 (0)