feat: managing a single contract
Adding the possibility to delete a single contract instead of deleting the whole client.
Closes:
MODIFY
src/app/app.module.ts
src/app/app.module.ts
@@ -69,6 +69,7 @@ import { AccountIdmComponent } from './components/account-idm/account-idm.compon
import { AccountOrganizationPermissionsComponent } from './components/account-organization-permissions/account-organization-permissions.component';
import { ApiPublicEndpointComponent } from './components/api-public-endpoint/api-public-endpoint.component';
import { MatRadioModule } from '@angular/material/radio';
+import { BreakContractComponent } from './components/dialogs/break-contract/break-contract.component';
export function initializeApp(
configService: ConfigService,
@@ -110,6 +111,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
ApiPublicEndpointComponent,
ApiStatusTagComponent,
AppComponent,
+ BreakContractComponent,
FooterComponent,
GaugeChartComponent,
HeaderComponent,

ADD
src/app/components/dialogs/break-contract/break-contract.component.html
+<!--
+ ~ Copyright 2022 Scheer PAS Schweiz AG
+ ~
+ ~ 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
+ ~ imitations under the License.
+ -->
+
+<h3 mat-dialog-title>
+ {{ 'DIALOGS.DELETE_CONTRACT.TITLE' | translate }}
+</h3>
+<mat-dialog-content>
+ <span
+ [innerHTML]="
+ 'DIALOGS.DELETE_CONTRACT.DELETE_SURE' | translate: translations
+ "></span>
+</mat-dialog-content>
+
+<mat-dialog-actions align="end">
+ <button mat-flat-button mat-dialog-close color="accent">
+ {{ 'COMMON.CANCEL' | translate }}
+ </button>
+ <button mat-flat-button (click)="breakContract()" color="warn">
+ {{ 'DIALOGS.DELETE_CONTRACT.DELETE_BTN' | translate }}
+ </button>
+</mat-dialog-actions>

ADD
src/app/components/dialogs/break-contract/break-contract.component.scss
+/*!
+ * Copyright 2022 Scheer PAS Schweiz AG
+ *
+ * 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
+ * imitations under the License.
+ */

ADD
src/app/components/dialogs/break-contract/break-contract.component.spec.ts
+/*
+ * Copyright 2022 Scheer PAS Schweiz AG
+ *
+ * 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
+ * imitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BreakContractComponent } from './break-contract.component';
+
+describe('BreakContractComponent', () => {
+ let component: BreakContractComponent;
+ let fixture: ComponentFixture<BreakContractComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [BreakContractComponent]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BreakContractComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});

ADD
src/app/components/dialogs/break-contract/break-contract.component.ts
+/*
+ * Copyright 2022 Scheer PAS Schweiz AG
+ *
+ * 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
+ * imitations under the License.
+ */
+
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { IContractExt } from '../../../interfaces/IContractExt';
+import { ContractService } from '../../../services/contract/contract.service';
+
+@Component({
+ selector: 'app-break-contract',
+ templateUrl: './break-contract.component.html',
+ styleUrls: ['./break-contract.component.scss']
+})
+export class BreakContractComponent {
+ contract: IContractExt;
+ translations = {
+ client: '',
+ api: ''
+ };
+
+ constructor(
+ private contractService: ContractService,
+ public dialogRef: MatDialogRef<BreakContractComponent>,
+ @Inject(MAT_DIALOG_DATA) public data: { contract: IContractExt }
+ ) {
+ this.contract = data.contract;
+ this.translations = {
+ client: this.contract.client.client.name,
+ api: this.contract.api.api.name
+ };
+ }
+
+ breakContract() {
+ this.contractService
+ .breakContract(this.contract)
+ .subscribe(() => this.dialogRef.close(true));
+ }
+}

MODIFY
src/app/components/dialogs/unregister-client/unregister-client.component.html
src/app/components/dialogs/unregister-client/unregister-client.component.html
<!--
- ~ Copyright 2021 Scheer PAS Schweiz AG
+ ~ Copyright 2022 Scheer PAS Schweiz AG
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
-->
<h3 mat-dialog-title>
- {{ 'DIALOGS.REMOVE_TITLE' | translate: clientNameVersion }}
+ {{ 'DIALOGS.DELETE_CLIENT.TITLE' | translate }}
</h3>
<mat-dialog-content>
<span
- [innerHTML]="'DIALOGS.REMOVE_SURE' | translate: clientNameVersion"></span>
+ [innerHTML]="
+ 'DIALOGS.DELETE_CLIENT.DELETE_SURE' | translate: clientNameVersion
+ "></span>
</mat-dialog-content>
<mat-dialog-actions align="end">
{{ 'COMMON.CANCEL' | translate }}
</button>
<button mat-flat-button (click)="onUnregister()" color="warn">
- {{ 'DIALOGS.REMOVE_BTN' | translate }}
+ {{ 'DIALOGS.DELETE_CLIENT.DELETE_BTN' | translate }}
</button>
</mat-dialog-actions>

MODIFY
src/app/components/dialogs/unregister-client/unregister-client.component.ts
src/app/components/dialogs/unregister-client/unregister-client.component.ts
@@ -66,7 +66,7 @@ export class UnregisterClientComponent {
catchError((err) => {
console.error('Deleting client failed: ', err);
this.snackbarService.showErrorSnackBar(
- this.translator.instant('CLIENTS.REMOVE_CLIENT_FAILED') as string
+ this.translator.instant('CLIENTS.DELETE_CLIENT_FAILED') as string
);
return EMPTY;
})

MODIFY
src/app/components/my-apps/my-apps.component.html
src/app/components/my-apps/my-apps.component.html
</h3>
</div>
<div class="app-api-header-labels">
+ <button
+ id="api-delete-btn"
+ *ngIf="client.deletable"
+ (click)="breakContract(contract)"
+ mat-icon-button
+ color="primary"
+ matTooltip="{{
+ 'CLIENTS.REMOVE_CONTRACT_TOOLTIP' | translate
+ }}">
+ <mat-icon>delete</mat-icon>
+ </button>
<span
matTooltip="API Status"
[style]="

