Skip to content

Commit

Permalink
Merge pull request #1127 from eclipse-tractusx/chore/#985-fe-contract…
Browse files Browse the repository at this point in the history
…-reference

chore(contracts): 985 implement contract reference to notification or…
  • Loading branch information
ds-mwesener authored Jun 28, 2024
2 parents f7c1298 + e80b7dd commit 2836655
Show file tree
Hide file tree
Showing 25 changed files with 426 additions and 65 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha
- XXX Added interceptor to EdcRestTemplates to log requests
- #915 Added section to documentation: EDC-BPN configuration
- #1037 Added link from contracts view to the corresponding filtered part table view
- #985 Added reference to part/notification under contract
- #786 Added icons on part table to let admin reload registry / sync assets via IRS

### Removed
Expand Down
9 changes: 7 additions & 2 deletions docs/src/docs/user/user-manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,18 @@ include::../../uml-diagrams/user-manual/bpn-edc-configuration.puml[]

==== Contracts - view and export

In the Contracts view an admin user can view contract agreements and sort them by the contract ID.
In the Contracts view an admin user can view contract agreements and sort them only by the contract ID.

With the quick filter buttons it is possible to filter out the contracts after types.
Either show contracts with notifications underneath, or only ones with parts underneath.

By clicking on the burger menu of a data row you can get to the detailed view of a contract.
Dependent on the contract type, you can directly navigate to the item under this contract.

Also, it's possible to select contracts and export/download them as a .csv file.

image::https://raw.githubusercontent.com/eclipse-tractusx/traceability-foss/main/docs/src/images/user-manual/navigation/admin_contract_view.png[]

By clicking on the burger menu of a data row you can get to the detailed view of a contract.

===== Contract detailed view

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 15 additions & 2 deletions frontend/src/app/mocks/services/admin-mock/admin.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

import {PaginationResponse} from '@core/model/pagination.model';
import {BpnConfigResponse, ContractResponse, ContractState} from '@page/admin/core/admin.model';
import { PaginationResponse } from '@core/model/pagination.model';
import { BpnConfigResponse, ContractResponse, ContractState, ContractType } from '@page/admin/core/admin.model';


