+
+
+
+ Active Contracts
+ Terminated Contracts
+ All Contracts
+
+
+
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts
index c12b53356..465ad591d 100644
--- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.component.ts
@@ -2,20 +2,28 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
import {FormControl} from '@angular/forms';
import {
BehaviorSubject,
+ EMPTY,
Observable,
Subject,
+ combineLatest,
concat,
distinctUntilChanged,
+ interval,
+ merge,
of,
share,
+ switchMap,
} from 'rxjs';
-import {filter, map, takeUntil} from 'rxjs/operators';
+import {catchError, filter, map, takeUntil} from 'rxjs/operators';
+import {ContractTerminationStatus} from '@sovity.de/edc-client';
import {AssetDetailDialogDataService} from 'src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service';
import {AssetDetailDialogService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.service';
+import {EdcApiService} from '../../../../core/services/api/edc-api.service';
import {Fetched} from '../../../../core/services/models/fetched';
import {value$} from '../../../../core/utils/form-group-utils';
import {filterNotNull} from '../../../../core/utils/rxjs-utils';
import {ContractAgreementCardMapped} from '../contract-agreement-cards/contract-agreement-card-mapped';
+import {ContractAgreementCardMappedService} from '../contract-agreement-cards/contract-agreement-card-mapped.service';
import {ContractAgreementPageData} from './contract-agreement-page.data';
import {ContractAgreementPageService} from './contract-agreement-page.service';
@@ -28,7 +36,9 @@ export class ContractAgreementPageComponent implements OnInit, OnDestroy {
page: Fetched
= Fetched.empty();
page$: Observable> = new Subject();
searchText = new FormControl('');
- deleteBusy = false;
+ terminationFilterControl: FormControl = new FormControl('ONGOING');
+
+ terminationFilter: ContractTerminationStatus | null = 'ONGOING';
private fetch$ = new BehaviorSubject(null);
@@ -36,29 +46,78 @@ export class ContractAgreementPageComponent implements OnInit, OnDestroy {
private assetDetailDialogDataService: AssetDetailDialogDataService,
private assetDetailDialogService: AssetDetailDialogService,
private contractAgreementPageService: ContractAgreementPageService,
+ private contractAgreementCardMappedService: ContractAgreementCardMappedService,
+ private apiService: EdcApiService,
) {}
ngOnInit(): void {
+ this.fetchContracts();
+ }
+
+ fetchContracts() {
this.page$ = this.contractAgreementPageService
- .contractAgreementPageData$(this.fetch$, 5000, this.searchText$())
+ .contractAgreementPageData$(
+ this.fetch$,
+ 5000,
+ this.searchText$(),
+ this.terminationFilter,
+ )
.pipe(takeUntil(this.ngOnDestroy$), share());
this.page$.subscribe((contractAgreementList) => {
this.page = contractAgreementList;
});
}
+ onTerminationFilterChange() {
+ if (this.terminationFilterControl.value === 'all') {
+ this.terminationFilter = null;
+ } else {
+ this.terminationFilter = this.terminationFilterControl
+ .value as ContractTerminationStatus;
+ }
+
+ this.fetchContracts();
+ }
+
onContractAgreementClick(card: ContractAgreementCardMapped) {
- const data$ = this.card$(card.contractAgreementId).pipe(
- map((card) =>
- this.assetDetailDialogDataService.contractAgreementDetails(card),
+ const refresh$ = new Subject();
+
+ const cardUpdates$ = merge(interval(5000), refresh$).pipe(
+ switchMap(() =>
+ this.apiService
+ .getContractAgreementById(card.contractAgreementId)
+ .pipe(catchError(() => EMPTY)),
+ ),
+ map((it) =>
+ this.contractAgreementCardMappedService.buildContractAgreementCardMapped(
+ it,
+ ),
),
);
- return this.assetDetailDialogService.open(data$, this.ngOnDestroy$);
- }
+ const cardUpdatesWithCorrectActive$ = combineLatest([
+ this.card$(card.contractAgreementId),
+ cardUpdates$,
+ ]).pipe(
+ map(([oldCard, newCard]) => ({
+ ...newCard,
+ isConsumingLimitsEnforced: oldCard.isConsumingLimitsEnforced,
+ statusText: oldCard.statusText,
+ showStatus: oldCard.showStatus,
+ canTransfer: oldCard.canTransfer,
+ statusTooltipText: oldCard.statusTooltipText,
+ })),
+ );
- refresh() {
- this.fetch$.next(null);
+ const dialogData$ = merge(of(card), cardUpdatesWithCorrectActive$).pipe(
+ map((card) =>
+ this.assetDetailDialogDataService.contractAgreementDetails(card, () =>
+ refresh$.next(undefined),
+ ),
+ ),
+ );
+
+ return this.assetDetailDialogService.open(dialogData$, this.ngOnDestroy$);
}
private card$(
@@ -76,6 +135,10 @@ export class ContractAgreementPageComponent implements OnInit, OnDestroy {
);
}
+ refresh() {
+ this.fetch$.next(null);
+ }
+
private searchText$(): Observable {
return (value$(this.searchText) as Observable).pipe(
map((it) => (it ?? '').trim()),
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.service.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.service.ts
index 1f4802073..4c127f0b7 100644
--- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.service.ts
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-page/contract-agreement-page.service.ts
@@ -5,6 +5,8 @@ import {
ConnectorLimits,
ContractAgreementCard,
ContractAgreementPage,
+ ContractTerminationStatus,
+ GetContractAgreementPageRequest,
} from '@sovity.de/edc-client';
import {ActiveFeatureSet} from '../../../../core/config/active-feature-set';
import {EdcApiService} from '../../../../core/services/api/edc-api.service';
@@ -21,11 +23,15 @@ export class ContractAgreementPageService {
private activeFeatureSet: ActiveFeatureSet,
) {}
+ activeTerminationFilter?: ContractTerminationStatus | null;
+
contractAgreementPageData$(
refresh$: Observable,
silentPollingInterval: number,
searchText$: Observable,
+ terminationStatusFilter?: ContractTerminationStatus | null,
): Observable> {
+ this.activeTerminationFilter = terminationStatusFilter;
return combineLatest([
refresh$.pipe(
switchMap(() =>
@@ -46,8 +52,14 @@ export class ContractAgreementPageService {
}
private fetchData(): Observable> {
+ const requestBody: GetContractAgreementPageRequest = {
+ contractAgreementPageQuery: {
+ terminationStatus: this.activeTerminationFilter ?? undefined,
+ },
+ };
+
return combineLatest([
- this.edcApiService.getContractAgreementPage(),
+ this.edcApiService.getContractAgreementPage(requestBody),
this.fetchLimits(),
]).pipe(
map(([contractAgreementPage, connectorLimits]) =>
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-data.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-data.ts
new file mode 100644
index 000000000..244defa73
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-data.ts
@@ -0,0 +1,6 @@
+import {UiAssetMapped} from '../../../../core/services/models/ui-asset-mapped';
+
+export interface ContractAgreementTerminationDialogData {
+ contractId: string;
+ asset: UiAssetMapped;
+}
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form-model.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form-model.ts
new file mode 100644
index 000000000..747fcdd36
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form-model.ts
@@ -0,0 +1,15 @@
+import {FormControl, ɵFormGroupValue} from '@angular/forms';
+
+/**
+ * Form Value Type
+ */
+export type ContractAgreementTransferDialogFormValue =
+ ɵFormGroupValue;
+
+/**
+ * Form Group Template Type
+ */
+export interface ContractAgreementTerminationDialogFormModel {
+ shortReason: FormControl;
+ detailedReason: FormControl;
+}
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form.ts
new file mode 100644
index 000000000..aaf2af2d5
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-form.ts
@@ -0,0 +1,32 @@
+import {Injectable} from '@angular/core';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+import {
+ ContractAgreementTerminationDialogFormModel,
+ ContractAgreementTransferDialogFormValue,
+} from './contract-agreement-termination-dialog-form-model';
+
+/**
+ * Handles AngularForms for ContractAgreementTerminationDialog
+ */
+@Injectable()
+export class ContractAgreementTerminationDialogForm {
+ all = this.buildFormGroup();
+
+ /**
+ * Quick access to full value
+ */
+ get value(): ContractAgreementTransferDialogFormValue {
+ return this.all.value;
+ }
+
+ constructor(private formBuilder: FormBuilder) {}
+
+ buildFormGroup(): FormGroup {
+ const formGroup = this.formBuilder.nonNullable.group({
+ shortReason: ['Terminated by user', Validators.required],
+ detailedReason: ['', [Validators.required, Validators.maxLength(1000)]],
+ });
+ formGroup.controls.shortReason.disable();
+ return formGroup;
+ }
+}
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-result.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-result.ts
new file mode 100644
index 000000000..753cd2db8
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog-result.ts
@@ -0,0 +1,4 @@
+export interface ContractAgreementTerminationDialogResult {
+ contractId: string;
+ lastUpdatedTime: Date | null;
+}
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.html b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.html
new file mode 100644
index 000000000..64cd56a8f
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.html
@@ -0,0 +1,71 @@
+Terminate Contract Agreement
+
+
+
+
+
+
+ I understand the consequences of terminating a contract.
+
+
+
+
+
+
+
+
+
diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.ts
new file mode 100644
index 000000000..84a32dc84
--- /dev/null
+++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-termination-dialog/contract-agreement-termination-dialog.component.ts
@@ -0,0 +1,88 @@
+import {Component, Inject, OnDestroy} from '@angular/core';
+import {MatCheckboxChange} from '@angular/material/checkbox';
+import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
+import {Observable, Subject} from 'rxjs';
+import {finalize} from 'rxjs/operators';
+import {IdResponseDto} from '@sovity.de/edc-client';
+import {EdcApiService} from '../../../../core/services/api/edc-api.service';
+import {NotificationService} from '../../../../core/services/notification.service';
+import {ContractAgreementTerminationDialogData} from './contract-agreement-termination-dialog-data';
+import {ContractAgreementTerminationDialogForm} from './contract-agreement-termination-dialog-form';
+import {ContractAgreementTerminationDialogResult} from './contract-agreement-termination-dialog-result';
+
+@Component({
+ selector: 'contract-agreement-transfer-dialog',
+ templateUrl: './contract-agreement-termination-dialog.component.html',
+ providers: [ContractAgreementTerminationDialogForm],
+})
+export class ContractAgreementTerminationDialogComponent implements OnDestroy {
+ loading = false;
+ checkboxChecked = false;
+
+ constructor(
+ public form: ContractAgreementTerminationDialogForm,
+ private dialogRef: MatDialogRef,
+ private edcApiService: EdcApiService,
+ private notificationService: NotificationService,
+ @Inject(MAT_DIALOG_DATA)
+ public data: ContractAgreementTerminationDialogData,
+ ) {}
+
+ public onCheckboxChange($event: MatCheckboxChange) {
+ this.checkboxChecked = $event.checked;
+ }
+
+ onSave() {
+ if (this.loading && !this.form.all.valid) {
+ return;
+ }
+
+ this.initiateTermination();
+ }
+
+ private initiateTermination() {
+ this.loading = true;
+ this.form.all.disable();
+
+ const value = this.form.value;
+ let request$: Observable;
+ request$ = this.edcApiService.terminateContractAgreement({
+ contractAgreementId: this.data.contractId,
+ contractTerminationRequest: {
+ reason: value.shortReason!,
+ detail: value.detailedReason!,
+ },
+ });
+
+ request$
+ .pipe(
+ finalize(() => {
+ this.loading = false;
+ this.form.all.enable();
+ }),
+ )
+ .subscribe({
+ next: (response) => {
+ this.close({
+ contractId: response.id,
+ lastUpdatedTime: response.lastUpdatedDate,
+ });
+ },
+ error: (error) => {
+ this.notificationService.showError('Contract termination failed!');
+ console.error('Contract termination failed', error);
+ },
+ });
+ }
+
+ private close(params: ContractAgreementTerminationDialogResult) {
+ this.dialogRef.close(params);
+ }
+
+ ngOnDestroy$ = new Subject();
+
+ ngOnDestroy(): void {
+ this.ngOnDestroy$.next(null);
+ this.ngOnDestroy$.complete();
+ }
+}