From bbf1c26cfb8408d6293f814f87564d0013ddd470 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 30 Aug 2021 15:07:48 +0200 Subject: [PATCH 01/36] Bumped develop version to 0.3.0-SNAPSHOT --- ahoy-server/pom.xml | 2 +- ahoy-ui/package.json | 2 +- ahoy-ui/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahoy-server/pom.xml b/ahoy-server/pom.xml index d1f67781..630aea03 100644 --- a/ahoy-server/pom.xml +++ b/ahoy-server/pom.xml @@ -22,7 +22,7 @@ za.co.lsd.ahoy ahoy - 0.2.0 + 0.3.0-SNAPSHOT ahoy-server diff --git a/ahoy-ui/package.json b/ahoy-ui/package.json index f6040ee9..8be7951b 100644 --- a/ahoy-ui/package.json +++ b/ahoy-ui/package.json @@ -1,6 +1,6 @@ { "name": "ahoy-ui", - "version": "0.2.0", + "version": "0.3.0-SNAPSHOT", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/ahoy-ui/pom.xml b/ahoy-ui/pom.xml index 82ac0683..bc9f5566 100644 --- a/ahoy-ui/pom.xml +++ b/ahoy-ui/pom.xml @@ -22,7 +22,7 @@ za.co.lsd.ahoy ahoy - 0.2.0 + 0.3.0-SNAPSHOT ahoy-ui pom diff --git a/pom.xml b/pom.xml index 454748b4..995d9074 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ za.co.lsd.ahoy ahoy - 0.2.0 + 0.3.0-SNAPSHOT pom ahoy From 9b3af51a330dc5f5a46226b33374aea25eb8136e Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Tue, 31 Aug 2021 13:55:22 +0200 Subject: [PATCH 02/36] As a Release Manager, I'd like to be able to manage my releases - moved releases to environment releases; keeping to show releases in an environment as releases is going to change to show all releases --- ahoy-ui/src/app/app-routing.module.ts | 3 + ahoy-ui/src/app/app.module.ts | 6 +- .../add-release-dialog.component.html | 0 .../add-release-dialog.component.scss | 0 .../add-release-dialog.component.ts | 6 +- .../environment-releases.component.html | 120 +++++++++++++++ .../environment-releases.component.scss | 84 +++++++++++ .../environment-releases.component.ts | 141 ++++++++++++++++++ .../environments/environments.component.html | 2 +- .../src/app/releases/releases.component.ts | 2 +- 10 files changed, 357 insertions(+), 7 deletions(-) rename ahoy-ui/src/app/{releases => environment-release}/add-release-dialog/add-release-dialog.component.html (100%) rename ahoy-ui/src/app/{releases => environment-release}/add-release-dialog/add-release-dialog.component.scss (100%) rename ahoy-ui/src/app/{releases => environment-release}/add-release-dialog/add-release-dialog.component.ts (93%) create mode 100644 ahoy-ui/src/app/environment-release/environment-releases.component.html create mode 100644 ahoy-ui/src/app/environment-release/environment-releases.component.scss create mode 100644 ahoy-ui/src/app/environment-release/environment-releases.component.ts diff --git a/ahoy-ui/src/app/app-routing.module.ts b/ahoy-ui/src/app/app-routing.module.ts index f4a90b57..6fb13fc3 100644 --- a/ahoy-ui/src/app/app-routing.module.ts +++ b/ahoy-ui/src/app/app-routing.module.ts @@ -23,6 +23,7 @@ import {ApplicationsComponent} from './applications/applications.component'; import {ClusterDetailComponent} from './clusters/cluster-detail/cluster-detail.component'; import {ClustersComponent} from './clusters/clusters.component'; import {DashboardComponent} from './dashboard/dashboard.component'; +import {EnvironmentReleasesComponent} from './environment-release/environment-releases.component'; import {EnvironmentDetailComponent} from './environments/environment-detail/environment-detail.component'; import {EnvironmentsComponent} from './environments/environments.component'; import {ReleaseHistoryComponent} from './release-history/release-history.component'; @@ -50,6 +51,8 @@ const routes: Routes = [ {path: 'release/:environmentId/:releaseId/config/:relVersionId/:appVersionId', component: ReleaseApplicationEnvironmentConfigComponent}, {path: 'releasehistory/:releaseId', component: ReleaseHistoryComponent}, + {path: 'environmentreleases/:environmentId', component: EnvironmentReleasesComponent}, + {path: 'environments', component: EnvironmentsComponent, canActivate: [SettingsGuard]}, {path: 'environment/:id', component: EnvironmentDetailComponent}, diff --git a/ahoy-ui/src/app/app.module.ts b/ahoy-ui/src/app/app.module.ts index fc698d59..c4913597 100644 --- a/ahoy-ui/src/app/app.module.ts +++ b/ahoy-ui/src/app/app.module.ts @@ -84,9 +84,11 @@ import {VerifyValidatorDirective} from './components/confirm-dialog/verify-valid import {DescriptionDialogComponent} from './components/description-dialog/description-dialog.component'; import {DashboardEnvironmentComponent} from './dashboard/dashboard-environment/dashboard-environment.component'; import {DashboardComponent} from './dashboard/dashboard.component'; +import {AddReleaseDialogComponent} from './environment-release/add-release-dialog/add-release-dialog.component'; import {EnvironmentReleaseDeploymentStatusComponent} from './environment-release/environment-release-deployment-status/environment-release-deployment-status.component'; import {EnvironmentReleaseStatusComponent} from './environment-release/environment-release-status/environment-release-status.component'; import {EnvironmentReleaseVersionsComponent} from './environment-release/environment-release-versions/environment-release-versions.component'; +import {EnvironmentReleasesComponent} from './environment-release/environment-releases.component'; import {EnvironmentDetailComponent} from './environments/environment-detail/environment-detail.component'; import {EnvironmentNameUniqueValidatorDirective} from './environments/environment-name-unique-validator.directive'; import {EnvironmentsComponent} from './environments/environments.component'; @@ -94,7 +96,6 @@ import {NotificationsComponent} from './notifications/notifications.component'; import {ReleaseHistoryComponent} from './release-history/release-history.component'; import {AddApplicationDialogComponent} from './releases/add-application-dialog/add-application-dialog.component'; import {ApplicationAllowedValidatorDirective} from './releases/add-application-dialog/application-allowed.directive'; -import {AddReleaseDialogComponent} from './releases/add-release-dialog/add-release-dialog.component'; import {CopyEnvironmentConfigDialogComponent} from './releases/copy-environment-config-dialog/copy-environment-config-dialog.component'; import {PromoteDialogComponent} from './releases/promote-dialog/promote-dialog.component'; import {ReleaseApplicationEnvironmentConfigComponent} from './releases/release-application-environment-config/release-application-environment-config.component'; @@ -170,7 +171,8 @@ import {ErrorService} from './util/error.service'; EnvironmentReleaseDeploymentStatusComponent, ApplicationConfigFilesComponent, ApplicationVolumesComponent, - ApplicationSecretsComponent + ApplicationSecretsComponent, + EnvironmentReleasesComponent ], imports: [ BrowserModule, diff --git a/ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.html b/ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.html similarity index 100% rename from ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.html rename to ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.html diff --git a/ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.scss b/ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.scss similarity index 100% rename from ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.scss rename to ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.scss diff --git a/ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.ts b/ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.ts similarity index 93% rename from ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.ts rename to ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.ts index 2dba7d3d..79560e16 100644 --- a/ahoy-ui/src/app/releases/add-release-dialog/add-release-dialog.component.ts +++ b/ahoy-ui/src/app/environment-release/add-release-dialog/add-release-dialog.component.ts @@ -15,10 +15,10 @@ */ import {Component, OnInit} from '@angular/core'; -import {Environment} from '../../environments/environment'; -import {Release} from '../release'; -import {ReleasesService} from '../releases.service'; import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {Environment} from '../../environments/environment'; +import {Release} from '../../releases/release'; +import {ReleasesService} from '../../releases/releases.service'; @Component({ selector: 'app-add-release-dialog', diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.html b/ahoy-ui/src/app/environment-release/environment-releases.component.html new file mode 100644 index 00000000..07d1766c --- /dev/null +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.html @@ -0,0 +1,120 @@ + + +
+
+ + + + +
+
+
Releases
+
+ +
+ There are no environments setup, click here to manage environments.. +
+
+ +
+
+
Releases in {{selectedEnvironment.name}}
+
+ + + + + + + + + + +
{{environment.cluster.name}}/{{environment.name}}
+
+ +
{{environment.cluster.name}}/{{environment.name}}
+
+
+
+
+ +
+ There are no releases in this environment, create a new release or add a existing release.. +
+ + + +
+ + + + +
+
+ + + Release + + + State + + + Applications + + + Version + + + + + + + + Release + {{er.release.name}} + + State + + + Applications + + + Version + + + + + + + + +
+ +
+ +
+
diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.scss b/ahoy-ui/src/app/environment-release/environment-releases.component.scss new file mode 100644 index 00000000..e2de5433 --- /dev/null +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.scss @@ -0,0 +1,84 @@ +/*! + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host ::ng-deep { + .table-header { + display: flex; + justify-content: space-between; + } + + .p-datatable.p-datatable-releases { + .p-datatable-header { + padding: 1rem; + text-align: left; + font-size: 1.5rem; + } + + .p-datatable-thead > tr > th { + text-align: left; + } + } + + .p-datatable.p-datatable-releases:not(.p-datatable-gridlines) { + .p-datatable-tbody > tr > td { + cursor: auto; + } + } + + /* Responsive */ + .p-datatable-releases .p-datatable-tbody > tr > td .p-column-title { + display: none; + } +} + +@media screen and (max-width: 960px) { + :host ::ng-deep { + .p-datatable { + &.p-datatable-releases { + .p-datatable-thead > tr > th, + .p-datatable-tfoot > tr > td { + display: none !important; + } + + .p-datatable-tbody > tr { + border-bottom: 1px solid var(--surface-d); + + > td:last-child { + text-align: center; + } + + > td { + text-align: left; + display: block; + border: 0 none !important; + width: 100% !important; + float: left; + clear: left; + border: 0 none; + + .p-column-title { + padding: .4rem; + min-width: 30%; + display: inline-block; + margin: -.4rem 1rem -.4rem -.4rem; + font-weight: bold; + } + } + } + } + } + } +} diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.ts b/ahoy-ui/src/app/environment-release/environment-releases.component.ts new file mode 100644 index 00000000..0b1b9c95 --- /dev/null +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.ts @@ -0,0 +1,141 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {ConfirmationService} from 'primeng/api'; +import {DialogService, DynamicDialogConfig} from 'primeng/dynamicdialog'; +import {filter, mergeMap} from 'rxjs/operators'; +import {AppBreadcrumbService} from '../app.breadcrumb.service'; +import {EnvironmentRelease, EnvironmentReleaseId} from '../environment-release/environment-release'; +import {EnvironmentReleaseService} from '../environment-release/environment-release.service'; +import {Environment} from '../environments/environment'; +import {EnvironmentService} from '../environments/environment.service'; +import {ReleaseService} from '../release/release.service'; +import {Release} from '../releases/release'; +import {ReleasesService} from '../releases/releases.service'; +import {TaskEvent} from '../taskevents/task-events'; +import {LoggerService} from '../util/logger.service'; +import {AddReleaseDialogComponent} from './add-release-dialog/add-release-dialog.component'; + +@Component({ + selector: 'app-environment-releases', + templateUrl: './environment-releases.component.html', + styleUrls: ['./environment-releases.component.scss'] +}) +export class EnvironmentReleasesComponent implements OnInit { + environments: Environment[] = undefined; + environmentReleases: EnvironmentRelease[] = undefined; + selectedEnvironment: Environment; + + constructor(private route: ActivatedRoute, + private router: Router, + private environmentService: EnvironmentService, + private environmentReleaseService: EnvironmentReleaseService, + private releasesService: ReleasesService, + private releaseService: ReleaseService, + private log: LoggerService, + private dialogService: DialogService, + private confirmationService: ConfirmationService, + private breadcrumbService: AppBreadcrumbService) { + } + + ngOnInit() { + this.setBreadcrumb(); + + const environmentId = +this.route.snapshot.paramMap.get('environmentId'); + + this.environmentService.getAll().subscribe((environments) => { + this.environments = environments; + this.getReleases(environmentId); + }); + } + + private getReleases(environmentId) { + this.log.debug('getting environment releases for environmentId=', environmentId); + this.environmentService.get(environmentId) + .subscribe(env => { + this.selectedEnvironment = env; + this.environmentReleaseService.getReleasesByEnvironment(environmentId) + .subscribe(envReleases => { + this.environmentReleases = envReleases; + this.setBreadcrumb(); + }); + }); + } + + private setBreadcrumb() { + if (this.selectedEnvironment) { + this.breadcrumbService.setItems([ + {label: this.selectedEnvironment.cluster.name, routerLink: '/clusters'}, + {label: this.selectedEnvironment.name, routerLink: '/environments', queryParams: {clusterId: this.selectedEnvironment.cluster.id}}, + {label: 'releases'} + ]); + + } else { + this.breadcrumbService.setItems([{label: 'releases'}]); + } + } + + addRelease() { + const dialogConfig = new DynamicDialogConfig(); + dialogConfig.header = `Add release to ${this.selectedEnvironment.cluster.name}/${this.selectedEnvironment.name}`; + dialogConfig.data = this.selectedEnvironment; + + const dialogRef = this.dialogService.open(AddReleaseDialogComponent, dialogConfig); + dialogRef.onClose.pipe( + filter((result) => result !== undefined), // cancelled + mergeMap((release: Release) => { + const environmentRelease = new EnvironmentRelease(); + environmentRelease.id = new EnvironmentReleaseId(); + + environmentRelease.environment = this.environmentService.link(this.selectedEnvironment.id); + environmentRelease.release = this.releasesService.link(release.id); + + return this.environmentReleaseService.save(environmentRelease); + }) + ).subscribe(() => { + this.getReleases(this.selectedEnvironment.id); + }); + } + + removeRelease(event: Event, environmentRelease: EnvironmentRelease) { + this.confirmationService.confirm({ + target: event.target, + message: `Are you sure you want to remove ${(environmentRelease.release as Release).name} from ${(environmentRelease.environment as Environment).name}?`, + icon: 'pi pi-exclamation-triangle', + accept: () => { + this.releaseService.remove(environmentRelease) + .subscribe(() => this.getReleases(this.selectedEnvironment.id)); + } + }); + } + + environmentChanged() { + this.getReleases(this.selectedEnvironment.id); + } + + taskEventOccurred(event: TaskEvent) { + if (event.releaseStatusChangedEvent) { + const statusChangedEvent = event.releaseStatusChangedEvent; + if (this.selectedEnvironment.id === statusChangedEvent.environmentReleaseId.environmentId) { + setTimeout(() => { + this.getReleases(this.selectedEnvironment.id); + }, 1000); + } + } + } +} diff --git a/ahoy-ui/src/app/environments/environments.component.html b/ahoy-ui/src/app/environments/environments.component.html index 12d13324..7de590e4 100644 --- a/ahoy-ui/src/app/environments/environments.component.html +++ b/ahoy-ui/src/app/environments/environments.component.html @@ -75,7 +75,7 @@
Environments
+ @@ -56,7 +56,7 @@
Releases in {{selectedEnvironment.name}}
- There are no releases in this environment, create a new release or add a existing release.. + There are no releases in this environment, create a new release or add a existing release..
{ - this.releaseService.remove(environmentRelease) + this.releaseManageService.remove(environmentRelease) .subscribe(() => this.getReleases(this.selectedEnvironment.id)); } }); diff --git a/ahoy-ui/src/app/environments/environment.service.ts b/ahoy-ui/src/app/environments/environment.service.ts index 8839489a..ab14115d 100644 --- a/ahoy-ui/src/app/environments/environment.service.ts +++ b/ahoy-ui/src/app/environments/environment.service.ts @@ -15,14 +15,13 @@ */ import {Injectable} from '@angular/core'; -import {LoggerService} from '../util/logger.service'; -import {RestClientService} from '../util/rest-client.service'; import {EMPTY, Observable, of} from 'rxjs'; -import {Environment} from './environment'; import {catchError, map, mergeMap, tap} from 'rxjs/operators'; -import {EnvironmentRelease} from '../environment-release/environment-release'; import {Notification} from '../notifications/notification'; import {NotificationsService} from '../notifications/notifications.service'; +import {LoggerService} from '../util/logger.service'; +import {RestClientService} from '../util/rest-client.service'; +import {Environment} from './environment'; @Injectable({ providedIn: 'root' @@ -52,8 +51,8 @@ export class EnvironmentService { ); } - getAllForPromotion(environmentRelease: EnvironmentRelease): Observable { - const url = `/data/environments/search/forPromotion?releaseId=${environmentRelease.id.releaseId}`; + getAllForPromotion(releaseId: number): Observable { + const url = `/data/environments/search/forPromotion?releaseId=${releaseId}`; return this.restClient.get(url).pipe( map(response => response._embedded.environments as Environment[]), tap((envs) => this.log.debug(`fetched ${envs.length} environments`)) diff --git a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts index a3a77e1b..1af13b2d 100644 --- a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts +++ b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts @@ -46,7 +46,7 @@ export class PromoteDialogComponent implements OnInit { } ngOnInit() { - this.environmentService.getAllForPromotion(this.environmentRelease) + this.environmentService.getAllForPromotion(this.environmentRelease.id.releaseId) .subscribe(environments => this.environments = environments); } diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index cf9b9c9e..b9ff9624 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -107,7 +107,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { this.menuItems = [ { label: 'Edit', icon: 'pi pi-fw pi-pencil', disabled: !this.canEdit(), - routerLink: `/release/edit/${this.environmentRelease.id.environmentId}/${this.environmentRelease.id.releaseId}/version/${this.releaseVersion.id}` + routerLink: `/environmentrelease/edit/${this.environmentRelease.id.environmentId}/${this.environmentRelease.id.releaseId}/version/${this.releaseVersion.id}` }, { label: 'History', icon: 'pi pi-fw pi-list', diff --git a/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.html b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.html new file mode 100644 index 00000000..d3e5481b --- /dev/null +++ b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.html @@ -0,0 +1,28 @@ + + +
+
+ + +
+
+ +
+ + +
diff --git a/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.scss b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.scss new file mode 100644 index 00000000..fe834d2c --- /dev/null +++ b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.scss @@ -0,0 +1,16 @@ +/*! + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.ts b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.ts new file mode 100644 index 00000000..a022ec2b --- /dev/null +++ b/ahoy-ui/src/app/releases/add-to-environment-dialog/add-to-environment-dialog.component.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Component, OnInit} from '@angular/core'; +import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {Environment} from '../../environments/environment'; +import {EnvironmentService} from '../../environments/environment.service'; +import {Release} from '../../releases/release'; + +@Component({ + selector: 'app-promote-dialog', + templateUrl: './add-to-environment-dialog.component.html', + styleUrls: ['./add-to-environment-dialog.component.scss'] +}) +export class AddToEnvironmentDialogComponent implements OnInit { + release: Release; + environments: Environment[]; + selected: Environment; + + constructor(private environmentService: EnvironmentService, + public ref: DynamicDialogRef, + public config: DynamicDialogConfig) { + const data = config.data; + this.release = data.release; + } + + ngOnInit() { + this.environmentService.getAllForPromotion(this.release.id) + .subscribe(environments => this.environments = environments); + } + + cancel() { + this.ref.destroy(); + } + + close(result: any) { + this.ref.close(result); + } +} diff --git a/ahoy-ui/src/app/releases/release-detail/release-detail.component.html b/ahoy-ui/src/app/releases/release-detail/release-detail.component.html new file mode 100644 index 00000000..c3d65b18 --- /dev/null +++ b/ahoy-ui/src/app/releases/release-detail/release-detail.component.html @@ -0,0 +1,67 @@ + + +
+
+ +
+
+
New release
+
+ + + + + + + + +
+
+ +
+ + + + Name invalid: should start with and use lower case letters and numbers + + + Release already exists + +
+ +
+ + + + Version already exists + +
+ +
+
+ +
+ +
+
diff --git a/ahoy-ui/src/app/releases/release-detail/release-detail.component.scss b/ahoy-ui/src/app/releases/release-detail/release-detail.component.scss new file mode 100644 index 00000000..fe834d2c --- /dev/null +++ b/ahoy-ui/src/app/releases/release-detail/release-detail.component.scss @@ -0,0 +1,16 @@ +/*! + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts b/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts new file mode 100644 index 00000000..64a95e9b --- /dev/null +++ b/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Location} from '@angular/common'; +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {mergeMap} from 'rxjs/operators'; +import {AppBreadcrumbService} from '../../app.breadcrumb.service'; +import {ApplicationService} from '../../applications/application.service'; +import {EnvironmentReleaseService} from '../../environment-release/environment-release.service'; +import {EnvironmentService} from '../../environments/environment.service'; +import {Release, ReleaseVersion} from '../../releases/release'; +import {ReleaseService} from '../../releases/release.service'; +import {LoggerService} from '../../util/logger.service'; + +@Component({ + selector: 'app-release-detail', + templateUrl: './release-detail.component.html', + styleUrls: ['./release-detail.component.scss'] +}) +export class ReleaseDetailComponent implements OnInit { + release: Release; + releaseVersion: ReleaseVersion; + releasesForValidation: Release[]; + + constructor( + private log: LoggerService, + private route: ActivatedRoute, + private router: Router, + private releasesService: ReleaseService, + private environmentService: EnvironmentService, + private environmentReleaseService: EnvironmentReleaseService, + private applicationService: ApplicationService, + private location: Location, + private breadcrumbService: AppBreadcrumbService) { + } + + ngOnInit() { + this.setBreadcrumb(); + + const releaseId = this.route.snapshot.paramMap.get('releaseId'); + + if (releaseId === 'new') { + this.release = new Release(); + this.releaseVersion = new ReleaseVersion(); + } + + this.releasesService.getAll() + .subscribe(rels => this.releasesForValidation = rels); + } + + private setBreadcrumb() { + this.breadcrumbService.setItems([ + {label: 'new release'} + ]); + } + + save() { + this.releasesService.save(this.release) + .pipe( + mergeMap(release => { + this.release = release; + this.releaseVersion.release = this.releasesService.link(release.id); + return this.releasesService.saveVersion(this.releaseVersion); + }) + ) + .subscribe(() => this.location.back()); + } + + cancel() { + this.release = undefined; + this.location.back(); + } +} diff --git a/ahoy-ui/src/app/releases/release.service.ts b/ahoy-ui/src/app/releases/release.service.ts index c384c85f..78a7e6d1 100644 --- a/ahoy-ui/src/app/releases/release.service.ts +++ b/ahoy-ui/src/app/releases/release.service.ts @@ -35,7 +35,15 @@ export class ReleaseService { const url = `/data/releases`; return this.restClient.get(url).pipe( map(response => response._embedded.releases as Release[]), - tap((apps) => this.log.debug(`fetched ${apps.length} releases`)) + tap((releases) => this.log.debug(`fetched ${releases.length} releases`)) + ); + } + + getAllSummary(): Observable { + const url = `/data/releases?projection=releaseSummary`; + return this.restClient.get(url).pipe( + map(response => response._embedded.releases as Release[]), + tap((releases) => this.log.debug(`fetched ${releases.length} releases`)) ); } @@ -43,7 +51,7 @@ export class ReleaseService { const url = `/data/releases/search/forAdd?environmentId=${environmentIdToIgnore}&projection=release`; return this.restClient.get(url).pipe( map(response => response._embedded.releases as Release[]), - tap((apps) => this.log.debug(`fetched ${apps.length} releases for add`)) + tap((releases) => this.log.debug(`fetched ${releases.length} releases for add`)) ); } @@ -51,7 +59,7 @@ export class ReleaseService { const url = `/data/environments/${environmentId}/releases`; return this.restClient.get(url).pipe( map(response => response._embedded.releases as Release[]), - tap((rels) => this.log.debug(`fetched ${rels.length} releases for environment=${environmentId}`)) + tap((releases) => this.log.debug(`fetched ${releases.length} releases for environment=${environmentId}`)) ); } @@ -84,6 +92,15 @@ export class ReleaseService { } } + delete(release: Release): Observable { + const id = release.id; + const url = `/data/releases/${id}`; + + return this.restClient.delete(url).pipe( + tap(() => this.log.debug('deleted release', release)) + ); + } + saveVersion(releaseVersion: ReleaseVersion): Observable { if (!releaseVersion.id) { this.log.debug('saving release version: ', releaseVersion); diff --git a/ahoy-ui/src/app/releases/release.ts b/ahoy-ui/src/app/releases/release.ts index 6fca5daf..db8840d1 100644 --- a/ahoy-ui/src/app/releases/release.ts +++ b/ahoy-ui/src/app/releases/release.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 LSD Information Technology (Pty) Ltd + * Copyright 2021 LSD Information Technology (Pty) Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,13 @@ */ import {ApplicationVersion} from '../applications/application'; +import {EnvironmentRelease} from '../environment-release/environment-release'; export class Release { id: number; name: string; releaseVersions: ReleaseVersion[]; + environmentReleases: EnvironmentRelease[]; } export class ReleaseVersion { diff --git a/ahoy-ui/src/app/releases/releases.component.html b/ahoy-ui/src/app/releases/releases.component.html index b9a3f9f7..73a82436 100644 --- a/ahoy-ui/src/app/releases/releases.component.html +++ b/ahoy-ui/src/app/releases/releases.component.html @@ -20,49 +20,25 @@ -
-
-
Releases
-
- -
- There are no environments setup, click here to manage environments.. -
-
- -
+
Releases
- - - - - - - -
{{environment.cluster.name}}/{{environment.name}}
-
- -
{{environment.cluster.name}}/{{environment.name}}
-
-
+
-
- There are no releases in this environment, create a new release or add a existing release.. +
+ There are no releases, create a new release..
- + [scrollable]="true" scrollHeight="600px" [filterDelay]="0" [globalFilterFields]="['name']" + *ngIf="releases && releases.length > 0">
@@ -73,42 +49,34 @@
Releases
- Release + Release - State - - - Applications - - - Version - - + Environments - + Release - {{er.release.name}} - - State - - - Applications - + {{release.name}} - Version - + Environments + + + - - + + + + diff --git a/ahoy-ui/src/app/releases/releases.component.scss b/ahoy-ui/src/app/releases/releases.component.scss index e2de5433..a5483af0 100644 --- a/ahoy-ui/src/app/releases/releases.component.scss +++ b/ahoy-ui/src/app/releases/releases.component.scss @@ -15,6 +15,20 @@ */ :host ::ng-deep { + .chip-environment { + cursor: pointer; + + &.status-deployed { + background: #C8E6C9; + color: #256029; + } + + &.status-undeployed { + background: #FEEDAF; + color: #8A5340; + } + } + .table-header { display: flex; justify-content: space-between; diff --git a/ahoy-ui/src/app/releases/releases.component.ts b/ahoy-ui/src/app/releases/releases.component.ts index 57114a35..7a4aa35b 100644 --- a/ahoy-ui/src/app/releases/releases.component.ts +++ b/ahoy-ui/src/app/releases/releases.component.ts @@ -20,14 +20,13 @@ import {ConfirmationService} from 'primeng/api'; import {DialogService, DynamicDialogConfig} from 'primeng/dynamicdialog'; import {filter, mergeMap} from 'rxjs/operators'; import {AppBreadcrumbService} from '../app.breadcrumb.service'; -import {AddReleaseDialogComponent} from '../environment-release/add-release-dialog/add-release-dialog.component'; import {EnvironmentRelease, EnvironmentReleaseId} from '../environment-release/environment-release'; import {EnvironmentReleaseService} from '../environment-release/environment-release.service'; import {Environment} from '../environments/environment'; import {EnvironmentService} from '../environments/environment.service'; -import {ReleaseManageService} from '../release-manage/release-manage.service'; import {TaskEvent} from '../taskevents/task-events'; import {LoggerService} from '../util/logger.service'; +import {AddToEnvironmentDialogComponent} from './add-to-environment-dialog/add-to-environment-dialog.component'; import {Release} from './release'; import {ReleaseService} from './release.service'; @@ -37,114 +36,70 @@ import {ReleaseService} from './release.service'; styleUrls: ['./releases.component.scss'] }) export class ReleasesComponent implements OnInit { - environments: Environment[] = undefined; - environmentReleases: EnvironmentRelease[] = undefined; - selectedEnvironment: Environment; + releases: Release[] = undefined; constructor(private route: ActivatedRoute, private router: Router, + private releaseService: ReleaseService, private environmentService: EnvironmentService, private environmentReleaseService: EnvironmentReleaseService, - private releasesService: ReleaseService, - private releaseService: ReleaseManageService, private log: LoggerService, private dialogService: DialogService, private confirmationService: ConfirmationService, private breadcrumbService: AppBreadcrumbService) { + + this.breadcrumbService.setItems([{label: 'releases'}]); } ngOnInit() { - this.setBreadcrumb(); - - const environmentId = +this.route.snapshot.queryParamMap.get('environmentId'); - - this.environmentService.getAll().subscribe((environments) => { - this.environments = environments; - - if (environmentId === 0) { - this.environmentService.getLastUsedId().subscribe((lastUsedEnvironmentId) => { - if (lastUsedEnvironmentId !== 0) { - this.getReleases(lastUsedEnvironmentId); - } - }); - } else { - this.getReleases(environmentId); - } - }); + this.getReleases(); } - private getReleases(environmentId) { - this.log.debug('getting environment releases for environmentId=', environmentId); - this.environmentService.get(environmentId) - .subscribe(env => { - this.selectedEnvironment = env; - this.environmentReleaseService.getReleasesByEnvironment(environmentId) - .subscribe(envReleases => { - this.environmentReleases = envReleases; - this.setBreadcrumb(); - }); + private getReleases() { + this.log.debug('getting all releases'); + this.releaseService.getAllSummary() + .subscribe((releases) => { + this.releases = releases; }); } - private setBreadcrumb() { - if (this.selectedEnvironment) { - this.breadcrumbService.setItems([ - {label: this.selectedEnvironment.cluster.name, routerLink: '/clusters'}, - {label: this.selectedEnvironment.name, routerLink: '/environments', queryParams: {clusterId: this.selectedEnvironment.cluster.id}}, - {label: 'releases'} - ]); - - } else { - this.breadcrumbService.setItems([{label: 'releases'}]); - } - } - - addRelease() { + addToEnvironment(release: Release) { const dialogConfig = new DynamicDialogConfig(); - dialogConfig.header = `Add release to ${this.selectedEnvironment.cluster.name}/${this.selectedEnvironment.name}`; - dialogConfig.data = this.selectedEnvironment; + dialogConfig.header = `Add ${release.name} to:`; + dialogConfig.data = {release}; - const dialogRef = this.dialogService.open(AddReleaseDialogComponent, dialogConfig); + const dialogRef = this.dialogService.open(AddToEnvironmentDialogComponent, dialogConfig); dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled - mergeMap((release: Release) => { + mergeMap((selectedEnvironment: Environment) => { const environmentRelease = new EnvironmentRelease(); environmentRelease.id = new EnvironmentReleaseId(); - environmentRelease.environment = this.environmentService.link(this.selectedEnvironment.id); - environmentRelease.release = this.releasesService.link(release.id); + environmentRelease.environment = this.environmentService.link(selectedEnvironment.id); + environmentRelease.release = this.releaseService.link(release.id); return this.environmentReleaseService.save(environmentRelease); }) - ).subscribe(() => { - this.getReleases(this.selectedEnvironment.id); - }); + ).subscribe(() => this.getReleases()); } - removeRelease(event: Event, environmentRelease: EnvironmentRelease) { + deleteRelease(event, release: Release) { this.confirmationService.confirm({ target: event.target, - message: `Are you sure you want to remove ${(environmentRelease.release as Release).name} from ${(environmentRelease.environment as Environment).name}?`, + message: `Are you sure you want to delete ${release.name}?`, icon: 'pi pi-exclamation-triangle', accept: () => { - this.releaseService.remove(environmentRelease) - .subscribe(() => this.getReleases(this.selectedEnvironment.id)); + this.releaseService.delete(release) + .subscribe(() => this.getReleases()); } }); } - environmentChanged() { - this.getReleases(this.selectedEnvironment.id); - } - taskEventOccurred(event: TaskEvent) { if (event.releaseStatusChangedEvent) { - const statusChangedEvent = event.releaseStatusChangedEvent; - if (this.selectedEnvironment.id === statusChangedEvent.environmentReleaseId.environmentId) { - setTimeout(() => { - this.getReleases(this.selectedEnvironment.id); - }, 1000); - } + setTimeout(() => { + this.getReleases(); + }, 1000); } } } From c5d483f529c739fec3051ce49ae83b2ba14c6683 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Wed, 1 Sep 2021 15:09:44 +0200 Subject: [PATCH 05/36] As a Release Manager, I'd like to be able to manage my releases - added ability to edit release; if not deployed - resized add to environment dialog --- .../release-detail.component.html | 4 ++-- .../release-detail.component.ts | 22 ++++++++++++++----- .../src/app/releases/releases.component.html | 6 +++++ .../src/app/releases/releases.component.ts | 10 +++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ahoy-ui/src/app/releases/release-detail/release-detail.component.html b/ahoy-ui/src/app/releases/release-detail/release-detail.component.html index c3d65b18..e1c42a64 100644 --- a/ahoy-ui/src/app/releases/release-detail/release-detail.component.html +++ b/ahoy-ui/src/app/releases/release-detail/release-detail.component.html @@ -15,7 +15,7 @@ -->
-
+
@@ -47,7 +47,7 @@
New release
-
+
{ + this.release = rel; + this.setBreadcrumb(); + }); } this.releasesService.getAll() @@ -64,7 +73,7 @@ export class ReleaseDetailComponent implements OnInit { private setBreadcrumb() { this.breadcrumbService.setItems([ - {label: 'new release'} + {label: (this.editMode ? 'edit' : 'new') + ' release'} ]); } @@ -73,8 +82,11 @@ export class ReleaseDetailComponent implements OnInit { .pipe( mergeMap(release => { this.release = release; - this.releaseVersion.release = this.releasesService.link(release.id); - return this.releasesService.saveVersion(this.releaseVersion); + if (!this.editMode) { + this.releaseVersion.release = this.releasesService.link(release.id); + return this.releasesService.saveVersion(this.releaseVersion); + } + return of(release); }) ) .subscribe(() => this.location.back()); diff --git a/ahoy-ui/src/app/releases/releases.component.html b/ahoy-ui/src/app/releases/releases.component.html index 73a82436..ee320af6 100644 --- a/ahoy-ui/src/app/releases/releases.component.html +++ b/ahoy-ui/src/app/releases/releases.component.html @@ -71,6 +71,12 @@
Releases
+ + + - + + + diff --git a/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts b/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts index d4ef5c32..c34adf21 100644 --- a/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts +++ b/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts @@ -31,10 +31,22 @@ export class ApplicationVersionsComponent { private confirmationService: ConfirmationService) { } - removeApplicationVersion(event: Event, applicationVersion: ApplicationVersion) { + canDelete(applicationVersion: ApplicationVersion) { + return !(applicationVersion.releaseVersions && applicationVersion.releaseVersions.length > 0); + } + + usedByReleaseVersions(applicationVersion: ApplicationVersion): string[] { + const usedBy = new Set(); + for (const releaseVersion of applicationVersion.releaseVersions) { + usedBy.add(`${releaseVersion.releaseName}:${releaseVersion.version}`); + } + return Array.from(usedBy.values()).sort(); + } + + delete(event: Event, applicationVersion: ApplicationVersion) { this.confirmationService.confirm({ target: event.target, - message: `Are you sure you want to remove version ${applicationVersion.version} from ${this.application.name}?`, + message: `Are you sure you want to delete version ${applicationVersion.version} from ${this.application.name}?`, icon: 'pi pi-exclamation-triangle', accept: () => { this.applicationService.deleteVersion(applicationVersion) diff --git a/ahoy-ui/src/app/applications/application.ts b/ahoy-ui/src/app/applications/application.ts index 9bd3cd79..194375c6 100644 --- a/ahoy-ui/src/app/applications/application.ts +++ b/ahoy-ui/src/app/applications/application.ts @@ -15,6 +15,7 @@ */ import {EnvironmentReleaseId} from '../environment-release/environment-release'; +import {ReleaseVersion} from '../releases/release'; import {DockerRegistry} from '../settings/docker-settings/docker-settings'; export class Application { @@ -40,6 +41,7 @@ export class ApplicationVersion { secrets: ApplicationSecret[]; configPath: string; status: ApplicationReleaseStatus; + releaseVersions: ReleaseVersion[]; } export class ApplicationEnvironmentVariable { diff --git a/ahoy-ui/src/app/applications/applications.component.html b/ahoy-ui/src/app/applications/applications.component.html index 78e851e1..9b694cd9 100644 --- a/ahoy-ui/src/app/applications/applications.component.html +++ b/ahoy-ui/src/app/applications/applications.component.html @@ -33,7 +33,7 @@
Applications
@@ -48,9 +48,10 @@
Applications
Application - Version + Latest version + Releases @@ -59,17 +60,25 @@
Applications
Application {{application.name}} - Version + Latest version {{application.latestApplicationVersion.version}} - + Releases + + + + - + + + diff --git a/ahoy-ui/src/app/applications/applications.component.ts b/ahoy-ui/src/app/applications/applications.component.ts index 7084ea52..27f80ded 100644 --- a/ahoy-ui/src/app/applications/applications.component.ts +++ b/ahoy-ui/src/app/applications/applications.component.ts @@ -15,14 +15,14 @@ */ import {Component, OnInit} from '@angular/core'; -import {ApplicationService} from './application.service'; -import {Application} from './application'; import {ActivatedRoute} from '@angular/router'; -import {LoggerService} from '../util/logger.service'; -import {AppBreadcrumbService} from '../app.breadcrumb.service'; -import {DialogUtilService} from '../components/dialog-util.service'; import {filter} from 'rxjs/operators'; +import {AppBreadcrumbService} from '../app.breadcrumb.service'; import {Confirmation} from '../components/confirm-dialog/confirm'; +import {DialogUtilService} from '../components/dialog-util.service'; +import {LoggerService} from '../util/logger.service'; +import {Application} from './application'; +import {ApplicationService} from './application.service'; @Component({ selector: 'app-applications', @@ -52,6 +52,25 @@ export class ApplicationsComponent implements OnInit { .subscribe(applications => this.applications = applications); } + canDelete(application: Application): boolean { + for (const applicationVersion of application.applicationVersions) { + if (applicationVersion.releaseVersions && applicationVersion.releaseVersions.length > 0) { + return false; + } + } + return true; + } + + usedByReleases(application: Application): string[] { + const usedBy = new Set(); + for (const applicationVersion of application.applicationVersions) { + for (const releaseVersion of applicationVersion.releaseVersions) { + usedBy.add(releaseVersion.releaseName); + } + } + return Array.from(usedBy.values()).sort(); + } + delete(event: Event, application: Application) { const confirmation = new Confirmation(`Are you sure you want to delete ${application.name}?`); confirmation.verify = true; diff --git a/ahoy-ui/src/app/releases/release.ts b/ahoy-ui/src/app/releases/release.ts index db8840d1..a191ba44 100644 --- a/ahoy-ui/src/app/releases/release.ts +++ b/ahoy-ui/src/app/releases/release.ts @@ -29,4 +29,5 @@ export class ReleaseVersion { version: string; release: Release | string; applicationVersions: ApplicationVersion[] = undefined; + releaseName: string; } diff --git a/ahoy-ui/src/app/releases/releases.component.html b/ahoy-ui/src/app/releases/releases.component.html index ee320af6..390c9c51 100644 --- a/ahoy-ui/src/app/releases/releases.component.html +++ b/ahoy-ui/src/app/releases/releases.component.html @@ -63,7 +63,7 @@
Releases
Environments - From b55b7a493609de665978fa7ce28bcb24ede48465 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Thu, 2 Sep 2021 15:24:48 +0200 Subject: [PATCH 07/36] As a Release Manager, I'd like to know the root cause of a failure - added ability to see the error trace on the notification description dialog - trace shown for all known and unknown errors --- ahoy-ui/src/app/clusters/cluster.service.ts | 8 ++++---- .../description-dialog.component.html | 12 ++++++++++-- .../components/description-dialog/description.ts | 6 ++++-- .../src/app/environments/environment.service.ts | 12 ++++++------ ahoy-ui/src/app/notifications/notification.ts | 15 +++++++++++++-- .../app/notifications/notifications.component.ts | 2 +- .../app/release-manage/release-manage.service.ts | 16 ++++++++-------- .../argo-settings/argo-settings.service.ts | 4 ++-- .../git-settings/git-settings.service.ts | 4 ++-- ahoy-ui/src/app/util/error.service.ts | 6 +++--- 10 files changed, 53 insertions(+), 32 deletions(-) diff --git a/ahoy-ui/src/app/clusters/cluster.service.ts b/ahoy-ui/src/app/clusters/cluster.service.ts index 97705d72..c6c9b5f6 100644 --- a/ahoy-ui/src/app/clusters/cluster.service.ts +++ b/ahoy-ui/src/app/clusters/cluster.service.ts @@ -99,9 +99,9 @@ export class ClusterService { const text = `${destroyedCluster.name} ` + `was destroyed`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to destroy cluster ${cluster.name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -115,9 +115,9 @@ export class ClusterService { const text = `Successfully connected to cluster '${cluster.name}'`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to connect to cluster ${cluster.name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); diff --git a/ahoy-ui/src/app/components/description-dialog/description-dialog.component.html b/ahoy-ui/src/app/components/description-dialog/description-dialog.component.html index 1f54b89d..1f9659d7 100644 --- a/ahoy-ui/src/app/components/description-dialog/description-dialog.component.html +++ b/ahoy-ui/src/app/components/description-dialog/description-dialog.component.html @@ -15,8 +15,16 @@ -->
-
- +
+
+ +
+ + + + + +
diff --git a/ahoy-ui/src/app/components/description-dialog/description.ts b/ahoy-ui/src/app/components/description-dialog/description.ts index 5fcc8e3f..a1c8f708 100644 --- a/ahoy-ui/src/app/components/description-dialog/description.ts +++ b/ahoy-ui/src/app/components/description-dialog/description.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 LSD Information Technology (Pty) Ltd + * Copyright 2021 LSD Information Technology (Pty) Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ export class Description { title: string; text: string; + trace: string; - constructor(title: string, text: string) { + constructor(title: string, text: string, trace?: string) { this.title = title; this.text = text; + this.trace = trace; } } diff --git a/ahoy-ui/src/app/environments/environment.service.ts b/ahoy-ui/src/app/environments/environment.service.ts index ab14115d..3a97bb2b 100644 --- a/ahoy-ui/src/app/environments/environment.service.ts +++ b/ahoy-ui/src/app/environments/environment.service.ts @@ -80,9 +80,9 @@ export class EnvironmentService { const text = `${createdEnvironment.name} ` + `was created in cluster ${createdEnvironment.cluster.name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to create environment ${environment.name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -100,9 +100,9 @@ export class EnvironmentService { const text = `${destroyedEnvironment.name} ` + `was destroyed from cluster ${destroyedEnvironment.cluster.name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to destroy environment ${environment.name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -119,9 +119,9 @@ export class EnvironmentService { const text = `${duplicatedEnvironment.name} ` + `was duplicated in cluster ${duplicatedEnvironment.cluster.name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to duplicate environment ${destEnvironment.name} from environment: ${sourceEnvironment.name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); diff --git a/ahoy-ui/src/app/notifications/notification.ts b/ahoy-ui/src/app/notifications/notification.ts index ff1355e2..7b33137d 100644 --- a/ahoy-ui/src/app/notifications/notification.ts +++ b/ahoy-ui/src/app/notifications/notification.ts @@ -14,17 +14,28 @@ * limitations under the License. */ +import {HttpErrorResponse} from '@angular/common/http'; + export class Notification { text: string; viewed: boolean; - error: boolean; time: Date; + error: any; + errorMessage: string; + errorTrace: string; - constructor(text: string, error = false) { + constructor(text: string, error?: any) { this.text = text; this.viewed = false; this.error = error; this.time = new Date(); + + if (error instanceof HttpErrorResponse && error.status !== 0) { + error = error.error; + + this.errorMessage = ('message' in error) ? error.message : `Unknown error: ${error.toString()}`; + this.errorTrace = ('trace' in error) ? error.trace : undefined; + } } minutesAgo() { diff --git a/ahoy-ui/src/app/notifications/notifications.component.ts b/ahoy-ui/src/app/notifications/notifications.component.ts index 677a8228..d9569aa5 100644 --- a/ahoy-ui/src/app/notifications/notifications.component.ts +++ b/ahoy-ui/src/app/notifications/notifications.component.ts @@ -108,6 +108,6 @@ export class NotificationsComponent implements OnInit, OnDestroy { } showDescription(notification: Notification) { - this.dialogUtilService.showDescriptionDialog(new Description('Notification', notification.text)); + this.dialogUtilService.showDescriptionDialog(new Description('Notification', notification.text, notification.errorTrace)); } } diff --git a/ahoy-ui/src/app/release-manage/release-manage.service.ts b/ahoy-ui/src/app/release-manage/release-manage.service.ts index 0ad0c59a..0423f7cb 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.service.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.service.ts @@ -47,10 +47,10 @@ export class ReleaseManageService { + `deployed to environment ${(deployedEnvironmentRelease.environment as Environment).name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to deploy ${(environmentRelease.release as Release).name} : ${releaseVersion.version} ` + `to environment ${(environmentRelease.environment as Environment).name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -67,10 +67,10 @@ export class ReleaseManageService { + `was undeployed from environment ${(unDeployedEnvironmentRelease.environment as Environment).name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to undeploy ${(environmentRelease.release as Release).name} ` + `from environment ${(environmentRelease.environment as Environment).name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -86,10 +86,10 @@ export class ReleaseManageService { + `was removed from environment ${(removedEnvironmentRelease.environment as Environment).name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to remove ${(environmentRelease.release as Release).name} ` + `from environment ${(environmentRelease.environment as Environment).name}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); @@ -105,9 +105,9 @@ export class ReleaseManageService { + `was promoted to environment ${(environmentRelease.environment as Environment).name}`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to promote release`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); diff --git a/ahoy-ui/src/app/settings/argo-settings/argo-settings.service.ts b/ahoy-ui/src/app/settings/argo-settings/argo-settings.service.ts index c985da8d..56c1d64f 100644 --- a/ahoy-ui/src/app/settings/argo-settings/argo-settings.service.ts +++ b/ahoy-ui/src/app/settings/argo-settings/argo-settings.service.ts @@ -70,9 +70,9 @@ export class ArgoSettingsService { const text = `Successfully connected to argocd '${argoSettings.argoServer}'`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to connect to argocd ${argoSettings.argoServer}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); diff --git a/ahoy-ui/src/app/settings/git-settings/git-settings.service.ts b/ahoy-ui/src/app/settings/git-settings/git-settings.service.ts index a110c0fd..697c1ed9 100644 --- a/ahoy-ui/src/app/settings/git-settings/git-settings.service.ts +++ b/ahoy-ui/src/app/settings/git-settings/git-settings.service.ts @@ -80,9 +80,9 @@ export class GitSettingsService { const text = `Successfully connected to git repo '${gitSettings.remoteRepoUri}'`; this.notificationsService.notification(new Notification(text)); }), - catchError(() => { + catchError((error) => { const text = `Failed to connect to git repo ${gitSettings.remoteRepoUri}`; - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); return EMPTY; }) ); diff --git a/ahoy-ui/src/app/util/error.service.ts b/ahoy-ui/src/app/util/error.service.ts index 20deafd7..c052396e 100644 --- a/ahoy-ui/src/app/util/error.service.ts +++ b/ahoy-ui/src/app/util/error.service.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 LSD Information Technology (Pty) Ltd + * Copyright 2021 LSD Information Technology (Pty) Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ * limitations under the License. */ +import {HttpErrorResponse} from '@angular/common/http'; import {ErrorHandler, Injectable, NgZone} from '@angular/core'; import {Notification} from '../notifications/notification'; import {NotificationsService} from '../notifications/notifications.service'; import {LoggerService} from './logger.service'; -import {HttpErrorResponse} from '@angular/common/http'; @Injectable() export class ErrorService implements ErrorHandler { @@ -38,7 +38,7 @@ export class ErrorService implements ErrorHandler { const text = ('message' in error) ? error.message : `Unknown error: ${error.toString()}`; this.zone.run(() => { - this.notificationsService.notification(new Notification(text, true)); + this.notificationsService.notification(new Notification(text, error)); }); } } From 24c8017bee79850f0f743662eb0eb1915aeb2107 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Fri, 3 Sep 2021 11:08:18 +0200 Subject: [PATCH 08/36] Prompt when deleting applications that still belong to existing releases - only displayed release name for application versions because the release versions would just add up over time otherwise --- .../application-versions/application-versions.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts b/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts index c34adf21..c47c7158 100644 --- a/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts +++ b/ahoy-ui/src/app/applications/application-versions/application-versions.component.ts @@ -38,7 +38,7 @@ export class ApplicationVersionsComponent { usedByReleaseVersions(applicationVersion: ApplicationVersion): string[] { const usedBy = new Set(); for (const releaseVersion of applicationVersion.releaseVersions) { - usedBy.add(`${releaseVersion.releaseName}:${releaseVersion.version}`); + usedBy.add(`${releaseVersion.releaseName}`); } return Array.from(usedBy.values()).sort(); } From fc03e623148ae94e405e78e664a1ac5fe6d350d7 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Fri, 3 Sep 2021 13:49:50 +0200 Subject: [PATCH 09/36] Adding a new app to an already deployed release and then removing it doesn't update the app count --- .../release-application-versions.component.ts | 14 ++++++++++--- .../release-manage.component.html | 3 ++- .../release-manage.component.ts | 20 +++++++++++-------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/ahoy-ui/src/app/release-manage/release-application-versions/release-application-versions.component.ts b/ahoy-ui/src/app/release-manage/release-application-versions/release-application-versions.component.ts index 66182f40..873b317a 100644 --- a/ahoy-ui/src/app/release-manage/release-application-versions/release-application-versions.component.ts +++ b/ahoy-ui/src/app/release-manage/release-application-versions/release-application-versions.component.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Component, EventEmitter, Input, OnInit} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {DialogService, DynamicDialogConfig} from 'primeng/dynamicdialog'; import {filter} from 'rxjs/operators'; import {Application, ApplicationEnvironmentConfig, ApplicationVersion} from '../../applications/application'; @@ -36,6 +36,7 @@ export class ReleaseApplicationVersionsComponent implements OnInit { @Input() environmentRelease: EnvironmentRelease; @Input() releaseVersion: ReleaseVersion; @Input() releaseChanged: EventEmitter<{ environmentRelease: EnvironmentRelease, releaseVersion: ReleaseVersion }>; + @Output() applicationVersionsChanged = new EventEmitter(); existingConfigs: Map; constructor( @@ -104,7 +105,10 @@ export class ReleaseApplicationVersionsComponent implements OnInit { filter((result) => result !== undefined) // cancelled ).subscribe((applicationVersion) => { this.releasesService.associateApplication(this.releaseVersion.id, applicationVersion.id) - .subscribe(() => this.getReleaseVersion()); + .subscribe(() => { + this.getReleaseVersion(); + this.applicationVersionsChanged.next(); + }); }); } @@ -117,7 +121,11 @@ export class ReleaseApplicationVersionsComponent implements OnInit { filter((conf) => conf !== undefined) ).subscribe(() => { this.releasesService.removeAssociatedApplication(this.releaseVersion.id, applicationVersion.id) - .subscribe(() => this.getReleaseVersion()); + .subscribe(() => { + this.getReleaseVersion(); + this.applicationVersionsChanged.next(); + } + ); }); } diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.html b/ahoy-ui/src/app/release-manage/release-manage.component.html index 3b6c510d..3252efdb 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.html +++ b/ahoy-ui/src/app/release-manage/release-manage.component.html @@ -104,7 +104,8 @@
{{environmentRelease.release.name}}
+ [releaseChanged]="releaseChanged" + (applicationVersionsChanged)="applicationVersionsChanged()">
diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index b9ff9624..4f410904 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -53,7 +53,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, private router: Router, - private releaseService: ReleaseManageService, + private releaseManageService: ReleaseManageService, private environmentService: EnvironmentService, private environmentReleaseService: EnvironmentReleaseService, private log: LoggerService, @@ -133,7 +133,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { private subscribeToEnvironmentReleaseChanged() { if (!this.environmentReleaseChangedSubscription) { - this.environmentReleaseChangedSubscription = this.releaseService.environmentReleaseChanged() + this.environmentReleaseChangedSubscription = this.releaseManageService.environmentReleaseChanged() .subscribe((environmentRelease) => { if (EnvironmentReleaseService.environmentReleaseEquals(this.environmentRelease, environmentRelease)) { this.getEnvironmentRelease(environmentRelease.id.environmentId, environmentRelease.id.releaseId, this.releaseVersion.id) @@ -171,7 +171,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { filter((conf) => conf !== undefined) ).subscribe((conf) => { const deployDetails = new DeployDetails(conf.input); - this.releaseService.deploy(this.environmentRelease, this.releaseVersion, deployDetails).subscribe(); + this.releaseManageService.deploy(this.environmentRelease, this.releaseVersion, deployDetails).subscribe(); }); } @@ -183,7 +183,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { this.dialogUtilService.showConfirmDialog(confirmation).pipe( filter((conf) => conf !== undefined) ).subscribe(() => { - this.releaseService.undeploy(this.environmentRelease).subscribe(); + this.releaseManageService.undeploy(this.environmentRelease).subscribe(); }); } @@ -200,7 +200,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled mergeMap((destEnvironment) => { - return this.releaseService.promote(this.environmentRelease.id, destEnvironment.id); + return this.releaseManageService.promote(this.environmentRelease.id, destEnvironment.id); }) ).subscribe((newEnvironmentRelease: EnvironmentRelease) => this.reload(newEnvironmentRelease.id.environmentId, this.releaseVersion.id)); } @@ -218,7 +218,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled mergeMap((version) => { - return this.releaseService.upgrade(this.releaseVersion.id, version); + return this.releaseManageService.upgrade(this.releaseVersion.id, version); }) ).subscribe((newReleaseVersion: ReleaseVersion) => this.reload(this.environmentRelease.id.environmentId, newReleaseVersion.id)); } @@ -240,7 +240,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled mergeMap((selectedReleaseVersion) => { - return this.releaseService.copyEnvConfig(this.environmentRelease.id, selectedReleaseVersion.id, this.releaseVersion.id); + return this.releaseManageService.copyEnvConfig(this.environmentRelease.id, selectedReleaseVersion.id, this.releaseVersion.id); }) ).subscribe(() => this.reload(this.environmentRelease.id.environmentId, this.releaseVersion.id)); } @@ -305,7 +305,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { filter((conf) => conf !== undefined) ).subscribe((conf) => { const deployDetails = new DeployDetails(conf.input); - this.releaseService.deploy(this.environmentRelease, this.environmentRelease.previousReleaseVersion, deployDetails) + this.releaseManageService.deploy(this.environmentRelease, this.environmentRelease.previousReleaseVersion, deployDetails) .subscribe(() => this.log.debug('rolled back release:', this.environmentRelease)); }); } @@ -329,4 +329,8 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { isCurrentEnvironmentRelease(environmentRelease: EnvironmentRelease) { return EnvironmentReleaseService.environmentReleaseEquals(this.environmentRelease, environmentRelease); } + + applicationVersionsChanged() { + this.reloadCurrent(); + } } From a5d165f05585b4e5328ba1908063c2d6859e0d9b Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Mon, 6 Sep 2021 14:00:23 +0200 Subject: [PATCH 10/36] UI: after some time the notification minutes go into negative --- ahoy-ui/src/app/notifications/notification.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ahoy-ui/src/app/notifications/notification.ts b/ahoy-ui/src/app/notifications/notification.ts index 7b33137d..988a8d42 100644 --- a/ahoy-ui/src/app/notifications/notification.ts +++ b/ahoy-ui/src/app/notifications/notification.ts @@ -40,6 +40,10 @@ export class Notification { minutesAgo() { const now = new Date(); - return now.getMinutes() - this.time.getMinutes(); + return this.minutesSinceEpoch(now) - this.minutesSinceEpoch(this.time); + } + + minutesSinceEpoch(d: Date) { + return Math.floor(d.getTime() / (1000.0 * 60)); } } From 9657d7f4b5e4a1b64fd7e067217cffd292b81278 Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Mon, 6 Sep 2021 14:27:30 +0200 Subject: [PATCH 11/36] Supply account url in auth config --- .../src/main/java/za/co/lsd/ahoy/server/auth/AuthInfo.java | 2 ++ ahoy-server/src/main/resources/application-dev.properties | 1 + ahoy-ui/package-lock.json | 2 +- ahoy-ui/src/app/app.topbar.component.html | 4 ++-- ahoy-ui/src/app/app.topbar.component.ts | 4 ++-- ahoy-ui/src/app/util/auth.service.ts | 7 +++++++ ahoy-ui/src/app/util/auth.ts | 1 + 7 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/auth/AuthInfo.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/auth/AuthInfo.java index 45835822..d7ac77ae 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/auth/AuthInfo.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/auth/AuthInfo.java @@ -27,4 +27,6 @@ public class AuthInfo { private String clientId; @Value("${ahoy.auth.issuer}") private String issuer; + @Value("${ahoy.auth.account-uri}") + private String accountUri; } diff --git a/ahoy-server/src/main/resources/application-dev.properties b/ahoy-server/src/main/resources/application-dev.properties index f83944a4..1381fe42 100644 --- a/ahoy-server/src/main/resources/application-dev.properties +++ b/ahoy-server/src/main/resources/application-dev.properties @@ -22,6 +22,7 @@ ahoy.cluster-type=kubernetes ahoy.auth.client-id=${AHOY_AUTH_CLIENTID} ahoy.auth.issuer=${AHOY_AUTH_ISSUER} ahoy.auth.jwk-set-uri=${AHOY_AUTH_JWK_SET_URI} +ahoy.auth.account-uri=${AHOY_AUTH_ACCOUNT_URI:} server.port=8080 diff --git a/ahoy-ui/package-lock.json b/ahoy-ui/package-lock.json index 5f73f217..b7d50e0f 100644 --- a/ahoy-ui/package-lock.json +++ b/ahoy-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "ahoy-ui", - "version": "0.2.0-SNAPSHOT", + "version": "0.3.0-SNAPSHOT", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/ahoy-ui/src/app/app.topbar.component.html b/ahoy-ui/src/app/app.topbar.component.html index 33aac7a5..63604071 100644 --- a/ahoy-ui/src/app/app.topbar.component.html +++ b/ahoy-ui/src/app/app.topbar.component.html @@ -56,8 +56,8 @@
-
  • - +
  • + Account diff --git a/ahoy-ui/src/app/app.topbar.component.ts b/ahoy-ui/src/app/app.topbar.component.ts index c278be89..87c03380 100644 --- a/ahoy-ui/src/app/app.topbar.component.ts +++ b/ahoy-ui/src/app/app.topbar.component.ts @@ -59,8 +59,8 @@ export class AppTopBarComponent { return this.authService.userInitials(); } - issuer(): string { - return this.authService.issuer(); + accountUri(): string { + return this.authService.accountUri(); } logout() { diff --git a/ahoy-ui/src/app/util/auth.service.ts b/ahoy-ui/src/app/util/auth.service.ts index e4a58256..af09423e 100644 --- a/ahoy-ui/src/app/util/auth.service.ts +++ b/ahoy-ui/src/app/util/auth.service.ts @@ -38,6 +38,7 @@ export class AuthService { disableAtHashCheck: true, showDebugInformation: true }; + private authInfo: AuthInfo; constructor(private router: Router, private oAuthService: OAuthService, @@ -45,6 +46,7 @@ export class AuthService { private log: LoggerService) { this.getAuthInfo().subscribe((authInfo) => { + this.authInfo = authInfo; this.authConfig.clientId = authInfo.clientId; this.authConfig.issuer = authInfo.issuer; this.oAuthService.configure(this.authConfig); @@ -64,6 +66,7 @@ export class AuthService { public logout() { this.log.debug('Logging out...'); + this.authInfo = null; this.oAuthService.logOut(); } @@ -71,6 +74,10 @@ export class AuthService { return this.authConfig.issuer; } + public accountUri(): string { + return this.authInfo.accountUri; + } + public accessToken(): string { return this.oAuthService.getAccessToken(); } diff --git a/ahoy-ui/src/app/util/auth.ts b/ahoy-ui/src/app/util/auth.ts index 3a71c129..a21a0af0 100644 --- a/ahoy-ui/src/app/util/auth.ts +++ b/ahoy-ui/src/app/util/auth.ts @@ -17,4 +17,5 @@ export class AuthInfo { clientId: string; issuer: string; + accountUri: string; } From d735e6e552a63f9b59f5474d45ff6f4bf28289d8 Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Mon, 6 Sep 2021 15:34:43 +0200 Subject: [PATCH 12/36] Supply account url in auth config. Fixed tests --- ahoy-server/src/test/resources/application-test.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/ahoy-server/src/test/resources/application-test.properties b/ahoy-server/src/test/resources/application-test.properties index f2cc2986..ce2272e3 100644 --- a/ahoy-server/src/test/resources/application-test.properties +++ b/ahoy-server/src/test/resources/application-test.properties @@ -20,6 +20,7 @@ ahoy.cluster-type=kubernetes ahoy.auth.client-id=ahoy ahoy.auth.issuer=http://test ahoy.auth.jwk-set-uri=http://test/keys +ahoy.auth.account-uri=http://test/account spring.datasource.url=jdbc:h2:file:./target/test-data/db;DB_CLOSE_DELAY=-1 spring.datasource.platform=h2 From 25c0e0e8279f90ebb6122083b7c97070f325f653 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Tue, 7 Sep 2021 10:50:37 +0200 Subject: [PATCH 13/36] As a Release Manager, I'd like to see all environments when managing my environments --- .../src/app/clusters/clusters.component.html | 3 - .../app/dashboard/dashboard.component.html | 2 +- .../environment-release-detail.component.ts | 2 +- .../environment-releases.component.ts | 2 +- .../environment-detail.component.html | 7 ++- .../environment-detail.component.ts | 62 +++++++++---------- .../environments/environments.component.html | 22 ++++--- .../environments/environments.component.ts | 55 ++++------------ .../promote-dialog.component.html | 2 +- .../release-manage.component.ts | 2 +- 10 files changed, 66 insertions(+), 93 deletions(-) diff --git a/ahoy-ui/src/app/clusters/clusters.component.html b/ahoy-ui/src/app/clusters/clusters.component.html index a2ef89a8..8eae1468 100644 --- a/ahoy-ui/src/app/clusters/clusters.component.html +++ b/ahoy-ui/src/app/clusters/clusters.component.html @@ -60,9 +60,6 @@
    Clusters
    {{cluster.host}} - diff --git a/ahoy-ui/src/app/dashboard/dashboard.component.html b/ahoy-ui/src/app/dashboard/dashboard.component.html index 115b99ed..456b9bd4 100644 --- a/ahoy-ui/src/app/dashboard/dashboard.component.html +++ b/ahoy-ui/src/app/dashboard/dashboard.component.html @@ -35,7 +35,7 @@
    Dashboard
  • - There are no environments in this cluster, create an environment.. + There are no environments in this cluster, create an environment..
    diff --git a/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts b/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts index ac65ef31..f4fa7c10 100644 --- a/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts +++ b/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts @@ -96,7 +96,7 @@ export class EnvironmentReleaseDetailComponent implements OnInit { private setBreadcrumb() { this.breadcrumbService.setItems([ {label: this.environment.cluster.name, routerLink: '/clusters'}, - {label: this.environment.name, routerLink: '/environments', queryParams: {clusterId: this.environment.cluster.id}}, + {label: this.environment.name, routerLink: '/environments'}, {label: (this.editMode ? 'edit' : 'new') + ' release'} ]); } diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.ts b/ahoy-ui/src/app/environment-release/environment-releases.component.ts index 4240905d..1dae986f 100644 --- a/ahoy-ui/src/app/environment-release/environment-releases.component.ts +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.ts @@ -81,7 +81,7 @@ export class EnvironmentReleasesComponent implements OnInit { if (this.selectedEnvironment) { this.breadcrumbService.setItems([ {label: this.selectedEnvironment.cluster.name, routerLink: '/clusters'}, - {label: this.selectedEnvironment.name, routerLink: '/environments', queryParams: {clusterId: this.selectedEnvironment.cluster.id}}, + {label: this.selectedEnvironment.name, routerLink: '/environments'}, {label: 'releases'} ]); diff --git a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.html b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.html index 2b9f7ad1..d5019f5d 100644 --- a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.html +++ b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.html @@ -15,7 +15,7 @@ -->
    -
    +
    @@ -43,6 +43,11 @@
    Duplicate environment {{sourceEnvironment.name}}
    +
    + +
    +
    diff --git a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts index 57cd620b..5e199b30 100644 --- a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts +++ b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts @@ -38,6 +38,7 @@ export class EnvironmentDetailComponent implements OnInit { editMode = false; sourceEnvironment: Environment; cluster: Cluster; + clusters: Cluster[] = undefined; environment: Environment; environmentsForValidation: Environment[]; @@ -52,47 +53,44 @@ export class EnvironmentDetailComponent implements OnInit { } ngOnInit() { - const clusterId = +this.route.snapshot.queryParamMap.get('clusterId'); - this.clusterService.get(clusterId) - .subscribe(cluster => { - this.cluster = cluster; + const id = this.route.snapshot.paramMap.get('id'); + if (id === 'new') { + this.environment = new Environment(); + const sourceEnvironmentId = +this.route.snapshot.queryParamMap.get('sourceEnvironmentId'); + if (sourceEnvironmentId) { + this.environmentService.get(sourceEnvironmentId) + .subscribe((env) => { + this.sourceEnvironment = env; + this.setBreadcrumb(); + }); + } - const id = this.route.snapshot.paramMap.get('id'); - if (id === 'new') { - this.environment = new Environment(); - const sourceEnvironmentId = +this.route.snapshot.queryParamMap.get('sourceEnvironmentId'); - if (sourceEnvironmentId) { - this.environmentService.get(sourceEnvironmentId) - .subscribe((env) => { - this.sourceEnvironment = env; - this.setBreadcrumb(); - }); - } - - const environmentId = +this.route.snapshot.queryParamMap.get('environmentId'); - const releaseId = +this.route.snapshot.queryParamMap.get('releaseId'); - if (environmentId && releaseId) { - this.environmentReleaseId = EnvironmentReleaseId.new(environmentId, releaseId); - } + const environmentId = +this.route.snapshot.queryParamMap.get('environmentId'); + const releaseId = +this.route.snapshot.queryParamMap.get('releaseId'); + if (environmentId && releaseId) { + this.environmentReleaseId = EnvironmentReleaseId.new(environmentId, releaseId); + } + this.setBreadcrumb(); + } else { + this.editMode = true; + this.environmentService.get(+id) + .subscribe((env) => { + this.environment = env; this.setBreadcrumb(); - } else { - this.editMode = true; - this.environmentService.get(+id) - .subscribe((env) => { - this.environment = env; - this.setBreadcrumb(); - }); - } - }); + }); + } + + this.clusterService.getAll().subscribe((clusters) => { + this.clusters = clusters; + }); - this.environmentService.getAllEnvironmentsByCluster(clusterId) + this.environmentService.getAll() .subscribe((environments) => this.environmentsForValidation = environments); } private setBreadcrumb() { this.breadcrumbService.setItems([ - {label: this.cluster.name, routerLink: '/clusters'}, {label: (!this.sourceEnvironment ? (this.editMode ? 'edit' : 'new') : 'duplicate') + ' environment'} ]); } diff --git a/ahoy-ui/src/app/environments/environments.component.html b/ahoy-ui/src/app/environments/environments.component.html index 7de590e4..3e036384 100644 --- a/ahoy-ui/src/app/environments/environments.component.html +++ b/ahoy-ui/src/app/environments/environments.component.html @@ -27,29 +27,24 @@
    Environments
    -
    +
    Environments
    - - - - +
    - There are no environments in this cluster, click here to create an environment.. + There are no environments, click here to create an environment..
    @@ -64,6 +59,9 @@
    Environments
    Environment + Cluster + + @@ -73,12 +71,16 @@
    Environments
    Environment {{environment.name}} + + Cluster + {{environment.cluster.name}} +
    diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index 4f410904..070c558e 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -125,7 +125,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { const rel = (this.environmentRelease.release as Release); this.breadcrumbService.setItems([ {label: env.cluster.name, routerLink: '/clusters'}, - {label: env.name, routerLink: '/environments', queryParams: {clusterId: env.cluster.id}}, + {label: env.name, routerLink: '/environments'}, {label: rel.name}, {label: this.releaseVersion.version} ]); From 858cd363da9709eebbff39dc905869b499ed8567 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Tue, 7 Sep 2021 13:20:36 +0200 Subject: [PATCH 14/36] As a Release Manager, I'd like to get an overview of all environments on the dashboard --- .../dashboard-environment.component.html | 1 + .../dashboard-environment.component.scss | 4 + .../app/dashboard/dashboard.component.html | 16 +--- .../src/app/dashboard/dashboard.component.ts | 74 ++++++------------- 4 files changed, 30 insertions(+), 65 deletions(-) diff --git a/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html b/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html index 660b3888..c5d9a691 100644 --- a/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html +++ b/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html @@ -22,6 +22,7 @@
    + {{environment.cluster.name}}
    Dashboard
    - - - - - - - - -
    +
    There are no clusters setup, create a new cluster..
    -
    - There are no environments in this cluster, create an environment.. +
    + There are no environments, create an environment..
    -
    +
    diff --git a/ahoy-ui/src/app/dashboard/dashboard.component.ts b/ahoy-ui/src/app/dashboard/dashboard.component.ts index a3e57f81..9dc3bd1c 100644 --- a/ahoy-ui/src/app/dashboard/dashboard.component.ts +++ b/ahoy-ui/src/app/dashboard/dashboard.component.ts @@ -15,13 +15,13 @@ */ import {Component, OnInit} from '@angular/core'; -import {EnvironmentService} from '../environments/environment.service'; -import {Environment} from '../environments/environment'; +import {ActivatedRoute} from '@angular/router'; +import {AppBreadcrumbService} from '../app.breadcrumb.service'; import {Cluster} from '../clusters/cluster'; import {ClusterService} from '../clusters/cluster.service'; -import {ActivatedRoute} from '@angular/router'; +import {Environment} from '../environments/environment'; +import {EnvironmentService} from '../environments/environment.service'; import {LoggerService} from '../util/logger.service'; -import {AppBreadcrumbService} from '../app.breadcrumb.service'; @Component({ selector: 'app-dashboard', @@ -29,63 +29,31 @@ import {AppBreadcrumbService} from '../app.breadcrumb.service'; styleUrls: ['./dashboard.component.scss'] }) export class DashboardComponent implements OnInit { - selectedCluster: Cluster; - environments: Environment[] = undefined; - clusters: Cluster[] = undefined; - - constructor( - private route: ActivatedRoute, - private clusterService: ClusterService, - private environmentService: EnvironmentService, - private log: LoggerService, - private breadcrumbService: AppBreadcrumbService) { - + environments: Environment[] = []; + clusters: Cluster[] = []; + + constructor(private route: ActivatedRoute, + private environmentService: EnvironmentService, + private clusterService: ClusterService, + private log: LoggerService, + private breadcrumbService: AppBreadcrumbService) { this.breadcrumbService.setItems([{label: 'dashboard'}]); } ngOnInit() { - const clusterId = +this.route.snapshot.queryParamMap.get('clusterId'); - - this.clusterService.getAll() - .subscribe((clusters) => { - this.clusters = clusters; + this.clusterService.getAll().subscribe((clusters) => { + this.clusters = clusters; + }); - if (clusterId === 0) { - this.clusterService.getLastUsedId().subscribe((lastUsedClusterId) => { - if (lastUsedClusterId !== 0) { - this.getEnvironments(lastUsedClusterId); - } - }); - } else { - this.getEnvironments(clusterId); - } - }); + this.getEnvironments(); } - private getEnvironments(clusterId: number) { - this.log.debug('getting environments for clusterId=', clusterId); + private getEnvironments() { + this.log.debug('getting all environments'); - this.clusterService.get(clusterId) - .subscribe(cluster => { - this.selectedCluster = cluster; - this.environmentService.getAllEnvironmentsByCluster(clusterId) - .subscribe(envs => this.environments = envs); + this.environmentService.getAll() + .subscribe((environments) => { + this.environments = environments; }); } - - compareClusters(c1: Cluster, c2: Cluster): boolean { - if (c1 === null) { - return c2 === null; - } - - if (c2 === null) { - return c1 === null; - } - - return c1.id === c2.id; - } - - clusterChanged() { - this.getEnvironments(this.selectedCluster.id); - } } From a261cd5016a664d8baa5171d3c3f2636ffebde3b Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Tue, 7 Sep 2021 13:34:28 +0200 Subject: [PATCH 15/36] As a Release Manager, I'd like to see all environments when managing my environments - changed dropdown to only show environment on environment releases page --- .../environment-releases.component.html | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.html b/ahoy-ui/src/app/environment-release/environment-releases.component.html index 48e0f86e..d64eda8f 100644 --- a/ahoy-ui/src/app/environment-release/environment-releases.component.html +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.html @@ -44,14 +44,7 @@
    Releases in {{selectedEnvironment.name}}
    - -
    {{environment.cluster.name}}/{{environment.name}}
    -
    - -
    {{environment.cluster.name}}/{{environment.name}}
    -
    -
    + (onChange)="environmentChanged()">
    From 2be8f47cbf91cf560b0501adc5f45d1e0d5bb78b Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Tue, 7 Sep 2021 14:57:27 +0200 Subject: [PATCH 16/36] =?UTF-8?q?UI:=20breadcrumb=20navigations=20don?= =?UTF-8?q?=E2=80=99t=20seem=20to=20make=20sense=20in=20its=20current=20fo?= =?UTF-8?q?rm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application-detail.component.ts | 15 ++++++++-- .../application-version-detail.component.ts | 30 +++++++++++++++---- .../environment-release-detail.component.ts | 5 ++-- .../environment-releases.component.ts | 1 - .../environment-detail.component.ts | 21 +++++++++++-- ...pplication-environment-config.component.ts | 30 ++++++++++++++----- .../release-manage.component.ts | 1 - .../release-detail.component.ts | 15 ++++++++-- 8 files changed, 93 insertions(+), 25 deletions(-) diff --git a/ahoy-ui/src/app/applications/application-detail/application-detail.component.ts b/ahoy-ui/src/app/applications/application-detail/application-detail.component.ts index 76b3e73d..e5eefdcc 100644 --- a/ahoy-ui/src/app/applications/application-detail/application-detail.component.ts +++ b/ahoy-ui/src/app/applications/application-detail/application-detail.component.ts @@ -66,9 +66,18 @@ export class ApplicationDetailComponent implements OnInit { } private setBreadcrumb() { - this.breadcrumbService.setItems([ - {label: (this.editMode ? 'edit' : 'new') + ' application'} - ]); + if (this.editMode) { + this.breadcrumbService.setItems([ + {label: 'applications', routerLink: '/applications'}, + {label: this.application.name}, + {label: 'edit'} + ]); + } else { + this.breadcrumbService.setItems([ + {label: 'applications', routerLink: '/applications'}, + {label: 'new'} + ]); + } } save() { diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts index 2c6ec565..92a65af8 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts @@ -17,6 +17,7 @@ import {Location} from '@angular/common'; import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; +import {AppBreadcrumbService} from '../../app.breadcrumb.service'; import {ReleaseService} from '../../releases/release.service'; import {Application, ApplicationConfig, ApplicationSecret, ApplicationVersion, ApplicationVolume} from '../application'; import {ApplicationService} from '../application.service'; @@ -41,11 +42,11 @@ export class ApplicationVersionDetailComponent implements OnInit { volumesCategory = false; secretsCategory = false; - constructor( - private route: ActivatedRoute, - private applicationService: ApplicationService, - private releasesService: ReleaseService, - private location: Location) { + constructor(private route: ActivatedRoute, + private applicationService: ApplicationService, + private releasesService: ReleaseService, + private location: Location, + private breadcrumbService: AppBreadcrumbService) { } ngOnInit() { @@ -68,6 +69,7 @@ export class ApplicationVersionDetailComponent implements OnInit { this.applicationVersion.environmentVariables = []; this.applicationVersion.healthEndpointScheme = 'HTTP'; + this.setBreadcrumb(); // load previous version details for convenience if (this.applicationVersionId && this.applicationVersionId > 0) { this.applicationService.getVersion(this.applicationVersionId) @@ -104,11 +106,29 @@ export class ApplicationVersionDetailComponent implements OnInit { } this.setCategoriesExpanded(); + this.setBreadcrumb(); }); } }); } + private setBreadcrumb() { + if (this.editMode) { + this.breadcrumbService.setItems([ + {label: 'applications', routerLink: '/applications'}, + {label: this.application.name, routerLink: `/application/${this.application.id}`}, + {label: this.applicationVersion.version}, + {label: 'edit'} + ]); + } else { + this.breadcrumbService.setItems([ + {label: 'applications', routerLink: '/applications'}, + {label: this.application.name, routerLink: `/application/${this.application.id}`}, + {label: 'new'} + ]); + } + } + private setCategoriesExpanded() { if (this.applicationVersion.servicePorts && this.applicationVersion.servicePorts.length > 0) { this.portsCategory = true; diff --git a/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts b/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts index f4fa7c10..0b670fd4 100644 --- a/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts +++ b/ahoy-ui/src/app/environment-release/environment-release-detail/environment-release-detail.component.ts @@ -95,9 +95,10 @@ export class EnvironmentReleaseDetailComponent implements OnInit { private setBreadcrumb() { this.breadcrumbService.setItems([ - {label: this.environment.cluster.name, routerLink: '/clusters'}, {label: this.environment.name, routerLink: '/environments'}, - {label: (this.editMode ? 'edit' : 'new') + ' release'} + {label: this.release.name, routerLink: `/release/${this.environment.id}/${this.release.id}/version/${this.releaseVersion.id}`}, + {label: this.releaseVersion.version}, + {label: this.editMode ? 'edit' : 'new'} ]); } diff --git a/ahoy-ui/src/app/environment-release/environment-releases.component.ts b/ahoy-ui/src/app/environment-release/environment-releases.component.ts index 1dae986f..4f7c2c8e 100644 --- a/ahoy-ui/src/app/environment-release/environment-releases.component.ts +++ b/ahoy-ui/src/app/environment-release/environment-releases.component.ts @@ -80,7 +80,6 @@ export class EnvironmentReleasesComponent implements OnInit { private setBreadcrumb() { if (this.selectedEnvironment) { this.breadcrumbService.setItems([ - {label: this.selectedEnvironment.cluster.name, routerLink: '/clusters'}, {label: this.selectedEnvironment.name, routerLink: '/environments'}, {label: 'releases'} ]); diff --git a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts index 5e199b30..91160ecb 100644 --- a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts +++ b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts @@ -90,9 +90,24 @@ export class EnvironmentDetailComponent implements OnInit { } private setBreadcrumb() { - this.breadcrumbService.setItems([ - {label: (!this.sourceEnvironment ? (this.editMode ? 'edit' : 'new') : 'duplicate') + ' environment'} - ]); + if (this.editMode) { + this.breadcrumbService.setItems([ + {label: 'environments', routerLink: '/environments'}, + {label: this.environment.name}, + {label: 'edit'} + ]); + } else if (this.sourceEnvironment) { + this.breadcrumbService.setItems([ + {label: 'environments', routerLink: '/environments'}, + {label: this.sourceEnvironment.name}, + {label: 'duplicate'} + ]); + } else { + this.breadcrumbService.setItems([ + {label: 'environments', routerLink: '/environments'}, + {label: 'new'} + ]); + } } save() { diff --git a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts index 1a5e67e0..5dba7b50 100644 --- a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts +++ b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts @@ -18,6 +18,7 @@ import {Location} from '@angular/common'; import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {mergeMap} from 'rxjs/operators'; +import {AppBreadcrumbService} from '../../app.breadcrumb.service'; import {Application, ApplicationEnvironmentConfig, ApplicationEnvironmentConfigId, ApplicationSecret, ApplicationVersion} from '../../applications/application'; import {ApplicationService} from '../../applications/application.service'; import {EnvironmentRelease, EnvironmentReleaseId} from '../../environment-release/environment-release'; @@ -44,13 +45,13 @@ export class ReleaseApplicationEnvironmentConfigComponent implements OnInit { volumesCategory = false; secretsCategory = false; - constructor( - private log: LoggerService, - private route: ActivatedRoute, - private applicationService: ApplicationService, - private environmentReleaseService: EnvironmentReleaseService, - private releasesService: ReleaseService, - private location: Location) { + constructor(private log: LoggerService, + private route: ActivatedRoute, + private applicationService: ApplicationService, + private environmentReleaseService: EnvironmentReleaseService, + private releasesService: ReleaseService, + private location: Location, + private breadcrumbService: AppBreadcrumbService) { } ngOnInit() { @@ -87,9 +88,24 @@ export class ReleaseApplicationEnvironmentConfigComponent implements OnInit { const clusterHost = (this.environmentRelease.environment as Environment).cluster.host; this.exampleRouteHost = `${releaseName}-${appName}-${envName}.${clusterHost}`; this.setCategoriesExpanded(); + this.setBreadcrumb(); }); } + private setBreadcrumb() { + const env = (this.environmentRelease.environment as Environment); + const rel = (this.environmentRelease.release as Release); + const app = (this.applicationVersion.application as Application); + this.breadcrumbService.setItems([ + {label: env.name, routerLink: '/environments'}, + {label: rel.name, routerLink: `/release/${rel.id}/${env.id}/version/${this.releaseVersion.id}`}, + {label: this.releaseVersion.version, routerLink: `/release/${rel.id}/${env.id}/version/${this.releaseVersion.id}`}, + {label: app.name}, + {label: this.applicationVersion.version}, + {label: 'env config'} + ]); + } + private setCategoriesExpanded() { if (this.environmentConfig.routeHostname) { this.routeCategory = true; diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index 070c558e..902c883f 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -124,7 +124,6 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { const env = (this.environmentRelease.environment as Environment); const rel = (this.environmentRelease.release as Release); this.breadcrumbService.setItems([ - {label: env.cluster.name, routerLink: '/clusters'}, {label: env.name, routerLink: '/environments'}, {label: rel.name}, {label: this.releaseVersion.version} diff --git a/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts b/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts index 97b75e21..00583ab4 100644 --- a/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts +++ b/ahoy-ui/src/app/releases/release-detail/release-detail.component.ts @@ -72,9 +72,18 @@ export class ReleaseDetailComponent implements OnInit { } private setBreadcrumb() { - this.breadcrumbService.setItems([ - {label: (this.editMode ? 'edit' : 'new') + ' release'} - ]); + if (this.editMode) { + this.breadcrumbService.setItems([ + {label: 'releases', routerLink: '/releases'}, + {label: this.release.name}, + {label: this.editMode ? 'edit' : 'new'} + ]); + } else { + this.breadcrumbService.setItems([ + {label: 'releases', routerLink: '/releases'}, + {label: this.editMode ? 'edit' : 'new'} + ]); + } } save() { From fb510007b6f968f4f1eeb88da4ff94192d3b9049 Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Fri, 10 Sep 2021 09:55:12 +0200 Subject: [PATCH 17/36] Introduced ENV variable AHOY_DEV_REPO_LOCATION for dev to supply the location of the git repo --- ahoy-server/src/main/resources/application-dev.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahoy-server/src/main/resources/application-dev.properties b/ahoy-server/src/main/resources/application-dev.properties index 1381fe42..4888c3ac 100644 --- a/ahoy-server/src/main/resources/application-dev.properties +++ b/ahoy-server/src/main/resources/application-dev.properties @@ -16,7 +16,7 @@ ahoy.release-name=ahoy ahoy.release-namespace=ahoy -ahoy.repo-path=./target/cache/repo +ahoy.repo-path=${AHOY_DEV_REPO_LOCATION:./target/cache/repo} ahoy.host=default.host ahoy.cluster-type=kubernetes ahoy.auth.client-id=${AHOY_AUTH_CLIENTID} From 1bee67faa8e44b8fa0dd0f063d9eb6724b7565f2 Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Fri, 10 Sep 2021 14:48:07 +0200 Subject: [PATCH 18/36] Ordering of entities across the board --- .../src/main/java/za/co/lsd/ahoy/server/ReleaseService.java | 2 +- .../za/co/lsd/ahoy/server/applications/Application.java | 1 + .../server/applications/ApplicationEnvironmentConfig.java | 5 +++++ .../lsd/ahoy/server/applications/ApplicationRepository.java | 4 ++-- .../co/lsd/ahoy/server/applications/ApplicationVersion.java | 5 +++++ .../main/java/za/co/lsd/ahoy/server/cluster/Cluster.java | 1 + .../za/co/lsd/ahoy/server/cluster/ClusterRepository.java | 4 ++-- .../java/za/co/lsd/ahoy/server/docker/DockerSettings.java | 2 ++ .../ahoy/server/environmentrelease/EnvironmentRelease.java | 1 + .../environmentrelease/EnvironmentReleaseRepository.java | 2 +- .../za/co/lsd/ahoy/server/environments/Environment.java | 1 + .../lsd/ahoy/server/environments/EnvironmentRepository.java | 6 +++--- .../main/java/za/co/lsd/ahoy/server/releases/Release.java | 2 ++ .../za/co/lsd/ahoy/server/releases/ReleaseRepository.java | 6 +++--- .../java/za/co/lsd/ahoy/server/releases/ReleaseVersion.java | 1 + ahoy-server/src/main/resources/application-dev.properties | 1 + ahoy-server/src/main/resources/application.properties | 2 ++ ahoy-server/src/main/resources/logback.xml | 2 ++ ahoy-ui/src/app/applications/application.service.ts | 2 +- ahoy-ui/src/app/clusters/cluster.service.ts | 2 +- ahoy-ui/src/app/environments/environment.service.ts | 2 +- ahoy-ui/src/app/releases/release.service.ts | 5 +++-- ahoy-ui/src/app/util/rest-client.service.ts | 6 ++++++ 23 files changed, 48 insertions(+), 17 deletions(-) diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java index b9b1522f..f4f93f31 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java @@ -186,7 +186,7 @@ public ReleaseVersion upgrade(Long releaseVersionId, String version) { ReleaseVersion upgradedReleaseVersion = new ReleaseVersion(version, currentReleaseVersion.getRelease(), new ArrayList<>(currentReleaseVersion.getApplicationVersions())); upgradedReleaseVersion = releaseVersionRepository.save(upgradedReleaseVersion); - Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id(currentReleaseVersion.getRelease().getId()); + Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(currentReleaseVersion.getRelease().getId()); for (EnvironmentRelease environmentRelease : environmentReleases) { for (ApplicationVersion applicationVersion : upgradedReleaseVersion.getApplicationVersions()) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/Application.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/Application.java index 5cd2d3e9..17d5871a 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/Application.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/Application.java @@ -37,6 +37,7 @@ public class Application { @OneToMany(mappedBy = "application", cascade = CascadeType.REMOVE) @JsonIgnore + @OrderBy("id") private List applicationVersions; public ApplicationVersion latestApplicationVersion() { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java index b06bf8c6..fda47439 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java @@ -24,6 +24,7 @@ import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import java.util.ArrayList; import java.util.List; @@ -43,18 +44,22 @@ public class ApplicationEnvironmentConfig { @OneToMany(mappedBy = "applicationEnvironmentConfig", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationEnvironmentConfigReference") + @OrderBy("id") private List environmentVariables; @OneToMany(mappedBy = "applicationEnvironmentConfig", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationEnvironmentConfigReference") + @OrderBy("id") private List configs; @OneToMany(mappedBy = "applicationEnvironmentConfig", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationEnvironmentConfigReference") + @OrderBy("id") private List volumes; @OneToMany(mappedBy = "applicationEnvironmentConfig", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationEnvironmentConfigReference") + @OrderBy("id") private List secrets; public ApplicationEnvironmentConfig(ApplicationDeploymentId id, ApplicationEnvironmentConfig applicationEnvironmentConfig) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationRepository.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationRepository.java index ec75cb4d..d00f386f 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationRepository.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationRepository.java @@ -16,9 +16,9 @@ package za.co.lsd.ahoy.server.applications; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository -public interface ApplicationRepository extends CrudRepository { +public interface ApplicationRepository extends PagingAndSortingRepository { } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java index 0c513b97..87f099f1 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java @@ -59,22 +59,27 @@ public class ApplicationVersion implements Serializable { @OneToMany(mappedBy = "applicationVersion", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationVersionReference") + @OrderBy("id") private List environmentVariables; @OneToMany(mappedBy = "applicationVersion", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationVersionReference") + @OrderBy("id") private List configs; @OneToMany(mappedBy = "applicationVersion", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationVersionReference") + @OrderBy("id") private List volumes; @OneToMany(mappedBy = "applicationVersion", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference("applicationVersionReference") + @OrderBy("id") private List secrets; @ManyToMany(mappedBy = "applicationVersions") @JsonIgnore + @OrderBy("id") private List releaseVersions; public ApplicationVersion(@NotNull String version, @NotNull String image, Application application) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/Cluster.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/Cluster.java index f12345ef..6976f075 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/Cluster.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/Cluster.java @@ -60,6 +60,7 @@ public class Cluster implements Serializable { @OneToMany(mappedBy = "cluster", cascade = CascadeType.REMOVE) @JsonIgnore @ToString.Exclude + @OrderBy("id") private List environments; public Cluster(@NotNull String name, @NotNull String masterUrl, @NotNull ClusterType type) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/ClusterRepository.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/ClusterRepository.java index 476b2cef..6c94e610 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/ClusterRepository.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/cluster/ClusterRepository.java @@ -16,9 +16,9 @@ package za.co.lsd.ahoy.server.cluster; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository -public interface ClusterRepository extends CrudRepository { +public interface ClusterRepository extends PagingAndSortingRepository { } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/docker/DockerSettings.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/docker/DockerSettings.java index e33f4f9c..3abe7ae3 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/docker/DockerSettings.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/docker/DockerSettings.java @@ -24,6 +24,7 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import java.util.List; @Entity @@ -34,5 +35,6 @@ public class DockerSettings { private Long id; @OneToMany(mappedBy = "dockerSettings", cascade = CascadeType.ALL, orphanRemoval = true) @JsonManagedReference + @OrderBy("id") private List dockerRegistries; } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentRelease.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentRelease.java index ba2eb333..ae978d3e 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentRelease.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentRelease.java @@ -65,6 +65,7 @@ public class EnvironmentRelease implements Serializable { @OneToMany(mappedBy = "environmentRelease", cascade = CascadeType.REMOVE) @JsonIgnore + @OrderBy("id") private List releaseHistories; public EnvironmentRelease(Environment environment, Release release) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentReleaseRepository.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentReleaseRepository.java index 75009655..373777a5 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentReleaseRepository.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environmentrelease/EnvironmentReleaseRepository.java @@ -27,7 +27,7 @@ public interface EnvironmentReleaseRepository extends CrudRepository { @RestResource(path = "byRelease", rel = "byRelease") - Iterable findByRelease_Id(@Param("releaseId") long releaseId); + Iterable findByRelease_Id_OrderByEnvironmentId(@Param("releaseId") long releaseId); Optional findByArgoCdUid(String argoCdUid); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/Environment.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/Environment.java index 3dbb3573..fd7e72f2 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/Environment.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/Environment.java @@ -45,6 +45,7 @@ public class Environment implements Serializable { @OneToMany(mappedBy = "environment", cascade = CascadeType.REMOVE) @JsonIgnore @ToString.Exclude + @OrderBy("environment.id") private List environmentReleases; public Environment(@NotNull String name, Cluster cluster) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/EnvironmentRepository.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/EnvironmentRepository.java index 74b857d2..b4ed8803 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/EnvironmentRepository.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/environments/EnvironmentRepository.java @@ -17,15 +17,15 @@ package za.co.lsd.ahoy.server.environments; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.stereotype.Repository; @Repository -public interface EnvironmentRepository extends CrudRepository { +public interface EnvironmentRepository extends PagingAndSortingRepository { @RestResource(path = "forPromotion", rel = "forPromotion") - @Query("select e from Environment e where e.id not in (select er.id.environmentId FROM EnvironmentRelease er where er.id.releaseId = :releaseId)") + @Query("select e from Environment e where e.id not in (select er.id.environmentId FROM EnvironmentRelease er where er.id.releaseId = :releaseId) order by e.id") Iterable findForPromotion(@Param("releaseId") long releaseId); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java index 6db60c83..d90d992c 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java @@ -39,10 +39,12 @@ public class Release implements Serializable { @OneToMany(mappedBy = "release") @JsonIgnore + @OrderBy("environment.id") private List environmentReleases; @OneToMany(mappedBy = "release", cascade = CascadeType.REMOVE) @JsonIgnore + @OrderBy("id") private List releaseVersions; public Release(@NotNull String name) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseRepository.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseRepository.java index de5127dd..367498cb 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseRepository.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseRepository.java @@ -17,15 +17,15 @@ package za.co.lsd.ahoy.server.releases; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.stereotype.Repository; @Repository -public interface ReleaseRepository extends CrudRepository { +public interface ReleaseRepository extends PagingAndSortingRepository { @RestResource(path = "forAdd", rel = "forAdd") - @Query("select r from Release r where r.id not in (select er.id.releaseId from EnvironmentRelease er where er.id.environmentId = :environmentId)") + @Query("select r from Release r where r.id not in (select er.id.releaseId from EnvironmentRelease er where er.id.environmentId = :environmentId) order by r.id") Iterable findForAdd(@Param("environmentId") long environmentId); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseVersion.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseVersion.java index 30160e2a..9b0eb86b 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseVersion.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseVersion.java @@ -46,6 +46,7 @@ public class ReleaseVersion implements Serializable { joinColumns = @JoinColumn(name = "RELEASE_VERSION_ID"), inverseJoinColumns = @JoinColumn(name = "APPLICATION_VERSIONS_ID")) @JsonIgnore + @OrderBy("id") private List applicationVersions; public ReleaseVersion(@NotNull String version, Release release, List applicationVersions) { diff --git a/ahoy-server/src/main/resources/application-dev.properties b/ahoy-server/src/main/resources/application-dev.properties index 4888c3ac..a604156a 100644 --- a/ahoy-server/src/main/resources/application-dev.properties +++ b/ahoy-server/src/main/resources/application-dev.properties @@ -34,5 +34,6 @@ spring.datasource.driverClassName=org.h2.Driver spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true spring.h2.console.enabled=true spring.h2.console.settings.web-allow-others=true diff --git a/ahoy-server/src/main/resources/application.properties b/ahoy-server/src/main/resources/application.properties index 062aa508..657eda40 100644 --- a/ahoy-server/src/main/resources/application.properties +++ b/ahoy-server/src/main/resources/application.properties @@ -15,5 +15,7 @@ # spring.data.rest.basePath=/data +spring.data.rest.max-page-size=10000 +spring.data.rest.default-page-size=10000 spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${ahoy.auth.jwk-set-uri} diff --git a/ahoy-server/src/main/resources/logback.xml b/ahoy-server/src/main/resources/logback.xml index d6c154d8..beb0b9dc 100644 --- a/ahoy-server/src/main/resources/logback.xml +++ b/ahoy-server/src/main/resources/logback.xml @@ -28,5 +28,7 @@ + + diff --git a/ahoy-ui/src/app/applications/application.service.ts b/ahoy-ui/src/app/applications/application.service.ts index bf8d52ac..aa84a241 100644 --- a/ahoy-ui/src/app/applications/application.service.ts +++ b/ahoy-ui/src/app/applications/application.service.ts @@ -33,7 +33,7 @@ export class ApplicationService { } getAll(): Observable { - const url = `/data/applications?projection=application`; + const url = `/data/applications?projection=application&sort=id`; return this.restClient.get(url).pipe( map(response => response._embedded.applications as Application[]), tap(apps => this.log.debug(`fetched ${apps.length} apps`)) diff --git a/ahoy-ui/src/app/clusters/cluster.service.ts b/ahoy-ui/src/app/clusters/cluster.service.ts index c6c9b5f6..9e71733a 100644 --- a/ahoy-ui/src/app/clusters/cluster.service.ts +++ b/ahoy-ui/src/app/clusters/cluster.service.ts @@ -37,7 +37,7 @@ export class ClusterService { } getAll(): Observable { - const url = `/data/clusters`; + const url = `/data/clusters?sort=id`; return this.restClient.get(url).pipe( map(response => response._embedded.clusters as Cluster[]), tap(apps => this.log.debug(`fetched ${apps.length} clusters`)) diff --git a/ahoy-ui/src/app/environments/environment.service.ts b/ahoy-ui/src/app/environments/environment.service.ts index 3a97bb2b..e076319e 100644 --- a/ahoy-ui/src/app/environments/environment.service.ts +++ b/ahoy-ui/src/app/environments/environment.service.ts @@ -37,7 +37,7 @@ export class EnvironmentService { } getAll(): Observable { - return this.restClient.get('/data/environments?projection=environment').pipe( + return this.restClient.get('/data/environments?projection=environment&sort=id').pipe( map(response => response._embedded.environments as Environment[]), tap((envs) => this.log.debug(`fetched ${envs.length} environments`)) ); diff --git a/ahoy-ui/src/app/releases/release.service.ts b/ahoy-ui/src/app/releases/release.service.ts index 78a7e6d1..637e995d 100644 --- a/ahoy-ui/src/app/releases/release.service.ts +++ b/ahoy-ui/src/app/releases/release.service.ts @@ -32,7 +32,7 @@ export class ReleaseService { } getAll(): Observable { - const url = `/data/releases`; + const url = `/data/releases?sort=id`; return this.restClient.get(url).pipe( map(response => response._embedded.releases as Release[]), tap((releases) => this.log.debug(`fetched ${releases.length} releases`)) @@ -40,7 +40,7 @@ export class ReleaseService { } getAllSummary(): Observable { - const url = `/data/releases?projection=releaseSummary`; + const url = `/data/releases?projection=releaseSummary&sort=id`; return this.restClient.get(url).pipe( map(response => response._embedded.releases as Release[]), tap((releases) => this.log.debug(`fetched ${releases.length} releases`)) @@ -55,6 +55,7 @@ export class ReleaseService { ); } + // TODO: the url seems wrong, fix or delete method getAllByEnvironment(environmentId: number): Observable { const url = `/data/environments/${environmentId}/releases`; return this.restClient.get(url).pipe( diff --git a/ahoy-ui/src/app/util/rest-client.service.ts b/ahoy-ui/src/app/util/rest-client.service.ts index 71465b0a..808576d8 100644 --- a/ahoy-ui/src/app/util/rest-client.service.ts +++ b/ahoy-ui/src/app/util/rest-client.service.ts @@ -43,6 +43,7 @@ export class RestClientService { } post(path: string, body?: T | any, progress = false): Observable { + this.log.trace("posting to path: " + path); this.startProgress(progress); return this.http.post(this.appsUrl + path, body, this.createOptions()).pipe( tap(() => this.stopProgress(progress)), @@ -51,6 +52,7 @@ export class RestClientService { } put(path: string, body?: T, headers?: { [key: string]: string }, progress = false): Observable { + this.log.trace("putting to path: " + path); this.startProgress(progress); return this.http.put(this.appsUrl + path, body, this.createOptions(undefined, headers)).pipe( tap(() => this.stopProgress(progress)), @@ -59,6 +61,7 @@ export class RestClientService { } patch(path: string, body?: T, headers?: { [key: string]: string }, progress = false): Observable { + this.log.trace("patching to path: " + path); this.startProgress(progress); return this.http.patch(this.appsUrl + path, body, this.createOptions(undefined, headers)).pipe( tap(() => this.stopProgress(progress)), @@ -67,6 +70,7 @@ export class RestClientService { } get(path: string, progress = false, defaultIfNotFound?: () => T): Observable { + this.log.trace("getting from path: " + path); this.startProgress(progress); return this.http.get(this.appsUrl + path, this.createOptions()).pipe( tap(() => this.stopProgress(progress)), @@ -75,6 +79,7 @@ export class RestClientService { } exists(path: string, progress = false): Observable { + this.log.trace("checking existence at path: " + path); this.startProgress(progress); return this.http.get(this.appsUrl + path, this.createOptions()).pipe( mergeMap(() => { @@ -87,6 +92,7 @@ export class RestClientService { } delete(path: string, progress = false): Observable { + this.log.trace("deleting at path: " + path); this.startProgress(progress); return this.http.delete(this.appsUrl + path, this.createOptions()).pipe( tap(() => this.stopProgress(progress)), From 28e17fcac68cb284b53ad534a8ac6bd2c2d90189 Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Fri, 10 Sep 2021 15:32:27 +0200 Subject: [PATCH 19/36] UI: add latest version column under Releases --- .../java/za/co/lsd/ahoy/server/releases/Release.java | 7 +++++++ .../co/lsd/ahoy/server/releases/ReleaseProjection.java | 3 +++ .../ahoy/server/releases/ReleaseSummaryProjection.java | 3 +++ ahoy-ui/src/app/releases/release.ts | 1 + ahoy-ui/src/app/releases/releases.component.html | 9 ++++++++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java index d90d992c..113d9508 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/Release.java @@ -47,6 +47,13 @@ public class Release implements Serializable { @OrderBy("id") private List releaseVersions; + public ReleaseVersion latestReleaseVersion() { + if (releaseVersions != null && releaseVersions.size() > 0) { + return releaseVersions.get(releaseVersions.size() - 1); + } + return null; + } + public Release(@NotNull String name) { this.name = name; } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseProjection.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseProjection.java index 04ed4555..ee0514be 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseProjection.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseProjection.java @@ -31,4 +31,7 @@ public interface ReleaseProjection { @Value("#{target.releaseVersions}") List getReleaseVersions(); + + @Value("#{target.latestReleaseVersion()}") + ReleaseVersionSummaryProjection getLatestReleaseVersion(); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseSummaryProjection.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseSummaryProjection.java index e5e6758a..9f0d205f 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseSummaryProjection.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/ReleaseSummaryProjection.java @@ -32,4 +32,7 @@ public interface ReleaseSummaryProjection { @Value("#{target.environmentReleases}") List getEnvironmentReleases(); + + @Value("#{target.latestReleaseVersion()}") + ReleaseVersionSummaryProjection getLatestReleaseVersion(); } diff --git a/ahoy-ui/src/app/releases/release.ts b/ahoy-ui/src/app/releases/release.ts index a191ba44..b8b376d1 100644 --- a/ahoy-ui/src/app/releases/release.ts +++ b/ahoy-ui/src/app/releases/release.ts @@ -22,6 +22,7 @@ export class Release { name: string; releaseVersions: ReleaseVersion[]; environmentReleases: EnvironmentRelease[]; + latestReleaseVersion: ReleaseVersion; } export class ReleaseVersion { diff --git a/ahoy-ui/src/app/releases/releases.component.html b/ahoy-ui/src/app/releases/releases.component.html index 390c9c51..1bf90039 100644 --- a/ahoy-ui/src/app/releases/releases.component.html +++ b/ahoy-ui/src/app/releases/releases.component.html @@ -37,7 +37,7 @@
    Releases
    @@ -52,6 +52,9 @@
    Releases
    Release + Latest version + + Environments @@ -61,6 +64,10 @@
    Releases
    Release {{release.name}} + Latest version + {{release.latestReleaseVersion.version}} + - + Environments Date: Mon, 13 Sep 2021 11:45:46 +0200 Subject: [PATCH 20/36] Local repo test failing due to delete recursive failing --- .../za/co/lsd/ahoy/server/git/LocalRepo.java | 8 +-- .../za/co/lsd/ahoy/server/util/FileUtils.java | 53 +++++++++++++++++++ .../co/lsd/ahoy/server/git/LocalRepoTest.java | 8 +-- 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 ahoy-server/src/main/java/za/co/lsd/ahoy/server/util/FileUtils.java diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/git/LocalRepo.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/git/LocalRepo.java index dfeacdf7..1f42aab7 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/git/LocalRepo.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/git/LocalRepo.java @@ -29,9 +29,9 @@ import org.eclipse.jgit.transport.*; import org.eclipse.jgit.util.FS; import org.springframework.stereotype.Component; -import org.springframework.util.FileSystemUtils; import za.co.lsd.ahoy.server.AhoyServerProperties; import za.co.lsd.ahoy.server.settings.SettingsProvider; +import za.co.lsd.ahoy.server.util.FileUtils; import javax.annotation.PostConstruct; import java.io.IOException; @@ -78,7 +78,7 @@ public void init() { } catch (Exception e) { try { - FileSystemUtils.deleteRecursively(localRepoPath); + FileUtils.deleteRecursively(localRepoPath); } catch (IOException ex) { log.warn("Failed to cleanup local repo directory"); } @@ -153,7 +153,7 @@ public void delete() { try { if (gitRepo != null) { log.info("Deleting local repo: {}", localRepoPath); - FileSystemUtils.deleteRecursively(localRepoPath); + FileUtils.deleteRecursively(localRepoPath); gitRepo = null; } } catch (Exception e) { @@ -292,7 +292,7 @@ public void close() throws Exception { try { if (Files.exists(workingTreePath)) { log.info("Deleting working tree: {}", workingTreePath); - FileSystemUtils.deleteRecursively(workingTreePath); + FileUtils.deleteRecursively(workingTreePath); } } catch (Exception e) { throw new LocalRepoException("Failed to delete working tree", e); diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/util/FileUtils.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/util/FileUtils.java new file mode 100644 index 00000000..aff82fa4 --- /dev/null +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/util/FileUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.lsd.ahoy.server.util; + +import org.springframework.lang.Nullable; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class FileUtils { + + public static boolean deleteRecursively(@Nullable Path root) throws IOException { + if (root == null) { + return false; + } + if (!Files.exists(root)) { + return false; + } + + Files.walkFileTree(root, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.deleteIfExists(dir); + return FileVisitResult.CONTINUE; + } + }); + return true; + } +} diff --git a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/git/LocalRepoTest.java b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/git/LocalRepoTest.java index 82e420d5..0681cf1d 100644 --- a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/git/LocalRepoTest.java +++ b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/git/LocalRepoTest.java @@ -36,7 +36,6 @@ import java.io.IOException; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -78,12 +77,7 @@ public void init() throws IOException, GitAPIException { @AfterEach public void cleanup() { testRemoteRepo = null; - try { - localRepo.delete(); - } catch (LocalRepoException e) { - if (!(e.getCause() instanceof NoSuchFileException)) - throw e; - } + localRepo.delete(); } @Test From c7ba7d96ccd22f1935a2a191e57659d41272aff8 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 13 Sep 2021 12:05:33 +0200 Subject: [PATCH 21/36] Error traces aren't coming through to the UI when deployed --- ahoy-server/src/main/resources/application.properties | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ahoy-server/src/main/resources/application.properties b/ahoy-server/src/main/resources/application.properties index 657eda40..ca7d6b97 100644 --- a/ahoy-server/src/main/resources/application.properties +++ b/ahoy-server/src/main/resources/application.properties @@ -1,5 +1,5 @@ # -# Copyright 2020 LSD Information Technology (Pty) Ltd +# Copyright 2021 LSD Information Technology (Pty) Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,3 +19,7 @@ spring.data.rest.max-page-size=10000 spring.data.rest.default-page-size=10000 spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${ahoy.auth.jwk-set-uri} + +server.error.include-message=always +server.error.include-stacktrace=always +server.error.include-exception=true From a1fa9dca4b4f39d9c4d2c16961179bb641027025 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 13 Sep 2021 13:53:50 +0200 Subject: [PATCH 22/36] UI: notifications list sorting seems to randomly change --- ahoy-ui/src/app/notifications/notifications.component.html | 2 +- ahoy-ui/src/app/notifications/notifications.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahoy-ui/src/app/notifications/notifications.component.html b/ahoy-ui/src/app/notifications/notifications.component.html index 476616c3..ffab3c7e 100644 --- a/ahoy-ui/src/app/notifications/notifications.component.html +++ b/ahoy-ui/src/app/notifications/notifications.component.html @@ -27,7 +27,7 @@
  • You have {{unreadNotifications()}} new notifications
  • -
  • +
  • diff --git a/ahoy-ui/src/app/notifications/notifications.component.ts b/ahoy-ui/src/app/notifications/notifications.component.ts index d9569aa5..e4139057 100644 --- a/ahoy-ui/src/app/notifications/notifications.component.ts +++ b/ahoy-ui/src/app/notifications/notifications.component.ts @@ -45,7 +45,7 @@ import {NotificationsService} from './notifications.service'; export class NotificationsComponent implements OnInit, OnDestroy { private notificationsSubscription: Subscription; private topBarMenuSubscription: Subscription; - private readonly NOTIFICATIONS_TO_SHOW = 5; + private readonly NOTIFICATIONS_TO_SHOW = 3; notifications: Notification[]; viewed = true; @@ -79,10 +79,10 @@ export class NotificationsComponent implements OnInit, OnDestroy { } private onNotification(notification: Notification) { - this.notifications.push(notification); + this.notifications.unshift(notification); const length = this.notifications.length; if (length > this.NOTIFICATIONS_TO_SHOW) { - this.notifications = this.notifications.slice(length - this.NOTIFICATIONS_TO_SHOW); + this.notifications.pop(); } this.viewed = false; this.messageService.add({ From 981027db0e47babef496f71a191f0649b0a8ae01 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 13 Sep 2021 14:30:54 +0200 Subject: [PATCH 23/36] UI: notifications list sorting seems to randomly change - reset number of notifications back to 5 --- ahoy-ui/src/app/notifications/notifications.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ahoy-ui/src/app/notifications/notifications.component.ts b/ahoy-ui/src/app/notifications/notifications.component.ts index e4139057..ede44f7d 100644 --- a/ahoy-ui/src/app/notifications/notifications.component.ts +++ b/ahoy-ui/src/app/notifications/notifications.component.ts @@ -45,7 +45,7 @@ import {NotificationsService} from './notifications.service'; export class NotificationsComponent implements OnInit, OnDestroy { private notificationsSubscription: Subscription; private topBarMenuSubscription: Subscription; - private readonly NOTIFICATIONS_TO_SHOW = 3; + private readonly NOTIFICATIONS_TO_SHOW = 5; notifications: Notification[]; viewed = true; From cda716f8c84a49e022c9c36af4f2719a93e7cd49 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 13 Sep 2021 15:25:35 +0200 Subject: [PATCH 24/36] When changing version and editing a release; from manage release, the incorrect version is opened... --- ahoy-ui/src/app/release-manage/release-manage.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index 902c883f..b550b31b 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -249,6 +249,7 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { ['/release', this.environmentRelease.id.environmentId, this.environmentRelease.id.releaseId, 'version', this.releaseVersion.id]) .then(() => this.setBreadcrumb()); this.releaseChanged.emit({environmentRelease: this.environmentRelease, releaseVersion: this.releaseVersion}); + this.setupMenuItems(); } reloadCurrent() { From 6174a91ce3cde7f4f02c657a8adfb77e909668cf Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Mon, 13 Sep 2021 16:28:43 +0200 Subject: [PATCH 25/36] Navigate to release manage from the releases list when clicking on an environment --- ahoy-ui/src/app/releases/releases.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ahoy-ui/src/app/releases/releases.component.html b/ahoy-ui/src/app/releases/releases.component.html index 1bf90039..5788569d 100644 --- a/ahoy-ui/src/app/releases/releases.component.html +++ b/ahoy-ui/src/app/releases/releases.component.html @@ -71,7 +71,8 @@
    Releases
    Environments + routerLink="/release/{{environmentRelease.id.environmentId}}/{{environmentRelease.id.releaseId}}/version/{{release.latestReleaseVersion.id}}"> + From b3cdcca1fad431603fbc447aa4d6600b7e99841e Mon Sep 17 00:00:00 2001 From: Rainer Schamm Date: Tue, 14 Sep 2021 11:03:03 +0200 Subject: [PATCH 26/36] UI: secrets named the same as existing secrets which are in use causes the name field to go read-only --- ahoy-ui/src/app/app.module.ts | 2 + .../application-secrets.component.html | 15 ++++-- .../application-secrets.component.ts | 5 ++ .../secret-name-unique-validator.directive.ts | 50 +++++++++++++++++++ .../application-version-detail.component.html | 1 + 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 ahoy-ui/src/app/applications/application-secrets/secret-name-unique-validator.directive.ts diff --git a/ahoy-ui/src/app/app.module.ts b/ahoy-ui/src/app/app.module.ts index 1f8584c2..5201caa7 100644 --- a/ahoy-ui/src/app/app.module.ts +++ b/ahoy-ui/src/app/app.module.ts @@ -115,6 +115,7 @@ import {GitSettingsComponent} from './settings/git-settings/git-settings.compone import {SettingsComponent} from './settings/settings.component'; import {TaskEventsListenerComponent} from './taskevents/task-events-listener/task-events-listener.component'; import {ErrorService} from './util/error.service'; +import {SecretNameUniqueValidatorDirective} from "./applications/application-secrets/secret-name-unique-validator.directive"; @NgModule({ declarations: [ @@ -150,6 +151,7 @@ import {ErrorService} from './util/error.service'; ApplicationVersionsComponent, ApplicationVersionDetailComponent, ApplicationNameUniqueValidatorDirective, + SecretNameUniqueValidatorDirective, ApplicationVersionUniqueValidatorDirective, ApplicationAllowedValidatorDirective, NotificationsComponent, diff --git a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html index d70e3f54..49b71d5e 100644 --- a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html +++ b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html @@ -30,11 +30,18 @@
    + [readonly]="secretInUse() && !secretAlreadyExists(secretIndex)" + [pTooltip]="(secretInUse() && !secretAlreadyExists(secretIndex)) ? 'Secret in use' : ''" + pattern="^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" + [appSecretNameUnique]="secrets" + [selectedIndex]="secretIndex"> - Name should start with and use lower case letters and numbers + + Name invalid: should start with and use lower case letters and numbers + + + Secret already exists +
    diff --git a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts index c47862f4..afb66218 100644 --- a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts +++ b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts @@ -25,6 +25,7 @@ import {ApplicationEnvironmentVariable, ApplicationSecret, ApplicationVolume} fr viewProviders: [{provide: ControlContainer, useExisting: NgForm}] }) export class ApplicationSecretsComponent implements AfterContentChecked { + @Input() parentForm: NgForm; @Input() secrets: ApplicationSecret[]; @Input() volumes: ApplicationVolume[]; @Input() environmentVariables: ApplicationEnvironmentVariable[]; @@ -69,4 +70,8 @@ export class ApplicationSecretsComponent implements AfterContentChecked { } return false; } + + secretAlreadyExists(secretIndex: number) { + return this.parentForm.form.controls['secretName' + secretIndex]?.hasError('secretNameNotUnique'); + } } diff --git a/ahoy-ui/src/app/applications/application-secrets/secret-name-unique-validator.directive.ts b/ahoy-ui/src/app/applications/application-secrets/secret-name-unique-validator.directive.ts new file mode 100644 index 00000000..a04e443e --- /dev/null +++ b/ahoy-ui/src/app/applications/application-secrets/secret-name-unique-validator.directive.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Directive, Input} from '@angular/core'; +import {AbstractControl, NG_VALIDATORS, Validator, ValidatorFn} from '@angular/forms'; +import {ApplicationSecret} from "../application"; + +@Directive({ + selector: '[appSecretNameUnique]', + providers: [{provide: NG_VALIDATORS, useExisting: SecretNameUniqueValidatorDirective, multi: true}] +}) +export class SecretNameUniqueValidatorDirective implements Validator { + @Input('appSecretNameUnique') secrets: ApplicationSecret[]; + @Input() selectedIndex: number; + + validate(control: AbstractControl): { [key: string]: any } | null { + return this.secrets ? this.checkSecretNameUnique(this.secrets)(control) : null; + } + + private checkSecretNameUnique(secrets: ApplicationSecret[]): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + + let notUnique = false; + for (let i = 0; i < secrets.length; i++) { + if (i === this.selectedIndex) { + continue; + } + if (secrets[i].name === control.value) { + notUnique = true; + break; + } + } + + return notUnique ? {secretNameNotUnique: {value: control.value}} : null; + }; + } +} diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html index 8be5365d..243a8888 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html @@ -142,6 +142,7 @@
    Configuration of {{application.name}}:{{editingVersion}} Date: Tue, 14 Sep 2021 13:41:33 +0200 Subject: [PATCH 27/36] UI: on initial setup; create environment from Dash returns to an empty dash --- .../dashboard-environment.component.html | 9 +++++++-- ahoy-ui/src/app/dashboard/dashboard.component.html | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html b/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html index c5d9a691..8b93deed 100644 --- a/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html +++ b/ahoy-ui/src/app/dashboard/dashboard-environment/dashboard-environment.component.html @@ -14,7 +14,7 @@ ~ limitations under the License. --> -
    +
    @@ -25,9 +25,14 @@ {{environment.cluster.name}}
    +
    + There are no releases in this environment, create or add a release.. +
    + + [scrollable]="false" + *ngIf="environmentReleases && environmentReleases.length > 0"> diff --git a/ahoy-ui/src/app/dashboard/dashboard.component.html b/ahoy-ui/src/app/dashboard/dashboard.component.html index fb6d6dc6..d9d514b7 100644 --- a/ahoy-ui/src/app/dashboard/dashboard.component.html +++ b/ahoy-ui/src/app/dashboard/dashboard.component.html @@ -33,7 +33,7 @@
    Dashboard
    - +
    From 005541866b862e07a518d5cd1fe9df7fda240cc1 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Wed, 15 Sep 2021 10:45:20 +0200 Subject: [PATCH 28/36] UI: removing tab view components from leftmost tab causes form to still be invalid causing save t... - done for application volumes --- ahoy-ui/src/app/app.module.ts | 8 +-- .../application-version-detail.component.html | 11 +++- .../application-version-detail.component.ts | 7 ++ .../application-volumes.component.ts | 53 --------------- .../multi-tab/multi-tab.component.html} | 15 ++--- .../multi-tab/multi-tab.component.scss} | 0 .../multi-tab/multi-tab.component.ts | 64 +++++++++++++++++++ 7 files changed, 90 insertions(+), 68 deletions(-) delete mode 100644 ahoy-ui/src/app/applications/application-volumes/application-volumes.component.ts rename ahoy-ui/src/app/{applications/application-volumes/application-volumes.component.html => components/multi-tab/multi-tab.component.html} (61%) rename ahoy-ui/src/app/{applications/application-volumes/application-volumes.component.scss => components/multi-tab/multi-tab.component.scss} (100%) create mode 100644 ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts diff --git a/ahoy-ui/src/app/app.module.ts b/ahoy-ui/src/app/app.module.ts index 5201caa7..13208b14 100644 --- a/ahoy-ui/src/app/app.module.ts +++ b/ahoy-ui/src/app/app.module.ts @@ -70,11 +70,11 @@ import {ApplicationEnvVariablesComponent} from './applications/application-env-v import {ApplicationNameUniqueValidatorDirective} from './applications/application-name-unique-validator.directive'; import {ApplicationSecretDataComponent} from './applications/application-secret-data/application-secret-data.component'; import {ApplicationSecretsComponent} from './applications/application-secrets/application-secrets.component'; +import {SecretNameUniqueValidatorDirective} from './applications/application-secrets/secret-name-unique-validator.directive'; import {ApplicationVersionDetailComponent} from './applications/application-version-detail/application-version-detail.component'; import {ApplicationVersionUniqueValidatorDirective} from './applications/application-version-unique-validator.directive'; import {ApplicationVersionsComponent} from './applications/application-versions/application-versions.component'; import {ApplicationVolumeDetailComponent} from './applications/application-volume-detail/application-volume-detail.component'; -import {ApplicationVolumesComponent} from './applications/application-volumes/application-volumes.component'; import {ApplicationsComponent} from './applications/applications.component'; import {DockerRegistriesComponent} from './applications/docker-registries/docker-registries.component'; import {ClusterDetailComponent} from './clusters/cluster-detail/cluster-detail.component'; @@ -82,6 +82,7 @@ import {ClustersComponent} from './clusters/clusters.component'; import {ConfirmDialogComponent} from './components/confirm-dialog/confirm-dialog.component'; import {VerifyValidatorDirective} from './components/confirm-dialog/verify-validator.directive'; import {DescriptionDialogComponent} from './components/description-dialog/description-dialog.component'; +import {MultiTabComponent} from './components/multi-tab/multi-tab.component'; import {DashboardEnvironmentComponent} from './dashboard/dashboard-environment/dashboard-environment.component'; import {DashboardComponent} from './dashboard/dashboard.component'; import {AddReleaseDialogComponent} from './environment-release/add-release-dialog/add-release-dialog.component'; @@ -115,7 +116,6 @@ import {GitSettingsComponent} from './settings/git-settings/git-settings.compone import {SettingsComponent} from './settings/settings.component'; import {TaskEventsListenerComponent} from './taskevents/task-events-listener/task-events-listener.component'; import {ErrorService} from './util/error.service'; -import {SecretNameUniqueValidatorDirective} from "./applications/application-secrets/secret-name-unique-validator.directive"; @NgModule({ declarations: [ @@ -174,11 +174,11 @@ import {SecretNameUniqueValidatorDirective} from "./applications/application-sec ApplicationVolumeDetailComponent, EnvironmentReleaseDeploymentStatusComponent, ApplicationConfigFilesComponent, - ApplicationVolumesComponent, ApplicationSecretsComponent, EnvironmentReleasesComponent, ReleaseDetailComponent, - AddToEnvironmentDialogComponent + AddToEnvironmentDialogComponent, + MultiTabComponent ], imports: [ BrowserModule, diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html index 243a8888..3d979eed 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html @@ -133,9 +133,14 @@
    Configuration of {{application.name}}:{{editingVersion}} - - + + + + + + + + diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts index 92a65af8..7a72a32b 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts @@ -18,6 +18,7 @@ import {Location} from '@angular/common'; import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {AppBreadcrumbService} from '../../app.breadcrumb.service'; +import {TabItemFactory} from '../../components/multi-tab/multi-tab.component'; import {ReleaseService} from '../../releases/release.service'; import {Application, ApplicationConfig, ApplicationSecret, ApplicationVersion, ApplicationVolume} from '../application'; import {ApplicationService} from '../application.service'; @@ -216,4 +217,10 @@ export class ApplicationVersionDetailComponent implements OnInit { return appSecret; }); } + + applicationVolumeFactory(): TabItemFactory { + return (): ApplicationVolume => { + return new ApplicationVolume(); + }; + } } diff --git a/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.ts b/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.ts deleted file mode 100644 index ea8f9e9d..00000000 --- a/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2021 LSD Information Technology (Pty) Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {AfterContentChecked, ChangeDetectorRef, Component, Input} from '@angular/core'; -import {ControlContainer, NgForm} from '@angular/forms'; -import {ApplicationSecret, ApplicationVolume} from '../application'; - -@Component({ - selector: 'app-application-volumes', - templateUrl: './application-volumes.component.html', - styleUrls: ['./application-volumes.component.scss'], - viewProviders: [{provide: ControlContainer, useExisting: NgForm}] -}) -export class ApplicationVolumesComponent implements AfterContentChecked { - @Input() volumes: ApplicationVolume[]; - @Input() secrets: ApplicationSecret[]; - selectedVolumeIndex = 0; - - constructor(private cd: ChangeDetectorRef) { - } - - ngAfterContentChecked(): void { - this.cd.detectChanges(); - } - - addVolume() { - this.volumes.push(new ApplicationVolume()); - setTimeout(() => this.selectedVolumeIndex = this.volumes.length - 1); - } - - deleteVolume() { - this.volumes.splice(this.selectedVolumeIndex, 1); - setTimeout(() => { - if (this.selectedVolumeIndex === this.volumes.length) { - // only move one tab back if its the last tab - this.selectedVolumeIndex = this.volumes.length - 1; - } - }); - } -} diff --git a/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.html b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html similarity index 61% rename from ahoy-ui/src/app/applications/application-volumes/application-volumes.component.html rename to ahoy-ui/src/app/components/multi-tab/multi-tab.component.html index a478a2b5..b6dead0c 100644 --- a/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.html +++ b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html @@ -16,17 +16,16 @@
    - - + +
    -
    - - +
    + + - - + diff --git a/ahoy-ui/src/app/applications/application-volumes/application-volumes.component.scss b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.scss similarity index 100% rename from ahoy-ui/src/app/applications/application-volumes/application-volumes.component.scss rename to ahoy-ui/src/app/components/multi-tab/multi-tab.component.scss diff --git a/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts new file mode 100644 index 00000000..329140e7 --- /dev/null +++ b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {AfterContentChecked, ChangeDetectorRef, Component, Input, OnInit, TemplateRef} from '@angular/core'; + +@Component({ + selector: 'app-multi-tab', + templateUrl: './multi-tab.component.html', + styleUrls: ['./multi-tab.component.scss'] +}) +export class MultiTabComponent implements OnInit, AfterContentChecked { + @Input() content: TemplateRef; + @Input() items: object[]; + @Input() itemFactory: TabItemFactory; + indexes: number[] = []; + indexCount = 0; + selectedIndex = 0; + + constructor(private cd: ChangeDetectorRef) { + } + + ngOnInit(): void { + for (const item of this.items) { + this.indexes.push(this.indexCount++); + } + } + + ngAfterContentChecked(): void { + this.cd.detectChanges(); + } + + add() { + const item = this.itemFactory(); + this.items.push(item); + this.indexes.push(this.indexCount++); + setTimeout(() => this.selectedIndex = this.items.length - 1); + } + + delete() { + this.items.splice(this.selectedIndex, 1); + this.indexes.splice(this.selectedIndex, 1); + setTimeout(() => { + if (this.selectedIndex === this.items.length) { + // only move one tab back if its the last tab + this.selectedIndex = this.items.length - 1; + } + }); + } +} + +export type TabItemFactory = () => T; From 90c5819d6eef482764a8369e764b51c9982452db Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Wed, 15 Sep 2021 11:59:29 +0200 Subject: [PATCH 29/36] UI: removing tab view components from leftmost tab causes form to still be invalid causing save t... - done for application volumes in environment config - done for application secrets in application and environment config --- .../application-secrets.component.html | 88 ++++++++----------- .../application-secrets.component.ts | 53 ++--------- .../application-version-detail.component.html | 25 ++++-- .../application-version-detail.component.ts | 28 ++++++ .../multi-tab/multi-tab.component.html | 5 +- .../multi-tab/multi-tab.component.ts | 20 +++++ ...lication-environment-config.component.html | 31 +++++-- ...pplication-environment-config.component.ts | 38 +++++++- 8 files changed, 170 insertions(+), 118 deletions(-) diff --git a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html index 49b71d5e..31ebcd9c 100644 --- a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html +++ b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.html @@ -14,65 +14,47 @@ ~ limitations under the License. --> -
    -
    - - +
    +
    + + + + Name invalid: should start with and use lower case letters and numbers + + + Secret already exists +
    -
    - - - -
    -
    - - - - Name invalid: should start with and use lower case letters and numbers - - - Secret already exists - -
    - -
    - -
    - - - - -
    +
    + +
    -
    - - -
    + + -
    - - -
    +
    -
    +
    + + +
    -
    +
    + + +
    - -
    +
    diff --git a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts index afb66218..299460c9 100644 --- a/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts +++ b/ahoy-ui/src/app/applications/application-secrets/application-secrets.component.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import {AfterContentChecked, ChangeDetectorRef, Component, Input} from '@angular/core'; +import {Component, Input} from '@angular/core'; import {ControlContainer, NgForm} from '@angular/forms'; -import {ApplicationEnvironmentVariable, ApplicationSecret, ApplicationVolume} from '../application'; +import {ApplicationSecret} from '../application'; @Component({ selector: 'app-application-secrets', @@ -24,51 +24,14 @@ import {ApplicationEnvironmentVariable, ApplicationSecret, ApplicationVolume} fr styleUrls: ['./application-secrets.component.scss'], viewProviders: [{provide: ControlContainer, useExisting: NgForm}] }) -export class ApplicationSecretsComponent implements AfterContentChecked { +export class ApplicationSecretsComponent { @Input() parentForm: NgForm; - @Input() secrets: ApplicationSecret[]; - @Input() volumes: ApplicationVolume[]; - @Input() environmentVariables: ApplicationEnvironmentVariable[]; - @Input() routeTlsSecretName: string; + @Input() secret: ApplicationSecret; + @Input() secretIndex: number; + @Input() secretsForValidation: ApplicationSecret[]; + @Input() secretInUse: (secret) => boolean; - selectedSecretIndex = 0; - - constructor(private cd: ChangeDetectorRef) { - } - - ngAfterContentChecked(): void { - this.cd.detectChanges(); - } - - addSecret() { - const applicationSecret = new ApplicationSecret(); - applicationSecret.type = 'Generic'; - applicationSecret.data = {}; - this.secrets.push(applicationSecret); - setTimeout(() => this.selectedSecretIndex = this.secrets.length - 1); - } - - deleteSecret() { - this.secrets.splice(this.selectedSecretIndex, 1); - setTimeout(() => { - if (this.selectedSecretIndex === this.secrets.length) { - // only move one tab back if its the last tab - this.selectedSecretIndex = this.secrets.length - 1; - } - }); - } - - secretInUse(): boolean { - const secret = this.secrets[this.selectedSecretIndex]; - if (secret && secret.name) { - const inUseInVolumes = this.volumes - .filter(volume => volume.type === 'Secret' && volume.secretName === secret.name).length > 0; - const inUseInEnvironmentVariables = this.environmentVariables - .filter(envVar => envVar.type === 'Secret' && envVar.secretName === secret.name).length > 0; - return inUseInVolumes || inUseInEnvironmentVariables || - (this.routeTlsSecretName !== undefined ? this.routeTlsSecretName === secret.name : false); - } - return false; + constructor() { } secretAlreadyExists(secretIndex: number) { diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html index 3d979eed..c75d6fe6 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.html @@ -133,9 +133,9 @@
    Configuration of {{application.name}}:{{editingVersion}} - + - + @@ -146,13 +146,20 @@
    Configuration of {{application.name}}:{{editingVersion}} - - + + + + + + + + diff --git a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts index 7a72a32b..4c9f3f0f 100644 --- a/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts +++ b/ahoy-ui/src/app/applications/application-version-detail/application-version-detail.component.ts @@ -223,4 +223,32 @@ export class ApplicationVersionDetailComponent implements OnInit { return new ApplicationVolume(); }; } + + applicationSecretFactory(): TabItemFactory { + return (): ApplicationSecret => { + const applicationSecret = new ApplicationSecret(); + applicationSecret.type = 'Generic'; + applicationSecret.data = {}; + return applicationSecret; + }; + } + + secretInUse() { + return (secret: ApplicationSecret): boolean => { + if (secret && secret.name) { + const inUseInVolumes = this.applicationVersion.volumes + .filter(volume => volume.type === 'Secret' && volume.secretName === secret.name).length > 0; + const inUseInEnvironmentVariables = this.applicationVersion.environmentVariables + .filter(envVar => envVar.type === 'Secret' && envVar.secretName === secret.name).length > 0; + return inUseInVolumes || inUseInEnvironmentVariables; + } + return false; + }; + } + + secretInUseTooltip() { + return (secret: ApplicationSecret): string => { + return 'Secret in use'; + }; + } } diff --git a/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html index b6dead0c..93afb829 100644 --- a/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html +++ b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.html @@ -17,7 +17,10 @@
    - + + +
    diff --git a/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts index 329140e7..4d689d17 100644 --- a/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts +++ b/ahoy-ui/src/app/components/multi-tab/multi-tab.component.ts @@ -25,6 +25,8 @@ export class MultiTabComponent implements OnInit, AfterContentChecked { @Input() content: TemplateRef; @Input() items: object[]; @Input() itemFactory: TabItemFactory; + @Input() deleteDisabled: (item) => boolean; + @Input() deleteDisabledTooltip: (item) => string; indexes: number[] = []; indexCount = 0; selectedIndex = 0; @@ -59,6 +61,24 @@ export class MultiTabComponent implements OnInit, AfterContentChecked { } }); } + + isDeleteDisabled(): boolean { + if (this.deleteDisabled) { + const item = this.items[this.selectedIndex]; + return this.deleteDisabled(item); + } + return false; + } + + getDeleteDisabledTooltip(): string { + if (this.deleteDisabled && this.deleteDisabledTooltip) { + const item = this.items[this.selectedIndex]; + if (this.deleteDisabled(item)) { + return this.deleteDisabledTooltip(item); + } + } + return ''; + } } export type TabItemFactory = () => T; diff --git a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.html b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.html index 9f1ab2b6..a3e37daf 100644 --- a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.html +++ b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.html @@ -94,20 +94,33 @@ - - + + + + + + + + - - + + + + + + + + diff --git a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts index 5dba7b50..e1763860 100644 --- a/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts +++ b/ahoy-ui/src/app/release-manage/release-application-environment-config/release-application-environment-config.component.ts @@ -19,8 +19,9 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {mergeMap} from 'rxjs/operators'; import {AppBreadcrumbService} from '../../app.breadcrumb.service'; -import {Application, ApplicationEnvironmentConfig, ApplicationEnvironmentConfigId, ApplicationSecret, ApplicationVersion} from '../../applications/application'; +import {Application, ApplicationEnvironmentConfig, ApplicationEnvironmentConfigId, ApplicationSecret, ApplicationVersion, ApplicationVolume} from '../../applications/application'; import {ApplicationService} from '../../applications/application.service'; +import {TabItemFactory} from '../../components/multi-tab/multi-tab.component'; import {EnvironmentRelease, EnvironmentReleaseId} from '../../environment-release/environment-release'; import {EnvironmentReleaseService} from '../../environment-release/environment-release.service'; import {Environment} from '../../environments/environment'; @@ -149,4 +150,39 @@ export class ReleaseApplicationEnvironmentConfigComponent implements OnInit { } return []; } + + applicationVolumeFactory(): TabItemFactory { + return (): ApplicationVolume => { + return new ApplicationVolume(); + }; + } + + applicationSecretFactory(): TabItemFactory { + return (): ApplicationSecret => { + const applicationSecret = new ApplicationSecret(); + applicationSecret.type = 'Generic'; + applicationSecret.data = {}; + return applicationSecret; + }; + } + + secretInUse() { + return (secret: ApplicationSecret): boolean => { + if (secret && secret.name) { + const inUseInVolumes = this.environmentConfig.volumes + .filter(volume => volume.type === 'Secret' && volume.secretName === secret.name).length > 0; + const inUseInEnvironmentVariables = this.environmentConfig.environmentVariables + .filter(envVar => envVar.type === 'Secret' && envVar.secretName === secret.name).length > 0; + return inUseInVolumes || inUseInEnvironmentVariables || + (this.environmentConfig.tlsSecretName !== undefined ? this.environmentConfig.tlsSecretName === secret.name : false); + } + return false; + }; + } + + secretInUseTooltip() { + return (secret: ApplicationSecret): string => { + return 'Secret in use'; + }; + } } From e9cf5366dbd36fd087e5e99696d964da17baf29a Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Wed, 15 Sep 2021 15:04:45 +0200 Subject: [PATCH 30/36] UI: removing tab view components from leftmost tab causes form to still be invalid causing save t... - done for application config files in application and environment config --- .../application-config-files.component.html | 37 ++++++++----------- .../application-config-files.component.ts | 29 ++++----------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.html b/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.html index e66e589f..1f6522f8 100644 --- a/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.html +++ b/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.html @@ -23,31 +23,26 @@ Application configuration file path; required for environment configuration file - - - -
    - - + -
    -
    - - -
    + +
    +
    + + +
    -
    - - -
    +
    + +
    +
    +
    + + - - -
    diff --git a/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.ts b/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.ts index 7b2eaf3f..675a1fb2 100644 --- a/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.ts +++ b/ahoy-ui/src/app/applications/application-config-files/application-config-files.component.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import {AfterContentChecked, ChangeDetectorRef, Component, Input} from '@angular/core'; +import {Component, Input} from '@angular/core'; import {ControlContainer, NgForm} from '@angular/forms'; +import {TabItemFactory} from '../../components/multi-tab/multi-tab.component'; import {ApplicationConfig, ApplicationVersion} from '../application'; @Component({ @@ -24,31 +25,17 @@ import {ApplicationConfig, ApplicationVersion} from '../application'; styleUrls: ['./application-config-files.component.scss'], viewProviders: [{provide: ControlContainer, useExisting: NgForm}] }) -export class ApplicationConfigFilesComponent implements AfterContentChecked { +export class ApplicationConfigFilesComponent { @Input() applicationVersion: ApplicationVersion; @Input() configs: ApplicationConfig[]; @Input() editPath = true; - selectedConfigIndex = 0; - constructor(private cd: ChangeDetectorRef) { + constructor() { } - ngAfterContentChecked(): void { - this.cd.detectChanges(); - } - - addConfig() { - this.configs.push(new ApplicationConfig()); - setTimeout(() => this.selectedConfigIndex = this.configs.length - 1); - } - - deleteConfig() { - this.configs.splice(this.selectedConfigIndex, 1); - setTimeout(() => { - if (this.selectedConfigIndex === this.configs.length) { - // only move one tab back if its the last tab - this.selectedConfigIndex = this.configs.length - 1; - } - }); + applicationConfigFactory(): TabItemFactory { + return (): ApplicationConfig => { + return new ApplicationConfig(); + }; } } From 5ec2c6dfd7b8a8fa0a7f474d07d742f76b439ab4 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Wed, 15 Sep 2021 15:37:37 +0200 Subject: [PATCH 31/36] UI: removing tab view components from leftmost tab causes form to still be invalid causing save t... - done for docker registries --- .../docker-settings.component.html | 64 +++++++++---------- .../docker-settings.component.ts | 29 +++------ 2 files changed, 37 insertions(+), 56 deletions(-) diff --git a/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.html b/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.html index 3b4df60a..123e5ca0 100644 --- a/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.html +++ b/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.html @@ -22,52 +22,46 @@
    Docker Registries
    - - - + -
    - - + -
    -
    - - -
    -
    - - -
    - -
    - - -
    + +
    +
    + + +
    +
    + + +
    -
    - - -
    - -
    - - -
    +
    + + +
    +
    + + +
    + +
    + +
    +
    +
    - - + -
    diff --git a/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.ts b/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.ts index 33af9f1f..d7e50c90 100644 --- a/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.ts +++ b/ahoy-ui/src/app/settings/docker-settings/docker-settings.component.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import {AfterContentChecked, ChangeDetectorRef, Component, OnInit} from '@angular/core'; +import {Component, OnInit} from '@angular/core'; import {AppBreadcrumbService} from '../../app.breadcrumb.service'; +import {TabItemFactory} from '../../components/multi-tab/multi-tab.component'; import {Notification} from '../../notifications/notification'; import {NotificationsService} from '../../notifications/notifications.service'; import {DockerRegistry, DockerSettings} from './docker-settings'; @@ -26,13 +27,12 @@ import {DockerSettingsService} from './docker-settings.service'; templateUrl: './docker-settings.component.html', styleUrls: ['./docker-settings.component.scss'] }) -export class DockerSettingsComponent implements OnInit, AfterContentChecked { +export class DockerSettingsComponent implements OnInit { dockerSettings: DockerSettings; hideDockerPassword = true; selectedIndex: number; - constructor(private cd: ChangeDetectorRef, - private dockerSettingsService: DockerSettingsService, + constructor(private dockerSettingsService: DockerSettingsService, private notificationsService: NotificationsService, private breadcrumbService: AppBreadcrumbService) { this.breadcrumbService.setItems([ @@ -41,10 +41,6 @@ export class DockerSettingsComponent implements OnInit, AfterContentChecked { ]); } - ngAfterContentChecked(): void { - this.cd.detectChanges(); - } - ngOnInit(): void { this.dockerSettingsService.get() .subscribe((dockerSettings) => { @@ -63,18 +59,9 @@ export class DockerSettingsComponent implements OnInit, AfterContentChecked { .subscribe(() => this.notificationsService.notification(notification)); } - addDockerRegistry() { - this.dockerSettings.dockerRegistries.push(new DockerRegistry()); - setTimeout(() => this.selectedIndex = this.dockerSettings.dockerRegistries.length - 1); - } - - deleteRegistry() { - this.dockerSettings.dockerRegistries.splice(this.selectedIndex, 1); - setTimeout(() => { - if (this.selectedIndex === this.dockerSettings.dockerRegistries.length) { - // only move one tab back if its the last tab - this.selectedIndex = this.dockerSettings.dockerRegistries.length - 1; - } - }); + dockerRegistryFactory(): TabItemFactory { + return (): DockerRegistry => { + return new DockerRegistry(); + }; } } From f4abe4fda87141f86e689cdd0188a545264be416 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Thu, 16 Sep 2021 13:17:24 +0200 Subject: [PATCH 32/36] As a Release Manager, I'd like an option to copy environment config when promoting and upgrading ... - done for upgrade --- .../co/lsd/ahoy/server/ReleaseController.java | 7 +-- .../za/co/lsd/ahoy/server/ReleaseService.java | 49 ++++++++++--------- .../ahoy/server/releases/UpgradeOptions.java | 25 ++++++++++ .../release-manage.component.ts | 7 +-- .../release-manage/release-manage.service.ts | 10 ++-- .../upgrade-dialog.component.html | 9 +++- .../upgrade-dialog.component.ts | 4 +- ahoy-ui/src/app/releases/release.ts | 5 ++ 8 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java index 5cb2d241..6f645df9 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java @@ -27,6 +27,7 @@ import za.co.lsd.ahoy.server.environmentrelease.EnvironmentReleaseRepository; import za.co.lsd.ahoy.server.releases.ReleaseVersion; import za.co.lsd.ahoy.server.releases.ReleaseVersionRepository; +import za.co.lsd.ahoy.server.releases.UpgradeOptions; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -82,9 +83,9 @@ public ResponseEntity promote(@PathVariable Long environment return new ResponseEntity<>(promotedEnvironmentRelease, new HttpHeaders(), HttpStatus.OK); } - @PostMapping("/upgrade/{releaseVersionId}/{version}") - public ResponseEntity upgrade(@PathVariable Long releaseVersionId, @PathVariable String version) { - ReleaseVersion upgradedReleaseVersion = releaseService.upgrade(releaseVersionId, version); + @PostMapping("/upgrade/{releaseVersionId}") + public ResponseEntity upgrade(@PathVariable Long releaseVersionId, @RequestBody UpgradeOptions upgradeOptions) { + ReleaseVersion upgradedReleaseVersion = releaseService.upgrade(releaseVersionId, upgradeOptions); return new ResponseEntity<>(upgradedReleaseVersion, new HttpHeaders(), HttpStatus.OK); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java index f4f93f31..34208f35 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 LSD Information Technology (Pty) Ltd + * Copyright 2021 LSD Information Technology (Pty) Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,7 @@ import za.co.lsd.ahoy.server.environments.EnvironmentRepository; import za.co.lsd.ahoy.server.releases.ReleaseVersion; import za.co.lsd.ahoy.server.releases.ReleaseVersionRepository; +import za.co.lsd.ahoy.server.releases.UpgradeOptions; import java.util.ArrayList; import java.util.Objects; @@ -56,12 +57,12 @@ public class ReleaseService { private ApplicationEventPublisher eventPublisher; public ReleaseService(EnvironmentRepository environmentRepository, - EnvironmentReleaseRepository environmentReleaseRepository, - ReleaseVersionRepository releaseVersionRepository, - ApplicationEnvironmentConfigRepository applicationEnvironmentConfigRepository, - ApplicationEnvironmentConfigProvider environmentConfigProvider, - ApplicationReleaseStatusRepository applicationReleaseStatusRepository, - ReleaseManager releaseManager) { + EnvironmentReleaseRepository environmentReleaseRepository, + ReleaseVersionRepository releaseVersionRepository, + ApplicationEnvironmentConfigRepository applicationEnvironmentConfigRepository, + ApplicationEnvironmentConfigProvider environmentConfigProvider, + ApplicationReleaseStatusRepository applicationReleaseStatusRepository, + ReleaseManager releaseManager) { this.environmentRepository = environmentRepository; this.environmentReleaseRepository = environmentReleaseRepository; this.releaseVersionRepository = releaseVersionRepository; @@ -177,30 +178,34 @@ public EnvironmentRelease promote(Long environmentId, Long releaseId, Long destE } @Transactional - public ReleaseVersion upgrade(Long releaseVersionId, String version) { + public ReleaseVersion upgrade(Long releaseVersionId, UpgradeOptions upgradeOptions) { ReleaseVersion currentReleaseVersion = releaseVersionRepository.findById(releaseVersionId) .orElseThrow(() -> new ResourceNotFoundException("Could not find release version: " + releaseVersionId)); - log.info("Upgrading release version: {} to version: {}", currentReleaseVersion.getVersion(), version); + log.info("Upgrading release version: {} to version: {}", currentReleaseVersion.getVersion(), upgradeOptions.getVersion()); - ReleaseVersion upgradedReleaseVersion = new ReleaseVersion(version, currentReleaseVersion.getRelease(), new ArrayList<>(currentReleaseVersion.getApplicationVersions())); + ReleaseVersion upgradedReleaseVersion = new ReleaseVersion(upgradeOptions.getVersion(), currentReleaseVersion.getRelease(), new ArrayList<>(currentReleaseVersion.getApplicationVersions())); upgradedReleaseVersion = releaseVersionRepository.save(upgradedReleaseVersion); - Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(currentReleaseVersion.getRelease().getId()); - for (EnvironmentRelease environmentRelease : environmentReleases) { + if (upgradeOptions.isCopyEnvironmentConfig()) { + log.info("Copy environment config selected, copying config to new version: {}", upgradeOptions.getVersion()); - for (ApplicationVersion applicationVersion : upgradedReleaseVersion.getApplicationVersions()) { - Optional currentEnvironmentConfig = environmentConfigProvider.environmentConfigFor( - environmentRelease, currentReleaseVersion, applicationVersion); + Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(currentReleaseVersion.getRelease().getId()); + for (EnvironmentRelease environmentRelease : environmentReleases) { - if (currentEnvironmentConfig.isPresent()) { - ApplicationDeploymentId id = new ApplicationDeploymentId( - environmentRelease.getId(), - upgradedReleaseVersion.getId(), - applicationVersion.getId()); + for (ApplicationVersion applicationVersion : upgradedReleaseVersion.getApplicationVersions()) { + Optional currentEnvironmentConfig = environmentConfigProvider.environmentConfigFor( + environmentRelease, currentReleaseVersion, applicationVersion); + + if (currentEnvironmentConfig.isPresent()) { + ApplicationDeploymentId id = new ApplicationDeploymentId( + environmentRelease.getId(), + upgradedReleaseVersion.getId(), + applicationVersion.getId()); - ApplicationEnvironmentConfig newEnvironmentConfig = new ApplicationEnvironmentConfig(id, currentEnvironmentConfig.get()); - applicationEnvironmentConfigRepository.save(newEnvironmentConfig); + ApplicationEnvironmentConfig newEnvironmentConfig = new ApplicationEnvironmentConfig(id, currentEnvironmentConfig.get()); + applicationEnvironmentConfigRepository.save(newEnvironmentConfig); + } } } } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java new file mode 100644 index 00000000..9677c19f --- /dev/null +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.lsd.ahoy.server.releases; + +import lombok.Data; + +@Data +public class UpgradeOptions { + private String version; + private boolean copyEnvironmentConfig; +} diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index b550b31b..9709eb0c 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -28,7 +28,7 @@ import {DeployDetails, EnvironmentRelease} from '../environment-release/environm import {EnvironmentReleaseService} from '../environment-release/environment-release.service'; import {Environment} from '../environments/environment'; import {EnvironmentService} from '../environments/environment.service'; -import {Release, ReleaseVersion} from '../releases/release'; +import {Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; import {TaskEvent} from '../taskevents/task-events'; import {LoggerService} from '../util/logger.service'; import {CopyEnvironmentConfigDialogComponent} from './copy-environment-config-dialog/copy-environment-config-dialog.component'; @@ -212,12 +212,13 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { const dialogConfig = new DynamicDialogConfig(); dialogConfig.header = `Upgrade ${(this.environmentRelease.release as Release).name}:${this.releaseVersion.version} to version:`; dialogConfig.data = {environmentRelease: this.environmentRelease, releaseVersion: this.releaseVersion}; + dialogConfig.width = '25%'; const dialogRef = this.dialogService.open(UpgradeDialogComponent, dialogConfig); dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled - mergeMap((version) => { - return this.releaseManageService.upgrade(this.releaseVersion.id, version); + mergeMap((upgradeOptions: UpgradeOptions) => { + return this.releaseManageService.upgrade(this.releaseVersion.id, upgradeOptions); }) ).subscribe((newReleaseVersion: ReleaseVersion) => this.reload(this.environmentRelease.id.environmentId, newReleaseVersion.id)); } diff --git a/ahoy-ui/src/app/release-manage/release-manage.service.ts b/ahoy-ui/src/app/release-manage/release-manage.service.ts index 0423f7cb..e441797d 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.service.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.service.ts @@ -21,7 +21,7 @@ import {DeployDetails, EnvironmentRelease, EnvironmentReleaseId} from '../enviro import {Environment} from '../environments/environment'; import {Notification} from '../notifications/notification'; import {NotificationsService} from '../notifications/notifications.service'; -import {Release, ReleaseVersion} from '../releases/release'; +import {Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; import {LoggerService} from '../util/logger.service'; import {RestClientService} from '../util/rest-client.service'; @@ -113,10 +113,10 @@ export class ReleaseManageService { ); } - upgrade(releaseVersionId: number, version: string): Observable { - this.log.debug(`upgrading release version: ${releaseVersionId} to version: ${version}`); - const url = `/api/release/upgrade/${releaseVersionId}/${version}`; - return this.restClient.post(url).pipe( + upgrade(releaseVersionId: number, upgradeOptions: UpgradeOptions): Observable { + this.log.debug(`upgrading release version: ${releaseVersionId} to version: ${upgradeOptions.version}`); + const url = `/api/release/upgrade/${releaseVersionId}`; + return this.restClient.post(url, upgradeOptions).pipe( tap((upgradedReleaseVersion) => this.log.debug('upgraded release version', upgradedReleaseVersion)) ); } diff --git a/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.html b/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.html index 5f05b700..d79c6390 100644 --- a/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.html +++ b/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.html @@ -18,7 +18,7 @@
    - @@ -27,10 +27,15 @@
    +
    + + +
    +
    - +
    diff --git a/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.ts b/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.ts index 2cfbe3d5..7f5f9c06 100644 --- a/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.ts +++ b/ahoy-ui/src/app/release-manage/upgrade-dialog/upgrade-dialog.component.ts @@ -17,7 +17,7 @@ import {Component} from '@angular/core'; import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; import {EnvironmentRelease} from '../../environment-release/environment-release'; -import {Release, ReleaseVersion} from '../../releases/release'; +import {Release, ReleaseVersion, UpgradeOptions} from '../../releases/release'; @Component({ selector: 'app-upgrade-dialog', @@ -28,7 +28,9 @@ export class UpgradeDialogComponent { environmentRelease: EnvironmentRelease; release: Release; releaseVersion: ReleaseVersion; + upgradeOptions = new UpgradeOptions(); version: string; + copyEnvironmentConfig: boolean; constructor(public ref: DynamicDialogRef, public config: DynamicDialogConfig) { diff --git a/ahoy-ui/src/app/releases/release.ts b/ahoy-ui/src/app/releases/release.ts index b8b376d1..6024eb09 100644 --- a/ahoy-ui/src/app/releases/release.ts +++ b/ahoy-ui/src/app/releases/release.ts @@ -32,3 +32,8 @@ export class ReleaseVersion { applicationVersions: ApplicationVersion[] = undefined; releaseName: string; } + +export class UpgradeOptions { + version: string; + copyEnvironmentConfig = true; +} From 5d81fdc34533852986fa920467895611bbcabad2 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Thu, 16 Sep 2021 14:35:12 +0200 Subject: [PATCH 33/36] As a Release Manager, I'd like an option to copy environment config when promoting and upgrading ... - done for promote --- .../co/lsd/ahoy/server/ReleaseController.java | 11 ++-- .../za/co/lsd/ahoy/server/ReleaseService.java | 62 ++++++++++++------- .../ahoy/server/releases/PromoteOptions.java | 25 ++++++++ .../environment-detail.component.ts | 14 +++-- .../promote-dialog.component.html | 24 +++++-- .../promote-dialog.component.ts | 3 +- .../release-manage.component.ts | 7 ++- .../release-manage/release-manage.service.ts | 10 +-- ahoy-ui/src/app/releases/release.ts | 5 ++ 9 files changed, 116 insertions(+), 45 deletions(-) create mode 100644 ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java index 6f645df9..4a651ba7 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseController.java @@ -25,6 +25,7 @@ import za.co.lsd.ahoy.server.environmentrelease.EnvironmentRelease; import za.co.lsd.ahoy.server.environmentrelease.EnvironmentReleaseId; import za.co.lsd.ahoy.server.environmentrelease.EnvironmentReleaseRepository; +import za.co.lsd.ahoy.server.releases.PromoteOptions; import za.co.lsd.ahoy.server.releases.ReleaseVersion; import za.co.lsd.ahoy.server.releases.ReleaseVersionRepository; import za.co.lsd.ahoy.server.releases.UpgradeOptions; @@ -77,14 +78,16 @@ public ResponseEntity undeploy(@PathVariable Long environmen return new ResponseEntity<>(undeployedEnvironmentRelease.get(), new HttpHeaders(), HttpStatus.OK); } - @PostMapping("/promote/{environmentId}/{releaseId}/{destEnvironmentId}") - public ResponseEntity promote(@PathVariable Long environmentId, @PathVariable Long releaseId, @PathVariable Long destEnvironmentId) { - EnvironmentRelease promotedEnvironmentRelease = releaseService.promote(environmentId, releaseId, destEnvironmentId); + @PostMapping("/promote/{environmentId}/{releaseId}") + public ResponseEntity promote(@PathVariable Long environmentId, @PathVariable Long releaseId, + @RequestBody PromoteOptions promoteOptions) { + EnvironmentRelease promotedEnvironmentRelease = releaseService.promote(environmentId, releaseId, promoteOptions); return new ResponseEntity<>(promotedEnvironmentRelease, new HttpHeaders(), HttpStatus.OK); } @PostMapping("/upgrade/{releaseVersionId}") - public ResponseEntity upgrade(@PathVariable Long releaseVersionId, @RequestBody UpgradeOptions upgradeOptions) { + public ResponseEntity upgrade(@PathVariable Long releaseVersionId, + @RequestBody UpgradeOptions upgradeOptions) { ReleaseVersion upgradedReleaseVersion = releaseService.upgrade(releaseVersionId, upgradeOptions); return new ResponseEntity<>(upgradedReleaseVersion, new HttpHeaders(), HttpStatus.OK); } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java index 34208f35..dc9c7fac 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java @@ -35,6 +35,7 @@ import za.co.lsd.ahoy.server.environments.Environment; import za.co.lsd.ahoy.server.environments.EnvironmentException; import za.co.lsd.ahoy.server.environments.EnvironmentRepository; +import za.co.lsd.ahoy.server.releases.PromoteOptions; import za.co.lsd.ahoy.server.releases.ReleaseVersion; import za.co.lsd.ahoy.server.releases.ReleaseVersionRepository; import za.co.lsd.ahoy.server.releases.UpgradeOptions; @@ -152,8 +153,9 @@ public Future remove(EnvironmentReleaseId environmentRelease } @Transactional - public EnvironmentRelease promote(Long environmentId, Long releaseId, Long destEnvironmentId) { + public EnvironmentRelease promote(Long environmentId, Long releaseId, PromoteOptions promoteOptions) { EnvironmentReleaseId environmentReleaseId = new EnvironmentReleaseId(environmentId, releaseId); + Long destEnvironmentId = promoteOptions.getDestEnvironmentId(); log.info("Promoting environment release: {} to environment: {}", environmentReleaseId, destEnvironmentId); EnvironmentRelease environmentRelease = environmentReleaseRepository.findById(environmentReleaseId) @@ -172,8 +174,17 @@ public EnvironmentRelease promote(Long environmentId, Long releaseId, Long destE promotedEnvironmentRelease.setId(new EnvironmentReleaseId()); promotedEnvironmentRelease.setRelease(environmentRelease.getRelease()); promotedEnvironmentRelease.setEnvironment(destEnvironment); + promotedEnvironmentRelease = environmentReleaseRepository.save(promotedEnvironmentRelease); - return environmentReleaseRepository.save(promotedEnvironmentRelease); + if (promoteOptions.isCopyEnvironmentConfig()) { + log.info("Copy environment config selected, copying config to promoted environment release: {}", promotedEnvironmentRelease); + + for (ReleaseVersion releaseVersion : environmentRelease.getRelease().getReleaseVersions()) { + copyEnvironmentConfig(environmentRelease, promotedEnvironmentRelease, releaseVersion); + } + } + + return promotedEnvironmentRelease; } } @@ -192,21 +203,7 @@ public ReleaseVersion upgrade(Long releaseVersionId, UpgradeOptions upgradeOptio Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(currentReleaseVersion.getRelease().getId()); for (EnvironmentRelease environmentRelease : environmentReleases) { - - for (ApplicationVersion applicationVersion : upgradedReleaseVersion.getApplicationVersions()) { - Optional currentEnvironmentConfig = environmentConfigProvider.environmentConfigFor( - environmentRelease, currentReleaseVersion, applicationVersion); - - if (currentEnvironmentConfig.isPresent()) { - ApplicationDeploymentId id = new ApplicationDeploymentId( - environmentRelease.getId(), - upgradedReleaseVersion.getId(), - applicationVersion.getId()); - - ApplicationEnvironmentConfig newEnvironmentConfig = new ApplicationEnvironmentConfig(id, currentEnvironmentConfig.get()); - applicationEnvironmentConfigRepository.save(newEnvironmentConfig); - } - } + copyEnvironmentConfig(environmentRelease, currentReleaseVersion, upgradedReleaseVersion); } } @@ -225,25 +222,46 @@ public EnvironmentRelease copyEnvConfig(Long environmentId, Long releaseId, Long ReleaseVersion destReleaseVersion = releaseVersionRepository.findById(destReleaseVersionId) .orElseThrow(() -> new ResourceNotFoundException("Could not find destReleaseVersionId: " + destReleaseVersionId)); + copyEnvironmentConfig(environmentRelease, sourceReleaseVersion, destReleaseVersion); + + return environmentRelease; + } + + /** + * Copies environment config from one release version to another for the same environment release. + */ + private void copyEnvironmentConfig(EnvironmentRelease environmentRelease, ReleaseVersion sourceReleaseVersion, ReleaseVersion destReleaseVersion) { + this.copyEnvironmentConfig(environmentRelease, sourceReleaseVersion, environmentRelease, destReleaseVersion); + } + + /** + * Copies environment config from one environment release to another for the same version. + */ + private void copyEnvironmentConfig(EnvironmentRelease sourceEnvironmentRelease, EnvironmentRelease destEnvironmentRelease, ReleaseVersion releaseVersion) { + this.copyEnvironmentConfig(sourceEnvironmentRelease, releaseVersion, destEnvironmentRelease, releaseVersion); + } + + /** + * Copies environment config from one environment release version to another environment release version. + */ + private void copyEnvironmentConfig(EnvironmentRelease sourceEnvironmentRelease, ReleaseVersion sourceReleaseVersion, EnvironmentRelease destEnvironmentRelease, ReleaseVersion destReleaseVersion) { for (ApplicationVersion applicationVersion : destReleaseVersion.getApplicationVersions()) { Optional sourceConfig = environmentConfigProvider.environmentConfigFor( - environmentRelease, sourceReleaseVersion, applicationVersion); + sourceEnvironmentRelease, sourceReleaseVersion, applicationVersion); if (sourceConfig.isPresent()) { Optional destConfig = environmentConfigProvider.environmentConfigFor( - environmentRelease, destReleaseVersion, applicationVersion); + destEnvironmentRelease, destReleaseVersion, applicationVersion); if (destConfig.isEmpty()) { ApplicationDeploymentId id = new ApplicationDeploymentId( - environmentRelease.getId(), + destEnvironmentRelease.getId(), destReleaseVersion.getId(), applicationVersion.getId()); applicationEnvironmentConfigRepository.save(new ApplicationEnvironmentConfig(id, sourceConfig.get())); } } } - - return environmentRelease; } @Async("deploymentTaskExecutor") diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java new file mode 100644 index 00000000..6cac691e --- /dev/null +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 LSD Information Technology (Pty) Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.lsd.ahoy.server.releases; + +import lombok.Data; + +@Data +public class PromoteOptions { + private Long destEnvironmentId; + private boolean copyEnvironmentConfig; +} diff --git a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts index 91160ecb..0463532e 100644 --- a/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts +++ b/ahoy-ui/src/app/environments/environment-detail/environment-detail.component.ts @@ -25,6 +25,7 @@ import {ClusterService} from '../../clusters/cluster.service'; import {EnvironmentReleaseId} from '../../environment-release/environment-release'; import {EnvironmentReleaseService} from '../../environment-release/environment-release.service'; import {ReleaseManageService} from '../../release-manage/release-manage.service'; +import {PromoteOptions} from '../../releases/release'; import {Environment} from '../environment'; import {EnvironmentService} from '../environment.service'; @@ -34,7 +35,8 @@ import {EnvironmentService} from '../environment.service'; styleUrls: ['./environment-detail.component.scss'] }) export class EnvironmentDetailComponent implements OnInit { - private environmentReleaseId: EnvironmentReleaseId; + private promoteEnvironmentReleaseId: EnvironmentReleaseId; + private promoteCopyEnvironmentConfig: boolean; editMode = false; sourceEnvironment: Environment; cluster: Cluster; @@ -68,7 +70,8 @@ export class EnvironmentDetailComponent implements OnInit { const environmentId = +this.route.snapshot.queryParamMap.get('environmentId'); const releaseId = +this.route.snapshot.queryParamMap.get('releaseId'); if (environmentId && releaseId) { - this.environmentReleaseId = EnvironmentReleaseId.new(environmentId, releaseId); + this.promoteEnvironmentReleaseId = EnvironmentReleaseId.new(environmentId, releaseId); + this.promoteCopyEnvironmentConfig = JSON.parse(this.route.snapshot.queryParamMap.get('copyEnvironmentConfig')); } this.setBreadcrumb(); @@ -122,9 +125,12 @@ export class EnvironmentDetailComponent implements OnInit { return of(environment); }), mergeMap((environment: Environment) => { - if (this.environmentReleaseId) { + if (this.promoteEnvironmentReleaseId) { // we're promoting to this new environment - return this.releaseService.promote(this.environmentReleaseId, environment.id); + const promoteOptions = new PromoteOptions(); + promoteOptions.destEnvironmentId = environment.id; + promoteOptions.copyEnvironmentConfig = this.promoteCopyEnvironmentConfig; + return this.releaseService.promote(this.promoteEnvironmentReleaseId, promoteOptions); } return of(environment); }) diff --git a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.html b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.html index 8450dbe8..0ae0a8b2 100644 --- a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.html +++ b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.html @@ -16,17 +16,29 @@
    -
    - - +
    + +
    + + +
    + +
    + + +
    + Please note: if copied, any environment specific configuration should be updated for the new environment +
    - +
    diff --git a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts index 1af13b2d..3942519b 100644 --- a/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts +++ b/ahoy-ui/src/app/release-manage/promote-dialog/promote-dialog.component.ts @@ -20,7 +20,7 @@ import {Cluster} from '../../clusters/cluster'; import {EnvironmentRelease} from '../../environment-release/environment-release'; import {Environment} from '../../environments/environment'; import {EnvironmentService} from '../../environments/environment.service'; -import {Release, ReleaseVersion} from '../../releases/release'; +import {PromoteOptions, Release, ReleaseVersion} from '../../releases/release'; @Component({ selector: 'app-promote-dialog', @@ -34,6 +34,7 @@ export class PromoteDialogComponent implements OnInit { release: Release; releaseVersion: ReleaseVersion; cluster: Cluster; + promoteOptions = new PromoteOptions(); constructor(private environmentService: EnvironmentService, public ref: DynamicDialogRef, diff --git a/ahoy-ui/src/app/release-manage/release-manage.component.ts b/ahoy-ui/src/app/release-manage/release-manage.component.ts index 9709eb0c..50e5a5cc 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.component.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.component.ts @@ -28,7 +28,7 @@ import {DeployDetails, EnvironmentRelease} from '../environment-release/environm import {EnvironmentReleaseService} from '../environment-release/environment-release.service'; import {Environment} from '../environments/environment'; import {EnvironmentService} from '../environments/environment.service'; -import {Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; +import {PromoteOptions, Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; import {TaskEvent} from '../taskevents/task-events'; import {LoggerService} from '../util/logger.service'; import {CopyEnvironmentConfigDialogComponent} from './copy-environment-config-dialog/copy-environment-config-dialog.component'; @@ -194,12 +194,13 @@ export class ReleaseManageComponent implements OnInit, OnDestroy { const dialogConfig = new DynamicDialogConfig(); dialogConfig.header = `Promote ${(this.environmentRelease.release as Release).name}:${this.releaseVersion.version} to:`; dialogConfig.data = {environmentRelease: this.environmentRelease, releaseVersion: this.releaseVersion}; + dialogConfig.width = '25%'; const dialogRef = this.dialogService.open(PromoteDialogComponent, dialogConfig); dialogRef.onClose.pipe( filter((result) => result !== undefined), // cancelled - mergeMap((destEnvironment) => { - return this.releaseManageService.promote(this.environmentRelease.id, destEnvironment.id); + mergeMap((promoteOptions: PromoteOptions) => { + return this.releaseManageService.promote(this.environmentRelease.id, promoteOptions); }) ).subscribe((newEnvironmentRelease: EnvironmentRelease) => this.reload(newEnvironmentRelease.id.environmentId, this.releaseVersion.id)); } diff --git a/ahoy-ui/src/app/release-manage/release-manage.service.ts b/ahoy-ui/src/app/release-manage/release-manage.service.ts index e441797d..981bca7a 100644 --- a/ahoy-ui/src/app/release-manage/release-manage.service.ts +++ b/ahoy-ui/src/app/release-manage/release-manage.service.ts @@ -21,7 +21,7 @@ import {DeployDetails, EnvironmentRelease, EnvironmentReleaseId} from '../enviro import {Environment} from '../environments/environment'; import {Notification} from '../notifications/notification'; import {NotificationsService} from '../notifications/notifications.service'; -import {Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; +import {PromoteOptions, Release, ReleaseVersion, UpgradeOptions} from '../releases/release'; import {LoggerService} from '../util/logger.service'; import {RestClientService} from '../util/rest-client.service'; @@ -95,10 +95,10 @@ export class ReleaseManageService { ); } - promote(environmentReleaseId: EnvironmentReleaseId, destEnvironmentId: number): Observable { - this.log.debug(`promoting environment release: ${environmentReleaseId} to environment: ${destEnvironmentId}`); - const url = `/api/release/promote/${environmentReleaseId.environmentId}/${environmentReleaseId.releaseId}/${destEnvironmentId}`; - return this.restClient.post(url).pipe( + promote(environmentReleaseId: EnvironmentReleaseId, promoteOptions: PromoteOptions): Observable { + this.log.debug(`promoting environment release: ${environmentReleaseId} to environment: ${promoteOptions.destEnvironmentId}`); + const url = `/api/release/promote/${environmentReleaseId.environmentId}/${environmentReleaseId.releaseId}`; + return this.restClient.post(url, promoteOptions).pipe( tap((environmentRelease) => { this.log.debug('promoted release to new environment', environmentRelease); const text = `${(environmentRelease.release as Release).name} ` diff --git a/ahoy-ui/src/app/releases/release.ts b/ahoy-ui/src/app/releases/release.ts index 6024eb09..c56baec8 100644 --- a/ahoy-ui/src/app/releases/release.ts +++ b/ahoy-ui/src/app/releases/release.ts @@ -33,6 +33,11 @@ export class ReleaseVersion { releaseName: string; } +export class PromoteOptions { + destEnvironmentId: number; + copyEnvironmentConfig = false; +} + export class UpgradeOptions { version: string; copyEnvironmentConfig = true; From 3462ac3a917616516afaf4adfab66fb4291be95b Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Thu, 16 Sep 2021 17:04:23 +0200 Subject: [PATCH 34/36] As a Release Manager, I'd like an option to copy environment config when promoting and upgrading ... - tests --- .../za/co/lsd/ahoy/server/ReleaseService.java | 4 +- .../ApplicationEnvironmentConfig.java | 18 +- .../applications/ApplicationVersion.java | 7 + .../ahoy/server/releases/PromoteOptions.java | 4 + .../ahoy/server/releases/UpgradeOptions.java | 4 + .../lsd/ahoy/server/ReleaseServiceTest.java | 221 +++++++++++++++++- 6 files changed, 240 insertions(+), 18 deletions(-) diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java index dc9c7fac..39507fdc 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/ReleaseService.java @@ -193,13 +193,13 @@ public ReleaseVersion upgrade(Long releaseVersionId, UpgradeOptions upgradeOptio ReleaseVersion currentReleaseVersion = releaseVersionRepository.findById(releaseVersionId) .orElseThrow(() -> new ResourceNotFoundException("Could not find release version: " + releaseVersionId)); - log.info("Upgrading release version: {} to version: {}", currentReleaseVersion.getVersion(), upgradeOptions.getVersion()); + log.info("Upgrading release version: {} to version: {}", currentReleaseVersion, upgradeOptions.getVersion()); ReleaseVersion upgradedReleaseVersion = new ReleaseVersion(upgradeOptions.getVersion(), currentReleaseVersion.getRelease(), new ArrayList<>(currentReleaseVersion.getApplicationVersions())); upgradedReleaseVersion = releaseVersionRepository.save(upgradedReleaseVersion); if (upgradeOptions.isCopyEnvironmentConfig()) { - log.info("Copy environment config selected, copying config to new version: {}", upgradeOptions.getVersion()); + log.info("Copy environment config selected, copying config to new version: {}", upgradedReleaseVersion); Iterable environmentReleases = environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(currentReleaseVersion.getRelease().getId()); for (EnvironmentRelease environmentRelease : environmentReleases) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java index fda47439..dc6c0aa8 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationEnvironmentConfig.java @@ -20,11 +20,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -import javax.persistence.CascadeType; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.OneToMany; -import javax.persistence.OrderBy; +import javax.persistence.*; import java.util.ArrayList; import java.util.List; @@ -67,10 +63,14 @@ public ApplicationEnvironmentConfig(ApplicationDeploymentId id, ApplicationEnvir this.replicas = applicationEnvironmentConfig.getReplicas(); this.routeHostname = applicationEnvironmentConfig.getRouteHostname(); this.routeTargetPort = applicationEnvironmentConfig.getRouteTargetPort(); - this.environmentVariables = new ArrayList<>(applicationEnvironmentConfig.getEnvironmentVariables()); - this.configs = new ArrayList<>(applicationEnvironmentConfig.getConfigs()); - this.volumes = new ArrayList<>(applicationEnvironmentConfig.getVolumes()); - this.secrets = new ArrayList<>(applicationEnvironmentConfig.getSecrets()); + this.environmentVariables = applicationEnvironmentConfig.getEnvironmentVariables() != null ? + new ArrayList<>(applicationEnvironmentConfig.getEnvironmentVariables()) : null; + this.configs = applicationEnvironmentConfig.getConfigs() != null ? + new ArrayList<>(applicationEnvironmentConfig.getConfigs()) : null; + this.volumes = applicationEnvironmentConfig.getVolumes() != null ? + new ArrayList<>(applicationEnvironmentConfig.getVolumes()) : null; + this.secrets = applicationEnvironmentConfig.getSecrets() != null ? + new ArrayList<>(applicationEnvironmentConfig.getSecrets()) : null; } public ApplicationEnvironmentConfig(String routeHostname, Integer routeTargetPort) { diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java index 87f099f1..8af55e48 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/applications/ApplicationVersion.java @@ -88,6 +88,13 @@ public ApplicationVersion(@NotNull String version, @NotNull String image, Applic this.application = application; } + public ApplicationVersion(@NotNull Long id, @NotNull String version, @NotNull String image, Application application) { + this.id = id; + this.version = version; + this.image = image; + this.application = application; + } + public boolean hasConfigs() { return configs != null && configs.size() > 0; } diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java index 6cac691e..3222e126 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/PromoteOptions.java @@ -16,9 +16,13 @@ package za.co.lsd.ahoy.server.releases; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor public class PromoteOptions { private Long destEnvironmentId; private boolean copyEnvironmentConfig; diff --git a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java index 9677c19f..3ae4c7df 100644 --- a/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java +++ b/ahoy-server/src/main/java/za/co/lsd/ahoy/server/releases/UpgradeOptions.java @@ -16,9 +16,13 @@ package za.co.lsd.ahoy.server.releases; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor public class UpgradeOptions { private String version; private boolean copyEnvironmentConfig; diff --git a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java index 28740cce..11fb62a7 100644 --- a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java +++ b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java @@ -18,14 +18,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import za.co.lsd.ahoy.server.applications.Application; -import za.co.lsd.ahoy.server.applications.ApplicationReleaseStatusRepository; -import za.co.lsd.ahoy.server.applications.ApplicationVersion; +import za.co.lsd.ahoy.server.applications.*; import za.co.lsd.ahoy.server.argocd.model.ArgoApplication; import za.co.lsd.ahoy.server.argocd.model.ArgoMetadata; import za.co.lsd.ahoy.server.argocd.model.HealthStatus; @@ -35,12 +34,12 @@ import za.co.lsd.ahoy.server.environmentrelease.EnvironmentReleaseId; import za.co.lsd.ahoy.server.environmentrelease.EnvironmentReleaseRepository; import za.co.lsd.ahoy.server.environments.Environment; -import za.co.lsd.ahoy.server.releases.Release; -import za.co.lsd.ahoy.server.releases.ReleaseHistory; -import za.co.lsd.ahoy.server.releases.ReleaseHistoryRepository; -import za.co.lsd.ahoy.server.releases.ReleaseVersion; +import za.co.lsd.ahoy.server.environments.EnvironmentRepository; +import za.co.lsd.ahoy.server.releases.*; +import java.util.ArrayList; import java.util.Collections; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -57,6 +56,14 @@ public class ReleaseServiceTest { private ApplicationReleaseStatusRepository applicationReleaseStatusRepository; @MockBean private ReleaseHistoryRepository releaseHistoryRepository; + @MockBean + private ReleaseVersionRepository releaseVersionRepository; + @MockBean + private ApplicationEnvironmentConfigRepository applicationEnvironmentConfigRepository; + @MockBean + private ApplicationEnvironmentConfigProvider environmentConfigProvider; + @MockBean + private EnvironmentRepository environmentRepository; @Autowired private ReleaseService releaseService; @@ -202,4 +209,204 @@ public void undeploy() throws Exception { verify(environmentReleaseRepository, times(1)).save(same(undeployedEnvironmentRelease)); verify(releaseHistoryRepository, times(1)).save(any(ReleaseHistory.class)); } + + @Test + public void upgrade() { + // given + Release release = new Release(1L, "release1"); + + Application application = new Application("app1"); + ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); + ReleaseVersion releaseVersion = new ReleaseVersion(1L, "1.0.0", release, Collections.singletonList(applicationVersion)); + + UpgradeOptions upgradeOptions = new UpgradeOptions("1.1.0", false); + + when(releaseVersionRepository.findById(1L)).thenReturn(Optional.of(releaseVersion)); + ReleaseVersion resultReleaseVersion = new ReleaseVersion(2L, upgradeOptions.getVersion(), releaseVersion.getRelease(), new ArrayList<>(releaseVersion.getApplicationVersions())); + when(releaseVersionRepository.save(any(ReleaseVersion.class))).thenReturn(resultReleaseVersion); + + // when + ReleaseVersion upgradedReleaseVersion = releaseService.upgrade(releaseVersion.getId(), upgradeOptions); + + // then + ArgumentCaptor releaseVersionCaptor = ArgumentCaptor.forClass(ReleaseVersion.class); + verify(releaseVersionRepository, times(1)).save(releaseVersionCaptor.capture()); + + ReleaseVersion savedReleaseVersion = releaseVersionCaptor.getValue(); + assertEquals("1.1.0", savedReleaseVersion.getVersion(), "Saved release version incorrect"); + assertEquals(release, savedReleaseVersion.getRelease(), "Saved release version has incorrect release"); + assertEquals(releaseVersion.getApplicationVersions(), savedReleaseVersion.getApplicationVersions(), "Saved released version doesn't have the application versions from the upgraded version"); + + assertSame(resultReleaseVersion, upgradedReleaseVersion, "The saved release version should have been returned"); + verifyNoInteractions(applicationEnvironmentConfigRepository); + } + + @Test + public void upgradeWithCopyEnvironmentConfig() { + // given + Cluster cluster = new Cluster(1L, "test-cluster", "https://kubernetes.default.svc", ClusterType.KUBERNETES); + Environment environment = new Environment(1L, "dev", cluster); + Release release = new Release(1L, "release1"); + EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); + + Application application = new Application("app1"); + ApplicationVersion applicationVersion = new ApplicationVersion(1L, "1.0.0", "image", application); + ReleaseVersion releaseVersion = new ReleaseVersion(1L, "1.0.0", release, Collections.singletonList(applicationVersion)); + + UpgradeOptions upgradeOptions = new UpgradeOptions("1.1.0", true); + + when(releaseVersionRepository.findById(1L)).thenReturn(Optional.of(releaseVersion)); + ReleaseVersion resultReleaseVersion = new ReleaseVersion(2L, upgradeOptions.getVersion(), releaseVersion.getRelease(), new ArrayList<>(releaseVersion.getApplicationVersions())); + when(releaseVersionRepository.save(any(ReleaseVersion.class))).thenReturn(resultReleaseVersion); + + when(environmentReleaseRepository.findByRelease_Id_OrderByEnvironmentId(release.getId())).thenReturn(Collections.singletonList(environmentRelease)); + + ApplicationEnvironmentConfig environmentConfig = new ApplicationEnvironmentConfig("myapp1-route", 8080); + when(environmentConfigProvider.environmentConfigFor(environmentRelease, releaseVersion, applicationVersion)).thenReturn(Optional.of(environmentConfig)); + when(environmentConfigProvider.environmentConfigFor(environmentRelease, resultReleaseVersion, applicationVersion)).thenReturn(Optional.empty()); + + // when + ReleaseVersion upgradedReleaseVersion = releaseService.upgrade(releaseVersion.getId(), upgradeOptions); + + // then + ArgumentCaptor releaseVersionCaptor = ArgumentCaptor.forClass(ReleaseVersion.class); + verify(releaseVersionRepository, times(1)).save(releaseVersionCaptor.capture()); + + ReleaseVersion savedReleaseVersion = releaseVersionCaptor.getValue(); + assertEquals("1.1.0", savedReleaseVersion.getVersion(), "Saved release version incorrect"); + assertEquals(release, savedReleaseVersion.getRelease(), "Saved release version has incorrect release"); + assertEquals(releaseVersion.getApplicationVersions(), savedReleaseVersion.getApplicationVersions(), "Saved released version doesn't have the application versions from the upgraded version"); + + assertSame(resultReleaseVersion, upgradedReleaseVersion, "The saved release version should have been returned"); + + ArgumentCaptor applicationEnvironmentConfigArgumentCaptor = ArgumentCaptor.forClass(ApplicationEnvironmentConfig.class); + verify(applicationEnvironmentConfigRepository, times(1)).save(applicationEnvironmentConfigArgumentCaptor.capture()); + + ApplicationEnvironmentConfig savedEnvironmentConfig = applicationEnvironmentConfigArgumentCaptor.getValue(); + assertEquals(new ApplicationDeploymentId(environmentRelease.getId(), resultReleaseVersion.getId(), applicationVersion.getId()), savedEnvironmentConfig.getId(), + "Environment config deployment ID incorrect; this means the config is not related to the correct entity"); + assertEquals("myapp1-route", savedEnvironmentConfig.getRouteHostname(), "Environment config route incorrect"); + assertEquals(8080, savedEnvironmentConfig.getRouteTargetPort(), "Environment config port incorrect"); + } + + @Test + public void promote() { + // given + Cluster cluster = new Cluster(1L, "test-cluster", "https://kubernetes.default.svc", ClusterType.KUBERNETES); + Environment environment = new Environment(1L, "dev", cluster); + Release release = new Release(1L, "release1"); + EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); + + Application application = new Application("app1"); + ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); + ReleaseVersion releaseVersion = new ReleaseVersion(1L, "1.0.0", release, Collections.singletonList(applicationVersion)); + + Environment destEnvironment = new Environment(2L, "qa", cluster); + + PromoteOptions promoteOptions = new PromoteOptions(destEnvironment.getId(), false); + + when(environmentReleaseRepository.findById(environmentRelease.getId())).thenReturn(Optional.of(environmentRelease)); + EnvironmentReleaseId resultEnvironmentReleaseId = new EnvironmentReleaseId(destEnvironment.getId(), release.getId()); + when(environmentReleaseRepository.findById(resultEnvironmentReleaseId)).thenReturn(Optional.empty()); + when(environmentRepository.findById(destEnvironment.getId())).thenReturn(Optional.of(destEnvironment)); + + EnvironmentRelease resultEnvironmentRelease = new EnvironmentRelease(resultEnvironmentReleaseId, destEnvironment, release); + when(environmentReleaseRepository.save(any(EnvironmentRelease.class))).thenReturn(resultEnvironmentRelease); + + // when + EnvironmentRelease promotedEnvironmentRelease = releaseService.promote(environment.getId(), release.getId(), promoteOptions); + + // then + ArgumentCaptor environmentReleaseArgumentCaptor = ArgumentCaptor.forClass(EnvironmentRelease.class); + verify(environmentReleaseRepository, times(1)).save(environmentReleaseArgumentCaptor.capture()); + + EnvironmentRelease savedEnvironmentRelease = environmentReleaseArgumentCaptor.getValue(); + assertEquals(release, savedEnvironmentRelease.getRelease(), "Saved environment release has incorrect release"); + assertEquals(destEnvironment, savedEnvironmentRelease.getEnvironment(), "Saved environment release has incorrect environment"); + + assertSame(resultEnvironmentRelease, promotedEnvironmentRelease, "The saved environment release should have been returned"); + verifyNoInteractions(applicationEnvironmentConfigRepository); + } + + @Test + public void promoteAlreadyExists() { + // given + Cluster cluster = new Cluster(1L, "test-cluster", "https://kubernetes.default.svc", ClusterType.KUBERNETES); + Environment environment = new Environment(1L, "dev", cluster); + Release release = new Release(1L, "release1"); + EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); + + Application application = new Application("app1"); + ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); + + Environment destEnvironment = new Environment(2L, "qa", cluster); + + PromoteOptions promoteOptions = new PromoteOptions(destEnvironment.getId(), false); + + when(environmentReleaseRepository.findById(environmentRelease.getId())).thenReturn(Optional.of(environmentRelease)); + EnvironmentReleaseId resultEnvironmentReleaseId = new EnvironmentReleaseId(destEnvironment.getId(), release.getId()); + EnvironmentRelease resultEnvironmentRelease = new EnvironmentRelease(resultEnvironmentReleaseId, destEnvironment, release); + when(environmentReleaseRepository.findById(resultEnvironmentReleaseId)).thenReturn(Optional.of(resultEnvironmentRelease)); + + // when + EnvironmentRelease promotedEnvironmentRelease = releaseService.promote(environment.getId(), release.getId(), promoteOptions); + + // then + verify(environmentReleaseRepository, never()).save(any(EnvironmentRelease.class)); + + assertSame(resultEnvironmentRelease, promotedEnvironmentRelease, "The saved environment release should have been returned"); + verifyNoInteractions(applicationEnvironmentConfigRepository); + } + + @Test + public void promoteWithCopyEnvironmentConfig() { + // given + Cluster cluster = new Cluster(1L, "test-cluster", "https://kubernetes.default.svc", ClusterType.KUBERNETES); + Environment environment = new Environment(1L, "dev", cluster); + Release release = new Release(1L, "release1"); + EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); + + Application application = new Application("app1"); + ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); + ReleaseVersion releaseVersion = new ReleaseVersion(1L, "1.0.0", release, Collections.singletonList(applicationVersion)); + release.setReleaseVersions(Collections.singletonList(releaseVersion)); + + Environment destEnvironment = new Environment(2L, "qa", cluster); + + PromoteOptions promoteOptions = new PromoteOptions(destEnvironment.getId(), true); + + when(environmentReleaseRepository.findById(environmentRelease.getId())).thenReturn(Optional.of(environmentRelease)); + EnvironmentReleaseId resultEnvironmentReleaseId = new EnvironmentReleaseId(destEnvironment.getId(), release.getId()); + when(environmentReleaseRepository.findById(resultEnvironmentReleaseId)).thenReturn(Optional.empty()); + when(environmentRepository.findById(destEnvironment.getId())).thenReturn(Optional.of(destEnvironment)); + + EnvironmentRelease resultEnvironmentRelease = new EnvironmentRelease(resultEnvironmentReleaseId, destEnvironment, release); + when(environmentReleaseRepository.save(any(EnvironmentRelease.class))).thenReturn(resultEnvironmentRelease); + + ApplicationEnvironmentConfig environmentConfig = new ApplicationEnvironmentConfig("myapp1-route", 8080); + when(environmentConfigProvider.environmentConfigFor(environmentRelease, releaseVersion, applicationVersion)).thenReturn(Optional.of(environmentConfig)); + when(environmentConfigProvider.environmentConfigFor(resultEnvironmentRelease, releaseVersion, applicationVersion)).thenReturn(Optional.empty()); + + // when + EnvironmentRelease promotedEnvironmentRelease = releaseService.promote(environment.getId(), release.getId(), promoteOptions); + + // then + ArgumentCaptor environmentReleaseArgumentCaptor = ArgumentCaptor.forClass(EnvironmentRelease.class); + verify(environmentReleaseRepository, times(1)).save(environmentReleaseArgumentCaptor.capture()); + + EnvironmentRelease savedEnvironmentRelease = environmentReleaseArgumentCaptor.getValue(); + assertEquals(release, savedEnvironmentRelease.getRelease(), "Saved environment release has incorrect release"); + assertEquals(destEnvironment, savedEnvironmentRelease.getEnvironment(), "Saved environment release has incorrect environment"); + + assertSame(resultEnvironmentRelease, promotedEnvironmentRelease, "The saved environment release should have been returned"); + + ArgumentCaptor applicationEnvironmentConfigArgumentCaptor = ArgumentCaptor.forClass(ApplicationEnvironmentConfig.class); + verify(applicationEnvironmentConfigRepository, times(1)).save(applicationEnvironmentConfigArgumentCaptor.capture()); + + ApplicationEnvironmentConfig savedEnvironmentConfig = applicationEnvironmentConfigArgumentCaptor.getValue(); + assertEquals(new ApplicationDeploymentId(resultEnvironmentRelease.getId(), releaseVersion.getId(), applicationVersion.getId()), savedEnvironmentConfig.getId(), + "Environment config deployment ID incorrect; this means the config is not related to the correct entity"); + assertEquals("myapp1-route", savedEnvironmentConfig.getRouteHostname(), "Environment config route incorrect"); + assertEquals(8080, savedEnvironmentConfig.getRouteTargetPort(), "Environment config port incorrect"); + } } From 18eef028f8579865c51de94fb353e9c9a20508e4 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Fri, 17 Sep 2021 09:49:53 +0200 Subject: [PATCH 35/36] As a Release Manager, I'd like an option to copy environment config when promoting and upgrading ... - removed unnecessary given objects from tests --- .../java/za/co/lsd/ahoy/server/ReleaseServiceTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java index 11fb62a7..cd2608e8 100644 --- a/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java +++ b/ahoy-server/src/test/java/za/co/lsd/ahoy/server/ReleaseServiceTest.java @@ -297,10 +297,6 @@ public void promote() { Release release = new Release(1L, "release1"); EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); - Application application = new Application("app1"); - ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); - ReleaseVersion releaseVersion = new ReleaseVersion(1L, "1.0.0", release, Collections.singletonList(applicationVersion)); - Environment destEnvironment = new Environment(2L, "qa", cluster); PromoteOptions promoteOptions = new PromoteOptions(destEnvironment.getId(), false); @@ -336,9 +332,6 @@ public void promoteAlreadyExists() { Release release = new Release(1L, "release1"); EnvironmentRelease environmentRelease = new EnvironmentRelease(new EnvironmentReleaseId(1L, 1L), environment, release); - Application application = new Application("app1"); - ApplicationVersion applicationVersion = new ApplicationVersion("1.0.0", "image", application); - Environment destEnvironment = new Environment(2L, "qa", cluster); PromoteOptions promoteOptions = new PromoteOptions(destEnvironment.getId(), false); From 9fc0973811348096901aa571a026897134f26b36 Mon Sep 17 00:00:00 2001 From: Clive Borrageiro Date: Fri, 17 Sep 2021 11:29:19 +0200 Subject: [PATCH 36/36] Releasing version 0.3.0 --- ahoy-server/pom.xml | 2 +- ahoy-ui/package.json | 2 +- ahoy-ui/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ahoy-server/pom.xml b/ahoy-server/pom.xml index 630aea03..37eb84e2 100644 --- a/ahoy-server/pom.xml +++ b/ahoy-server/pom.xml @@ -22,7 +22,7 @@ za.co.lsd.ahoy ahoy - 0.3.0-SNAPSHOT + 0.3.0 ahoy-server diff --git a/ahoy-ui/package.json b/ahoy-ui/package.json index 8be7951b..b9b11809 100644 --- a/ahoy-ui/package.json +++ b/ahoy-ui/package.json @@ -1,6 +1,6 @@ { "name": "ahoy-ui", - "version": "0.3.0-SNAPSHOT", + "version": "0.3.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/ahoy-ui/pom.xml b/ahoy-ui/pom.xml index bc9f5566..de910c21 100644 --- a/ahoy-ui/pom.xml +++ b/ahoy-ui/pom.xml @@ -22,7 +22,7 @@ za.co.lsd.ahoy ahoy - 0.3.0-SNAPSHOT + 0.3.0 ahoy-ui pom diff --git a/pom.xml b/pom.xml index 995d9074..6146c4bf 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ za.co.lsd.ahoy ahoy - 0.3.0-SNAPSHOT + 0.3.0 pom ahoy