export const getBpnConfig = (): BpnConfigResponse[] => [
Expand Down Expand Up @@ -221,6 +221,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
content: [
{
"contractId": "abc1",
'contractType': ContractType.ASSET_AS_BUILT,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2024-02-26T13:38:07+01:00",
"endDate": null,
Expand All @@ -229,6 +230,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc2",
'contractType': ContractType.ASSET_AS_BUILT,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -237,6 +239,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc3",
'contractType': ContractType.ASSET_AS_BUILT,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -245,6 +248,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc4",
'contractType': ContractType.ASSET_AS_BUILT,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -253,6 +257,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc5",
'contractType': ContractType.ASSET_AS_PLANNED,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -261,6 +266,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc6",
'contractType': ContractType.ASSET_AS_PLANNED,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -269,6 +275,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc7",
'contractType': ContractType.ASSET_AS_PLANNED,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -277,6 +284,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc8",
'contractType': ContractType.ASSET_AS_PLANNED,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -285,6 +293,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc9",
'contractType': ContractType.NOTIFICATION,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -293,6 +302,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc10",
'contractType': ContractType.NOTIFICATION,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -301,6 +311,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc11",
'contractType': ContractType.NOTIFICATION,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -309,6 +320,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc12",
'contractType': ContractType.NOTIFICATION,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand All @@ -317,6 +329,7 @@ export const getContracts = (): PaginationResponse<ContractResponse> => {
},
{
"contractId": "abc13",
'contractType': ContractType.NOTIFICATION,
"counterpartyAddress": "https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp",
"creationDate": "2022-05-01T12:34:12",
"endDate": null,
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/app/modules/page/admin/core/admin.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type BpnConfigFormGroup = FormGroup<{ bpnConfig: FormArray<FormControl<Bp

export interface Contract {
contractId: string,
contractType: ContractType,
counterpartyAddress: string,
creationDate: CalendarDateModel,
endDate: CalendarDateModel,
Expand All @@ -63,19 +64,28 @@ export interface Contract {

export interface ContractResponse {
contractId: string,
contractType: ContractType,
counterpartyAddress: string,
creationDate: string,
endDate: string,
state: ContractState,
policy: string
}

export enum ContractType {
ASSET_AS_BUILT = 'ASSET_AS_BUILT',
ASSET_AS_PLANNED = 'ASSET_AS_PLANNED',
NOTIFICATION = 'NOTIFICATION',
UNKNOWN = 'UNKNOWN'
}

export type ContractsResponse = PaginationResponse<ContractResponse>;
export type Contracts = Pagination<Contract>;
export function assembleContract(contractResponse: ContractResponse): Contract {

return {
contractId: contractResponse.contractId,
contractType: contractResponse.contractType,
counterpartyAddress: contractResponse.counterpartyAddress,
creationDate: new CalendarDateModel(contractResponse.creationDate),
endDate: new CalendarDateModel(contractResponse.endDate),
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/app/modules/page/admin/core/admin.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {assembleContracts} from '@page/admin/core/admin.model';
import {TableHeaderSort} from '@shared/components/table/table.model';
import {getContracts} from '../../../../mocks/services/admin-mock/admin.model';
import {AdminService} from './admin.service';
import {ApiService} from '@core/api/api.service';
import {of} from 'rxjs';
import { ApiService } from '@core/api/api.service';
import { assembleContracts } from '@page/admin/core/admin.model';
import { TableHeaderSort } from '@shared/components/table/table.model';
import { of } from 'rxjs';
import { getContracts } from '../../../../mocks/services/admin-mock/admin.model';
import { AdminService } from './admin.service';

describe('AdminService', () => {
let adminService: AdminService;
Expand All @@ -29,7 +29,7 @@ describe('AdminService', () => {

it('should create filter list', () => {
const filter = { key1: ['value1', 'value2'], key2: ['value3'] };
const expectedFilterList = ['key1,EQUAL,value1,AND', 'key1,EQUAL,value2,AND', 'key2,EQUAL,value3,AND'];
const expectedFilterList = [ 'key1,EQUAL,value1,OR', 'key1,EQUAL,value2,OR', 'key2,EQUAL,value3,OR' ];

const createdFilterList = adminService['createFilterList'](filter);
expect(createdFilterList).toEqual(expectedFilterList);
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/app/modules/page/admin/core/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

import {HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {ApiService} from '@core/api/api.service';
import {Pagination} from '@core/model/pagination.model';
import {environment} from '@env';
import {AdminAssembler} from '@page/admin/core/admin.assembler';
import { HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ApiService } from '@core/api/api.service';
import { Pagination } from '@core/model/pagination.model';
import { environment } from '@env';
import { AdminAssembler } from '@page/admin/core/admin.assembler';
import {
assembleContract,
BpnConfig,
BpnConfigResponse,
Contract,
ContractResponse,
} from '@page/admin/core/admin.model';
import {TableHeaderSort} from '@shared/components/table/table.model';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import { TableHeaderSort } from '@shared/components/table/table.model';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class AdminService {
Expand Down Expand Up @@ -109,7 +109,7 @@ export class AdminService {
Object.entries(filter).forEach(([ entry, values ]) => {
if (values.length) {
values.forEach(value => {
filterList.push(`${ entry },EQUAL,${ value },AND`);
filterList.push(`${ entry },EQUAL,${ value },OR`);
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
</mat-card-header>

<mat-card-content>
<div class="flex flex-row justify-end mb-2">
<div class="flex flex-row justify-between my-4">
<app-contracts-quick-filter (buttonClickEvent)="filterContractType($event)">
</app-contracts-quick-filter>
<div class="detail--action--button"
matTooltip="{{'pageAdmin.contracts.selectAtleastOne' | i18n}}"
matTooltipClass="table--header--tooltip"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { AdminModule } from '@page/admin/admin.module';
import { AdminFacade } from '@page/admin/core/admin.facade';
import { assembleContract } from '@page/admin/core/admin.model';
import { assembleContract, ContractType } from '@page/admin/core/admin.model';
import { AdminService } from '@page/admin/core/admin.service';
import { TableHeaderSort } from '@shared/components/table/table.model';
import { ToastService } from '@shared/components/toasts/toast.service';
import { NotificationService } from '@shared/service/notification.service';
import { PartsService } from '@shared/service/parts.service';
import { renderComponent } from '@tests/test-render.utils';
import { of } from 'rxjs';
import { getContracts } from '../../../../../mocks/services/admin-mock/admin.model';
Expand All @@ -14,16 +17,43 @@ import { ContractsComponent } from './contracts.component';
describe('ContractTableComponent', () => {

const mockAdminFacade = {
getContracts: jasmine.createSpy().and.returnValue(of(getContracts)),
getContracts: jasmine.createSpy().and.returnValue(of(getContracts())),
contracts$: of({ data: { content: getContracts().content } }),
setContracts: jasmine.createSpy(),
unsubscribeContracts: jasmine.createSpy(),
selectedContract: null,
};

const routerMock = {
navigate: jasmine.createSpy('navigate'),
};

const notificationServiceMock = {
getNotifications: jasmine.createSpy().and.returnValue(of({ content: [ { id: 'notification-id' } ] })),
};

const partsServiceMock = {
getPartsByFilter: jasmine.createSpy().and.returnValue(of({
content: [ {
id: 'part-id',
partId: 'part-unique-id',
} ],
})),
};

const toastServiceMock = {
error: jasmine.createSpy('error'),
};

const renderContractTableComponent = () => renderComponent(ContractsComponent, {
imports: [ AdminModule ],
providers: [ { provide: AdminFacade, useValue: mockAdminFacade }, { provide: Router, useValue: routerMock } ],
providers: [
{ provide: AdminFacade, useValue: mockAdminFacade },
{ provide: Router, useValue: routerMock },
{ provide: NotificationService, useValue: notificationServiceMock },
{ provide: PartsService, useValue: partsServiceMock },
{ provide: ToastService, useValue: toastServiceMock },
],
});

let createElementSpy: jasmine.Spy;
Expand Down Expand Up @@ -87,18 +117,10 @@ describe('ContractTableComponent', () => {

});

it('should navigate if viewAssets clicked', async () => {
const { fixture } = await renderContractTableComponent();
const { componentInstance } = fixture;
componentInstance.viewAssetsClicked.emit({ contractId: 'test' });

expect(routerMock.navigate).toHaveBeenCalled();
});

it('should emit viewAssetsClicked', async () => {
const { fixture } = await renderContractTableComponent();
const { componentInstance } = fixture;
let spy = spyOn(componentInstance.viewAssetsClicked, 'emit');
let spy = spyOn(componentInstance.viewItemsClicked, 'emit');
const viewAssetsAction = componentInstance.tableConfig.menuActionsConfig.filter(action => action.label === 'actions.viewParts')[0];
viewAssetsAction.action(null);
expect(spy).toHaveBeenCalled();
Expand All @@ -111,8 +133,8 @@ describe('ContractTableComponent', () => {

let result = componentInstance.convertArrayOfObjectsToCSV([ getContracts().content[0] ]);

expect(result).toEqual('contractId,counterpartyAddress,creationDate,endDate,state,policy\n' +
'abc1,https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp,2024-02-26T13:38:07+01:00,,Finalized,jsontextaspolicy');
expect(result).toEqual('contractId,contractType,counterpartyAddress,creationDate,endDate,state,policy\n' +
'abc1,ASSET_AS_BUILT,https://trace-x-edc-e2e-a.dev.demo.catena-x.net/api/v1/dsp,2024-02-26T13:38:07+01:00,,Finalized,jsontextaspolicy');

});
it('should download CSV file', async () => {
Expand Down Expand Up @@ -148,4 +170,49 @@ describe('ContractTableComponent', () => {
// Ensure that the link is removed from the document body after being clicked
expect(document.body.removeChild).toHaveBeenCalledWith(link);
});


it('should show error when contractType is NOTIFICATION and no notifications are found', async () => {
notificationServiceMock.getNotifications.and.returnValue(of({ content: [] }));

const { fixture } = await renderContractTableComponent();
const { componentInstance } = fixture;
const data = { contractId: 'contract-id', contractType: ContractType.NOTIFICATION };

componentInstance.viewItemsClicked.emit(data);
fixture.detectChanges();

expect(notificationServiceMock.getNotifications).toHaveBeenCalledWith(0, 1, [], undefined, undefined, { contractAgreementId: 'contract-id' });
expect(toastServiceMock.error).toHaveBeenCalledWith('pageAdmin.contracts.noItemsFoundError');
});


it('should show error when contractType is not NOTIFICATION and no parts are found', async () => {
partsServiceMock.getPartsByFilter.and.returnValue(of({ content: [] }));

const { fixture } = await renderContractTableComponent();
const { componentInstance } = fixture;
const data = { contractId: 'contract-id', contractType: ContractType.ASSET_AS_BUILT };

componentInstance.viewItemsClicked.emit(data);
fixture.detectChanges();

expect(partsServiceMock.getPartsByFilter).toHaveBeenCalledWith({ contractAgreementId: 'contract-id' }, true);
expect(toastServiceMock.error).toHaveBeenCalledWith('pageAdmin.contracts.noItemsFoundError');
});

it('should filter contract type', async () => {
const { fixture } = await renderContractTableComponent();
const { componentInstance } = fixture;

let spy = spyOn(componentInstance['contractsFacade'], 'setContracts');

componentInstance.filterContractType({ contractType: [ ContractType.ASSET_AS_BUILT ] });
expect(componentInstance.contractFilter?.contractType).toEqual(Object({ contractType: [ 'ASSET_AS_BUILT' ] }));
expect(spy).toHaveBeenCalled();

});



});
Loading

0 comments on commit 2836655

Please sign in to comment.