mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
[ui] Edit images (#7068)
* [api] Update Image * [ui] edit images * static ui files
This commit is contained in:
@@ -180,6 +180,43 @@ describe('devfile editor spec', () => {
|
||||
.should('contain.text', 'Yes, the image is not referenced by any command');
|
||||
});
|
||||
|
||||
it('displays a modified image', () => {
|
||||
cy.init();
|
||||
|
||||
cy.selectTab(TAB_IMAGES);
|
||||
cy.getByDataCy('image-name').type('created-image');
|
||||
cy.getByDataCy('image-image-name').type('an-image-name');
|
||||
cy.getByDataCy('image-build-context').type('/path/to/build/context');
|
||||
cy.getByDataCy('image-dockerfile-uri').type('/path/to/dockerfile');
|
||||
cy.getByDataCy('image-create').click();
|
||||
|
||||
cy.getByDataCy('image-info').first()
|
||||
.should('contain.text', 'created-image')
|
||||
.should('contain.text', 'an-image-name')
|
||||
.should('contain.text', '/path/to/build/context')
|
||||
.should('contain.text', '/path/to/dockerfile');
|
||||
|
||||
cy.getByDataCy('image-build-startup').first()
|
||||
.should('contain.text', 'Yes, the image is not referenced by any command');
|
||||
|
||||
cy.getByDataCy('image-edit').click();
|
||||
cy.getByDataCy('image-auto-build-always').click();
|
||||
cy.getByDataCy('image-image-name').type('{selectAll}{del}another-image-name');
|
||||
cy.getByDataCy('image-build-context').type('/new/path/to/build/context');
|
||||
cy.getByDataCy('image-dockerfile-uri').type('/new/path/to/dockerfile');
|
||||
cy.getByDataCy('image-save').click();
|
||||
|
||||
cy.getByDataCy('image-info').first()
|
||||
.should('contain.text', 'created-image')
|
||||
.should('contain.text', 'another-image-name')
|
||||
.should('contain.text', '/new/path/to/build/context')
|
||||
.should('contain.text', '/new/path/to/dockerfile');
|
||||
|
||||
cy.getByDataCy('image-build-startup').first()
|
||||
.should('contain.text', 'Yes, forced');
|
||||
|
||||
});
|
||||
|
||||
it('displays a created image with forced build', () => {
|
||||
cy.init();
|
||||
|
||||
|
||||
1
ui/src/app/api-gen/.openapi-generator/FILES
generated
1
ui/src/app/api-gen/.openapi-generator/FILES
generated
@@ -27,6 +27,7 @@ model/devstateContainerPostRequest.ts
|
||||
model/devstateDevfilePutRequest.ts
|
||||
model/devstateEventsPutRequest.ts
|
||||
model/devstateExecCommandPostRequest.ts
|
||||
model/devstateImageImageNamePatchRequest.ts
|
||||
model/devstateImagePostRequest.ts
|
||||
model/devstateQuantityValidPostRequest.ts
|
||||
model/devstateResourcePostRequest.ts
|
||||
|
||||
71
ui/src/app/api-gen/api/devstate.service.ts
generated
71
ui/src/app/api-gen/api/devstate.service.ts
generated
@@ -39,6 +39,8 @@ import { DevstateEventsPutRequest } from '../model/devstateEventsPutRequest';
|
||||
// @ts-ignore
|
||||
import { DevstateExecCommandPostRequest } from '../model/devstateExecCommandPostRequest';
|
||||
// @ts-ignore
|
||||
import { DevstateImageImageNamePatchRequest } from '../model/devstateImageImageNamePatchRequest';
|
||||
// @ts-ignore
|
||||
import { DevstateImagePostRequest } from '../model/devstateImagePostRequest';
|
||||
// @ts-ignore
|
||||
import { DevstateQuantityValidPostRequest } from '../model/devstateQuantityValidPostRequest';
|
||||
@@ -1049,6 +1051,75 @@ export class DevstateService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an image
|
||||
* @param imageName Image name to update
|
||||
* @param devstateImageImageNamePatchRequest
|
||||
* @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 devstateImageImageNamePatch(imageName: string, devstateImageImageNamePatchRequest?: DevstateImageImageNamePatchRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<DevfileContent>;
|
||||
public devstateImageImageNamePatch(imageName: string, devstateImageImageNamePatchRequest?: DevstateImageImageNamePatchRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<DevfileContent>>;
|
||||
public devstateImageImageNamePatch(imageName: string, devstateImageImageNamePatchRequest?: DevstateImageImageNamePatchRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<DevfileContent>>;
|
||||
public devstateImageImageNamePatch(imageName: string, devstateImageImageNamePatchRequest?: DevstateImageImageNamePatchRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
|
||||
if (imageName === null || imageName === undefined) {
|
||||
throw new Error('Required parameter imageName was null or undefined when calling devstateImageImageNamePatch.');
|
||||
}
|
||||
|
||||
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/image/${this.configuration.encodeParam({name: "imageName", value: imageName, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`;
|
||||
return this.httpClient.request<DevfileContent>('patch', `${this.configuration.basePath}${localVarPath}`,
|
||||
{
|
||||
context: localVarHttpContext,
|
||||
body: devstateImageImageNamePatchRequest,
|
||||
responseType: <any>responseType_,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: localVarHeaders,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new image to the Devfile
|
||||
* @param devstateImagePostRequest
|
||||
|
||||
31
ui/src/app/api-gen/model/devstateImageImageNamePatchRequest.ts
generated
Normal file
31
ui/src/app/api-gen/model/devstateImageImageNamePatchRequest.ts
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 DevstateImageImageNamePatchRequest {
|
||||
imageName?: string;
|
||||
args?: Array<string>;
|
||||
buildContext?: string;
|
||||
rootRequired?: boolean;
|
||||
uri?: string;
|
||||
autoBuild?: DevstateImageImageNamePatchRequest.AutoBuildEnum;
|
||||
}
|
||||
export namespace DevstateImageImageNamePatchRequest {
|
||||
export type AutoBuildEnum = 'never' | 'undefined' | 'always';
|
||||
export const AutoBuildEnum = {
|
||||
Never: 'never' as AutoBuildEnum,
|
||||
Undefined: 'undefined' as AutoBuildEnum,
|
||||
Always: 'always' as AutoBuildEnum
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
1
ui/src/app/api-gen/model/models.ts
generated
1
ui/src/app/api-gen/model/models.ts
generated
@@ -17,6 +17,7 @@ export * from './devstateContainerPostRequest';
|
||||
export * from './devstateDevfilePutRequest';
|
||||
export * from './devstateEventsPutRequest';
|
||||
export * from './devstateExecCommandPostRequest';
|
||||
export * from './devstateImageImageNamePatchRequest';
|
||||
export * from './devstateImagePostRequest';
|
||||
export * from './devstateQuantityValidPostRequest';
|
||||
export * from './devstateResourcePostRequest';
|
||||
|
||||
@@ -24,6 +24,9 @@ export class MultiTextComponent implements ControlValueAccessor {
|
||||
texts: string[] = [];
|
||||
|
||||
writeValue(value: any) {
|
||||
if (value == null) {
|
||||
value = [];
|
||||
}
|
||||
this.texts = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="main">
|
||||
<h2>Add a new image</h2>
|
||||
<h2 *ngIf="!image">Add a new image</h2>
|
||||
<h2 *ngIf="image">Edit image <i>{{image.name}}</i></h2>
|
||||
<div class="description">An Image defines how to build a container image.</div>
|
||||
<form [formGroup]="form">
|
||||
<div class="toggle-group-div">
|
||||
@@ -31,6 +32,7 @@
|
||||
|
||||
</form>
|
||||
|
||||
<button data-cy="image-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new image" (click)="create()">Create</button>
|
||||
<button *ngIf="!image" data-cy="image-create" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="create new image" (click)="create()">Create</button>
|
||||
<button *ngIf="image" data-cy="image-save" [disabled]="form.invalid" mat-flat-button color="primary" matTooltip="save image" (click)="save()">Save</button>
|
||||
<button *ngIf="cancelable" mat-flat-button (click)="cancel()">Cancel</button>
|
||||
</div>
|
||||
@@ -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 { Image } from 'src/app/api-gen';
|
||||
@@ -11,8 +11,11 @@ import { TelemetryService } from 'src/app/services/telemetry.service';
|
||||
})
|
||||
export class ImageComponent {
|
||||
@Input() cancelable: boolean = false;
|
||||
@Input() image: Image | undefined;
|
||||
|
||||
@Output() canceled = new EventEmitter<void>();
|
||||
@Output() created = new EventEmitter<Image>();
|
||||
@Output() saved = new EventEmitter<Image>();
|
||||
|
||||
form: FormGroup;
|
||||
|
||||
@@ -35,7 +38,29 @@ export class ImageComponent {
|
||||
this.created.emit(this.form.value);
|
||||
}
|
||||
|
||||
save() {
|
||||
const newValue = this.form.value;
|
||||
newValue.name = this.image?.name;
|
||||
this.telemetry.track("[ui] edit volume");
|
||||
this.saved.emit(this.form.value);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.canceled.emit();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
console.log("changes", changes);
|
||||
if (!changes['image']) {
|
||||
return;
|
||||
}
|
||||
const img = changes['image'].currentValue;
|
||||
if (img == undefined) {
|
||||
this.form.get('name')?.enable();
|
||||
} else {
|
||||
this.form.reset();
|
||||
this.form.patchValue(img);
|
||||
this.form.get('name')?.disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,17 @@ export class DevstateService {
|
||||
});
|
||||
}
|
||||
|
||||
saveImage(image: Image): Observable<DevfileContent> {
|
||||
return this.http.patch<DevfileContent>(this.base+"/image/"+image.name, {
|
||||
imageName: image.imageName,
|
||||
args: image.args,
|
||||
buildContext: image.buildContext,
|
||||
rootRequired: image.rootRequired,
|
||||
uri: image.uri,
|
||||
autoBuild: image.autoBuild,
|
||||
});
|
||||
}
|
||||
|
||||
addResource(resource: Resource): Observable<DevfileContent> {
|
||||
return this.http.post<DevfileContent>(this.base+"/resource", {
|
||||
name: resource.name,
|
||||
|
||||
@@ -38,19 +38,22 @@
|
||||
|
||||
<mat-card-actions>
|
||||
<button mat-button color="warn" (click)="delete(image.name)">Delete</button>
|
||||
<button data-cy="image-edit" mat-button (click)="edit(image)">Edit</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</mat-card>
|
||||
|
||||
<app-image
|
||||
*ngIf="forceDisplayAdd || images == undefined || images.length == 0"
|
||||
[cancelable]="forceDisplayAdd"
|
||||
*ngIf="forceDisplayForm || images == undefined || images.length == 0"
|
||||
[cancelable]="forceDisplayForm"
|
||||
(canceled)="undisplayAddForm()"
|
||||
(created)="onCreated($event)"
|
||||
[image]="editingImage"
|
||||
(saved)="onSaved($event)"
|
||||
></app-image>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!forceDisplayAdd && images != undefined && images.length > 0">
|
||||
<ng-container *ngIf="!forceDisplayForm && images != undefined && images.length > 0">
|
||||
<button class="fab" mat-fab color="primary" (click)="displayAddForm()">
|
||||
<mat-icon class="material-icons-outlined">add</mat-icon>
|
||||
</button>
|
||||
|
||||
@@ -10,8 +10,9 @@ import { Image } from 'src/app/api-gen';
|
||||
})
|
||||
export class ImagesComponent implements OnInit {
|
||||
|
||||
forceDisplayAdd: boolean = false;
|
||||
forceDisplayForm: boolean = false;
|
||||
images: Image[] | undefined = [];
|
||||
editingImage: Image | undefined;
|
||||
|
||||
constructor(
|
||||
private state: StateService,
|
||||
@@ -25,19 +26,24 @@ export class ImagesComponent implements OnInit {
|
||||
if (this.images == null) {
|
||||
return
|
||||
}
|
||||
that.forceDisplayAdd = false;
|
||||
that.forceDisplayForm = false;
|
||||
});
|
||||
}
|
||||
|
||||
displayAddForm() {
|
||||
this.forceDisplayAdd = true;
|
||||
this.editingImage = 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 ImagesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
edit(image: Image) {
|
||||
this.editingImage = image;
|
||||
this.displayForm();
|
||||
}
|
||||
|
||||
onCreated(image: Image) {
|
||||
const result = this.devstate.addImage(image);
|
||||
result.subscribe({
|
||||
@@ -66,6 +77,18 @@ export class ImagesComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
onSaved(image: Image) {
|
||||
const result = this.devstate.saveImage(image);
|
||||
result.subscribe({
|
||||
next: value => {
|
||||
this.state.changeDevfileYaml(value);
|
||||
},
|
||||
error: error => {
|
||||
alert(error.error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user