serval-admin: onboarding-requests: add export spreadsheet#3954
Conversation
There was a problem hiding this comment.
Pull request overview
Adds spreadsheet export capability to the Serval Administration → Onboarding Requests tab, and refactors a date-parsing helper into shared utilities for reuse across the client app.
Changes:
- Added CSV/TSV export UI and an export service to generate separated-value content and filenames.
- Extracted
parseDateintoapp/shared/utils.tsand updated existing Serval build report type interpretation to use it. - Added unit tests for
parseDateand the new onboarding-requests export service.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/utils.ts | Adds shared parseDate() utility. |
| src/SIL.XForge.Scripture/ClientApp/src/app/shared/utils.spec.ts | Adds tests covering parseDate() behavior. |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/serval-build-report.ts | Switches DTO type interpretation to use shared parseDate(). |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests.component.ts | Wires export actions into the onboarding requests UI. |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests.component.html | Adds export button + menu to trigger CSV/TSV downloads. |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests.component.scss | Styles the new filter/export bar and button group. |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests-export.service.ts | Implements CSV/TSV generation + filename construction for exports. |
| src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests-export.service.spec.ts | Adds unit tests for export formatting, fallbacks, and filename generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * Parses a value into a Date, returning undefined if the value is null, not a string/number, or not a valid date. | ||
| */ | ||
| export function parseDate(value: unknown): Date | undefined { | ||
| if (value instanceof Date) return value; |
| <button mat-raised-button color="primary" class="export-csv-button" (click)="exportCsv()"> | ||
| <mat-icon>download</mat-icon> | ||
| <span class="export-item-label"> | ||
| Export CSV | ||
| <app-info text="Download a .csv (comma-separated values) spreadsheet of the table data"></app-info> | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
This seems like the pattern we have for the other serval admin components
| <button mat-menu-item (click)="exportTsv()"> | ||
| <mat-icon>download</mat-icon> | ||
| <span class="export-item-label"> | ||
| Export TSV | ||
| <app-info text="Download a .tsv (tab-separated values) spreadsheet of the table data"></app-info> | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
This seems like the pattern we already have with other serval admin components.
| <button mat-raised-button color="primary" class="export-menu-button" [matMenuTriggerFor]="exportMenu"> | ||
| <mat-icon>arrow_drop_down</mat-icon> | ||
| </button> |
| requests.map(async request => { | ||
| const sfProjectId = request.submission.projectId; | ||
| const projectDoc = await this.servalAdministrationService.get(sfProjectId); | ||
| const projectShortName = projectDoc?.data?.shortName ?? sfProjectId; | ||
| return [ |
There was a problem hiding this comment.
I don't plan to optimize fetching projects in the Onboarding Requests export at this time. I set escapeFormulae to true.
| private async export(extension: 'csv' | 'tsv'): Promise<void> { | ||
| const requests = this.filteredRequests; | ||
| if (requests.length === 0) { | ||
| this.noticeService.show('No data to export.'); | ||
| return; | ||
| } | ||
|
|
||
| const content = | ||
| extension === 'csv' ? await this.exportService.createCsv(requests) : await this.exportService.createTsv(requests); | ||
| const mimeType = extension === 'csv' ? 'text/csv;charset=utf-8;' : 'text/tab-separated-values;charset=utf-8;'; | ||
| const blob = new Blob([content], { type: mimeType }); | ||
| saveAs(blob, this.exportService.exportFilename(extension)); | ||
| } |
There was a problem hiding this comment.
(global error handling)
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3954 +/- ##
==========================================
- Coverage 80.88% 80.88% -0.01%
==========================================
Files 634 635 +1
Lines 41036 41076 +40
Branches 6661 6693 +32
==========================================
+ Hits 33193 33223 +30
- Misses 6807 6815 +8
- Partials 1036 1038 +2 ☔ View full report in Codecov by Harness. |
RaymondLuong3
left a comment
There was a problem hiding this comment.
@RaymondLuong3 reviewed 8 files and all commit messages, made 3 comments, and resolved 2 discussions.
Reviewable status: all files reviewed, 5 unresolved discussions (waiting on marksvc).
src/SIL.XForge.Scripture/ClientApp/src/app/serval-administration/onboarding-requests/onboarding-requests-export.service.ts line 79 at r2 (raw file):
assigneeNames.get(request.assigneeId) ?? '', request.id, sfProjectId
It would be better if we could assign these entries to a model, and then have a helper function to extra the data in the correct order depending on the expected order of the output file. That way things are guaranteed to be in the correct order. This can come in a follow up PR if desired.
Code quote:
this.onboardingRequestService.getResolution(request.resolution).label,
request.submission.formData.name,
request.submission.formData.translationLanguageIsoCode,
projectShortName,
request.submission.formData.translationLanguageName,
assigneeNames.get(request.assigneeId) ?? '',
request.id,
sfProjectId| <button mat-raised-button color="primary" class="export-csv-button" (click)="exportCsv()"> | ||
| <mat-icon>download</mat-icon> | ||
| <span class="export-item-label"> | ||
| Export CSV | ||
| <app-info text="Download a .csv (comma-separated values) spreadsheet of the table data"></app-info> | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
This seems like the pattern we have for the other serval admin components
| <button mat-menu-item (click)="exportTsv()"> | ||
| <mat-icon>download</mat-icon> | ||
| <span class="export-item-label"> | ||
| Export TSV | ||
| <app-info text="Download a .tsv (tab-separated values) spreadsheet of the table data"></app-info> | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
This seems like the pattern we already have with other serval admin components.
41b12ae to
576191a
Compare
This patch adds an "Export CSV" button to the Serval Administration, Onboarding Requests tab. A similar button and feature can be found on the Serval Builds tab. The parseDate function was moved out into app/shared/utils.ts to be reused.
576191a to
0b0a367
Compare
This patch adds an "Export CSV" button to the Serval Administration,
Onboarding Requests tab. A similar button and feature can be found on
the Serval Builds tab.
The parseDate function was moved out into app/shared/utils.ts to be
reused.
Exporting the Onboarding Request data is helpful for our internal analyses.
Screenshot showing the "Export CSV" button, with "Export TSV" as an alternate option in the drop-down:

Screenshot showing Export button in context, on the Onboarding Requests tab:

Open in Devin Review
This change is