Skip to content

Commit eb00beb

Browse files
added import/export (#655)
1 parent 1597ade commit eb00beb

40 files changed

+1645
-73
lines changed

src/app/app.module.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ import { SystemRolesComponent } from './components/admin-app/admin-roles/roles/r
107107
import { AdminRolesComponent } from './components/admin-app/admin-roles/admin-roles.component';
108108
import { NameDialogComponent } from './components/shared/name-dialog/name-dialog.component';
109109
import { TeamRolesComponent } from './components/admin-app/admin-roles/team-roles/team-roles.component';
110+
import { AdminAppTemplateExportComponent } from './components/admin-app/admin-app-template-export/admin-app-template-export.component';
111+
import { AdminAppTemplateImportComponent } from './components/admin-app/admin-app-template-import/admin-app-template-import.component';
112+
import { AdminAppViewExportComponent } from './components/admin-app/admin-app-view-export/admin-app-view-export.component';
113+
import { AdminAppViewImportComponent } from './components/admin-app/admin-app-view-import/admin-app-view-import.component';
110114

111115
@NgModule({
112116
exports: [
@@ -148,6 +152,7 @@ import { TeamRolesComponent } from './components/admin-app/admin-roles/team-role
148152
ScrollingModule,
149153
],
150154
imports: [],
155+
declarations: [],
151156
})
152157
export class AngularMaterialModule {}
153158

@@ -198,6 +203,10 @@ export const myCustomTooltipDefaults: MatTooltipDefaultOptions = {
198203
SystemRolesComponent,
199204
AdminRolesComponent,
200205
TeamRolesComponent,
206+
AdminAppTemplateExportComponent,
207+
AdminAppTemplateImportComponent,
208+
AdminAppViewExportComponent,
209+
AdminAppViewImportComponent,
201210
],
202211
imports: [
203212
BrowserModule,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!--
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
-->
5+
6+
<h2>Export Application Templates</h2>
7+
8+
<form *ngIf="!(hasErrors$ | async)" [formGroup]="form" (ngSubmit)="export()">
9+
<mat-form-field *ngIf="isArchiveable">
10+
<mat-label>Archive Type</mat-label>
11+
<mat-select formControlName="archiveType">
12+
<mat-option
13+
*ngFor="let archiveType of archiveTypes"
14+
[value]="archiveType"
15+
>
16+
{{ archiveType }}
17+
</mat-option>
18+
</mat-select>
19+
</mat-form-field>
20+
21+
<div>
22+
<mat-form-field floatLabel="always" appearance="none">
23+
<mat-label>Include Icons</mat-label>
24+
<mat-slide-toggle
25+
formControlName="includeIcons"
26+
matTooltip="Include icon files in the exported archive"
27+
></mat-slide-toggle>
28+
<textarea matInput hidden></textarea>
29+
</mat-form-field>
30+
31+
<mat-form-field floatLabel="always" appearance="none">
32+
<mat-label>Embed Icons</mat-label>
33+
<mat-slide-toggle
34+
formControlName="embedIcons"
35+
matTooltip="Embed included Icons into exported JSON as data URIs"
36+
></mat-slide-toggle>
37+
<textarea matInput hidden></textarea>
38+
</mat-form-field>
39+
</div>
40+
41+
<div class="d-flex justify-content-around">
42+
<button
43+
type="submit"
44+
color="primary"
45+
mat-raised-button
46+
[disabled]="!form.valid || loading"
47+
>
48+
{{
49+
loading
50+
? 'Exporting....'
51+
: 'Export ' + (ids.length == 0 ? 'All' : '(' + ids.length + ')')
52+
}}
53+
</button>
54+
55+
<button type="reset" color="primary" mat-raised-button (click)="cancel()">
56+
Cancel
57+
</button>
58+
</div>
59+
</form>
60+
61+
<div *ngIf="hasErrors$ | async">
62+
<h3 class="text-danger">
63+
Some errors occurred during export. Check errors.txt in the archive for
64+
details.
65+
</h3>
66+
67+
<div class="d-flex justify-content-around">
68+
<button mat-raised-button color="primary" (click)="cancel()">OK</button>
69+
</div>
70+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
*/
5+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
*/
5+
6+
import { ComponentFixture, TestBed } from '@angular/core/testing';
7+
8+
import { AdminAppTemplateExportComponent } from './admin-app-template-export.component';
9+
10+
describe('AdminAppTemplateExportComponent', () => {
11+
let component: AdminAppTemplateExportComponent;
12+
let fixture: ComponentFixture<AdminAppTemplateExportComponent>;
13+
14+
beforeEach(() => {
15+
TestBed.configureTestingModule({
16+
declarations: [AdminAppTemplateExportComponent]
17+
});
18+
fixture = TestBed.createComponent(AdminAppTemplateExportComponent);
19+
component = fixture.componentInstance;
20+
fixture.detectChanges();
21+
});
22+
23+
it('should create', () => {
24+
expect(component).toBeTruthy();
25+
});
26+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
2+
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
3+
4+
import {
5+
Component,
6+
OnInit,
7+
Input,
8+
ChangeDetectionStrategy,
9+
Output,
10+
EventEmitter,
11+
} from '@angular/core';
12+
import {
13+
UntypedFormGroup,
14+
UntypedFormBuilder,
15+
Validators,
16+
AbstractControl,
17+
} from '@angular/forms';
18+
import { ApplicationService, ArchiveType } from '../../../generated/player-api';
19+
import FileDownloadUtils from '../../../utilities/file-download-utils';
20+
import HttpHeaderUtils from '../../../utilities/http-header-utils';
21+
import { BehaviorSubject, finalize, map } from 'rxjs';
22+
23+
@Component({
24+
selector: 'app-admin-app-template-export',
25+
templateUrl: './admin-app-template-export.component.html',
26+
styleUrls: ['./admin-app-template-export.component.scss'],
27+
changeDetection: ChangeDetectionStrategy.OnPush,
28+
})
29+
export class AdminAppTemplateExportComponent {
30+
@Input() ids: string[];
31+
32+
@Output() complete = new EventEmitter<boolean>();
33+
34+
form: UntypedFormGroup;
35+
archiveTypes = Object.keys(ArchiveType);
36+
typeString: string;
37+
includeIcons: AbstractControl;
38+
loading = false;
39+
hasErrors = new BehaviorSubject(false);
40+
hasErrors$ = this.hasErrors.asObservable();
41+
42+
constructor(
43+
private applicationService: ApplicationService,
44+
formBuilder: UntypedFormBuilder
45+
) {
46+
this.form = formBuilder.group({
47+
archiveType: [this.archiveTypes[0]],
48+
includeIcons: [false, Validators.required],
49+
embedIcons: [{ value: false, disabled: true }, Validators.required],
50+
});
51+
52+
this.includeIcons = this.form.get('includeIcons');
53+
const embedIcons = this.form.get('embedIcons');
54+
55+
this.includeIcons.valueChanges.subscribe((enabled: boolean) => {
56+
if (enabled) {
57+
embedIcons.enable();
58+
} else {
59+
embedIcons.disable();
60+
}
61+
});
62+
}
63+
64+
export() {
65+
this.onExport(
66+
this.ids,
67+
ArchiveType[this.form.value.archiveType],
68+
this.form.value.includeIcons,
69+
this.includeIcons.value ? this.form.value.embedIcons : false
70+
);
71+
}
72+
73+
cancel() {
74+
this.complete.emit(false);
75+
}
76+
77+
private onExport(
78+
ids: string[],
79+
archiveType: ArchiveType,
80+
includeIcons,
81+
embedIcons
82+
) {
83+
this.loading = true;
84+
this.applicationService
85+
.exportApplicationTemplates(
86+
includeIcons,
87+
embedIcons,
88+
archiveType,
89+
ids,
90+
'response'
91+
)
92+
.pipe(
93+
map((response) => {
94+
return {
95+
blob: response.body,
96+
filename: HttpHeaderUtils.getFilename(response.headers),
97+
hasErrors: HttpHeaderUtils.hasArchiveErrors(response.headers),
98+
};
99+
}),
100+
finalize(() => (this.loading = false))
101+
)
102+
.subscribe((result) => {
103+
FileDownloadUtils.downloadFile(result.blob, result.filename);
104+
105+
if (result.hasErrors) {
106+
this.hasErrors.next(true);
107+
} else {
108+
this.complete.emit(true);
109+
}
110+
});
111+
}
112+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!--
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
-->
5+
6+
<h2>Import {{ typeString | titlecase }}</h2>
7+
8+
<form *ngIf="!(finished$ | async)" [formGroup]="form" (ngSubmit)="import()">
9+
<div class="d-flex flex-column gap-2">
10+
<div class="d-flex align-items-center gap-2">
11+
<button
12+
type="button"
13+
color="primary"
14+
mat-raised-button
15+
(click)="fileInput.click()"
16+
>
17+
Choose File
18+
</button>
19+
<input
20+
hidden
21+
(change)="onFileSelected($event)"
22+
#fileInput
23+
type="file"
24+
accept=".zip, .tar.gz, .tgz"
25+
/>
26+
<span class="file-name">{{ this.form.value.archive?.name }}</span>
27+
</div>
28+
29+
<mat-form-field floatLabel="always" appearance="none" fxLayout="row">
30+
<mat-label>Overwrite Existing</mat-label>
31+
<mat-slide-toggle
32+
formControlName="overwriteExisting"
33+
matTooltip="If an Application Template with a matching Id already exists, overwrite it. Otherwise, report as a failure."
34+
></mat-slide-toggle>
35+
<textarea matInput hidden></textarea>
36+
</mat-form-field>
37+
</div>
38+
39+
<div class="d-flex justify-content-around">
40+
<button
41+
type="submit"
42+
color="primary"
43+
mat-raised-button
44+
[disabled]="!form.valid || loading"
45+
>
46+
{{ loading ? 'Importing...' : 'Import' }}
47+
</button>
48+
49+
<button type="reset" color="primary" mat-raised-button (click)="cancel()">
50+
Cancel
51+
</button>
52+
</div>
53+
</form>
54+
55+
<div *ngIf="result$ | async as result">
56+
<ng-container *ngIf="result.failures.length > 0">
57+
<h3 class="text-danger">
58+
Error: The following Application Templates already exist and were not
59+
updated:
60+
</h3>
61+
62+
<ul>
63+
<li *ngFor="let appTemplate of result.failures">
64+
{{ appTemplate }}
65+
</li>
66+
</ul>
67+
</ng-container>
68+
69+
<ng-container *ngIf="result.failures.length == 0">
70+
<h3 class="text-success">Import Successful</h3>
71+
</ng-container>
72+
73+
<div class="d-flex justify-content-around">
74+
<button mat-raised-button color="primary" (click)="cancel()">OK</button>
75+
</div>
76+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/*
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
*/
5+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
3+
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
4+
*/
5+
6+
import { ComponentFixture, TestBed } from '@angular/core/testing';
7+
8+
import { AdminAppTemplateImportComponent } from './admin-app-template-import.component';
9+
10+
describe('AdminAppTemplateImportComponent', () => {
11+
let component: AdminAppTemplateImportComponent;
12+
let fixture: ComponentFixture<AdminAppTemplateImportComponent>;
13+
14+
beforeEach(() => {
15+
TestBed.configureTestingModule({
16+
declarations: [AdminAppTemplateImportComponent]
17+
});
18+
fixture = TestBed.createComponent(AdminAppTemplateImportComponent);
19+
component = fixture.componentInstance;
20+
fixture.detectChanges();
21+
});
22+
23+
it('should create', () => {
24+
expect(component).toBeTruthy();
25+
});
26+
});

0 commit comments

Comments
 (0)