diff --git a/ui/src/app/service/logout.interceptor.service.ts b/ui/src/app/service/logout.interceptor.service.ts index 88ddd0f74a..e6bde5e260 100644 --- a/ui/src/app/service/logout.interceptor.service.ts +++ b/ui/src/app/service/logout.interceptor.service.ts @@ -1,4 +1,5 @@ import {Injectable} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {Observable} from 'rxjs/Observable'; import {ToastService} from '../shared/toast/ToastService'; @@ -9,7 +10,11 @@ import 'rxjs/add/observable/throw'; @Injectable() export class LogoutInterceptor implements HttpInterceptor { - constructor(private _toast: ToastService, private _authStore: AuthentificationStore, private _router: Router) { + constructor( + private _toast: ToastService, + private _authStore: AuthentificationStore, + private _router: Router, + private _translate: TranslateService) { } intercept(req: HttpRequest, next: HttpHandler): Observable> { @@ -25,7 +30,11 @@ export class LogoutInterceptor implements HttpInterceptor { if (e.error && e.error.message) { this._toast.error(e.statusText, e.error.message); } else { - this._toast.error(e.statusText, JSON.parse(e.message)); + try { + this._toast.error(e.statusText, JSON.parse(e.message)); + } catch (e) { + this._toast.error(e.statusText, this._translate.instant('common_error')); + } } } return Observable.throw(e); diff --git a/ui/src/app/service/pipeline/pipeline.service.ts b/ui/src/app/service/pipeline/pipeline.service.ts index aca8eed072..172eb4e90c 100644 --- a/ui/src/app/service/pipeline/pipeline.service.ts +++ b/ui/src/app/service/pipeline/pipeline.service.ts @@ -6,7 +6,7 @@ import {GroupPermission} from '../../model/group.model'; import {Stage} from '../../model/stage.model'; import {Job} from '../../model/job.model'; import {Parameter} from '../../model/parameter.model'; -import {HttpClient, HttpParams} from '@angular/common/http'; +import {HttpClient, HttpParams, HttpHeaders} from '@angular/common/http'; /** * Service to access Pipeline from API. @@ -49,6 +49,20 @@ export class PipelineService { return this._http.put('/project/' + key + '/pipeline/' + oldName, pipeline); } + /** + * Import a pipeline + * @param key Project unique key + * @param workflow pipelineCode to import + */ + importPipeline(key: string, pipelineCode: string): Observable> { + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'application/x-yaml'); + let params = new HttpParams(); + params = params.append('format', 'yaml'); + + return this._http.post>(`/project/${key}/import/pipeline`, pipelineCode, {headers, params}); + } + /** * Delete a pipeline * @param key Project unique key diff --git a/ui/src/app/service/pipeline/pipeline.store.ts b/ui/src/app/service/pipeline/pipeline.store.ts index e8d33c1f65..43b04b29b0 100644 --- a/ui/src/app/service/pipeline/pipeline.store.ts +++ b/ui/src/app/service/pipeline/pipeline.store.ts @@ -96,6 +96,15 @@ export class PipelineStore { this._pipeline.next(cache.delete(pipKey)); } + /** + * Import a pipeline + * @param key Project unique key + * @param workflow pipelineCode to import + */ + importPipeline(key: string, pipelineCode: string): Observable> { + return this._pipelineService.importPipeline(key, pipelineCode); + } + /** * Create a new pipeline and put it in the store * @param key Project unique key diff --git a/ui/src/app/shared/button/upload/upload.button.component.ts b/ui/src/app/shared/button/upload/upload.button.component.ts new file mode 100644 index 0000000000..f15e813669 --- /dev/null +++ b/ui/src/app/shared/button/upload/upload.button.component.ts @@ -0,0 +1,33 @@ +import {EventEmitter, Output, Input, Component} from '@angular/core'; + +@Component({ + selector: 'app-upload-button', + templateUrl: './upload.button.html', + styleUrls: ['./upload.button.scss'] +}) +export class UploadButtonComponent { + + @Input() accept: string; + + @Input() size: string; + @Output() event = new EventEmitter(); + + showConfirmation = false; + + constructor() {} + + fileEvent(event) { + if (!event || !event.target || !event.target.files || !event.target.files[0]) { + return; + } + let file = event.target.files[0]; + let reader = new FileReader(); + let that = this; + + reader.onloadend = function(e: any) { + that.event.emit(e.target.result); + }; + + reader.readAsText(file); + } +} diff --git a/ui/src/app/shared/button/upload/upload.button.html b/ui/src/app/shared/button/upload/upload.button.html new file mode 100644 index 0000000000..ba9e76fcab --- /dev/null +++ b/ui/src/app/shared/button/upload/upload.button.html @@ -0,0 +1,5 @@ + + diff --git a/ui/src/app/shared/button/upload/upload.button.scss b/ui/src/app/shared/button/upload/upload.button.scss new file mode 100644 index 0000000000..80f9d795a4 --- /dev/null +++ b/ui/src/app/shared/button/upload/upload.button.scss @@ -0,0 +1,8 @@ +.inputfile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 70c1241371..ba38bf3154 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -12,6 +12,7 @@ import {PermissionService} from './permission/permission.service'; import {PermissionListComponent} from './permission/list/permission.list.component'; import {PermissionFormComponent} from './permission/form/permission.form.component'; import {DeleteButtonComponent} from './button/delete/delete.button'; +import {UploadButtonComponent} from './button/upload/upload.button.component'; import {ToastService} from './toast/ToastService'; import {BreadcrumbComponent} from './breadcrumb/breadcrumb.component'; import {ActionComponent} from './action/action.component'; @@ -90,6 +91,7 @@ import {VCSStrategyComponent} from './vcs/vcs.strategy.component'; CommitListComponent, CutPipe, DeleteButtonComponent, + UploadButtonComponent, ForMapPipe, GroupFormComponent, HistoryComponent, @@ -166,6 +168,7 @@ import {VCSStrategyComponent} from './vcs/vcs.strategy.component'; CommonModule, CutPipe, DeleteButtonComponent, + UploadButtonComponent, DragulaModule, ForMapPipe, FormsModule, diff --git a/ui/src/app/views/pipeline/add/pipeline.add.component.ts b/ui/src/app/views/pipeline/add/pipeline.add.component.ts index 51b12c2f9f..0d58d79c07 100644 --- a/ui/src/app/views/pipeline/add/pipeline.add.component.ts +++ b/ui/src/app/views/pipeline/add/pipeline.add.component.ts @@ -5,6 +5,7 @@ import {TranslateService} from '@ngx-translate/core'; import {ToastService} from '../../../shared/toast/ToastService'; import {ActivatedRoute, Router} from '@angular/router'; import {Project} from '../../../model/project.model'; +import {finalize} from 'rxjs/operators'; @Component({ selector: 'app-pipeline-add', @@ -18,6 +19,18 @@ export class PipelineAddComponent { pipelineType: Array; newPipeline = new Pipeline(); + codeMirrorConfig: any; + pipToImport = `# Pipeline example +version: v1.0 +name: root +jobs: +- job: run + stage: Stage 1 + steps: + - script: + - echo "I'm the first step" +`; + pipelineNamePattern: RegExp = new RegExp('^[a-zA-Z0-9._-]{1,}$'); pipPatternError = false; @@ -31,6 +44,13 @@ export class PipelineAddComponent { this.pipelineType = list.toArray(); this.newPipeline.type = this.pipelineType[0]; }); + + this.codeMirrorConfig = { + mode: 'text/x-yaml', + lineWrapping: true, + lineNumbers: true, + autoRefresh: true, + }; } createPipeline(): void { @@ -53,4 +73,22 @@ export class PipelineAddComponent { }); } + + goToProject(): void { + this._router.navigate(['/project', this.project.key], {queryParams: {tab: 'pipelines'}}); + } + + importPipeline() { + this.loadingCreate = true; + this._pipStore.importPipeline(this.project.key, this.pipToImport) + .pipe(finalize(() => this.loadingCreate = false)) + .subscribe(() => { + this._toast.success('', this._translate.instant('pipeline_added')); + this.goToProject(); + }); + } + + fileEvent(event) { + this.pipToImport = event; + } } diff --git a/ui/src/app/views/pipeline/add/pipeline.add.html b/ui/src/app/views/pipeline/add/pipeline.add.html index 18a9fa921b..62233085e8 100644 --- a/ui/src/app/views/pipeline/add/pipeline.add.html +++ b/ui/src/app/views/pipeline/add/pipeline.add.html @@ -2,10 +2,45 @@

{{ 'pipeline_create' | translate }}

+
+
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+
+
+
+
+ {{'common_or' | translate}} +
+
+
+
+ +
-
+
@@ -26,6 +61,23 @@

{{ 'pipeline_create' | translate }}

+
+
+ + +
+
+ + +
+
+
diff --git a/ui/src/app/views/pipeline/add/pipeline.add.scss b/ui/src/app/views/pipeline/add/pipeline.add.scss index 562134f15b..c7788e041e 100644 --- a/ui/src/app/views/pipeline/add/pipeline.add.scss +++ b/ui/src/app/views/pipeline/add/pipeline.add.scss @@ -9,5 +9,6 @@ h2 { text-align: center; - } border-color: #202f3c; -} \ No newline at end of file + } + border-color: #202f3c; +} diff --git a/ui/src/app/views/workflow/add/workflow.add.component.ts b/ui/src/app/views/workflow/add/workflow.add.component.ts index ad9dbff8e2..8723d55615 100644 --- a/ui/src/app/views/workflow/add/workflow.add.component.ts +++ b/ui/src/app/views/workflow/add/workflow.add.component.ts @@ -22,7 +22,18 @@ export class WorkflowAddComponent { codemirror: CodemirrorComponent; codeMirrorConfig: any; - wfToImport: string; + wfToImport = `# Example of workflow +name: myWorkflow +version: v1.0 +workflow: + myBuild: + pipeline: build + myTest: + depends_on: + - myBuild + when: + - success + pipeline: test`; updated = false; loading = false; @@ -87,4 +98,8 @@ export class WorkflowAddComponent { this.goToProject(); }); } + + fileEvent(event) { + this.wfToImport = event; + } } diff --git a/ui/src/app/views/workflow/add/workflow.add.html b/ui/src/app/views/workflow/add/workflow.add.html index 7108600649..b19ce56ee0 100644 --- a/ui/src/app/views/workflow/add/workflow.add.html +++ b/ui/src/app/views/workflow/add/workflow.add.html @@ -34,7 +34,6 @@

{{ 'workflow_create' | translate }}

@@ -44,6 +43,22 @@

{{ 'workflow_create' | translate }}

+ +
+
+ + +
+
+
+
+
+
+ {{'common_or' | translate}} +
+
+
+
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index ca819048c1..10cf488c8e 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -210,6 +210,7 @@ "common_no_environment": "There is no environment linked", "common_usage": "Usage", "common_orientation": "Orientation", + "common_or": "OR", "common_copy_clipboard": "Copy to clipboad", "common_select": "Select", "common_create": "Create", diff --git a/ui/src/assets/i18n/fr.json b/ui/src/assets/i18n/fr.json index 8f7936a9a8..1cc42be851 100644 --- a/ui/src/assets/i18n/fr.json +++ b/ui/src/assets/i18n/fr.json @@ -210,6 +210,7 @@ "common_no_environment": "Il n'y a aucun environnement lié", "common_usage": "Usage", "common_orientation": "Orientation", + "common_or": "OU", "common_copy_clipboard": "Copier dans le presse papier", "common_select": "Sélectionner", "common_create": "Créer",