-
Notifications
You must be signed in to change notification settings - Fork 319
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NAS-129579 / 24.10-RC.1 / Add custom app form (by RehanY147) (#10630)
* Empty commit to create PR on github. You should reset it * NAS-129579: Add custom app form --------- Co-authored-by: Evgeny Stepanovych <[email protected]>
- Loading branch information
Showing
97 changed files
with
672 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/app/pages/apps/components/custom-app-form/custom-app-form.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<ix-modal-header | ||
[requiredRoles]="requiredRoles" | ||
[title]="'Custom App' | translate" | ||
[loading]="isLoading" | ||
></ix-modal-header> | ||
|
||
<mat-card> | ||
<mat-card-content> | ||
<form | ||
class="ix-form-container" | ||
[formGroup]="form" | ||
(submit)="onSubmit()" | ||
> | ||
<ix-input | ||
formControlName="release_name" | ||
[required]="true" | ||
[label]="'Name' | translate" | ||
></ix-input> | ||
<ix-code-editor | ||
formControlName="custom_compose_config_string" | ||
[language]="CodeEditorLanguage.Yaml" | ||
[label]="'Custom Config' | translate" | ||
[tooltip]="tooltip" | ||
[required]="true" | ||
></ix-code-editor> | ||
<ix-form-actions> | ||
<button | ||
*ixRequiresRoles="requiredRoles" | ||
type="submit" | ||
mat-button | ||
color="primary" | ||
ixTest="save" | ||
[disabled]="!form.valid || isLoading" | ||
> | ||
{{ 'Save' | translate }} | ||
</button> | ||
</ix-form-actions> | ||
</form> | ||
</mat-card-content> | ||
</mat-card> |
98 changes: 98 additions & 0 deletions
98
src/app/pages/apps/components/custom-app-form/custom-app-form.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { HarnessLoader } from '@angular/cdk/testing'; | ||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; | ||
import { ReactiveFormsModule } from '@angular/forms'; | ||
import { MatButtonHarness } from '@angular/material/button/testing'; | ||
import { createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; | ||
import { MockModule } from 'ng-mocks'; | ||
import { of } from 'rxjs'; | ||
import { mockAuth } from 'app/core/testing/utils/mock-auth.utils'; | ||
import { mockJob, mockWebSocket } from 'app/core/testing/utils/mock-websocket.utils'; | ||
import { CatalogAppState } from 'app/enums/catalog-app-state.enum'; | ||
import { App } from 'app/interfaces/app.interface'; | ||
import { DialogService } from 'app/modules/dialog/dialog.service'; | ||
import { IxCodeEditorHarness } from 'app/modules/forms/ix-forms/components/ix-code-editor/ix-code-editor.harness'; | ||
import { IxInputHarness } from 'app/modules/forms/ix-forms/components/ix-input/ix-input.harness'; | ||
import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/ix-slide-in-ref'; | ||
import { IxFormsModule } from 'app/modules/forms/ix-forms/ix-forms.module'; | ||
import { PageHeaderModule } from 'app/modules/page-header/page-header.module'; | ||
import { CustomAppFormComponent } from 'app/pages/apps/components/custom-app-form/custom-app-form.component'; | ||
import { ApplicationsService } from 'app/pages/apps/services/applications.service'; | ||
import { ErrorHandlerService } from 'app/services/error-handler.service'; | ||
import { WebSocketService } from 'app/services/ws.service'; | ||
|
||
const fakeApp = { | ||
name: 'test-app-one', | ||
version: '1', | ||
id: 'test-app-one', | ||
state: CatalogAppState.Running, | ||
upgrade_available: true, | ||
human_version: '2022.10_1.0.7', | ||
metadata: { | ||
app_version: '2022.10_1.0.8', | ||
icon: 'path-to-icon', | ||
train: 'stable', | ||
}, | ||
} as App; | ||
|
||
describe('CustomAppFormComponent', () => { | ||
let spectator: Spectator<CustomAppFormComponent>; | ||
let loader: HarnessLoader; | ||
|
||
const createComponent = createComponentFactory({ | ||
component: CustomAppFormComponent, | ||
imports: [ | ||
IxFormsModule, | ||
MockModule(PageHeaderModule), | ||
ReactiveFormsModule, | ||
], | ||
providers: [ | ||
mockAuth(), | ||
mockProvider(ApplicationsService, { | ||
getAllApps: jest.fn(() => { | ||
return of([fakeApp]); | ||
}), | ||
}), | ||
mockProvider(ErrorHandlerService), | ||
mockProvider(DialogService, { | ||
jobDialog: jest.fn(() => ({ | ||
afterClosed: jest.fn(() => of()), | ||
})), | ||
}), | ||
mockProvider(IxSlideInRef), | ||
mockWebSocket([ | ||
mockJob('app.create'), | ||
]), | ||
], | ||
}); | ||
|
||
beforeEach(() => { | ||
spectator = createComponent(); | ||
loader = TestbedHarnessEnvironment.loader(spectator.fixture); | ||
}); | ||
|
||
it('closes slide in when successfully submitted', async () => { | ||
const appNameControl = await loader.getHarness(IxInputHarness); | ||
await appNameControl.setValue('test'); | ||
const configControl = await loader.getHarness(IxCodeEditorHarness); | ||
await configControl.setValue('config'); | ||
spectator.detectChanges(); | ||
const button = await loader.getHarness(MatButtonHarness); | ||
await button.click(); | ||
|
||
expect(spectator.inject(WebSocketService).job).toHaveBeenCalledWith('app.create', [{ | ||
custom_app: true, | ||
custom_compose_config_string: 'config', | ||
app_name: 'test', | ||
}]); | ||
expect(spectator.inject(DialogService).jobDialog).toHaveBeenCalled(); | ||
}); | ||
|
||
it('forbidden app names are not allowed', async () => { | ||
const appNameControl = await loader.getHarness(IxInputHarness); | ||
await appNameControl.setValue('test-app-one'); | ||
spectator.detectChanges(); | ||
|
||
const button = await loader.getHarness(MatButtonHarness); | ||
expect(button.isDisabled()).toBeTruthy(); | ||
}); | ||
}); |
87 changes: 87 additions & 0 deletions
87
src/app/pages/apps/components/custom-app-form/custom-app-form.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { | ||
ChangeDetectionStrategy, ChangeDetectorRef, Component, | ||
OnInit, | ||
} from '@angular/core'; | ||
import { Validators } from '@angular/forms'; | ||
import { FormBuilder } from '@ngneat/reactive-forms'; | ||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; | ||
import { TranslateService } from '@ngx-translate/core'; | ||
import { map } from 'rxjs'; | ||
import { CodeEditorLanguage } from 'app/enums/code-editor-language.enum'; | ||
import { Role } from 'app/enums/role.enum'; | ||
import { AppCreate } from 'app/interfaces/app.interface'; | ||
import { DialogService } from 'app/modules/dialog/dialog.service'; | ||
import { IxSlideInRef } from 'app/modules/forms/ix-forms/components/ix-slide-in/ix-slide-in-ref'; | ||
import { forbiddenAsyncValues } from 'app/modules/forms/ix-forms/validators/forbidden-values-validation/forbidden-values-validation'; | ||
import { ApplicationsService } from 'app/pages/apps/services/applications.service'; | ||
import { ErrorHandlerService } from 'app/services/error-handler.service'; | ||
import { WebSocketService } from 'app/services/ws.service'; | ||
|
||
@UntilDestroy() | ||
@Component({ | ||
selector: 'ix-custom-app-form', | ||
templateUrl: './custom-app-form.component.html', | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class CustomAppFormComponent implements OnInit { | ||
protected requiredRoles = [Role.AppsWrite]; | ||
protected readonly CodeEditorLanguage = CodeEditorLanguage; | ||
protected form = this.fb.group({ | ||
release_name: ['', Validators.required], | ||
custom_compose_config_string: ['\n\n', Validators.required], | ||
}); | ||
protected isLoading = false; | ||
protected tooltip = this.translate.instant('Add custom app config in Yaml format.'); | ||
protected forbiddenAppNames$ = this.appService.getAllApps().pipe(map((apps) => apps.map((app) => app.name))); | ||
constructor( | ||
private fb: FormBuilder, | ||
private translate: TranslateService, | ||
private cdr: ChangeDetectorRef, | ||
private ws: WebSocketService, | ||
private errorHandler: ErrorHandlerService, | ||
private dialogService: DialogService, | ||
private appService: ApplicationsService, | ||
private dialogRef: IxSlideInRef<CustomAppFormComponent>, | ||
) {} | ||
|
||
ngOnInit(): void { | ||
this.addForbiddenAppNamesValidator(); | ||
} | ||
|
||
protected addForbiddenAppNamesValidator(): void { | ||
this.form.controls.release_name.setAsyncValidators(forbiddenAsyncValues(this.forbiddenAppNames$)); | ||
this.form.controls.release_name.updateValueAndValidity(); | ||
} | ||
|
||
protected onSubmit(): void { | ||
this.isLoading = true; | ||
this.cdr.markForCheck(); | ||
const data = this.form.value; | ||
this.dialogService.jobDialog( | ||
this.ws.job( | ||
'app.create', | ||
[{ | ||
custom_app: true, | ||
app_name: data.release_name, | ||
custom_compose_config_string: data.custom_compose_config_string, | ||
} as AppCreate], | ||
), | ||
{ | ||
title: this.translate.instant('Custom App'), | ||
canMinimize: false, | ||
description: this.translate.instant('Creating custom app'), | ||
}, | ||
).afterClosed().pipe( | ||
untilDestroyed(this), | ||
).subscribe({ | ||
next: () => { | ||
this.dialogRef.close(); | ||
}, | ||
error: (error) => { | ||
this.isLoading = false; | ||
this.cdr.markForCheck(); | ||
this.errorHandler.showErrorModal(error); | ||
}, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.