Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ import { SystemRolesComponent } from './components/admin-app/admin-roles/roles/r
import { AdminRolesComponent } from './components/admin-app/admin-roles/admin-roles.component';
import { NameDialogComponent } from './components/shared/name-dialog/name-dialog.component';
import { TeamRolesComponent } from './components/admin-app/admin-roles/team-roles/team-roles.component';
import { AdminAppTemplateExportComponent } from './components/admin-app/admin-app-template-export/admin-app-template-export.component';
import { AdminAppTemplateImportComponent } from './components/admin-app/admin-app-template-import/admin-app-template-import.component';
import { AdminAppViewExportComponent } from './components/admin-app/admin-app-view-export/admin-app-view-export.component';
import { AdminAppViewImportComponent } from './components/admin-app/admin-app-view-import/admin-app-view-import.component';

@NgModule({
exports: [
Expand Down Expand Up @@ -148,6 +152,7 @@ import { TeamRolesComponent } from './components/admin-app/admin-roles/team-role
ScrollingModule,
],
imports: [],
declarations: [],
})
export class AngularMaterialModule {}

Expand Down Expand Up @@ -198,6 +203,10 @@ export const myCustomTooltipDefaults: MatTooltipDefaultOptions = {
SystemRolesComponent,
AdminRolesComponent,
TeamRolesComponent,
AdminAppTemplateExportComponent,
AdminAppTemplateImportComponent,
AdminAppViewExportComponent,
AdminAppViewImportComponent,
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
-->

<h2>Export Application Templates</h2>

<form *ngIf="!(hasErrors$ | async)" [formGroup]="form" (ngSubmit)="export()">
<mat-form-field *ngIf="isArchiveable">
<mat-label>Archive Type</mat-label>
<mat-select formControlName="archiveType">
<mat-option
*ngFor="let archiveType of archiveTypes"
[value]="archiveType"
>
{{ archiveType }}
</mat-option>
</mat-select>
</mat-form-field>

<div>
<mat-form-field floatLabel="always" appearance="none">
<mat-label>Include Icons</mat-label>
<mat-slide-toggle
formControlName="includeIcons"
matTooltip="Include icon files in the exported archive"
></mat-slide-toggle>
<textarea matInput hidden></textarea>
</mat-form-field>

<mat-form-field floatLabel="always" appearance="none">
<mat-label>Embed Icons</mat-label>
<mat-slide-toggle
formControlName="embedIcons"
matTooltip="Embed included Icons into exported JSON as data URIs"
></mat-slide-toggle>
<textarea matInput hidden></textarea>
</mat-form-field>
</div>

<div class="d-flex justify-content-around">
<button
type="submit"
color="primary"
mat-raised-button
[disabled]="!form.valid || loading"
>
{{
loading
? 'Exporting....'
: 'Export ' + (ids.length == 0 ? 'All' : '(' + ids.length + ')')
}}
</button>

<button type="reset" color="primary" mat-raised-button (click)="cancel()">
Cancel
</button>
</div>
</form>

<div *ngIf="hasErrors$ | async">
<h3 class="text-danger">
Some errors occurred during export. Check errors.txt in the archive for
details.
</h3>

<div class="d-flex justify-content-around">
<button mat-raised-button color="primary" (click)="cancel()">OK</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
*/

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AdminAppTemplateExportComponent } from './admin-app-template-export.component';