MODIFY
src/app/components/my-apps/my-apps.component.ts
src/app/components/my-apps/my-apps.component.ts
@@ -54,6 +54,7 @@ import { ClientService } from '../../services/client/client.service';
import { IClientVersionExt } from '../../interfaces/IClientVersionSummaryExt';
import { ContractService } from '../../services/contract/contract.service';
import { OrganizationService } from '../../services/org/organization.service';
+import { BreakContractComponent } from '../dialogs/break-contract/break-contract.component';
@Component({
selector: 'app-my-apps',
@@ -389,7 +390,7 @@ export class MyAppsComponent implements OnInit {
dialogRef.componentInstance.unregisterEmitter.subscribe(() => {
this.snackbarService.showPrimarySnackBar(
- this.translator.instant('CLIENTS.CLIENT_REMOVED') as string
+ this.translator.instant('CLIENTS.CLIENT_DELETED') as string
);
this.fetchData();
@@ -397,6 +398,22 @@ export class MyAppsComponent implements OnInit {
});
}
+ breakContract(contract: IContractExt): void {
+ const dialogRef = this.dialog.open(BreakContractComponent, {
+ data: { contract: contract },
+ autoFocus: false
+ });
+
+ dialogRef.afterClosed().subscribe((contractDeleted: boolean) => {
+ if (contractDeleted) {
+ this.snackbarService.showPrimarySnackBar(
+ this.translator.instant('CLIENTS.CONTRACT_DELETED') as string
+ );
+ this.fetchData();
+ }
+ });
+ }
+
onSetSection($event: { contract: IContractExt; section: ISection }): void {
this.setSection($event.contract, $event.section);
this.cdr.detectChanges();

MODIFY
src/app/services/backend/backend.service.ts
src/app/services/backend/backend.service.ts
@@ -494,6 +494,20 @@ export class BackendService {
);
}
+ public breakContract(
+ organizationId: string,
+ clientId: string,
+ clientVersion: string,
+ contractId: number
+ ): Observable<void> {
+ const path = `organizations/${organizationId}/clients/${clientId}/versions/${clientVersion}/contracts/${contractId}`;
+
+ return this.http.delete<void>(
+ this.generateUrlFromPath(path),
+ this.httpOptions
+ );
+ }
+
public deleteClient(
organizationId: string,
clientId: string

MODIFY
src/app/services/contract/contract.service.ts
src/app/services/contract/contract.service.ts
import { Injectable } from '@angular/core';
import { BackendService } from '../backend/backend.service';
-import { forkJoin, Observable, switchMap } from 'rxjs';
+import { EMPTY, forkJoin, Observable, switchMap } from 'rxjs';
import {
IClientVersion,
IContract,
IContractSummary
} from '../../interfaces/ICommunication';
-import { defaultIfEmpty, map } from 'rxjs/operators';
+import { catchError, defaultIfEmpty, map } from 'rxjs/operators';
import { IContractExt } from '../../interfaces/IContractExt';
import { ApiService } from '../api/api.service';
import { PolicyService } from '../policy/policy.service';
+import { SnackbarService } from '../snackbar/snackbar.service';
+import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
@@ -34,7 +36,9 @@ export class ContractService {
constructor(
private backendService: BackendService,
private apiService: ApiService,
- private policyService: PolicyService
+ private policyService: PolicyService,
+ private snackbarService: SnackbarService,
+ private translator: TranslateService
) {}
public getContractSummaries(
@@ -134,4 +138,23 @@ export class ContractService {
})
);
}
+
+ public breakContract(contract: IContractExt | IContract): Observable<void> {
+ return this.backendService
+ .breakContract(
+ contract.client.client.organization.id,
+ contract.client.client.id,
+ contract.client.version,
+ contract.id
+ )
+ .pipe(
+ catchError((err) => {
+ console.error('Deleting contract failed: ', err);
+ this.snackbarService.showErrorSnackBar(
+ this.translator.instant('CLIENTS.DELETE_CONTRACT_FAILED') as string
+ );
+ return EMPTY;
+ })
+ );
+ }
}

MODIFY
src/assets/i18n/en.json
src/assets/i18n/en.json
"UNSUB_FROM_API": "Unsubscribe from API",
"UNSUB_FROM_THIS_API": "Unsubscribe from this API",
"UNSUB": "Unsubscribe",
- "CLIENT_REMOVED": "The client and its related contracts have been removed",
+ "CLIENT_DELETED": "The client and its related contracts have been deleted",
+ "CONTRACT_DELETED": "The contract has been deleted",
"REMOVE_CLIENT_TOOLTIP": "Remove client",
+ "REMOVE_CONTRACT_TOOLTIP": "Break contract",
"USED": "used",
"RESETS_AT": "Resets at",
"RESETS_EVERY_SECOND": "Resets every second",
"CLIENTS_AND_APIS": "Clients and APIs",
"NO_CLIENTS_FOUND": "No clients were found",
"FAILED_WHILE_FETCHING_CLIENTS": "Failed while fetching clients",
- "REMOVE_CLIENT_FAILED": "Failed while removing the client",
+ "DELETE_CLIENT_FAILED": "Failed while removing the client",
+ "DELETE_CONTRACT_FAILED": "Failed while removing the contract",
"NO_PROBES": "There is no policy data for the current API",
"RETIRED_API": "This API is retired and can no longer be used.",
"SELECT": "Select from available Clients",
}
},
"DIALOGS": {
- "REMOVE_TITLE": "Remove Client",
- "REMOVE_SURE": "Are you sure you want to <span class='warn'>remove</span> the client <b>{{value}}</b> and all of its contracts?",
- "REMOVE_BTN": "Remove"
+ "DELETE_CLIENT": {
+ "TITLE":"Delete Client",
+ "DELETE_SURE": "Are you sure you want to <span class='warn'>delete</span> the client <b>{{value}}</b> and all of its contracts?",
+ "DELETE_BTN": "Delete Client"
+ },
+ "DELETE_CONTRACT": {
+ "TITLE":"Delete Contract",
+ "DELETE_SURE": "Are you sure you want to <span class='warn'>delete</span> the contract between <b>{{client}}</b> and <b>{{api}}</b>?",
+ "DELETE_BTN": "Delete Contract"
+ }
},
"API_DETAILS": {
"PLANS": "Plans",

feat: managing a single contract

Adding the possibility to delete a single contract instead of deleting the whole client.

Closes: PAS-3570