Add /devfile PUT and GET endpoints (#6950)

* Serve /devfile

* Implement /devfile endpoints

* Load/Save devfile from UI

* Required metadata fields in the response

* Add an Apply button on 1st tab

* Fix: validate new devfile, not previous one

* Add generated UI files to gitattributes file

* Fix rebase
This commit is contained in:
Philippe Martin
2023-07-06 18:30:48 +02:00
committed by GitHub
parent c4b103d9c4
commit 2c3d2ea0b1
29 changed files with 629 additions and 140 deletions

View File

@@ -14,6 +14,8 @@ model/componentGet200Response.ts
model/compositeCommand.ts
model/container.ts
model/devfileContent.ts
model/devfileGet200Response.ts
model/devfilePutRequest.ts
model/devstateApplyCommandPostRequest.ts
model/devstateChartGet200Response.ts
model/devstateCommandCommandNameMovePostRequest.ts
@@ -34,6 +36,7 @@ model/image.ts
model/imageCommand.ts
model/instanceGet200Response.ts
model/metadata.ts
model/metadataRequest.ts
model/models.ts
model/resource.ts
param.ts

View File

@@ -25,6 +25,10 @@ import { ComponentGet200Response } from '../model/componentGet200Response';
// @ts-ignore
import { DevfileContent } from '../model/devfileContent';
// @ts-ignore
import { DevfileGet200Response } from '../model/devfileGet200Response';
// @ts-ignore
import { DevfilePutRequest } from '../model/devfilePutRequest';
// @ts-ignore
import { DevstateApplyCommandPostRequest } from '../model/devstateApplyCommandPostRequest';
// @ts-ignore
import { DevstateChartGet200Response } from '../model/devstateChartGet200Response';
@@ -55,7 +59,7 @@ import { GeneralSuccess } from '../model/generalSuccess';
// @ts-ignore
import { InstanceGet200Response } from '../model/instanceGet200Response';
// @ts-ignore
import { Metadata } from '../model/metadata';
import { MetadataRequest } from '../model/metadataRequest';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
@@ -246,6 +250,125 @@ export class DefaultService {
);
}
/**
* Get the raw content of the Devfile used by the current dev session
* @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 devfileGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<DevfileGet200Response>;
public devfileGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<DevfileGet200Response>>;
public devfileGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<DevfileGet200Response>>;
public devfileGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
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();
}
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 = `/devfile`;
return this.httpClient.request<DevfileGet200Response>('get', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Updates the Devfile used by the current dev session
* @param devfilePutRequest
* @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 devfilePut(devfilePutRequest?: DevfilePutRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<GeneralSuccess>;
public devfilePut(devfilePutRequest?: DevfilePutRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<GeneralSuccess>>;
public devfilePut(devfilePutRequest?: DevfilePutRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<GeneralSuccess>>;
public devfilePut(devfilePutRequest?: DevfilePutRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
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 = `/devfile`;
return this.httpClient.request<GeneralSuccess>('put', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
body: devfilePutRequest,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* Add a new Apply Command to the Devfile
* @param devstateApplyCommandPostRequest
@@ -1235,14 +1358,14 @@ export class DefaultService {
/**
* Updates the metadata for the Devfile
* @param metadata
* @param metadataRequest
* @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 devstateMetadataPut(metadata?: Metadata, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<DevfileContent>;
public devstateMetadataPut(metadata?: Metadata, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<DevfileContent>>;
public devstateMetadataPut(metadata?: Metadata, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<DevfileContent>>;
public devstateMetadataPut(metadata?: Metadata, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
public devstateMetadataPut(metadataRequest?: MetadataRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<DevfileContent>;
public devstateMetadataPut(metadataRequest?: MetadataRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<DevfileContent>>;
public devstateMetadataPut(metadataRequest?: MetadataRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<DevfileContent>>;
public devstateMetadataPut(metadataRequest?: MetadataRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
let localVarHeaders = this.defaultHeaders;
@@ -1288,7 +1411,7 @@ export class DefaultService {
return this.httpClient.request<DevfileContent>('put', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
body: metadata,
body: metadataRequest,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,

View File

@@ -0,0 +1,17 @@
/**
* 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 DevfileGet200Response {
content?: string;
}

View File

@@ -0,0 +1,17 @@
/**
* 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 DevfilePutRequest {
content: string;
}

View File

@@ -12,18 +12,18 @@
export interface Metadata {
name?: string;
version?: string;
displayName?: string;
description?: string;
tags?: string;
architectures?: string;
icon?: string;
globalMemoryLimit?: string;
projectType?: string;
language?: string;
website?: string;
provider?: string;
supportUrl?: string;
name: string;
version: string;
displayName: string;
description: string;
tags: string;
architectures: string;
icon: string;
globalMemoryLimit: string;
projectType: string;
language: string;
website: string;
provider: string;
supportUrl: string;
}

View File

@@ -11,7 +11,7 @@
*/
export interface DevstateMetadataPutRequest {
export interface MetadataRequest {
name?: string;
version?: string;
displayName?: string;

View File

@@ -5,6 +5,8 @@ export * from './componentGet200Response';
export * from './compositeCommand';
export * from './container';
export * from './devfileContent';
export * from './devfileGet200Response';
export * from './devfilePutRequest';
export * from './devstateApplyCommandPostRequest';
export * from './devstateChartGet200Response';
export * from './devstateCommandCommandNameMovePostRequest';
@@ -25,4 +27,5 @@ export * from './image';
export * from './imageCommand';
export * from './instanceGet200Response';
export * from './metadata';
export * from './metadataRequest';
export * from './resource';

View File

@@ -18,8 +18,9 @@
<mat-label>Devfile YAML</mat-label>
<textarea data-cy="yaml-input" matInput #input id="input" rows="20" [value]="devfileYaml"></textarea>
</mat-form-field>
<button data-cy="yaml-save" mat-flat-button color="primary" (click)="onButtonClick(input.value)">Save</button>
<button data-cy="yaml-clear" mat-flat-button color="warn" (click)="clear()">Clear</button>
<button data-cy="yaml-send" matTooltip="Save Devfile to disk" mat-flat-button color="primary" (click)="onButtonClick(input.value, true)">Save</button>
<button data-cy="yaml-save" matTooltip="Apply changes to other tabs" mat-flat-button color="normal" (click)="onButtonClick(input.value, false)">Apply</button>
<button data-cy="yaml-clear" matTooltip="Clear Devfile content" mat-flat-button color="normal" (click)="clear()">Clear</button>
</div>
</mat-tab>

View File

@@ -4,6 +4,7 @@ import { DomSanitizer } from '@angular/platform-browser';
import { MermaidService } from './services/mermaid.service';
import { StateService } from './services/state.service';
import { MatIconRegistry } from "@angular/material/icon";
import { OdoapiService } from './services/odoapi.service';
@Component({
selector: 'app-root',
@@ -20,6 +21,7 @@ export class AppComponent implements OnInit {
protected sanitizer: DomSanitizer,
private matIconRegistry: MatIconRegistry,
private wasmGo: DevstateService,
private odoApi: OdoapiService,
private mermaid: MermaidService,
private state: StateService,
) {
@@ -35,10 +37,12 @@ export class AppComponent implements OnInit {
loading.style.visibility = "hidden";
}
const devfile = this.wasmGo.getDevfileContent();
const devfile = this.odoApi.getDevfile();
devfile.subscribe({
next: (devfile) => {
this.onButtonClick(devfile.content);
if (devfile.content != undefined) {
this.onButtonClick(devfile.content, false);
}
}
});
@@ -62,12 +66,20 @@ export class AppComponent implements OnInit {
});
}
onButtonClick(content: string){
onButtonClick(content: string, save: boolean){
const result = this.wasmGo.setDevfileContent(content);
result.subscribe({
next: (value) => {
this.errorMessage = '';
this.state.changeDevfileYaml(value);
this.state.changeDevfileYaml(value);
if (save) {
this.odoApi.saveDevfile(value.content).subscribe({
next: () => {},
error: (error) => {
this.errorMessage = error.error.message;
}
});
}
},
error: (error) => {
this.errorMessage = error.error.message;
@@ -79,7 +91,7 @@ export class AppComponent implements OnInit {
if (confirm('You will delete the content of the Devfile. Continue?')) {
this.wasmGo.clearDevfileContent().subscribe({
next: (value) => {
this.onButtonClick(value.content);
this.onButtonClick(value.content, false);
}
});
}

View File

@@ -64,5 +64,5 @@
</mat-form-field>
</form>
<button [disabled]="form.invalid" mat-flat-button color="primary" (click)="onSave()">Save</button>
<button [disabled]="form.invalid" mat-flat-button color="primary" (click)="onSave()">Apply</button>
</div>

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { OdoapiService } from './odoapi.service';
describe('OdoapiService', () => {
let service: OdoapiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(OdoapiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { DevfileGet200Response, GeneralSuccess } from '../api-gen';
@Injectable({
providedIn: 'root'
})
export class OdoapiService {
private base = "/api/v1";
constructor(private http: HttpClient) { }
getDevfile(): Observable<DevfileGet200Response> {
return this.http.get<DevfileGet200Response>(this.base+"/devfile");
}
saveDevfile(content: string): Observable<GeneralSuccess> {
return this.http.put<GeneralSuccess>(this.base+"/devfile", {
content: content
});
}
}