[ui] Edit resources (#7062)

* [ui] Edit Resource

* Add deployByDefault
This commit is contained in:
Philippe Martin
2023-09-01 16:12:07 +02:00
committed by GitHub
parent e9dbded83b
commit cb8494387d
21 changed files with 466 additions and 15 deletions

View File

@@ -277,6 +277,28 @@ describe('devfile editor spec', () => {
.should('contain.text', 'No, disabled');
});
it('displays an updated resource, with manifest', () => {
cy.init();
cy.selectTab(TAB_RESOURCES);
cy.getByDataCy('resource-name').type('created-resource');
cy.getByDataCy('resource-toggle-inlined').click();
cy.getByDataCy('resource-manifest').type('a-resource-manifest');
cy.getByDataCy('resource-create').click();
cy.getByDataCy('resource-info').first()
.should('contain.text', 'created-resource')
.should('contain.text', 'a-resource-manifest');
cy.getByDataCy('resource-edit').click();
cy.getByDataCy('resource-manifest').type('{selectAll}{del}another-resource-manifest');
cy.getByDataCy('resource-save').click();
cy.getByDataCy('resource-info').first()
.should('contain.text', 'created-resource')
.should('contain.text', 'another-resource-manifest');
});
it('displays a created volume', () => {
cy.init();

View File

@@ -30,6 +30,7 @@ model/devstateExecCommandPostRequest.ts
model/devstateImagePostRequest.ts
model/devstateQuantityValidPostRequest.ts
model/devstateResourcePostRequest.ts
model/devstateResourceResourceNamePatchRequest.ts
model/devstateVolumePostRequest.ts
model/devstateVolumeVolumeNamePatchRequest.ts
model/endpoint.ts

View File

@@ -45,6 +45,8 @@ import { DevstateQuantityValidPostRequest } from '../model/devstateQuantityValid
// @ts-ignore
import { DevstateResourcePostRequest } from '../model/devstateResourcePostRequest';
// @ts-ignore
import { DevstateResourceResourceNamePatchRequest } from '../model/devstateResourceResourceNamePatchRequest';
// @ts-ignore
import { DevstateVolumePostRequest } from '../model/devstateVolumePostRequest';
// @ts-ignore
import { DevstateVolumeVolumeNamePatchRequest } from '../model/devstateVolumeVolumeNamePatchRequest';
@@ -1365,6 +1367,75 @@ export class DevstateService {
);
}
/**
* Update a Kubernetes resource
* @param resourceName Resource name to update
* @param devstateResourceResourceNamePatchRequest
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public devstateResourceResourceNamePatch(resourceName: string, devstateResourceResourceNamePatchRequest?: DevstateResourceResourceNamePatchRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<DevfileContent>;
public devstateResourceResourceNamePatch(resourceName: string, devstateResourceResourceNamePatchRequest?: DevstateResourceResourceNamePatchRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<DevfileContent>>;
public devstateResourceResourceNamePatch(resourceName: string, devstateResourceResourceNamePatchRequest?: DevstateResourceResourceNamePatchRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<DevfileContent>>;
public devstateResourceResourceNamePatch(resourceName: string, devstateResourceResourceNamePatchRequest?: DevstateResourceResourceNamePatchRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (resourceName === null || resourceName === undefined) {
throw new Error('Required parameter resourceName was null or undefined when calling devstateResourceResourceNamePatch.');
}
let localVarHeaders = this.defaultHeaders;
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
'application/json'
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}
let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}
// to determine the Content-Type header
const consumes: string[] = [
'application/json'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
}
let responseType_: 'text' | 'json' | 'blob' = 'json';
if (localVarHttpHeaderAcceptSelected) {
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
responseType_ = 'json';
} else {
responseType_ = 'blob';
}
}
let localVarPath = `/devstate/resource/${this.configuration.encodeParam({name: "resourceName", value: resourceName, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`;
return this.httpClient.request<DevfileContent>('patch', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
body: devstateResourceResourceNamePatchRequest,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Add a new Volume to the Devfile
* @param devstateVolumePostRequest

View File

@@ -0,0 +1,28 @@
/**
* odo dev
* API interface for \'odo dev\'
*
* The version of the OpenAPI document: 0.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface DevstateResourceResourceNamePatchRequest {
inlined?: string;
uri?: string;
deployByDefault?: DevstateResourceResourceNamePatchRequest.DeployByDefaultEnum;
}
export namespace DevstateResourceResourceNamePatchRequest {
export type DeployByDefaultEnum = 'never' | 'undefined' | 'always';
export const DeployByDefaultEnum = {
Never: 'never' as DeployByDefaultEnum,
Undefined: 'undefined' as DeployByDefaultEnum,
Always: 'always' as DeployByDefaultEnum
};
}

View File

@@ -20,6 +20,7 @@ export * from './devstateExecCommandPostRequest';
export * from './devstateImagePostRequest';
export * from './devstateQuantityValidPostRequest';
export * from './devstateResourcePostRequest';
export * from './devstateResourceResourceNamePatchRequest';
export * from './devstateVolumePostRequest';
export * from './devstateVolumeVolumeNamePatchRequest';
export * from './endpoint';

View File

@@ -1,5 +1,6 @@
<div class="main">
<h2>Add a new resource</h2>
<h2 *ngIf="!resource">Add a new resource</h2>
<h2 *ngIf="resource">Edit resource <i>{{resource.name}}</i></h2>
<div class="description">A Resource defines a Kubernetes resource. Its definition can be given either by a URI pointing to a manifest file or by an inlined YAML manifest.</div>
<form [formGroup]="form">
<div class="toggle-group-div">
@@ -16,7 +17,7 @@
</mat-form-field>
<span class="toggleUriInlined">
<mat-button-toggle-group (change)="changeUriOrInlined($event.value)">
<mat-button-toggle-group formControlName="_choice" (change)="changeUriOrInlined($event.value)">
<mat-button-toggle data-cy="resource-toogle-uri" value="uri" checked>Specify URI</mat-button-toggle>
<mat-button-toggle data-cy="resource-toggle-inlined" value="inlined">Inlined content</mat-button-toggle>
</mat-button-toggle-group>
@@ -34,6 +35,7 @@
</form>
<button data-cy="resource-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new resource" (click)="create()">Create</button>
<button *ngIf="!resource" data-cy="resource-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new resource" (click)="create()">Create</button>
<button *ngIf="resource" data-cy="resource-save" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="save resource" (click)="save()">Save</button>
<button *ngIf="cancelable" mat-flat-button (click)="cancel()">Cancel</button>
</div>

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { PATTERN_COMPONENT_ID } from '../patterns';
import { Resource } from 'src/app/api-gen';
@@ -11,8 +11,11 @@ import { TelemetryService } from 'src/app/services/telemetry.service';
})
export class ResourceComponent {
@Input() cancelable: boolean = false;
@Input() resource: Resource | undefined;
@Output() canceled = new EventEmitter<void>();
@Output() created = new EventEmitter<Resource>();
@Output() saved = new EventEmitter<Resource>();
form: FormGroup;
uriOrInlined: string = 'uri';
@@ -22,6 +25,7 @@ export class ResourceComponent {
) {
this.form = new FormGroup({
name: new FormControl("", [Validators.required, Validators.pattern(PATTERN_COMPONENT_ID)]),
_choice: new FormControl("uri"),
uri: new FormControl("", [Validators.required]),
inlined: new FormControl("", []),
deployByDefault: new FormControl("undefined"),
@@ -50,7 +54,32 @@ export class ResourceComponent {
this.created.emit(this.form.value);
}
save() {
const newValue = this.form.value;
newValue.name = this.resource?.name;
this.telemetry.track("[ui] edit resource");
this.saved.emit(this.form.value);
}
cancel() {
this.canceled.emit();
}
ngOnChanges(changes: SimpleChanges) {
if (!changes['resource']) {
return;
}
const res = changes['resource'].currentValue;
if (res == undefined) {
this.form.get('name')?.enable();
} else {
this.form.reset();
this.form.patchValue(res);
if (res['inlined']) {
this.form.get('_choice')?.setValue('inlined');
this.changeUriOrInlined('inlined');
}
this.form.get('name')?.disable();
}
}
}

View File

@@ -57,6 +57,14 @@ export class DevstateService {
});
}
saveResource(resource: Resource): Observable<DevfileContent> {
return this.http.patch<DevfileContent>(this.base+"/resource/"+resource.name, {
inlined: resource.inlined,
uri: resource.uri,
deployByDefault: resource.deployByDefault,
});
}
addVolume(volume: Volume): Observable<DevfileContent> {
return this.http.post<DevfileContent>(this.base+"/volume", {
name: volume.name,

View File

@@ -20,20 +20,23 @@
<mat-card-actions>
<button mat-button color="warn" (click)="delete(resource.name)">Delete</button>
<button data-cy="resource-edit" mat-button (click)="edit(resource)">Edit</button>
</mat-card-actions>
</mat-card>
<app-resource
*ngIf="forceDisplayAdd || resources == undefined || resources.length == 0"
[cancelable]="forceDisplayAdd"
*ngIf="forceDisplayForm || resources == undefined || resources.length == 0"
[cancelable]="forceDisplayForm"
(canceled)="undisplayAddForm()"
(created)="onCreated($event)"
[resource]="editingResource"
(saved)="onSaved($event)"
></app-resource>
</div>
<ng-container *ngIf="!forceDisplayAdd && resources != undefined && resources.length > 0">
<ng-container *ngIf="!forceDisplayForm && resources != undefined && resources.length > 0">
<button class="fab" mat-fab color="primary" (click)="displayAddForm()">
<mat-icon class="material-icons-outlined">add</mat-icon>
</button>

View File

@@ -10,8 +10,9 @@ import { Resource } from 'src/app/api-gen';
})
export class ResourcesComponent implements OnInit {
forceDisplayAdd: boolean = false;
forceDisplayForm: boolean = false;
resources: Resource[] | undefined = [];
editingResource: Resource | undefined;
constructor(
private state: StateService,
@@ -25,19 +26,24 @@ export class ResourcesComponent implements OnInit {
if (this.resources == null) {
return
}
that.forceDisplayAdd = false;
that.forceDisplayForm = false;
});
}
displayAddForm() {
this.forceDisplayAdd = true;
this.editingResource = undefined;
this.displayForm();
}
displayForm() {
this.forceDisplayForm = true;
setTimeout(() => {
this.scrollToBottom();
}, 0);
}
undisplayAddForm() {
this.forceDisplayAdd = false;
this.forceDisplayForm = false;
}
delete(name: string) {
@@ -54,6 +60,11 @@ export class ResourcesComponent implements OnInit {
}
}
edit(resource: Resource) {
this.editingResource = resource;
this.displayForm();
}
onCreated(resource: Resource) {
const result = this.devstate.addResource(resource);
result.subscribe({
@@ -65,7 +76,19 @@ export class ResourcesComponent implements OnInit {
}
});
}
onSaved(resource: Resource) {
const result = this.devstate.saveResource(resource);
result.subscribe({
next: value => {
this.state.changeDevfileYaml(value);
},
error: error => {
alert(error.error.message);
}
});
}
scrollToBottom() {
window.scrollTo(0,document.body.scrollHeight);
}