Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: specific data offer UI for on request asset #766

Merged
merged 8 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
);
});
});
31 changes: 31 additions & 0 deletions src/app/core/services/mailto-link-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Injectable} from '@angular/core';
import {removeUndefinedValues} from '../utils/record-utils';

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

constructor() {}
kulgg marked this conversation as resolved.
Show resolved Hide resolved

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
Loading