describe('AdminAppTemplateExportComponent', () => {
let component: AdminAppTemplateExportComponent;
let fixture: ComponentFixture<AdminAppTemplateExportComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AdminAppTemplateExportComponent]
});
fixture = TestBed.createComponent(AdminAppTemplateExportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import {
Component,
OnInit,
Input,
ChangeDetectionStrategy,
Output,
EventEmitter,
} from '@angular/core';
import {
UntypedFormGroup,
UntypedFormBuilder,
Validators,
AbstractControl,
} from '@angular/forms';
import { ApplicationService, ArchiveType } from '../../../generated/player-api';
import FileDownloadUtils from '../../../utilities/file-download-utils';
import HttpHeaderUtils from '../../../utilities/http-header-utils';
import { BehaviorSubject, finalize, map } from 'rxjs';

@Component({
selector: 'app-admin-app-template-export',
templateUrl: './admin-app-template-export.component.html',
styleUrls: ['./admin-app-template-export.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminAppTemplateExportComponent implements OnInit {
@Input() ids: string[];

@Output() complete = new EventEmitter<boolean>();

form: UntypedFormGroup;
isArchiveable: boolean = true;
Comment thread
sei-aschlackman marked this conversation as resolved.
Outdated
archiveTypes = Object.keys(ArchiveType);
typeString: string;
includeIcons: AbstractControl;
loading = false;
hasErrors = new BehaviorSubject(false);
hasErrors$ = this.hasErrors.asObservable();

constructor(
private applicationService: ApplicationService,
formBuilder: UntypedFormBuilder
) {
this.form = formBuilder.group({
archiveType: [this.archiveTypes[0]],
includeIcons: [false, Validators.required],
embedIcons: [{ value: false, disabled: true }, Validators.required],
});

this.includeIcons = this.form.get('includeIcons');
const embedIcons = this.form.get('embedIcons');

this.includeIcons.valueChanges.subscribe((enabled: boolean) => {
if (enabled) {
embedIcons.enable();
} else {
embedIcons.disable();
}
});
}

ngOnInit() {}
Comment thread
sei-aschlackman marked this conversation as resolved.
Outdated

export() {
this.onExport(
this.ids,
ArchiveType[this.form.value.archiveType],
this.form.value.includeIcons,
this.includeIcons.value ? this.form.value.embedIcons : false
);
}

cancel() {
this.complete.emit(false);
}

private onExport(
ids: string[],
archiveType: ArchiveType,
includeIcons,
embedIcons
) {
this.loading = true;
this.applicationService
.exportApplicationTemplates(
includeIcons,
embedIcons,
archiveType,
ids,
'response'
)
.pipe(
map((response) => {
return {
blob: response.body,
filename: HttpHeaderUtils.getFilename(response.headers),
hasErrors: HttpHeaderUtils.hasArchiveErrors(response.headers),
};
}),
finalize(() => (this.loading = false))
)
.subscribe((result) => {
FileDownloadUtils.downloadFile(result.blob, result.filename);

if (result.hasErrors) {
this.hasErrors.next(true);
} else {
this.complete.emit(true);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!--
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
-->

<h2>Import {{ typeString | titlecase }}</h2>

<form *ngIf="!(finished$ | async)" [formGroup]="form" (ngSubmit)="import()">
<div class="d-flex flex-column gap-2">
<div class="d-flex align-items-center gap-2">
<button
type="button"
color="primary"
mat-raised-button
(click)="fileInput.click()"
>
Choose File
</button>
<input
hidden
(change)="onFileSelected($event)"
#fileInput
type="file"
accept=".zip, .tar.gz, .tgz"
/>
<span class="file-name">{{ this.form.value.archive?.name }}</span>
</div>

<mat-form-field floatLabel="always" appearance="none" fxLayout="row">
<mat-label>Overwrite Existing</mat-label>
<mat-slide-toggle
formControlName="overwriteExisting"
matTooltip="If an Application Template with a matching Id already exists, overwrite it. Otherwise, report as a failure."
></mat-slide-toggle>
<textarea matInput hidden></textarea>
</mat-form-field>
</div>

<div class="d-flex justify-content-around">
<button
type="submit"
color="primary"
mat-raised-button
[disabled]="!form.valid || loading"
>
{{ loading ? 'Importing...' : 'Import' }}
</button>

<button type="reset" color="primary" mat-raised-button (click)="cancel()">
Cancel
</button>
</div>
</form>

<div *ngIf="result$ | async as result">
<ng-container *ngIf="result.failures.length > 0">
<h3 class="text-danger">
Error: The following Application Templates already exist and were not
updated:
</h3>

<ul>
<li *ngFor="let appTemplate of result.failures">
{{ appTemplate }}
</li>
</ul>
</ng-container>

<ng-container *ngIf="result.failures.length == 0">
<h3 class="text-success">Import Successful</h3>
</ng-container>

<div class="d-flex justify-content-around">
<button mat-raised-button color="primary" (click)="cancel()">OK</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
*/

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
*/

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AdminAppTemplateImportComponent } from './admin-app-template-import.component';

describe('AdminAppTemplateImportComponent', () => {
let component: AdminAppTemplateImportComponent;
let fixture: ComponentFixture<AdminAppTemplateImportComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AdminAppTemplateImportComponent]
});
fixture = TestBed.createComponent(AdminAppTemplateImportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading