Skip to content

Commit

Permalink
feat: specific data offer UI for on request asset (#766)
Browse files Browse the repository at this point in the history
  • Loading branch information
kulgg committed Jul 23, 2024
1 parent 83a17b3 commit c5d5902
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ the detailed section referring to by linking pull requests or issues.

- Both providers and consumers can now terminate contracts.
- Contracts can be filtered by their termination status.
- Adjusted data offer card/detail dialog UI to differentiate live and on request assets

#### Patch

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
<mat-icon
*ngIf="data.type === 'contract-agreement'"
class="mat-card-avatar-icon"
[class.text-warn]="data.contractAgreement?.terminationStatus === 'TERMINATED'">
[class.text-warn]="
data.contractAgreement?.terminationStatus === 'TERMINATED'
">
{{
data.contractAgreement!!.direction === 'PROVIDING'
? data.contractAgreement!!.isTerminated
Expand Down Expand Up @@ -56,7 +58,7 @@
<div class="flex flex-col space-y-[10px]" mat-dialog-content>
<!-- Progress Bar -->
<mat-progress-bar
*ngIf="showProgressBar"
*ngIf="isProgressBarVisible"
color="primary"
mode="indeterminate"></mat-progress-bar>

Expand Down Expand Up @@ -134,10 +136,22 @@

<!-- Contract Offers -->
<contract-offer-mini-list
*ngIf="data.type === 'data-offer'"
*ngIf="isLiveDataOffer"
[data]="data.dataOffer!"
(negotiateClick)="onNegotiateClick($event)">
</contract-offer-mini-list>

<div *ngIf="isOnRequestDataOffer">
<div class="property-grid-group-title">On Request Data Offer</div>
<div>
This data offer is not available for immediate consumption. However, the
creator left an email address to contact them if interested.
</div>
<div>
Click <span class="!font-medium">Contact</span> to email the data offer
provider.
</div>
</div>
</div>

<mat-dialog-actions>
Expand All @@ -162,10 +176,7 @@
</button>

<ng-container
*ngIf="
data.type === 'data-offer' &&
data.dataOffer!.contractOffers.length === 1
">
*ngIf="isLiveDataOffer && data.dataOffer!.contractOffers.length === 1">
<button
*ngIf="data.asset.isOwnConnector"
disabled
Expand Down Expand Up @@ -199,6 +210,14 @@
</button>
</ng-container>

<a
*ngIf="isOnRequestDataOffer"
mat-raised-button
color="primary"
[href]="this.onRequestContactLink">
Contact
</a>

<!-- Contact Agreement Buttons -->
<button
*ngIf="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {Observable, Subject, isObservable} from 'rxjs';
import {filter, finalize, takeUntil} from 'rxjs/operators';
import {UiContractOffer} from '@sovity.de/edc-client';
import {MailtoLinkBuilder} from 'src/app/core/services/mailto-link-builder';
import {EdcApiService} from '../../../core/services/api/edc-api.service';
import {ContractNegotiationService} from '../../../core/services/contract-negotiation.service';
import {UiAssetMapped} from '../../../core/services/models/ui-asset-mapped';
Expand Down Expand Up @@ -44,7 +45,7 @@ export class AssetDetailDialogComponent implements OnDestroy {

loading = false;

get showProgressBar(): boolean {
get isProgressBarVisible(): boolean {
switch (this.data.type) {
case 'data-offer':
return (
Expand All @@ -59,6 +60,31 @@ export class AssetDetailDialogComponent implements OnDestroy {
}
}

get isLiveDataOffer(): boolean {
return (
this.data.type === 'data-offer' &&
this.data.asset.dataSourceAvailability === 'LIVE'
);
}

get isOnRequestDataOffer(): boolean {
return (
this.data.type === 'data-offer' &&
this.data.asset.dataSourceAvailability === 'ON_REQUEST'
);
}

get onRequestContactLink(): string {
if (!this.asset.onRequestContactEmail) {
throw new Error('On request asset must have contact email');
}
return this.mailtoLinkBuilder.buildMailtoUrl(
this.asset.onRequestContactEmail,
this.asset.onRequestContactEmailSubject ??
"I'm interested in your data offer",
);
}

constructor(
private edcApiService: EdcApiService,
private notificationService: NotificationService,
Expand All @@ -67,6 +93,7 @@ export class AssetDetailDialogComponent implements OnDestroy {
@Inject(MAT_DIALOG_DATA)
private _data: AssetDetailDialogData | Observable<AssetDetailDialogData>,
public contractNegotiationService: ContractNegotiationService,
private mailtoLinkBuilder: MailtoLinkBuilder,
) {
if (isObservable(this._data)) {
this._data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {DataOffer} from '../../../core/services/models/data-offer';
@Component({
selector: 'contract-offer-icon',
template: `
<!-- Negotiation Success Indicator -->
<!-- Negotiation Success Overlay Icon -->
<div *ngIf="isNegotiated()" style="position: absolute;">
<mat-icon
class="mat-card-avatar-icon"
Expand All @@ -19,8 +19,18 @@ import {DataOffer} from '../../../core/services/models/data-offer';
</mat-icon>
</div>
<!-- Icon -->
<mat-icon class="mat-card-avatar-icon">sim_card</mat-icon>
<!-- Live Icon -->
<mat-icon *ngIf="!isOnRequestAsset()" class="mat-card-avatar-icon"
>sim_card</mat-icon
>
<!-- On Request Icon -->
<mat-icon
*ngIf="this.isOnRequestAsset()"
class="mat-card-avatar-icon"
style="transform: scaleX(-1);"
>contact_page</mat-icon
>
`,
})
export class ContractOfferIconComponent {
Expand All @@ -34,4 +44,8 @@ export class ContractOfferIconComponent {
this.contractNegotiationService.isNegotiated(it),
);
}

isOnRequestAsset(): boolean {
return this.dataOffer.asset.dataSourceAvailability === 'ON_REQUEST';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ let dataOffers: UiDataOffer[] = [
},
],
},
{
endpoint: 'http://existing-other-connector/api/dsp',
participantId: 'MDSL1234XX.C1234XX',
asset: TestAssets.onRequestAsset,
contractOffers: [
{
contractOfferId: 'on-request-contract-offer',
policy: TestPolicies.failedMapping,
},
],
},
];

export const getCatalogPageDataOffers = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
ContractAgreementCard,
ContractAgreementPage,
ContractAgreementTransferProcess,
GetContractAgreementPageRequest,
ContractTerminationStatus,
IdResponseDto,
InitiateTransferRequest,
} from '@sovity.de/edc-client';
Expand Down Expand Up @@ -191,14 +191,12 @@ let contractAgreements: ContractAgreementCard[] = [
},
];
export const contractAgreementPage = (
request: GetContractAgreementPageRequest,
terminationStatus?: ContractTerminationStatus,
): ContractAgreementPage => {
return {
contractAgreements: request.contractAgreementPageQuery?.terminationStatus
contractAgreements: terminationStatus
? contractAgreements.filter(
(agreement) =>
agreement.terminationStatus ===
request.contractAgreementPageQuery!.terminationStatus,
(x) => x.terminationStatus === terminationStatus,
)
: contractAgreements,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ export namespace TestAssets {
This is a short description text that should be fully rendered without being **collapsed**. No *show more* button should be visible.
`;

export const onRequestAsset: UiAsset = {
dataSourceAvailability: 'ON_REQUEST',
assetId: 'comfee-or',
title: 'Comfee OR',
description: '',
descriptionShortText: '',
connectorEndpoint: 'https://my-other-connector/api/dsp',
participantId: 'MDSL1234XX.C1234XX',
creatorOrganizationName: 'my-org',
temporalCoverageFrom: new Date('2024-01-01'),
onRequestContactEmail: 'mail@mail.mail',
onRequestContactEmailSubject: "Request for asset 'comfee-or'",
isOwnConnector: false,
};

export const boring: UiAsset = {
dataSourceAvailability: 'LIVE',
assetId: 'data-sample-ckd-skd-demands-2023-Jan',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {ConnectorLimits} from '@sovity.de/edc-client';
import {contractAgreementPage} from './contract-agreement-fake-service';

export const connectorLimits = (): ConnectorLimits => ({
numActiveConsumingContractAgreements: contractAgreementPage(
{},
).contractAgreements.filter(
(it) => it.direction === 'CONSUMING' && it.terminationStatus === 'ONGOING',
).length,
numActiveConsumingContractAgreements:
contractAgreementPage().contractAgreements.filter(
(it) =>
it.direction === 'CONSUMING' && it.terminationStatus === 'ONGOING',
).length,
maxActiveConsumingContractAgreements: 1,
});
4 changes: 2 additions & 2 deletions src/app/core/services/api/fake-backend/edc-fake-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ export const EDC_FAKE_BACKEND: FetchAPI = async (

.url('ui/pages/contract-agreement-page')
.on('POST', () => {
const request = ContractAgreementPageQueryFromJSON(body);
const page = contractAgreementPage({contractAgreementPageQuery: request});
const pageQuery = body ? ContractAgreementPageQueryFromJSON(body) : null;
const page = contractAgreementPage(pageQuery?.terminationStatus);
return ok(ContractAgreementPageToJSON(page));
})

Expand Down
24 changes: 24 additions & 0 deletions src/app/core/services/mailto-link-builder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {MailtoLinkBuilder} from './mailto-link-builder';

describe('mailto-link-builder', () => {
const builder = new MailtoLinkBuilder();

it('build link with all fields', () => {
expect(
builder.buildMailtoUrl(
'test@test.com',
'subject abc',
'body',
'cc',
'bcc',
),
).toEqual(
'mailto:test@test.com?subject=subject%20abc&body=body&cc=cc&bcc=bcc',
);
});
it('build link with only email', () => {
expect(builder.buildMailtoUrl('test@test.com')).toEqual(
'mailto:test@test.com',
);
});
});
29 changes: 29 additions & 0 deletions src/app/core/services/mailto-link-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {Injectable} from '@angular/core';
import {removeUndefinedValues} from '../utils/record-utils';

@Injectable({providedIn: 'root'})
export class MailtoLinkBuilder {
private readonly MAILTO = 'mailto:';

buildMailtoUrl(
email: string,
subject?: string,
body?: string,
cc?: string,
bcc?: string,
): string {
const queryParams = new URLSearchParams(
removeUndefinedValues({
subject,
body,
cc,
bcc,
}),
);
// URLSearchParams replaces spaces with '+', so we need to replace them with '%20' for the mail scenario
const queryParamsStr = queryParams.toString().replaceAll('+', '%20');
const queryStr = queryParamsStr ? `?${queryParamsStr}` : '';

return `${this.MAILTO}${email}${queryStr}`;
}
}
12 changes: 12 additions & 0 deletions src/app/core/utils/record-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ export function removeNullValues(
);
}

/**
* Remove fields with undefined values from property records
* @param obj object / record
*/
export function removeUndefinedValues(
obj: Record<string, string | undefined>,
): Record<string, string> {
return Object.fromEntries(
Object.entries(obj).filter(([_, v]) => v != null) as [string, string][],
);
}

/**
* Maps keys of a given object
* @param obj object
Expand Down

0 comments on commit c5d5902

Please sign in to comment.