From c04f0342701db2a31e9203baf91bcb058b91598b Mon Sep 17 00:00:00 2001 From: Joel Dietz <145654504+JoelDietz@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:44:36 +0100 Subject: [PATCH 01/11] [DO 1294][patch] fixed wrongly named my parts filter api calls (#46) * fix: renamed the wrongly named filter columns and added the sending of the wrong sort request. * fix: the naming of the filter option sent in the status selection. * fix: the partID for myParts and shared Parts * fix: catenaXSideId wrong mocking data, css class error that leads to wrong sizeing on the dashboard, disappearing table when filtered for something with no match, --- .../customerPartsAsPlanned.model.ts | 480 +++++++++--------- .../supplierPartsAsPlanned.model.ts | 295 ++++++----- .../partsAsPlanned/partsAsPlanned.model.ts | 24 +- .../partsAsPlanned.test.model.ts | 171 ++++--- .../alerts/presentation/alerts.component.html | 7 +- .../alerts/presentation/alerts.component.scss | 17 +- .../presentation/dashboard.component.html | 138 +++-- .../modules/page/parts/model/parts.model.ts | 231 +++++---- .../shared/assembler/parts.assembler.ts | 87 ++-- .../multi-select-autocomplete.component.ts | 14 +- .../parts-table/parts-table.component.html | 10 +- .../parts-table/parts-table.component.scss | 6 +- .../parts-table/parts-table.component.spec.ts | 27 +- .../parts-table/parts-table.component.ts | 70 ++- .../components/table/table.component.html | 6 +- .../components/table/table.component.ts | 2 + .../notification-tab.component.html | 5 +- .../notification-tab.component.ts | 5 + .../presentation/notification.component.html | 2 + ...antic-data-model-to-camelcase.pipe.spec.ts | 44 +- ...t-semantic-data-model-to-camelcase.pipe.ts | 63 ++- frontend/src/assets/locales/de/common.json | 8 +- frontend/src/assets/locales/en/common.json | 2 +- 23 files changed, 892 insertions(+), 822 deletions(-) diff --git a/frontend/src/app/mocks/services/otherParts-mock/customerPartsAsPlanned.model.ts b/frontend/src/app/mocks/services/otherParts-mock/customerPartsAsPlanned.model.ts index c65c5efdd3..82507952db 100644 --- a/frontend/src/app/mocks/services/otherParts-mock/customerPartsAsPlanned.model.ts +++ b/frontend/src/app/mocks/services/otherParts-mock/customerPartsAsPlanned.model.ts @@ -1,290 +1,292 @@ export const customerPartsAsPlannedModel = [ { - "id": "urn:uuid:6ec3f1db-2798-454b-a73f-0d21a8966c74", - "idShort": "--", - "semanticModelId": "NO-613963493493659233961306", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [{ - "id": "urn:uuid:c47b9f8b-48d0-4ef4-8f0b-e965a225cb8d", - "idShort": null - }], - "parentRelations": [ + id: 'urn:uuid:6ec3f1db-2798-454b-a73f-0d21a8966c74', + idShort: '--', + semanticModelId: 'NO-613963493493659233961306', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:c47b9f8b-48d0-4ef4-8f0b-e965a225cb8d", - "idShort": null - } + id: 'urn:uuid:c47b9f8b-48d0-4ef4-8f0b-e965a225cb8d', + idShort: null, + }, + ], + parentRelations: [ + { + id: 'urn:uuid:c47b9f8b-48d0-4ef4-8f0b-e965a225cb8d', + idShort: null, + }, ], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "SERIALPART", - "classification": "component", - "detailAspectModels": [ + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'SERIALPART', + classification: 'component', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - "validityPeriodFrom": "01.01.2023", - "validityPeriodTo": "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890AA', + }, + }, + ], }, { - "id": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e43842", - "idShort": "--", - "semanticModelId": "12345678ABC", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "JUSTINSEQUENCE", - "classification": "product", - "detailAspectModels": [ + id: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e43842', + idShort: '--', + semanticModelId: '12345678ABC', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [], + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'JUSTINSEQUENCE', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890BB', + }, + }, + ], }, { - "id": "urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccad12", - "idShort": "--", - "semanticModelId": "OMAYSKEITUGNVHKKX", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [ + id: 'urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccad12', + idShort: '--', + semanticModelId: 'OMAYSKEITUGNVHKKX', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a", - "idShort": null - } + id: 'urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a', + idShort: null, + }, ], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "OMAYSKEITUGNVHKKX", - "semanticDataModel": "SERIALPART", - "classification": "product", - "detailAspectModels": [ + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: 'OMAYSKEITUGNVHKKX', + semanticDataModel: 'SERIALPART', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890CC', + }, + }, + ], }, { - "id": "urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccvf54", - "idShort": "--", - "semanticModelId": "OMAYSKEITUGNVHKKX", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [ + id: 'urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccvf54', + idShort: '--', + semanticModelId: 'OMAYSKEITUGNVHKKX', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a", - "idShort": null - } + id: 'urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a', + idShort: null, + }, ], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "OMAYSKEITUGNVHKKX", - "semanticDataModel": "SERIALPART", - "classification": "product", - "detailAspectModels": [ + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: 'OMAYSKEITUGNVHKKX', + semanticDataModel: 'SERIALPART', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890DD', + }, + }, + ], }, { - "id": "urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccaf88", - "idShort": "--", - "semanticModelId": "OMAYSKEITUGNVHKKX", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [ + id: 'urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccaf88', + idShort: '--', + semanticModelId: 'OMAYSKEITUGNVHKKX', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a", - "idShort": null - } + id: 'urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a', + idShort: null, + }, ], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "OMAYSKEITUGNVHKKX", - "semanticDataModel": "SERIALPART", - "classification": "product", - "detailAspectModels": [ + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: 'OMAYSKEITUGNVHKKX', + semanticDataModel: 'SERIALPART', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890EE', + }, + }, + ], }, { - "id": "urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccav85", - "idShort": "myShortId", - "semanticModelId": "OMAYSKEITUGNVHKKX", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [ + id: 'urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccav85', + idShort: 'myShortId', + semanticModelId: 'OMAYSKEITUGNVHKKX', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a", - "idShort": null - } + id: 'urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a', + idShort: null, + }, ], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "OMAYSKEITUGNVHKKX", - "semanticDataModel": "SERIALPART", - "classification": "product", - "detailAspectModels": [ + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: 'OMAYSKEITUGNVHKKX', + semanticDataModel: 'SERIALPART', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890YY', + }, + }, + ], }, { - "id": "urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccag25", - "idShort": "--", - "semanticModelId": "OMAYSKEITUGNVHKKX", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "CustomerAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "CUSTOMER", - "childRelations": [ + id: 'urn:uuid:d8030bbf-a874-49fb-b2e1-7610f0ccag25', + idShort: '--', + semanticModelId: 'OMAYSKEITUGNVHKKX', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'CustomerAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'CUSTOMER', + childRelations: [ { - "id": "urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a", - "idShort": null - } + id: 'urn:uuid:5205f736-8fc2-4585-b869-6bf36842369a', + idShort: null, + }, ], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "OMAYSKEITUGNVHKKX", - "semanticDataModel": "SERIALPART", - "classification": "product", - "detailAspectModels": [ + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: 'OMAYSKEITUGNVHKKX', + semanticDataModel: 'SERIALPART', + classification: 'product', + detailAspectModels: [ { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } - ] + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890ZZ', + }, + }, + ], }, -] +]; diff --git a/frontend/src/app/mocks/services/otherParts-mock/supplierPartsAsPlanned.model.ts b/frontend/src/app/mocks/services/otherParts-mock/supplierPartsAsPlanned.model.ts index 141ed65d9b..417ee4e913 100644 --- a/frontend/src/app/mocks/services/otherParts-mock/supplierPartsAsPlanned.model.ts +++ b/frontend/src/app/mocks/services/otherParts-mock/supplierPartsAsPlanned.model.ts @@ -1,157 +1,156 @@ export const supplierPartsAsPlannedAssets = [ { - "id": "urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fa01", - "idShort": "--", - "semanticModelId": "NO-341449848714937445621543", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "SupplierAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "SUPPLIER", - "childRelations": [], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "BATCH", - "classification": "component", - "detailAspectModels": [ - { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2020", - functionValidUntil: "01.02.2020" - } + id: 'urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fa01', + idShort: '--', + semanticModelId: 'NO-341449848714937445621543', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'SupplierAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'SUPPLIER', + childRelations: [], + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'BATCH', + classification: 'component', + detailAspectModels: [ + { + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2020', + functionValidUntil: '01.02.2020', + }, + }, + { + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890AA', + }, + }, + ], }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } -] -}, -{ - "id": "urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fa03", - "idShort": "--", - "semanticModelId": "NO-341449848714937445621543", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "SupplierAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "SUPPLIER", - "childRelations": [], - "parentRelations": [ - { - "id": "urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fb02", - "idShort": null - } -], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "BATCH", - "classification": "component", - "detailAspectModels": [ - { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2020", - functionValidUntil: "01.02.2020" - } + id: 'urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fa03', + idShort: '--', + semanticModelId: 'NO-341449848714937445621543', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'SupplierAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'SUPPLIER', + childRelations: [], + parentRelations: [ + { + id: 'urn:uuid:1be6ec59-40fb-4993-9836-acb0e284fb02', + idShort: null, + }, + ], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'BATCH', + classification: 'component', + detailAspectModels: [ + { + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2020', + functionValidUntil: '01.02.2020', + }, + }, + { + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890BB', + }, + }, + ], }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } -] -}, -{ - "id": "urn:uuid:4a5e9ff6-2d5c-4510-a90e-d55af3ba502f", - "idShort": "--", - "semanticModelId": "NO-246880451848384868750731", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "SupplierAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "SUPPLIER", - "childRelations": [], - "parentRelations": [ - { - "id": "urn:uuid:f11ddc62-3bd5-468f-b7b0-110fe13ed0cd", - "idShort": null - } -], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "SERIALPART", - "classification": "component", - "detailAspectModels": [ - { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2020", - functionValidUntil: "01.02.2020" - } + id: 'urn:uuid:4a5e9ff6-2d5c-4510-a90e-d55af3ba502f', + idShort: '--', + semanticModelId: 'NO-246880451848384868750731', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'SupplierAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'SUPPLIER', + childRelations: [], + parentRelations: [ + { + id: 'urn:uuid:f11ddc62-3bd5-468f-b7b0-110fe13ed0cd', + idShort: null, + }, + ], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'SERIALPART', + classification: 'component', + detailAspectModels: [ + { + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2020', + functionValidUntil: '01.02.2020', + }, + }, + { + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890YY', + }, + }, + ], }, { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } -] -}, -{ - "id": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e43842", - "idShort": "--", - "semanticModelId": "12345678ABC", - "businessPartner": "BPNL00000003CML1", - "manufacturerName": "MyManufacturerName", - nameAtManufacturer: "SupplierAsPlannedPartName", - manufacturerPartId: "ManuPartID", - "owner": "SUPPLIER", - "childRelations": [], - "parentRelations": [], - "activeAlert": false, - "underInvestigation": false, - "qualityType": "Ok", - "van": "--", - "semanticDataModel": "JUSTINSEQUENCE", - "classification": "product", - "detailAspectModels": [ - { - "type": "AS_PLANNED", - "data": { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } + id: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e43842', + idShort: '--', + semanticModelId: '12345678ABC', + businessPartner: 'BPNL00000003CML1', + manufacturerName: 'MyManufacturerName', + nameAtManufacturer: 'SupplierAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + owner: 'SUPPLIER', + childRelations: [], + parentRelations: [], + activeAlert: false, + underInvestigation: false, + qualityType: 'Ok', + van: '--', + semanticDataModel: 'JUSTINSEQUENCE', + classification: 'product', + detailAspectModels: [ + { + type: 'AS_PLANNED', + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, + }, + { + type: 'PART_SITE_INFORMATION_AS_PLANNED', + data: { + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890ZZ', + }, + }, + ], }, - { - "type": "PART_SITE_INFORMATION_AS_PLANNED", - "data": { - "functionValidUntil": "Sat Feb 08 03:30:48 GMT 2025", - "function": "production", - "functionValidFrom": "Wed Aug 21 00:10:36 GMT 2019", - "catenaXSiteId": "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } -] -}, - -] +]; diff --git a/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.model.ts b/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.model.ts index 440e5c31d9..c2ac800f66 100644 --- a/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.model.ts +++ b/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.model.ts @@ -56,7 +56,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890AA', }, }, ], @@ -97,7 +97,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890AA', }, }, ], @@ -138,7 +138,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890AA', }, }, ], @@ -174,7 +174,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890BB', }, }, ], @@ -220,7 +220,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890CC', }, }, ], @@ -257,7 +257,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890CC', }, }, ], @@ -299,7 +299,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890DD', }, }, ], @@ -341,7 +341,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890DD', }, }, ], @@ -382,7 +382,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890YY', }, }, ], @@ -423,7 +423,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890YY', }, }, ], @@ -464,7 +464,7 @@ export const mockBmwAsPlannedAssets = [ functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890ZZ', }, }, ], @@ -494,7 +494,7 @@ const MockEmptyPart: PartResponse = { functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', function: 'production', functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', - catenaXSiteId: 'urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01', + catenaXSiteId: 'BPNS1234567890ZZ', }, }, ], diff --git a/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model.ts b/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model.ts index a0613ae5dc..0ba52f108e 100644 --- a/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model.ts +++ b/frontend/src/app/mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model.ts @@ -21,33 +21,34 @@ import { DetailAspectType } from '@page/parts/model/detailAspectModel.model'; import { PartResponse, PartsResponse, QualityType, SemanticDataModel } from '@page/parts/model/parts.model'; -import {Owner} from '@page/parts/model/owner.enum'; +import { Owner } from '@page/parts/model/owner.enum'; export const MOCK_part_5 = { id: 'MOCK_part_5', idShort: 'MOCK_part_5', businessPartner: 'BPNCML1000001', manufacturerName: 'Mercedes Benz', - nameAtManufacturer: "K-130", - manufacturerPartId: "ManuPartID", - classification: "A-Level", - semanticModelId: "semanticID", - detailAspectModels: [{ - type: DetailAspectType.AS_PLANNED, - data: { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } - }, + nameAtManufacturer: 'K-130', + manufacturerPartId: 'ManuPartID', + classification: 'A-Level', + semanticModelId: 'semanticID', + detailAspectModels: [ + { + type: DetailAspectType.AS_PLANNED, + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, + }, { type: DetailAspectType.PART_SITE_INFORMATION_AS_PLANNED, data: { - functionValidUntil: "Sat Feb 08 03:30:48 GMT 2025", - function: "production", - functionValidFrom: "Wed Aug 21 00:10:36 GMT 2019", - catenaXSiteId: "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890AA', + }, + }, ], owner: Owner.OWN, childRelations: [], @@ -64,26 +65,27 @@ export const MOCK_part_4 = { idShort: 'MOCK_part_4', businessPartner: 'BPNCML000001', manufacturerName: 'Daimler', - nameAtManufacturer: "F-Klasse", - manufacturerPartId: "ManuPartID", - semanticModelId: "semanticID", + nameAtManufacturer: 'F-Klasse', + manufacturerPartId: 'ManuPartID', + semanticModelId: 'semanticID', classification: 'B-Level', - detailAspectModels: [{ - type: DetailAspectType.AS_PLANNED, - data: { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } - }, + detailAspectModels: [ + { + type: DetailAspectType.AS_PLANNED, + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, + }, { type: DetailAspectType.PART_SITE_INFORMATION_AS_PLANNED, data: { - functionValidUntil: "Sat Feb 08 03:30:48 GMT 2025", - function: "production", - functionValidFrom: "Wed Aug 21 00:10:36 GMT 2019", - catenaXSiteId: "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890BB', + }, + }, ], owner: Owner.OWN, childRelations: [], @@ -99,30 +101,31 @@ export const MOCK_part_3 = { id: 'MOCK_part_3', idShort: 'MOCK_part_3', businessPartner: 'Mercedes-Benz', - semanticModelId: "semanticID", + semanticModelId: 'semanticID', manufacturerName: 'BWM AG', nameAtManufacturer: 'Back Door Left', - manufacturerPartId: "ManuPartID", + manufacturerPartId: 'ManuPartID', classification: 'C-Level', - detailAspectModels: [{ - type: DetailAspectType.AS_PLANNED, - data: { - validityPeriodFrom: "01.01.2022", - validityPeriodTo: "01.02.2022", - } - }, + detailAspectModels: [ + { + type: DetailAspectType.AS_PLANNED, + data: { + validityPeriodFrom: '01.01.2022', + validityPeriodTo: '01.02.2022', + }, + }, { type: DetailAspectType.PART_SITE_INFORMATION_AS_PLANNED, data: { - functionValidUntil: "Sat Feb 08 03:30:48 GMT 2025", - function: "production", - functionValidFrom: "Wed Aug 21 00:10:36 GMT 2019", - catenaXSiteId: "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890CC', + }, + }, ], owner: Owner.OWN, - childRelations: [ { id: MOCK_part_5.id, idShort: MOCK_part_5.idShort } ], + childRelations: [{ id: MOCK_part_5.id, idShort: MOCK_part_5.idShort }], parentRelations: [], activeAlert: false, underInvestigation: false, @@ -136,26 +139,27 @@ export const MOCK_part_2 = { idShort: 'MOCK_part_2', businessPartner: 'BMW', manufacturerName: 'BMW AG', - nameAtManufacturer: "MyAsPlannedPartName", - manufacturerPartId: "ManuPartID", - semanticModelId: "semanticID", + nameAtManufacturer: 'MyAsPlannedPartName', + manufacturerPartId: 'ManuPartID', + semanticModelId: 'semanticID', classification: 'A-Level', - detailAspectModels: [{ - type: DetailAspectType.AS_PLANNED, - data: { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } - }, + detailAspectModels: [ + { + type: DetailAspectType.AS_PLANNED, + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, + }, { type: DetailAspectType.PART_SITE_INFORMATION_AS_PLANNED, data: { - functionValidUntil: "Sat Feb 08 03:30:48 GMT 2025", - function: "production", - functionValidFrom: "Wed Aug 21 00:10:36 GMT 2019", - catenaXSiteId: "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890DD', + }, + }, ], owner: Owner.OWN, childRelations: [{ id: MOCK_part_4.id, idShort: MOCK_part_4.idShort }], @@ -171,27 +175,28 @@ export const MOCK_part_1 = { id: 'MOCK_part_1', idShort: 'MOCK_part_1', businessPartner: 'Audi', - semanticModelId: "mySemanticModelId", + semanticModelId: 'mySemanticModelId', manufacturerName: 'Audi AG', - nameAtManufacturer: "MyAsPlannedPartName", - manufacturerPartId: "ManuPartID", + nameAtManufacturer: 'MyAsPlannedPartName', + manufacturerPartId: 'ManuPartID', classification: 'C-Level', - detailAspectModels: [{ - type: DetailAspectType.AS_PLANNED, - data: { - validityPeriodFrom: "01.01.2023", - validityPeriodTo: "01.02.2023", - } - }, + detailAspectModels: [ + { + type: DetailAspectType.AS_PLANNED, + data: { + validityPeriodFrom: '01.01.2023', + validityPeriodTo: '01.02.2023', + }, + }, { type: DetailAspectType.PART_SITE_INFORMATION_AS_PLANNED, data: { - functionValidUntil: "Sat Feb 08 03:30:48 GMT 2025", - function: "production", - functionValidFrom: "Wed Aug 21 00:10:36 GMT 2019", - catenaXSiteId: "urn:uuid:0733946c-59c6-41ae-9570-cb43a6e4da01" - } - } + functionValidUntil: 'Sat Feb 08 03:30:48 GMT 2025', + function: 'production', + functionValidFrom: 'Wed Aug 21 00:10:36 GMT 2019', + catenaXSiteId: 'BPNS1234567890ZZ', + }, + }, ], owner: Owner.OWN, childRelations: [ @@ -203,7 +208,7 @@ export const MOCK_part_1 = { underInvestigation: false, qualityType: QualityType.Ok, van: 'myvan1', - semanticDataModel: SemanticDataModel.SERIALPART + semanticDataModel: SemanticDataModel.SERIALPART, }; export const mockAssets: PartsResponse = { diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html index b7079030ac..522075b8f3 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html @@ -79,6 +79,11 @@

(onQueuedAndRequestedTableConfigChanged)="onQueuedAndRequestedTableConfigChange($event)" (selected)="openDetailPage($event)" [menuActionsConfig]="menuActionsConfig" - > + >
+ No alert +
{{ 'commonAlert.noAlerts' | i18n }}
+
{{ 'commonAlert.noAlertsDescription' | i18n }}
+
diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.scss b/frontend/src/app/modules/page/alerts/presentation/alerts.component.scss index 6afe5a92ff..72a3d8a09f 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.scss +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.scss @@ -1,4 +1,19 @@ .tableScroll { height: 547px !important; overflow: auto !important; -} \ No newline at end of file +} +.noAlerts { + @apply font-semiBold; + text-align: center; + font-size: 24px; + line-height: 20px; + letter-spacing: 0.1px; +} + +.description { + @apply font-regular; + text-align: center; + font-size: 16px; + line-height: 20px; + letter-spacing: 0.1px; +} diff --git a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html index 3fbc71265b..266809e44c 100644 --- a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html +++ b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html @@ -21,58 +21,70 @@
- + customContext: { + label: 'pageDashboard.totalOfMyParts.label', + icon: 'my_parts_icon', + link: 'parts' + } + " + >
- +
- + customContext: { + label: 'pageDashboard.totalInvestigations.label', + icon: 'content_paste_search', + link: 'investigations' + } + " + >
- +
@@ -96,12 +108,21 @@

- -
- No quality investigations icon + +
+ No quality investigations icon
{{ 'commonInvestigation.noInvestigations' | i18n }}
{{ 'commonInvestigation.noInvestigationsDescription' | i18n }}
@@ -131,10 +152,16 @@

- -
+ +
No alert
{{ 'commonAlert.noAlerts' | i18n }}
{{ 'commonAlert.noAlertsDescription' | i18n }}
@@ -145,16 +172,29 @@

- + - + - - \ No newline at end of file + + +

diff --git a/frontend/src/app/modules/page/parts/model/parts.model.ts b/frontend/src/app/modules/page/parts/model/parts.model.ts index 25d009a761..a27ed1120d 100644 --- a/frontend/src/app/modules/page/parts/model/parts.model.ts +++ b/frontend/src/app/modules/page/parts/model/parts.model.ts @@ -26,162 +26,161 @@ import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; import { Owner } from '@page/parts/model/owner.enum'; export interface Part { - id: string; - idShort: string; - name: string; - manufacturer: string; - manufacturerPartId: string; - nameAtManufacturer: string; - businessPartner: string - semanticModel?: SemanticModel; - semanticModelId: string; - qualityType: QualityType; - children: string[]; - parents?: string[]; - error?: boolean; - activeInvestigation?: boolean; - activeAlert: boolean; - van: string; - semanticDataModel: SemanticDataModel; - classification: string; - - mainAspectType: MainAspectType; - - // aspectmodel props are temporarely hardcoded here because Tables and Views only accept root level prop array - // as built - partId?: string; - customerPartId?: string; - nameAtCustomer?: string; - manufacturingDate?: string; - manufacturingCountry?: string; - - // as planned - validityPeriodFrom?: string; - validityPeriodTo?: string; - //partSiteInformationAsPlanned - catenaXSiteId: string; - psFunction: string; - functionValidFrom?: string; - functionValidUntil?: string; - - // count of notifications - activeAlerts: number; - activeInvestigations: number; + id: string; + idShort: string; + name: string; + manufacturer: string; + manufacturerPartId: string; + nameAtManufacturer: string; + businessPartner: string; + semanticModel?: SemanticModel; + semanticModelId: string; + qualityType: QualityType; + children: string[]; + parents?: string[]; + error?: boolean; + activeInvestigation?: boolean; + activeAlert: boolean; + van: string; + semanticDataModel: SemanticDataModel; + semanticDataModelInCamelCase?: SemanticDataModelInCamelCase; + classification: string; + + mainAspectType: MainAspectType; + + // aspectmodel props are temporarely hardcoded here because Tables and Views only accept root level prop array + // as built + partId?: string; + customerPartId?: string; + nameAtCustomer?: string; + manufacturingDate?: string; + manufacturingCountry?: string; + + // as planned + validityPeriodFrom?: string; + validityPeriodTo?: string; + //partSiteInformationAsPlanned + catenaXSiteId: string; + psFunction: string; + functionValidFrom?: string; + functionValidUntil?: string; + + // count of notifications + activeAlerts: number; + activeInvestigations: number; } export interface PartResponse { - id: string; - idShort: string; - semanticModelId: string; - manufacturerPartId: string; - businessPartner: string; - manufacturerName: string; - nameAtManufacturer: string; - owner: Owner; - childRelations: Relation[]; - parentRelations: Relation[]; - activeAlert: boolean; - underInvestigation: boolean; - qualityType: QualityType; - van: string; - semanticDataModel: SemanticDataModel; - classification: string; - detailAspectModels: DetailAspectModel[]; - - // TODO: Delete ? flag when AsPlanned Parts do not return the props anymore - qualityAlertsInStatusActive?: number; - qualityInvestigationsInStatusActive?: number; - + id: string; + idShort: string; + semanticModelId: string; + manufacturerPartId: string; + businessPartner: string; + manufacturerName: string; + nameAtManufacturer: string; + owner: Owner; + childRelations: Relation[]; + parentRelations: Relation[]; + activeAlert: boolean; + underInvestigation: boolean; + qualityType: QualityType; + van: string; + semanticDataModel: SemanticDataModel; + classification: string; + detailAspectModels: DetailAspectModel[]; + + // TODO: Delete ? flag when AsPlanned Parts do not return the props anymore + qualityAlertsInStatusActive?: number; + qualityInvestigationsInStatusActive?: number; } export type PartsResponse = PaginationResponse; // TODO: needs to be aligned with Severity in the future in terms of coding standards and use export enum QualityType { - Ok = 'Ok', - Minor = 'Minor', - Major = 'Major', - Critical = 'Critical', - LifeThreatening = 'LifeThreatening', + Ok = 'Ok', + Minor = 'Minor', + Major = 'Major', + Critical = 'Critical', + LifeThreatening = 'LifeThreatening', } export enum SemanticDataModel { - BATCH = 'BATCH', - SERIALPART = 'SERIALPART', - PARTASPLANNED = 'PARTASPLANNED', - JUSTINSEQUENCE = 'JUSTINSEQUENCE', - UNKNOWN = 'UNKNOWN' + BATCH = 'BATCH', + SERIALPART = 'SERIALPART', + PARTASPLANNED = 'PARTASPLANNED', + JUSTINSEQUENCE = 'JUSTINSEQUENCE', + UNKNOWN = 'UNKNOWN', } export enum SemanticDataModelInCamelCase { - BATCH = "Batch", - SERIALPART = 'SerialPart', - PARTASPLANNED = 'PartAsPlanned', - JUSTINSEQUENCE = 'JustInSequence', - UNKNOWN = 'Unknown' + BATCH = 'Batch', + SERIALPART = 'SerialPart', + PARTASPLANNED = 'PartAsPlanned', + JUSTINSEQUENCE = 'JustInSequence', + UNKNOWN = 'Unknown', } export interface Relation { - id: string; - idShort: string; + id: string; + idShort: string; } export interface AssetAsBuiltFilter { - id?: string, - idShort?: string, - name?: string, - manufacturer?: string, - partId?: string, - manufacturerPartId?: string, - customerPartId?: string, - classification?: string, - nameAtCustomer?: string, - semanticModelId?: string, - semanticDataModel?: string[], - manufacturingDate?: string, - manufacturingCountry?: string + id?: string; + idShort?: string; + name?: string; + manufacturer?: string; + partId?: string; + manufacturerPartId?: string; + customerPartId?: string; + classification?: string; + nameAtCustomer?: string; + semanticModelId?: string; + semanticDataModel?: string[]; + manufacturingDate?: string; + manufacturingCountry?: string; } export interface AssetAsPlannedFilter { - id?: string, - idShort?: string, - name?: string, - manufacturer?: string, - manufacturerPartId?: string, - classification?: string, - semanticDataModel?: string[], - semanticModelId?: string, - validityPeriodFrom?: string, - validityPeriodTo?: string, - psFunction?: string, - catenaXSiteId?: string, - functionValidFrom?: string, - functionValidUntil?: string, + id?: string; + idShort?: string; + name?: string; + manufacturer?: string; + manufacturerPartId?: string; + classification?: string; + semanticDataModel?: string[]; + semanticModelId?: string; + validityPeriodFrom?: string; + validityPeriodTo?: string; + psFunction?: string; + catenaXSiteId?: string; + functionValidFrom?: string; + functionValidUntil?: string; } export interface AssetAsDesignedFilter { - id?: string, + id?: string; } export interface AssetAsSupportedFilter { - id?: string, + id?: string; } export interface AssetAsOrderedFilter { - id?: string, + id?: string; } export interface AssetAsRecycledFilter { - id?: string, + id?: string; } - export enum FilterOperator { - EQUAL = 'EQUAL', - AT_LOCAL_DATE = 'AT_LOCAL_DATE', - STARTS_WITH = 'STARTS_WITH' + EQUAL = 'EQUAL', + AT_LOCAL_DATE = 'AT_LOCAL_DATE', + STARTS_WITH = 'STARTS_WITH', } export function getFilterOperatorValue(operator: FilterOperator) { - return operator as string; + return operator as string; } diff --git a/frontend/src/app/modules/shared/assembler/parts.assembler.ts b/frontend/src/app/modules/shared/assembler/parts.assembler.ts index 190b52d44a..c3f8b11cb0 100644 --- a/frontend/src/app/modules/shared/assembler/parts.assembler.ts +++ b/frontend/src/app/modules/shared/assembler/parts.assembler.ts @@ -35,12 +35,11 @@ import { OperatorFunction } from 'rxjs'; import { map } from 'rxjs/operators'; export class PartsAssembler { - public static createSemanticModelFromPartResponse(partResponse: PartResponse): SemanticModel { - let proplist= {}; - partResponse.detailAspectModels.forEach((detailAspectModel) => { - proplist = {...proplist, ...detailAspectModel.data}; - }) + let proplist = {}; + partResponse.detailAspectModels.forEach(detailAspectModel => { + proplist = { ...proplist, ...detailAspectModel.data }; + }); return proplist; } @@ -64,8 +63,10 @@ export class PartsAssembler { const validityPeriodTo = (partResponse.detailAspectModels[0].data as AsPlannedAspectModel)?.validityPeriodTo; const catenaXSiteId = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned)?.catenaXSiteId; const psFunction = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned)?.function; - const functionValidFrom = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned)?.functionValidFrom; - const functionValidUntil = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned)?.functionValidUntil; + const functionValidFrom = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned) + ?.functionValidFrom; + const functionValidUntil = (partResponse.detailAspectModels[1]?.data as PartSiteInformationAsPlanned) + ?.functionValidUntil; let mappedPart = { id: partResponse.id, @@ -107,8 +108,7 @@ export class PartsAssembler { // count of notifications activeAlerts: partResponse.qualityAlertsInStatusActive, activeInvestigations: partResponse.qualityInvestigationsInStatusActive, - - } + }; return mappedPart; } @@ -120,7 +120,10 @@ export class PartsAssembler { return { ...PartsAssembler.assemblePart(partResponse, mainAspectType), qualityType: partResponse.qualityType }; } - public static assembleParts(parts: PaginationResponse, mainAspectType: MainAspectType): Pagination { + public static assembleParts( + parts: PaginationResponse, + mainAspectType: MainAspectType, + ): Pagination { return PaginationAssembler.assemblePagination(PartsAssembler.assemblePart, parts, mainAspectType); } @@ -129,7 +132,10 @@ export class PartsAssembler { return partCopy.map(part => PartsAssembler.assemblePart(part, mainAspectType)); } - public static assembleOtherParts(parts: PaginationResponse, mainAspectType: MainAspectType): Pagination { + public static assembleOtherParts( + parts: PaginationResponse, + mainAspectType: MainAspectType, + ): Pagination { return PaginationAssembler.assemblePagination(PartsAssembler.assembleOtherPart, parts, mainAspectType); } @@ -138,25 +144,18 @@ export class PartsAssembler { return viewData; } - const { - name, - semanticDataModel, - semanticModelId, - manufacturingDate, - manufacturingCountry, - classification , - - } = viewData.data; - return { data: { + const { name, semanticDataModel, semanticModelId, manufacturingDate, manufacturingCountry, classification } = + viewData.data; + return { + data: { name, semanticDataModel, semanticModelId, manufacturingDate, manufacturingCountry, - classification , - - - } as Part }; + classification, + } as Part, + }; } public static mapPartForView(): OperatorFunction, View> { @@ -169,23 +168,14 @@ export class PartsAssembler { return viewData; } - // exclude 'van' if is a partAsPlanned - if(viewData.data?.mainAspectType === MainAspectType.AS_BUILT) { - const { - manufacturer, - manufacturerPartId, - nameAtManufacturer, - van, - } = viewData.data; - return { data: { manufacturer, manufacturerPartId, nameAtManufacturer, van } as Part }; - } else { - const { - manufacturer, - manufacturerPartId, - nameAtManufacturer, - } = viewData.data; - return { data: { manufacturer, manufacturerPartId, nameAtManufacturer } as Part }; - } + // exclude 'van' if is a partAsPlanned + if (viewData.data?.mainAspectType === MainAspectType.AS_BUILT) { + const { manufacturer, manufacturerPartId, nameAtManufacturer, van } = viewData.data; + return { data: { manufacturer, manufacturerPartId, nameAtManufacturer, van } as Part }; + } else { + const { manufacturer, manufacturerPartId, nameAtManufacturer } = viewData.data; + return { data: { manufacturer, manufacturerPartId, nameAtManufacturer } as Part }; + } }); } @@ -195,7 +185,7 @@ export class PartsAssembler { return; } // if no customer data is available then return partSiteInformation - if(!viewData.data?.nameAtCustomer && !viewData.data?.customerPartId && viewData.data?.functionValidFrom) { + if (!viewData.data?.nameAtCustomer && !viewData.data?.customerPartId && viewData.data?.functionValidFrom) { const { catenaXSiteId, psFunction, functionValidFrom, functionValidUntil } = viewData.data; return { data: { catenaXSiteId, psFunction, functionValidFrom, functionValidUntil } as Part }; } @@ -210,15 +200,13 @@ export class PartsAssembler { return ''; } - - const localToApiMapping = new Map([ ['id', 'id'], ['idShort', 'idShort'], ['semanticModelId', 'semanticModelId'], ['manufacturer', 'manufacturerName'], ['manufacturerPartId', 'manufacturerPartId'], - ['partId', "manufacturerPartId"], + ['partId', 'partId'], ['nameAtManufacturer', 'nameAtManufacturer'], ['businessPartner', 'businessPartner'], ['name', 'nameAtManufacturer'], @@ -236,13 +224,10 @@ export class PartsAssembler { ['psFunction', 'function'], ['functionValidFrom', 'functionValidFrom'], ['functionValidUntil', 'functionValidUntil'], - [ 'activeAlerts', 'qualityAlertsInStatusActive' ], - [ 'activeInvestigations', 'qualityInvestigationsInStatusActive' ], - + ['activeAlerts', 'qualityAlertsInStatusActive'], + ['activeInvestigations', 'qualityInvestigationsInStatusActive'], ]); - - return `${localToApiMapping.get(sorting[0]) || sorting},${sorting[1]}`; } } diff --git a/frontend/src/app/modules/shared/components/multi-select-autocomplete/multi-select-autocomplete.component.ts b/frontend/src/app/modules/shared/components/multi-select-autocomplete/multi-select-autocomplete.component.ts index 2f886c2232..33af3b9f8f 100644 --- a/frontend/src/app/modules/shared/components/multi-select-autocomplete/multi-select-autocomplete.component.ts +++ b/frontend/src/app/modules/shared/components/multi-select-autocomplete/multi-select-autocomplete.component.ts @@ -58,6 +58,8 @@ export class MultiSelectAutocompleteComponent implements OnChanges { formControl = new FormControl(); @Input() panelWidth = 'auto'; + @Input() + maxDate = new Date(); @Input() multiple = false; @@ -90,7 +92,6 @@ export class MultiSelectAutocompleteComponent implements OnChanges { displayString = ''; selectedCheckboxOptions: Array = []; filterActive = ''; - maxDate: Date; searched = false; constructor( @@ -100,14 +101,15 @@ export class MultiSelectAutocompleteComponent implements OnChanges { @Inject(LOCALE_ID) private locale: string, ) { registerLocaleData(localeDe, 'de', localeDeExtra); - this.maxDate = new Date(); this._adapter.setLocale(locale); } ngOnInit(): void { - this.formControl.valueChanges.pipe(startWith(0), pairwise()).subscribe(([prev, next]: [any, any]) => { - this.theSearchElement = next; - this.searched = true; - }); + if (this.textSearch) { + this.formControl.valueChanges.pipe(startWith(0), pairwise()).subscribe(([prev, next]: [any, any]) => { + this.theSearchElement = next; + this.searched = true; + }); + } if (this.isDate) { this.filterName = 'filterLabelDate'; } else if (this.multiple) { diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index 4f1c648190..a5dc100892 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -163,6 +163,7 @@

{{ 'table.noResultFound' | i18n }}

[disabled]="!tableConfig.sortableColumns?.[column]" mat-header-cell [ngClass]="{ hasQualityAlertsHeader: column === '!' }" + class="table--header--id: column === 'id'" (click)="sortingEventTrigger(column)" > @@ -201,13 +202,16 @@

{{ 'table.noResultFound' | i18n }}

-
+
{{ 'table.noResultFound' | i18n }}

- {{ element[column] | date : 'yyyy-MM-dd' }} + {{ element[column] | autoFormat | i18n }} diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss index bb8cd3063c..2cd7f9f079 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss @@ -96,7 +96,7 @@ justify-content: center; &__icon { - transform: scale(2); + transform: scale(1); } } @@ -317,6 +317,10 @@ tr.error { border-bottom: 1px solid #d2d2d2 !important; } +.table--header--id { + min-width: 300px; +} + .table--header--sort--indicator { margin-left: 5px; } diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.spec.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.spec.ts index 3d489a9139..bf5d4cb8d4 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.spec.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.spec.ts @@ -403,9 +403,8 @@ describe('PartsTableComponent', () => { const filterActivatedList = { id: [], idShort: [], - name: [], - manufacturer: [], - partId: [], + nameAtManufacturer: [], + manufacturerName: [], manufacturerPartId: [], customerPartId: [], classification: [], @@ -414,15 +413,14 @@ describe('PartsTableComponent', () => { semanticDataModel: [], manufacturingDate: [], manufacturingCountry: [], - activeAlerts: [], - activeInvestigations: [], + qualityAlertsInStatusActive: [], + qualityInvestigationsInStatusActive: [], }; const filterActivatedListTwo = { id: 'Test', idShort: [], - name: [], - manufacturer: [], - partId: [], + nameAtManufacturer: [], + manufacturerName: [], manufacturerPartId: [], customerPartId: [], classification: [], @@ -431,15 +429,14 @@ describe('PartsTableComponent', () => { semanticDataModel: [], manufacturingDate: [], manufacturingCountry: [], - activeAlerts: [], - activeInvestigations: [], + qualityAlertsInStatusActive: [], + qualityInvestigationsInStatusActive: [], }; const filterActivatedListThree = { id: 'Test', idShort: [], - name: [], - manufacturer: [], - partId: [], + nameAtManufacturer: [], + manufacturerName: [], manufacturerPartId: [], customerPartId: [], classification: [], @@ -448,8 +445,8 @@ describe('PartsTableComponent', () => { semanticDataModel: [SemanticDataModel.JUSTINSEQUENCE], manufacturingDate: [], manufacturingCountry: [], - activeAlerts: [], - activeInvestigations: [], + qualityAlertsInStatusActive: [], + qualityInvestigationsInStatusActive: [], }; spyOn(componentInstance.filterActivated, 'emit'); diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index d63bd0dcc3..aa03cf6355 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -153,26 +153,20 @@ export class PartsTableComponent implements OnInit { filter: { filterKey: 'Filter', headerKey: 'Filter', isTextSearch: true, option: this.optionTextSearch }, id: { filterKey: 'id', headerKey: 'filterId', isTextSearch: true, option: this.optionTextSearch }, idShort: { filterKey: 'idShort', headerKey: 'filterIdShort', isTextSearch: true, option: this.optionTextSearch }, - name: { filterKey: 'name', headerKey: 'filterName', isTextSearch: true, option: this.optionTextSearch }, nameAtManufacturer: { filterKey: 'nameAtManufacturer', headerKey: 'filterName', isTextSearch: true, option: this.optionTextSearch, - }, - manufacturer: { - filterKey: 'manufacturer', - headerKey: 'filterManufacturer', - isTextSearch: true, - option: this.optionTextSearch, + column: 'name', }, manufacturerName: { filterKey: 'manufacturerName', headerKey: 'filterManufacturer', isTextSearch: true, option: this.optionTextSearch, + column: 'manufacturer', }, - partId: { filterKey: 'partId', headerKey: 'filterPartId', isTextSearch: true, option: this.optionTextSearch }, // Part number / Batch Number / JIS Number manufacturerPartId: { filterKey: 'manufacturerPartId', headerKey: 'filterManufacturerPartId', @@ -214,6 +208,7 @@ export class PartsTableComponent implements OnInit { headerKey: 'filterManufacturingDate', isTextSearch: false, isDate: true, + maxDate: new Date(), option: this.optionTextSearch, }, manufacturingCountry: { @@ -223,16 +218,18 @@ export class PartsTableComponent implements OnInit { option: this.optionTextSearch, }, activeAlerts: { - filterKey: 'activeAlerts', + filterKey: 'qualityAlertsInStatusActive', headerKey: 'filterActiveAlerts', isTextSearch: true, option: this.optionTextSearch, + column: 'activeAlerts', }, activeInvestigations: { - filterKey: 'activeInvestigations', + filterKey: 'qualityInvestigationsInStatusActive', headerKey: 'filterActiveInvestigations', isTextSearch: true, option: this.optionTextSearch, + column: 'activeInvestigations', }, validityPeriodFrom: { filterKey: 'validityPeriodFrom', @@ -240,6 +237,7 @@ export class PartsTableComponent implements OnInit { isTextSearch: false, isDate: true, option: this.optionTextSearch, + maxDate: new Date(), }, validityPeriodTo: { filterKey: 'validityPeriodTo', @@ -247,12 +245,14 @@ export class PartsTableComponent implements OnInit { isTextSearch: false, isDate: true, option: this.optionTextSearch, + maxDate: null, }, - psFunction: { - filterKey: 'psFunction', + function: { + filterKey: 'function', headerKey: 'filterPsFunction', isTextSearch: true, option: this.optionTextSearch, + column: 'psFunction', }, catenaXSiteId: { filterKey: 'catenaXSiteId', @@ -266,6 +266,7 @@ export class PartsTableComponent implements OnInit { isTextSearch: false, isDate: true, option: this.optionTextSearch, + maxDate: new Date(), }, functionValidUntil: { filterKey: 'functionValidUntil', @@ -273,6 +274,7 @@ export class PartsTableComponent implements OnInit { isTextSearch: false, isDate: true, option: this.optionTextSearch, + maxDate: null, }, }; @@ -341,7 +343,6 @@ export class PartsTableComponent implements OnInit { 'idShort', 'name', 'manufacturer', - 'partId', 'manufacturerPartId', 'customerPartId', 'classification', @@ -377,7 +378,6 @@ export class PartsTableComponent implements OnInit { idShort: true, name: true, manufacturer: true, - partId: true, manufacturerPartId: true, customerPartId: true, classification: true, @@ -454,7 +454,7 @@ export class PartsTableComponent implements OnInit { 'semanticDataModel', 'name', 'manufacturer', - 'partId', + 'manufacturerPartId', 'semanticModelId', 'manufacturingDate', 'activeAlerts', @@ -465,7 +465,7 @@ export class PartsTableComponent implements OnInit { semanticDataModel: true, name: true, manufacturer: true, - partId: true, + manufacturerPartId: true, semanticModelId: true, manufacturingDate: true, activeAlerts: true, @@ -495,7 +495,7 @@ export class PartsTableComponent implements OnInit { 'semanticDataModel', 'name', 'manufacturer', - 'partId', + 'manufacturerPartId', 'semanticModelId', 'manufacturingDate', 'activeAlerts', @@ -506,7 +506,7 @@ export class PartsTableComponent implements OnInit { semanticDataModel: true, name: true, manufacturer: true, - partId: true, + manufacturerPartId: true, semanticModelId: true, manufacturingDate: true, activeAlerts: true, @@ -790,9 +790,8 @@ export class PartsTableComponent implements OnInit { this.filterKeyOptions.filter, this.filterKeyOptions.id, this.filterKeyOptions.idShort, - this.filterKeyOptions.name, - this.filterKeyOptions.manufacturer, - this.filterKeyOptions.partId, + this.filterKeyOptions.nameAtManufacturer, + this.filterKeyOptions.manufacturerName, this.filterKeyOptions.manufacturerPartId, this.filterKeyOptions.customerPartId, this.filterKeyOptions.classification, @@ -808,9 +807,8 @@ export class PartsTableComponent implements OnInit { assetAsBuiltFilterFormGroup = { id: new FormControl([]), idShort: new FormControl([]), - name: new FormControl([]), - manufacturer: new FormControl([]), - partId: new FormControl([]), + nameAtManufacturer: new FormControl([]), + manufacturerName: new FormControl([]), manufacturerPartId: new FormControl([]), customerPartId: new FormControl([]), classification: new FormControl([]), @@ -819,22 +817,22 @@ export class PartsTableComponent implements OnInit { semanticDataModel: new FormControl([]), manufacturingDate: new FormControl([]), manufacturingCountry: new FormControl([]), - activeAlerts: new FormControl([]), - activeInvestigations: new FormControl([]), + qualityAlertsInStatusActive: new FormControl([]), + qualityInvestigationsInStatusActive: new FormControl([]), }; assetAsPlannedFilterFormGroup = { id: new FormControl([]), idShort: new FormControl([]), - name: new FormControl([]), - manufacturer: new FormControl([]), + nameAtManufacturer: new FormControl([]), + manufacturerName: new FormControl([]), manufacturerPartId: new FormControl([]), classification: new FormControl([]), semanticDataModel: new FormControl([]), semanticModelId: new FormControl([]), validityPeriodFrom: new FormControl([]), validityPeriodTo: new FormControl([]), - psFunction: new FormControl([]), + function: new FormControl([]), catenaXSiteId: new FormControl([]), functionValidFrom: new FormControl([]), functionValidUntil: new FormControl([]), @@ -866,8 +864,8 @@ export class PartsTableComponent implements OnInit { manufacturerPartId: new FormControl([]), semanticModelId: new FormControl([]), manufacturingDate: new FormControl([]), - activeAlerts: new FormControl([]), - activeInvestigations: new FormControl([]), + qualityAlertsInStatusActive: new FormControl([]), + qualityInvestigationsInStatusActive: new FormControl([]), }; assetAsBuiltCustomerFilterFormGroup = { @@ -878,8 +876,8 @@ export class PartsTableComponent implements OnInit { manufacturerPartId: new FormControl([]), semanticModelId: new FormControl([]), manufacturingDate: new FormControl([]), - activeAlerts: new FormControl([]), - activeInvestigations: new FormControl([]), + qualityAlertsInStatusActive: new FormControl([]), + qualityInvestigationsInStatusActive: new FormControl([]), }; private readonly assetAsPlannedCustomerFilterConfiguration: any[] = [ @@ -928,15 +926,15 @@ export class PartsTableComponent implements OnInit { this.filterKeyOptions.filter, this.filterKeyOptions.id, this.filterKeyOptions.idShort, - this.filterKeyOptions.name, - this.filterKeyOptions.manufacturer, + this.filterKeyOptions.nameAtManufacturer, + this.filterKeyOptions.manufacturerName, this.filterKeyOptions.manufacturerPartId, this.filterKeyOptions.classification, this.filterKeyOptions.semanticDataModel, this.filterKeyOptions.semanticModelId, this.filterKeyOptions.validityPeriodFrom, this.filterKeyOptions.validityPeriodTo, - this.filterKeyOptions.psFunction, + this.filterKeyOptions.function, this.filterKeyOptions.catenaXSiteId, this.filterKeyOptions.functionValidFrom, this.filterKeyOptions.functionValidUntil, diff --git a/frontend/src/app/modules/shared/components/table/table.component.html b/frontend/src/app/modules/shared/components/table/table.component.html index d04312201a..feb239924c 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.html +++ b/frontend/src/app/modules/shared/components/table/table.component.html @@ -19,7 +19,11 @@ SPDX-License-Identifier: Apache-2.0 --> -
+

{{ tableHeader | i18n }}

diff --git a/frontend/src/app/modules/shared/components/table/table.component.ts b/frontend/src/app/modules/shared/components/table/table.component.ts index a6e1ed3a62..6a46fe8f66 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.ts +++ b/frontend/src/app/modules/shared/components/table/table.component.ts @@ -123,6 +123,7 @@ export class TableComponent { @Output() configChanged = new EventEmitter(); @Output() multiSelect = new EventEmitter(); @Output() clickSelectAction = new EventEmitter(); + @Output() filterChange = new EventEmitter(); public readonly dataSource = new MatTableDataSource(); public readonly selection = new SelectionModel(true, []); @@ -241,6 +242,7 @@ export class TableComponent { }; } this.filtering[filterName] = filterAdded; + this.filterChange.emit(); this.configChanged.emit({ page: 0, pageSize: this.pageSize, sorting: this.sorting, filtering: this.filtering }); } diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html index a66d912f9b..6624eed761 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html @@ -23,7 +23,7 @@ {{ onItemCountChange(notifications.data.totalItems) }} -
+
- + diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts index cfbb249db4..e581d13c67 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts @@ -61,6 +61,7 @@ export class NotificationTabComponent implements AfterViewInit { @ViewChild('userTmp') userTemplate: TemplateRef; public tableConfig: TableConfig; + public filteredContent = false; public ngAfterViewInit(): void { const defaultColumns: DisplayColumns[] = ['createdDate', 'description', 'status']; @@ -97,4 +98,8 @@ export class NotificationTabComponent implements AfterViewInit { public onTableConfigChange(tableEventConfig: TableEventConfig): void { this.tableConfigChanged.emit(tableEventConfig); } + + public onFilterChange(): void { + this.filteredContent = true; + } } diff --git a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html index b25c6b59a0..4365087721 100644 --- a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html +++ b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html @@ -43,6 +43,7 @@ (tableConfigChanged)="onReceivedTableConfigChanged.emit($event)" (itemCount)="onItemCountChanged($event)" (selected)="selected.emit($event)" + > @@ -65,6 +66,7 @@ [filterConfig]="queuedAndRequestedFilterConfig" (tableConfigChanged)="onQueuedAndRequestedTableConfigChanged.emit($event)" (selected)="selected.emit($event)" + > diff --git a/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.spec.ts b/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.spec.ts index 3cbb69f3af..19f526793c 100644 --- a/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.spec.ts +++ b/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.spec.ts @@ -29,11 +29,9 @@ describe('FormatPartlistSemanticDataModelToCamelCasePipe', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [ - FormatPartlistSemanticDataModelToCamelCasePipe - ] + providers: [FormatPartlistSemanticDataModelToCamelCasePipe], }); - formatPartlistSemanticDataModelToCamelCasePipe = TestBed.inject(FormatPartlistSemanticDataModelToCamelCasePipe) + formatPartlistSemanticDataModelToCamelCasePipe = TestBed.inject(FormatPartlistSemanticDataModelToCamelCasePipe); }); [ @@ -49,26 +47,34 @@ describe('FormatPartlistSemanticDataModelToCamelCasePipe', () => { option: SemanticDataModel.PARTASPLANNED, expected: 'PartAsPlanned', }, + { + option: SemanticDataModel.JUSTINSEQUENCE, + expected: 'JustInSequence', + }, + { + option: SemanticDataModel.UNKNOWN, + expected: 'Unknown', + }, ].forEach(object => { - - it(`should transform semanticDataModel from ${object.option} to ${object.expected}`, function() { - let partList = [PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT), PartsAssembler.assemblePart(MOCK_part_2, MainAspectType.AS_BUILT)]; + it(`should transform semanticDataModel from ${object.option} to ${object.expected}`, function () { + let partList = [ + PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT), + PartsAssembler.assemblePart(MOCK_part_2, MainAspectType.AS_BUILT), + ]; partList.forEach(part => { - part.semanticDataModel = object.option - }) + part.semanticDataModel = object.option; + }); partList.map(part => { - expect(part.semanticDataModel).toEqual(object.option) - }) + expect(part.semanticDataModel).toEqual(object.option); + }); - - let transformedPartData = formatPartlistSemanticDataModelToCamelCasePipe.transform(partList) + let transformedPartData = formatPartlistSemanticDataModelToCamelCasePipe.transform(partList); transformedPartData.map(part => { - expect(part.semanticDataModel).toEqual(object.expected); - }) - - }) - }) -}) + expect(part.semanticDataModelInCamelCase).toEqual(object.expected); + }); + }); + }); +}); diff --git a/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.ts b/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.ts index 7b08a89f0d..5855e1c6ad 100644 --- a/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.ts +++ b/frontend/src/app/modules/shared/pipes/format-partlist-semantic-data-model-to-camelcase.pipe.ts @@ -20,43 +20,38 @@ import { Pipe, PipeTransform } from '@angular/core'; import { Part, SemanticDataModelInCamelCase } from '@page/parts/model/parts.model'; @Pipe({ - name: 'formatPartlistSemanticDataModelToCamelCase' + name: 'formatPartlistSemanticDataModelToCamelCase', }) export class FormatPartlistSemanticDataModelToCamelCasePipe implements PipeTransform { - transform(partList: Part[] | any[]): Part[] | any[] { - - partList.forEach(part => { - switch (part.semanticDataModel.toString().toLowerCase()) { - - case 'batch': { - part.semanticDataModel = SemanticDataModelInCamelCase.BATCH; - break; - } - case 'serialpart': { - part.semanticDataModel = SemanticDataModelInCamelCase.SERIALPART; - break; - } - case 'partasplanned': { - part.semanticDataModel = SemanticDataModelInCamelCase.PARTASPLANNED; - break; - } - case 'justinsequence': { - part.semanticDataModel = SemanticDataModelInCamelCase.JUSTINSEQUENCE; - break; - } - default: { - part.semanticDataModel = SemanticDataModelInCamelCase.UNKNOWN; - break; - } - + partList.forEach(part => { + switch (part.semanticDataModel.toString().toLowerCase()) { + case 'batch': { + part.semanticDataModelInCamelCase = SemanticDataModelInCamelCase.BATCH; + break; } - return { - ...part, - semanticDataModel: part.semanticDataModel - }; - }); - return partList; - + case 'serialpart': { + part.semanticDataModelInCamelCase = SemanticDataModelInCamelCase.SERIALPART; + break; + } + case 'partasplanned': { + part.semanticDataModelInCamelCase = SemanticDataModelInCamelCase.PARTASPLANNED; + break; + } + case 'justinsequence': { + part.semanticDataModelInCamelCase = SemanticDataModelInCamelCase.JUSTINSEQUENCE; + break; + } + default: { + part.semanticDataModelInCamelCase = SemanticDataModelInCamelCase.UNKNOWN; + break; + } + } + return { + ...part, + semanticDataModelInCamelCase: part.semanticDataModelInCamelCase, + }; + }); + return partList; } } diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 9958782143..548534d29f 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -78,11 +78,11 @@ "name": "Name", "manufacturer": "Hersteller", "partId": "Produktnummer", - "manufacturerPartId": "Hersteller produktnummer", - "customerPartId": "Kunden produktnummer", + "manufacturerPartId": "Hersteller Produktnummer", + "customerPartId": "Kunden Produktnummer", "classification": "Klassifizierung", - "nameAtCustomer": "Kunden produktname", - "nameAtManufacturer": "Hersteller produktname", + "nameAtCustomer": "Kunden Produktname", + "nameAtManufacturer": "Hersteller Produktname", "semanticModelId": "Semantische Modellnummer", "qualityType": "Qualitätsstufe", "manufacturingDate": "Produktionsdatum", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 69ee6e4098..64d96bbf35 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -237,7 +237,7 @@ "queuedAndRequested": "Queued & Sent" }, "status": { - "SENT": "Sent", + "SENT": "Requested", "CANCELED": "Cancelled", "CLOSED": "Closed", "CREATED": "Queued", From 869d87c630a9b6afe2f2bee9e85b889b8a58f9da Mon Sep 17 00:00:00 2001 From: viannbreslerAccso <134606480+viannbreslerAccso@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:01:52 +0100 Subject: [PATCH 02/11] [DO-1579][minor]: Quality investigations page * chore: [DO-1578] stash changes * [DO-1578][major] : Implementation of quality investigations designs and table column settings * chore: [DO-1578] stash progress * chore: [DO-1578] stashing progress * chore: [DO-1578] Fixed merge conflicts * chore: [DO-1578] Fixed failing unit tests * chore: [DO-1579] fix code duplication * chore: [DO-1579] PR feedback * chore: [DO-1579] Fix duplication * chore: [DO-1579] PR feedback * chore: [DO-1578] Fixed gap next to table * chore: [DO-1579] Cleanup --- .../modules/core/user/table-settings.model.ts | 25 ++ .../core/user/table-settings.service.ts | 61 ++++ .../alerts/presentation/alerts.component.html | 36 +- .../alerts/presentation/alerts.component.ts | 4 + .../presentation/dashboard.component.html | 96 +++--- .../presentation/dashboard.component.scss | 4 + .../presentation/dashboard.component.ts | 3 + .../investigations.component.html | 76 +++-- .../investigations.component.scss | 15 + .../presentation/investigations.component.ts | 21 +- .../notification-overview.component.scss | 12 +- .../parts-table/parts-table.component.html | 182 +++++----- .../parts-table/parts-table.component.scss | 26 +- .../parts-table/parts-table.component.ts | 310 ++++++++---------- .../parts-table/table-view-config.model.ts | 25 ++ .../table-settings.component.html | 91 +++++ .../table-settings.component.scss | 191 +++++++++++ .../table-settings.component.spec.ts | 150 +++++++++ .../table-settings.component.ts | 163 +++++++++ .../components/table/table.component.html | 236 +++++++------ .../components/table/table.component.scss | 31 +- .../components/table/table.component.spec.ts | 18 +- .../components/table/table.component.ts | 150 ++++++++- .../shared/components/table/table.model.ts | 44 +-- .../notification-tab.component.html | 41 ++- .../notification-tab.component.scss | 4 + .../notification-tab.component.ts | 6 +- .../presentation/notification.component.html | 38 +-- .../notification.component.spec.ts | 6 +- .../presentation/notification.component.ts | 15 +- .../src/app/modules/shared/shared.module.ts | 6 +- frontend/src/assets/images/drag_indicator.svg | 3 + .../src/assets/images/icons/refresh_icon.svg | 3 + frontend/src/assets/locales/de/common.json | 8 +- frontend/src/assets/locales/en/common.json | 8 +- frontend/src/theme/base.scss | 2 +- 36 files changed, 1543 insertions(+), 567 deletions(-) create mode 100644 frontend/src/app/modules/core/user/table-settings.model.ts create mode 100644 frontend/src/app/modules/core/user/table-settings.service.ts create mode 100644 frontend/src/app/modules/page/investigations/presentation/investigations.component.scss create mode 100644 frontend/src/app/modules/shared/components/parts-table/table-view-config.model.ts create mode 100644 frontend/src/app/modules/shared/components/table-settings/table-settings.component.html create mode 100644 frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss create mode 100644 frontend/src/app/modules/shared/components/table-settings/table-settings.component.spec.ts create mode 100644 frontend/src/app/modules/shared/components/table-settings/table-settings.component.ts create mode 100644 frontend/src/assets/images/drag_indicator.svg create mode 100644 frontend/src/assets/images/icons/refresh_icon.svg diff --git a/frontend/src/app/modules/core/user/table-settings.model.ts b/frontend/src/app/modules/core/user/table-settings.model.ts new file mode 100644 index 0000000000..b9a23db27c --- /dev/null +++ b/frontend/src/app/modules/core/user/table-settings.model.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +export interface TableViewSettings { + columnsForDialog: string[], //--> string list in order how they are sorted + columnSettingsOptions: Map, //--> in order of sorted Rows - also source for table -> convert to list of truth + columnsForTable: string[], //--> string list that saves the column string in the order of the dialog and only if they are true + filterColumnsForTable: string[], +} diff --git a/frontend/src/app/modules/core/user/table-settings.service.ts b/frontend/src/app/modules/core/user/table-settings.service.ts new file mode 100644 index 0000000000..e2f93d02b2 --- /dev/null +++ b/frontend/src/app/modules/core/user/table-settings.service.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Injectable } from '@angular/core'; +import { PartTableType } from '@shared/components/table/table.model'; +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class TableSettingsService { + private settingsKey = 'TableViewSettings'; + private changeEvent = new Subject(); + + storeTableSettings(partTableType: PartTableType, tableSettingsList: any ): void { + // before setting anything, all maps in new tableSettingList should be stringified + Object.keys(tableSettingsList).forEach(tableSetting => { + const newMap = tableSettingsList[tableSetting].columnSettingsOptions; + tableSettingsList[tableSetting].columnSettingsOptions = JSON.stringify(Array.from(newMap.entries())); + }) + localStorage.setItem(this.settingsKey, JSON.stringify(tableSettingsList)); + } + + // this returns whole settings whether empty / not for part / etc. + getStoredTableSettings(): any { + const settingsJson = localStorage.getItem(this.settingsKey); + let settingsObject = settingsJson ? JSON.parse(settingsJson) : null; + if(!settingsObject) return; + + // iterate through all tabletypes and parse columnSettingsOption to a map + Object.keys(settingsObject).forEach(tableSetting => { + settingsObject[tableSetting].columnSettingsOptions = new Map(JSON.parse(settingsObject[tableSetting].columnSettingsOptions)); + + }); + return settingsObject; + } + + emitChangeEvent() { + this.changeEvent.next(); + } + + getEvent() { + return this.changeEvent.asObservable(); + } +} diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html index 522075b8f3..1945f0e098 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html @@ -22,14 +22,15 @@

- Alerts icon + Alerts icon {{ 'pageTitle.alerts' | i18n }}

- [parentFormGroup]="searchFormGroup" [parentControlName]="'alertSearch'" [displayClearButton]="true" - prefixIconColor="accent" - > + prefixIconColor="accent">
- - + + (onReceivedTableConfigChanged)="onReceivedTableConfigChange($event)" (onQueuedAndRequestedTableConfigChanged)="onQueuedAndRequestedTableConfigChange($event)" (selected)="openDetailPage($event)" - [menuActionsConfig]="menuActionsConfig" - >
- No alert + [menuActionsConfig]="menuActionsConfig"> +
+ No alert
{{ 'commonAlert.noAlerts' | i18n }}
{{ 'commonAlert.noAlertsDescription' | i18n }}
-
+
+
- + \ No newline at end of file diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts index 607e2d3d86..929dbf2c5d 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts @@ -27,6 +27,7 @@ import { AlertsFacade } from '@page/alerts/core/alerts.facade'; import { NotificationMenuActionsAssembler } from '@shared/assembler/notificationMenuActions.assembler'; import { NotificationCommonModalComponent } from '@shared/components/notification-common-modal/notification-common-modal.component'; import { + PartTableType, MenuActionConfig, TableEventConfig, TableHeaderSort, @@ -44,6 +45,7 @@ import { NotificationStatus } from '@shared/model/notification.model'; @Component({ selector: 'app-alerts', templateUrl: './alerts.component.html', + styleUrls: ['./alerts.component.scss'], }) export class AlertsComponent { @ViewChild(NotificationCommonModalComponent) notificationCommonModalComponent: NotificationCommonModalComponent; @@ -149,6 +151,8 @@ export class AlertsComponent { private pagination: TableEventConfig = { page: 0, pageSize: 50, sorting: ['createdDate', 'desc'] }; + protected readonly PartTableType = PartTableType; + constructor( public readonly helperService: AlertHelperService, private readonly alertsFacade: AlertsFacade, diff --git a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html index 266809e44c..ff911d3952 100644 --- a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html +++ b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.html @@ -21,8 +21,7 @@
- + ">
- + ">
- + ">
- + ">
@@ -94,11 +86,15 @@

- Investigations icon + Investigations icon {{ 'pageTitle.investigations' | i18n }} {{ 'table.latestFive' | i18n }}

- +
remove_red_eye {{ 'commonInvestigation.viewAll' | i18n }} @@ -108,21 +104,19 @@

- + (selected)="onNotificationSelected($event, true)">
- No quality investigations icon + alt="No quality investigations icon" />
{{ 'commonInvestigation.noInvestigations' | i18n }}
{{ 'commonInvestigation.noInvestigationsDescription' | i18n }}
@@ -137,11 +131,15 @@

- Alert icon + Alert icon {{ 'pageTitle.alerts' | i18n }} {{ 'table.latestFive' | i18n }}

- +
remove_red_eye {{ 'commonAlert.viewAll' | i18n }} @@ -152,17 +150,18 @@

- + [optionalColumns]="[ 'severity', 'createdBy']" + (selected)="onNotificationSelected($event, false)">
- No alert + No alert
{{ 'commonAlert.noAlerts' | i18n }}
{{ 'commonAlert.noAlertsDescription' | i18n }}
@@ -171,30 +170,27 @@

- - + + [stats]="numbers?.data"> - - + + [stats]="0"> - - + - -

+ [stats]="''"> + \ No newline at end of file diff --git a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.scss b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.scss index b8bcba7fad..2b88916233 100644 --- a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.scss +++ b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.scss @@ -74,4 +74,8 @@ font-size: 16px; line-height: 20px; letter-spacing: 0.1px; +} + +.notications-tab { + padding-bottom: 10px; } \ No newline at end of file diff --git a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.ts b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.ts index cf0d7b678c..55e8b89408 100644 --- a/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.ts +++ b/frontend/src/app/modules/page/dashboard/presentation/dashboard.component.ts @@ -27,6 +27,7 @@ import { View } from '@shared/model/view.model'; import { CloseNotificationModalComponent } from '@shared/modules/notification/modal/close/close-notification-modal.component'; import { Observable } from 'rxjs'; import { DashboardFacade } from '../abstraction/dashboard.facade'; +import { PartTableType } from '@shared/components/table/table.model'; @Component({ selector: 'app-dashboard', @@ -49,6 +50,8 @@ export class DashboardComponent implements OnInit, OnDestroy { public readonly alertsLink: string; public readonly alertsParams: Record; + protected readonly PartTableType = PartTableType; + constructor(private readonly dashboardFacade: DashboardFacade, private readonly router: Router) { this.numberOfMyParts$ = this.dashboardFacade.numberOfMyParts$; this.numberOfOtherParts$ = this.dashboardFacade.numberOfOtherParts$; diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.html b/frontend/src/app/modules/page/investigations/presentation/investigations.component.html index 7e0684f538..92204985d7 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.html +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.html @@ -19,24 +19,58 @@ SPDX-License-Identifier: Apache-2.0 --> - - - + + +
+
+

+ Investigation icon + {{ 'pageTitle.investigations' | i18n }} +

+
+
+
+ +
+
+ + + +
+ No quality investigations icon +
{{ 'commonInvestigation.noInvestigations' | i18n }}
+
{{ 'commonInvestigation.noInvestigationsDescription' | i18n }}
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.scss b/frontend/src/app/modules/page/investigations/presentation/investigations.component.scss new file mode 100644 index 0000000000..f80cd2fc12 --- /dev/null +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.scss @@ -0,0 +1,15 @@ +.no-investigations { + @apply font-semiBold; + text-align: center; + font-size: 24px; + line-height: 20px; + letter-spacing: 0.1px; +} + +.description { + @apply font-regular; + text-align: center; + font-size: 16px; + line-height: 20px; + letter-spacing: 0.1px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts index 6d0224fba8..8dfa59bf8f 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts @@ -26,21 +26,25 @@ import { InvestigationDetailFacade } from '@page/investigations/core/investigati import { InvestigationHelperService } from '@page/investigations/core/investigation-helper.service'; import { NotificationMenuActionsAssembler } from '@shared/assembler/notificationMenuActions.assembler'; import { NotificationCommonModalComponent } from '@shared/components/notification-common-modal/notification-common-modal.component'; -import { MenuActionConfig, TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; +import { MenuActionConfig, PartTableType, TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; import { TableSortingUtil } from '@shared/components/table/tableSortingUtil'; import { NotificationTabInformation } from '@shared/model/notification-tab-information'; import { Notification, NotificationStatusGroup } from '@shared/model/notification.model'; import { TranslationContext } from '@shared/model/translation-context.model'; import { Subscription } from 'rxjs'; import { InvestigationsFacade } from '../core/investigations.facade'; +import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'app-investigations', templateUrl: './investigations.component.html', + styleUrls: ['./investigations.component.scss'], }) export class InvestigationsComponent { @ViewChild(NotificationCommonModalComponent) notificationCommonModalComponent: NotificationCommonModalComponent; + public searchFormGroup = new FormGroup({}); + public searchControl: FormControl; public readonly investigationsReceived$; public readonly investigationsQueuedAndRequested$; @@ -52,7 +56,9 @@ export class InvestigationsComponent { private paramSubscription: Subscription; - private pagination: TableEventConfig = { page: 0, pageSize: 50, sorting: [ 'createdDate', 'desc' ] }; + private pagination: TableEventConfig = { page: 0, pageSize: 50, sorting: ['createdDate', 'desc'] }; + + protected readonly PartTableType = PartTableType; constructor( public readonly helperService: InvestigationHelperService, @@ -71,7 +77,6 @@ export class InvestigationsComponent { window.addEventListener('keyup', (event) => { this.ctrlKeyState = event.ctrlKey; }); - } public ngOnInit(): void { @@ -80,6 +85,10 @@ export class InvestigationsComponent { this.investigationsFacade.setReceivedInvestigation(this.pagination.page, this.pagination.pageSize, this.investigationReceivedSortList); this.investigationsFacade.setQueuedAndRequestedInvestigations(this.pagination.page, this.pagination.pageSize, this.investigationQueuedAndRequestedSortList); }); + + const searchControlName = 'investigationSearch'; + this.searchFormGroup.addControl(searchControlName, new FormControl([])); + this.searchControl = this.searchFormGroup.get(searchControlName) as unknown as FormControl; } public ngAfterViewInit(): void { @@ -109,13 +118,17 @@ export class InvestigationsComponent { const { link } = getRoute(INVESTIGATION_BASE_ROUTE); const tabIndex = this.route.snapshot.queryParamMap.get('tabIndex'); const tabInformation: NotificationTabInformation = { tabIndex: tabIndex, pageNumber: this.pagination.page }; - this.router.navigate([ `/${ link }/${ notification.id }` ], { queryParams: tabInformation }); + this.router.navigate([`/${link}/${notification.id}`], { queryParams: tabInformation }); } public handleConfirmActionCompletedEvent() { this.ngOnInit(); } + public triggerSearch(): void { + // TODO: implement search + } + private setTableSortingList(sorting: TableHeaderSort, notificationTable: NotificationStatusGroup): void { const tableSortList = notificationTable === NotificationStatusGroup.RECEIVED ? this.investigationReceivedSortList : this.investigationQueuedAndRequestedSortList; diff --git a/frontend/src/app/modules/shared/components/notification-overview/notification-overview.component.scss b/frontend/src/app/modules/shared/components/notification-overview/notification-overview.component.scss index 040e064531..4d95eeb884 100644 --- a/frontend/src/app/modules/shared/components/notification-overview/notification-overview.component.scss +++ b/frontend/src/app/modules/shared/components/notification-overview/notification-overview.component.scss @@ -67,11 +67,13 @@ } &--CREATED { - @apply bg-createdLight text-white; + border: solid 1px; + border-color: #F58220; + @apply bg-queuedLight text-queuedDark; } &--SENT { - @apply bg-pendingLight text-white; + @apply bg-requestedLight text-requestedDark; } &--ACCEPTED { @@ -89,10 +91,4 @@ &--DECLINED { @apply bg-declinedLight text-declinedDark; } - - &--QUEUED { - border: solid 1px; - border-color: #F58220; - @apply bg-queuedLight text-queuedDark; - } } \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index a5dc100892..b34d97f4b5 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -25,16 +25,16 @@
- + variant="flat">
- Create alert icon + Create alert icon
{{ 'page.selectedParts.action' | i18n }}
@@ -45,9 +45,8 @@
-
- +
- + + class="table--header--row"> - - + - + data-testid="table-component--body-row"> - + mat-row> - - + - + + + + + + + + + + + - - - + - -
- + + showFirstLastButtons>
+
- build + build

{{ 'table.noResultFound' | i18n }}

{{ 'table.tryAgain' | i18n }}

+ * + +
+
+
-
- +
+ + data-testid="select-all--test-id">
- + + data-testid="select-one--test-id"> {{ 'table.noResultFound' | i18n }} mat-header-cell [ngClass]="{ hasQualityAlertsHeader: column === '!' }" class="table--header--id: column === 'id'" - (click)="sortingEventTrigger(column)" - > + (click)="sortingEventTrigger(column)">
{{ - tableConfig?.header?.[column] | i18n }} -
- + sorting arrow - + sorting arrow + class="table--header--cell--content--sort--dsc" />
-
- + filterIcon + class="table--header--row__menu--dropdown" />
- + -
- + + [formControl]="filterFormGroup.controls[filter.filterKey]">
@@ -224,14 +234,18 @@

{{ 'table.noResultFound' | i18n }}

-
+
{{ i + 1 + '.' }}{{ item[1] === 'asc' ? '↑' : item[1] === 'desc' ? '↓' : '' }}
+
@@ -239,19 +253,18 @@

{{ 'table.noResultFound' | i18n }}

- + [ngTemplateOutletContext]="{ value: element[column], row: element }">
- +
!
@@ -267,6 +280,7 @@

{{ 'table.noResultFound' | i18n }}

{{ pureColumn }} - + {{ value | autoFormat | i18n }} - + \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss index 2cd7f9f079..c5d6309d2d 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss @@ -23,6 +23,14 @@ max-width: 100%; } +.settings-icon { + padding-left: 13px; +} + +.table-menu { + width: 40px; +} + .table--select-all { display: inline-flex; align-items: center; @@ -30,7 +38,7 @@ &__menu { background: rgba(0, 0, 0, 0.04); - & > .table--select-all__dropdown:hover { + &>.table--select-all__dropdown:hover { background: none; } } @@ -153,6 +161,7 @@ height: 405px; width: 100%; overflow: auto !important; + scrollbar-width: thin; } .table--notification--cell { @@ -239,10 +248,12 @@ tr.error { } } } + .menu-input-field { border-radius: 10px; box-shadow: 0px 15px 25px 0px #3333330d, -10px -10px 15px 0px #ffffff1a, 0px 0px 25px 0px #33333308; } + .menu-input-field .mat-mdc-menu-content { padding: 0; } @@ -250,6 +261,7 @@ tr.error { .mdc-menu-surface { border-radius: 10px !important; } + .expand-row { height: 0; } @@ -271,6 +283,7 @@ tr.error { font-size: 16px; width: 100%; } + .table--header--row .mat-sort-header-content { width: 100%; } @@ -283,17 +296,21 @@ tr.error { display: flex; justify-content: space-between; width: 100%; + &--content { display: flex; flex-direction: row; align-items: center; + &--sort { width: 24px; + &--asc { width: 10px; transform: rotate(180deg); margin: 9.5px 7px; } + &--dsc { width: 10px; margin: 9.5px 7px; @@ -307,6 +324,7 @@ tr.error { display: flex; justify-content: center; align-content: center; + &--dropdown { width: 12px; } @@ -348,8 +366,8 @@ tr.error { padding: 0 !important; } -.checkbox + .mdc-data-table__header-cell, -.checkbox + .mdc-data-table__cell { +.checkbox+.mdc-data-table__header-cell, +.checkbox+.mdc-data-table__cell { @apply text-secondary; padding: 0 !important; } @@ -373,4 +391,4 @@ tr.error { .mdc-tooltip--multiline { white-space: pre-line; } -} +} \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index aa03cf6355..abbfea3f60 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -48,6 +48,10 @@ import { import { addSelectedValues, removeSelectedValues } from '@shared/helper/table-helper'; import { isDateFilter } from '@shared/helper/filter-helper'; import i18next from 'i18next'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { TableSettingsService } from '@core/user/table-settings.service'; +import { TableViewConfig } from './table-view-config.model'; +import { TableSettingsComponent } from '../table-settings/table-settings.component'; @Component({ selector: 'app-parts-table', @@ -59,8 +63,7 @@ export class PartsTableComponent implements OnInit { @ViewChild(MatSort) sort: MatSort; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('tableElement', { read: ElementRef }) tableElementRef: ElementRef; - @ViewChildren(MultiSelectAutocompleteComponent) - multiSelectAutocompleteComponents: QueryList; + @ViewChildren(MultiSelectAutocompleteComponent) multiSelectAutocompleteComponents: QueryList; @Input() multiSelectActive = false; @Input() labelId: string; @@ -353,6 +356,7 @@ export class PartsTableComponent implements OnInit { 'manufacturingCountry', 'activeAlerts', 'activeInvestigations', + 'menu' ]; private readonly displayedColumnsAsPlannedForTable: string[] = [ @@ -371,6 +375,7 @@ export class PartsTableComponent implements OnInit { 'catenaXSiteId', 'functionValidFrom', 'functionValidUntil', + 'menu' ]; private readonly sortableColumnsAsBuilt: Record = { @@ -533,17 +538,6 @@ export class PartsTableComponent implements OnInit { private pageSize: number; private sorting: TableHeaderSort; - ngOnInit() { - this.handleAsBuiltTableType(); - this.handleAsPlannedTableType(); - this.handleAsDesignedTableType(); - this.handleAsOrderedTableType(); - this.handleAsRecycledTableType(); - this.handleAsSupportedTableType(); - if (this.tableConfig.sortableColumns) { - this.setupSortingEvent(); - } - } ngAfterViewInit() { this.paginator._intl.itemsPerPageLabel = 'Show'; @@ -566,35 +560,22 @@ export class PartsTableComponent implements OnInit { this.filterActivated.emit(filterValues); } - private handleAsPlannedTableType(): void { - switch (this.tableType) { - case PartTableType.AS_PLANNED_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedCustomerForTable, - this.displayedColumnsAsPlannedCustomer, - this.sortableColumnsAsPlannedCustomer, - this.assetAsPlannedCustomerFilterConfiguration, - this.assetAsPlannedCustomerFilterFormGroup, - ); - break; - case PartTableType.AS_PLANNED_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedForTable, - this.displayedColumnsAsPlanned, - this.sortableColumnsAsPlanned, - this.assetAsPlannedFilterConfiguration, - this.assetAsPlannedFilterFormGroup, - ); - break; - case PartTableType.AS_PLANNED_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedSupplierForTable, - this.displayedColumnsAsPlannedSupplier, - this.sortableColumnsAsPlannedSupplier, - this.assetAsPlannedSupplierFilterConfiguration, - this.assetAsPlannedSupplierFilterFormGroup, - ); - break; + constructor(private readonly tableViewSettingsService: TableSettingsService, private dialog: MatDialog) { } + + public defaultColumns: string[]; + + private tableViewConfig: TableViewConfig; + + ngOnInit() { + this.initializeTableViewSettings() + + this.tableViewSettingsService.getEvent().subscribe(() => { + this.setupTableViewSettings(); + }) + this.setupTableViewSettings(); + + if (this.tableConfig.sortableColumns) { + this.setupSortingEvent(); } } @@ -613,6 +594,7 @@ export class PartsTableComponent implements OnInit { ): any { const headerKey = 'table.column'; this.tableConfig = { + ...this.tableConfig, displayedColumns: displayedColumnsForTable, header: CreateHeaderFromColumns(displayedColumnsForTable, headerKey), sortableColumns: sortableColumns, @@ -626,162 +608,113 @@ export class PartsTableComponent implements OnInit { } } - private handleAsBuiltTableType(): void { - switch (this.tableType) { - case PartTableType.AS_BUILT_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsBuiltForTable, - this.displayedColumnsAsBuilt, - this.sortableColumnsAsBuilt, - this.assetAsBuiltFilterConfiguration, - this.assetAsBuiltFilterFormGroup, - ); - break; - case PartTableType.AS_BUILT_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsBuiltCustomerForTable, - this.displayedColumnsAsBuiltCustomer, - this.sortableColumnsAsBuiltCustomer, - this.assetAsBuiltCustomerFilterConfiguration, - this.assetAsBuiltCustomerFilterFormGroup, - ); - break; - case PartTableType.AS_BUILT_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsBuiltSupplierForTable, - this.displayedColumnsAsBuiltSupplier, - this.sortableColumnsAsBuiltSupplier, - this.assetAsBuiltSupplierFilterConfiguration, - this.assetAsBuiltSupplierFilterFormGroup, - ); - break; + private setupTableViewSettings() { + const settingsList = this.tableViewSettingsService.getStoredTableSettings(); + // check if there are table settings list + if (settingsList) { + // if yes, check if there is a table-setting for this table type + if (settingsList[this.tableType]) { + // if yes, get the effective displayedcolumns from the settings and set the tableconfig after it. + this.setupTableConfigurations(settingsList[this.tableType].columnsForTable, settingsList[this.tableType].filterColumnsForTable, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + } else { + // if no, create new a table setting for this.tabletype and put it into the list. Additionally, intitialize default table configuration + settingsList[this.tableType] = this.getSettingsList(); + this.tableViewSettingsService.storeTableSettings(this.tableType, settingsList); + this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + } + } else { + // if no, create new list and a settings entry for this.tabletype with default values and set correspondingly the tableconfig + const newTableSettingsList = { + [this.tableType]: { + columnsForDialog: this.tableViewConfig.displayedColumnsForTable, + columnSettingsOptions: this.getDefaultColumnVisibilityMap(), + columnsForTable: this.tableViewConfig.displayedColumnsForTable, + filterColumnsForTable: this.tableViewConfig.displayedColumns + } + } + this.tableViewSettingsService.storeTableSettings(this.tableType, newTableSettingsList); + this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); } } - private handleAsOrderedTableType(): void { - switch (this.tableType) { - case PartTableType.AS_ORDERED_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedCustomerForTable, - this.displayedColumnsAsPlannedCustomer, - this.sortableColumnsAsPlannedCustomer, - this.assetAsPlannedCustomerFilterConfiguration, - this.assetAsPlannedCustomerFilterFormGroup, - ); - break; - case PartTableType.AS_ORDERED_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedForTable, - this.displayedColumnsAsPlanned, - this.sortableColumnsAsPlanned, - this.assetAsPlannedFilterConfiguration, - this.assetAsPlannedFilterFormGroup, - ); - break; - case PartTableType.AS_ORDERED_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedSupplierForTable, - this.displayedColumnsAsPlannedSupplier, - this.sortableColumnsAsPlannedSupplier, - this.assetAsPlannedSupplierFilterConfiguration, - this.assetAsPlannedSupplierFilterFormGroup, - ); - break; + private getSettingsList(): any { + return { + columnsForDialog: this.tableViewConfig.displayedColumnsForTable, + columnSettingsOptions: this.getDefaultColumnVisibilityMap(), + columnsForTable: this.tableViewConfig.displayedColumnsForTable, + filterColumnsForTable: this.tableViewConfig.displayedColumns } } - private handleAsDesignedTableType(): void { - switch (this.tableType) { - case PartTableType.AS_DESIGNED_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedCustomerForTable, - this.displayedColumnsAsPlannedCustomer, - this.sortableColumnsAsPlannedCustomer, - this.assetAsPlannedCustomerFilterConfiguration, - this.assetAsPlannedCustomerFilterFormGroup, - ); - break; - case PartTableType.AS_DESIGNED_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedForTable, - this.displayedColumnsAsPlanned, - this.sortableColumnsAsPlanned, - this.assetAsPlannedFilterConfiguration, - this.assetAsPlannedFilterFormGroup, - ); - break; - case PartTableType.AS_DESIGNED_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedSupplierForTable, - this.displayedColumnsAsPlannedSupplier, - this.sortableColumnsAsPlannedSupplier, - this.assetAsPlannedSupplierFilterConfiguration, - this.assetAsPlannedSupplierFilterFormGroup, - ); - break; + private getDefaultColumnVisibilityMap(): Map { + const initialColumnMap = new Map(); + for (const column of this.tableViewConfig.displayedColumnsForTable) { + initialColumnMap.set(column, true); } + return initialColumnMap; } - private handleAsSupportedTableType(): void { + private initializeTableViewSettings(): void { switch (this.tableType) { - case PartTableType.AS_SUPPORTED_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedCustomerForTable, - this.displayedColumnsAsPlannedCustomer, - this.sortableColumnsAsPlannedCustomer, - this.assetAsPlannedCustomerFilterConfiguration, - this.assetAsPlannedCustomerFilterFormGroup, - ); + case PartTableType.AS_PLANNED_CUSTOMER: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsPlannedCustomer, + displayedColumnsForTable: this.displayedColumnsAsPlannedCustomerForTable, + filterConfiguration: this.assetAsPlannedCustomerFilterConfiguration, + filterFormGroup: this.assetAsPlannedCustomerFilterFormGroup, + sortableColumns: this.sortableColumnsAsPlannedCustomer + } break; + + // TODO add other table view configurations when they are implemented + case PartTableType.AS_ORDERED_OWN: case PartTableType.AS_SUPPORTED_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedForTable, - this.displayedColumnsAsPlanned, - this.sortableColumnsAsPlanned, - this.assetAsPlannedFilterConfiguration, - this.assetAsPlannedFilterFormGroup, - ); + case PartTableType.AS_RECYCLED_OWN: + case PartTableType.AS_DESIGNED_OWN: + case PartTableType.AS_PLANNED_OWN: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsPlanned, + displayedColumnsForTable: this.displayedColumnsAsPlannedForTable, + filterConfiguration: this.assetAsPlannedFilterConfiguration, + filterFormGroup: this.assetAsPlannedFilterFormGroup, + sortableColumns: this.sortableColumnsAsPlanned + } break; - case PartTableType.AS_SUPPORTED_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedSupplierForTable, - this.displayedColumnsAsPlannedSupplier, - this.sortableColumnsAsPlannedSupplier, - this.assetAsPlannedSupplierFilterConfiguration, - this.assetAsPlannedSupplierFilterFormGroup, - ); + case PartTableType.AS_PLANNED_SUPPLIER: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsPlannedSupplier, + displayedColumnsForTable: this.displayedColumnsAsPlannedSupplierForTable, + filterConfiguration: this.assetAsPlannedSupplierFilterConfiguration, + filterFormGroup: this.assetAsPlannedSupplierFilterFormGroup, + sortableColumns: this.sortableColumnsAsPlannedSupplier + } break; - } - } - - private handleAsRecycledTableType(): void { - switch (this.tableType) { - case PartTableType.AS_RECYCLED_CUSTOMER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedCustomerForTable, - this.displayedColumnsAsPlannedCustomer, - this.sortableColumnsAsPlannedCustomer, - this.assetAsPlannedCustomerFilterConfiguration, - this.assetAsPlannedCustomerFilterFormGroup, - ); + case PartTableType.AS_BUILT_OWN: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsBuilt, + displayedColumnsForTable: this.displayedColumnsAsBuiltForTable, + filterConfiguration: this.assetAsBuiltFilterConfiguration, + filterFormGroup: this.assetAsBuiltFilterFormGroup, + sortableColumns: this.sortableColumnsAsBuilt + } break; - case PartTableType.AS_RECYCLED_OWN: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedForTable, - this.displayedColumnsAsPlanned, - this.sortableColumnsAsPlanned, - this.assetAsPlannedFilterConfiguration, - this.assetAsPlannedFilterFormGroup, - ); + case PartTableType.AS_BUILT_CUSTOMER: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsBuiltCustomer, + displayedColumnsForTable: this.displayedColumnsAsBuiltCustomerForTable, + filterConfiguration: this.assetAsBuiltCustomerFilterConfiguration, + filterFormGroup: this.assetAsBuiltCustomerFilterFormGroup, + sortableColumns: this.sortableColumnsAsBuiltCustomer + } break; - case PartTableType.AS_RECYCLED_SUPPLIER: - this.setupTableConfigurations( - this.displayedColumnsAsPlannedSupplierForTable, - this.displayedColumnsAsPlannedSupplier, - this.sortableColumnsAsPlannedSupplier, - this.assetAsPlannedSupplierFilterConfiguration, - this.assetAsPlannedSupplierFilterFormGroup, - ); + case PartTableType.AS_BUILT_SUPPLIER: + this.tableViewConfig = { + displayedColumns: this.displayedColumnsAsBuiltSupplier, + displayedColumnsForTable: this.displayedColumnsAsBuiltSupplierForTable, + filterConfiguration: this.assetAsBuiltSupplierFilterConfiguration, + filterFormGroup: this.assetAsBuiltSupplierFilterFormGroup, + sortableColumns: this.sortableColumnsAsBuiltSupplier + } break; } } @@ -940,6 +873,19 @@ export class PartsTableComponent implements OnInit { this.filterKeyOptions.functionValidUntil, ]; + openDialog(): void { + const config = new MatDialogConfig(); + config.autoFocus = false; + config.data = { + title: "table.tableSettings.title", + panelClass: "custom", + tableType: this.tableType, + defaultColumns: this.tableViewConfig.displayedColumnsForTable, + defaultFilterColumns: this.tableViewConfig.displayedColumns + }; + this.dialog.open(TableSettingsComponent, config) + } + public areAllRowsSelected(): boolean { return this.dataSource.data.every(data => this.isSelected(data)); } diff --git a/frontend/src/app/modules/shared/components/parts-table/table-view-config.model.ts b/frontend/src/app/modules/shared/components/parts-table/table-view-config.model.ts new file mode 100644 index 0000000000..d2b0600e15 --- /dev/null +++ b/frontend/src/app/modules/shared/components/parts-table/table-view-config.model.ts @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +export interface TableViewConfig { + displayedColumnsForTable: string[], + displayedColumns: string[], + sortableColumns: Record, + filterConfiguration: any[], + filterFormGroup: any +} diff --git a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.html b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.html new file mode 100644 index 0000000000..f000a957ca --- /dev/null +++ b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.html @@ -0,0 +1,91 @@ + +
+
+
+
{{title | i18n}}
+
+ X +
+
+
+
+
+ {{'table.tableSettings.selectAll' | i18n}} + + < +
+ +
+ +
+ ↓ + + {{'table.column.' + item | i18n}} +
+
+
+
+
+
+
+
+ + +
\ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss new file mode 100644 index 0000000000..43f9e92e6a --- /dev/null +++ b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss @@ -0,0 +1,191 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +@import '../../../../../theme/base.scss'; +@tailwind base; + +.container { + width: 240px; + overflow: hidden !important; +} + +.dialog--item--container { + width: 100%; + padding: 3px; + display: flex; + flex-direction: row; + align-items: center; + box-sizing: border-box; +} + +.dialog--item--container_static { + width: 100%; + padding: 3px; + display: flex; + flex-direction: row; + align-items: center; + box-sizing: border-box; +} + +.dialog--content { + display: flex; + flex-direction: column; + + .table--header--tooltip { + .mdc-tooltip__surface { + background-color: rgb(48, 48, 48); + @apply font-medium; + color: white; + max-width: 300px; + padding: 15px; + font-size: 14px; + line-height: 20px; + + } + + .mdc-tooltip--multiline { + white-space: pre-line; + } + } + + .select-all-container { + display: flex; + justify-content: space-between; + padding-left: 16px; + } + + .placeholder { + background: #ccc; + border: dotted 3px #999; + min-height: 24px; + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); + } + + .dialog--header--container { + display: flex; + padding-bottom: 24px; + justify-content: space-between; + } + + .mat-icon { + width: 32px; + height: 32px; + font-size: 30px; + margin-top: 4px; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + } + + .dialog--content--menu--order { + display: flex; + gap: 8px; + margin-right: 12px; + } + + .dialog--header--text { + @extend .title-small; + font-weight: 600; + font-size: 16px; + text-align: left; + justify-self: center; + color: black; + } + + .dialog--columns--container { + font-size: 18px; + } + + .divider { + color: #D2D2D2; + } + + .dialog--columns--columns--list .mdc-form-field { + height: 24px !important; + } + + .drag-icon { + margin-left: 5px; + opacity: 0; + } + + .dialog--item--container:hover { + background-color: rgb(237, 240, 244); + } + + .dialog--item--container:hover img { + opacity: 1; + } + + .dialog--columns--columns--list { + @extend .body-xsmall; + font-size: 12px; + line-height: 13.2px; + font-weight: 400; + } + + // .dialog--columns--columns--list:hover { + // background-color: rgb(237, 240, 244); + // } +} + +.dialog--actions--container { + background-color: rgb(237, 240, 244); + width: 100%; + display: flex; + justify-content: center; + + .dialog--actions--save--button { + display: inline-flex; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + position: relative; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + outline: 0px; + border: 0px; + margin: 0px; + cursor: pointer; + user-select: none; + vertical-align: middle; + appearance: none; + text-decoration: none; + font-family: LibreFranklin, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-weight: 500; + line-height: 1.5; + color: rgb(255, 255, 255); + min-width: 64px; + transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + + background-color: rgb(15, 113, 203); + border-radius: 50px; + font-size: 18px; + padding: 14px 32px; + box-shadow: rgba(15, 113, 203, 0.4) 0px 0px 0px 3px; + } + + .dialog--actions--save--button:hover, + .dialog--actions--save--button:active, + .dialog--actions--save--button:focus { + background-color: rgb(13, 85, 175) + } + +} \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.spec.ts b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.spec.ts new file mode 100644 index 0000000000..73a3d014c1 --- /dev/null +++ b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.spec.ts @@ -0,0 +1,150 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +import { APP_INITIALIZER } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { TableSettingsService } from '@core/user/table-settings.service'; +import { PartTableType } from '@shared/components/table/table.model'; +import { SharedModule } from '@shared/shared.module'; +import { I18NEXT_SERVICE, I18NextModule, ITranslationService } from 'angular-i18next'; +import { TableSettingsComponent } from './table-settings.component'; + +describe('TableSettingsComponent', () => { + let component: TableSettingsComponent; + let fixture: ComponentFixture; + let tableSettingsService: TableSettingsService; + + beforeEach(async () => { + const tableSettingsServiceSpy = jasmine.createSpyObj('TableSettingsService', [ + 'getStoredTableSettings', + 'storeTableSettings', + 'emitChangeEvent', + 'getEvent', + ]); + tableSettingsServiceSpy.getStoredTableSettings.and.callFake(() => { + return { + [PartTableType.AS_BUILT_OWN]: { + columnSettingsOptions: new Map(), + columnsForDialog: ['column1', 'column2'], + columnsForTable: ['column1'], + filterColumnsForTable: ['filterColumn1'], + }, + }; + }); + + tableSettingsServiceSpy.storeTableSettings.and.callFake((partTableType, tableSettingsList) => { + return; + }); + + TestBed.configureTestingModule({ + declarations: [TableSettingsComponent], + imports: [ + SharedModule, + I18NextModule.forRoot(), + ], + providers: [ + { + provide: MatDialogRef, + useValue: { + close: () => { }, + }, + }, + { + provide: MAT_DIALOG_DATA, + useValue: { + title: 'Test Title', + panelClass: 'test-dialog', + tableType: PartTableType.AS_BUILT_OWN, + defaultColumns: ['column1', 'column2'], + defaultFilterColumns: ['filterColumn1', 'filterColumn2'], + }, + }, + { + provide: TableSettingsService, + useValue: tableSettingsServiceSpy, + }, + { + provide: APP_INITIALIZER, + useFactory: (i18next: ITranslationService) => { + return () => + i18next.init({ + lng: 'en', + supportedLngs: ['en', 'de'], + resources: {}, + }); + }, + deps: [I18NEXT_SERVICE], + multi: true, + }, + ], + }); + + await TestBed.compileComponents(); + fixture = TestBed.createComponent(TableSettingsComponent); + component = fixture.componentInstance; + tableSettingsService = TestBed.inject(TableSettingsService); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize the component properties', () => { + // Assert that component properties are correctly initialized based on MAT_DIALOG_DATA + expect(component.title).toEqual('Test Title'); + expect(component.panelClass).toEqual('test-dialog'); + expect(component.tableType).toEqual(PartTableType.AS_BUILT_OWN); + expect(component.defaultColumns).toEqual(['column1', 'column2']); + expect(component.defaultFilterColumns).toEqual(['filterColumn1', 'filterColumn2']); + expect(component.isCustomerTable).toEqual(false); + }); + + it('should call save method and update tableSettingsService', () => { + const columnOptions = new Map(); + columnOptions.set('column1', true); + + component.handleCheckBoxChange('column1', true); + component.save(); + + // Check that setColumnVisibilitySettings was called with the updated settings + expect(tableSettingsService.storeTableSettings).toHaveBeenCalledWith(PartTableType.AS_BUILT_OWN, { + [PartTableType.AS_BUILT_OWN]: { + columnSettingsOptions: columnOptions, + columnsForDialog: ['column1', 'column2'], + columnsForTable: ['column1'], + filterColumnsForTable: ['filterColumn1'], + }, + }); + }); + + it('should reset columns', () => { + component.dialogColumns = ['column1', 'column2', 'column3']; + + component.resetColumns(); + + expect(component.dialogColumns).toEqual(['column1', 'column2']); + expect(component.selectAllSelected).toBe(true); + }); + + it('should close the dialog', () => { + spyOn(component.dialogRef, 'close'); + component.dialogRef.close() + expect(component.dialogRef.close).toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.ts b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.ts new file mode 100644 index 0000000000..d1d8c7e09d --- /dev/null +++ b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.ts @@ -0,0 +1,163 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { Component, EventEmitter, Inject, Output, ViewEncapsulation } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { TableViewSettings } from '@core/user/table-settings.model'; +import { TableSettingsService } from '@core/user/table-settings.service'; +import { PartTableType } from '@shared/components/table/table.model'; +import { + CdkDragDrop, + moveItemInArray, + transferArrayItem, +} from '@angular/cdk/drag-drop'; + +@Component({ + selector: 'app-table-settings', + templateUrl: 'table-settings.component.html', + styleUrls: ['table-settings.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class TableSettingsComponent { + + @Output() changeSettingsEvent = new EventEmitter(); + title: string; + panelClass: string; + + tableType: PartTableType; + defaultColumns: string[]; + defaultFilterColumns: string[] + + columnOptions: Map; + dialogColumns: string[]; + tableColumns: string[]; + filterColumns: string[]; + + selectAllSelected: boolean; + selectedColumn: string = null; + + isCustomerTable: boolean; + + dragActive = false; + + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, public readonly tableSettingsService: TableSettingsService) { + // Layout + this.title = data.title; + this.panelClass = data.panelClass; + this.isCustomerTable = data.tableType === PartTableType.AS_BUILT_CUSTOMER || data.tableType === PartTableType.AS_PLANNED_CUSTOMER + // Passed Data + this.tableType = data.tableType; + this.defaultColumns = data.defaultColumns; + this.defaultFilterColumns = data.defaultFilterColumns; + + // Storage Data + this.columnOptions = tableSettingsService.getStoredTableSettings()[this.tableType].columnSettingsOptions; + this.dialogColumns = tableSettingsService.getStoredTableSettings()[this.tableType].columnsForDialog; + this.tableColumns = tableSettingsService.getStoredTableSettings()[this.tableType].columnsForTable; + this.filterColumns = tableSettingsService.getStoredTableSettings()[this.tableType].filterColumnsForTable; + + this.selectAllSelected = this.dialogColumns.length === this.tableColumns.length; + + if (dialogRef?.afterClosed) + dialogRef.afterClosed().subscribe(result => { + this.save(); + }); + } + + save() { + // build new tableColumns how they should be displayed + let newTableColumns: string[] = []; + let newTableFilterColumns: string[] = []; + // iterate over dialogColumns + for (const column of this.dialogColumns) { + // if item in dialogColumns is true in columnOptions --> add to new tableColumns + if (this.columnOptions.get(column)) { + newTableColumns.push(column); + // ignore select column in customertable + if ((column === 'select') && !this.isCustomerTable) { + newTableFilterColumns.push('Filter'); + } else { + newTableFilterColumns.push('filter' + column.charAt(0).toUpperCase() + column.slice(1)) + } + } + } + + // get Settingslist + let tableSettingsList = this.tableSettingsService.getStoredTableSettings(); + + // set this tableType Settings from SettingsList to the new one + tableSettingsList[this.tableType] = { + columnSettingsOptions: this.columnOptions, + columnsForDialog: this.dialogColumns, + columnsForTable: newTableColumns, + filterColumnsForTable: newTableFilterColumns + } as TableViewSettings; + + // save all values back to localstorage + this.tableSettingsService.storeTableSettings(this.tableType, tableSettingsList); + + // trigger action that table will refresh + this.tableSettingsService.emitChangeEvent(); + } + + handleCheckBoxChange(item: string, isChecked: boolean) { + this.columnOptions.set(item, isChecked); + } + + handleListItemClick(event: MouseEvent, item: string) { + let element = event.target as HTMLElement; + + if (element.tagName !== 'INPUT') { + this.selectedColumn = item; + element.classList.toggle('selected-item'); + } + } + + dragging(state: boolean) { + this.dragActive = state; + } + + selectAll(isChecked: boolean) { + for (let column of this.dialogColumns) { + if (column === 'select' || column === 'menu' || column === 'settings') { + continue; + } + this.columnOptions.set(column, isChecked); + } + this.selectAllSelected = true; + } + + resetColumns() { + this.dialogColumns = [...this.defaultColumns]; + this.selectAll(true); + } + + drop(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex, + ); + } + } +} diff --git a/frontend/src/app/modules/shared/components/table/table.component.html b/frontend/src/app/modules/shared/components/table/table.component.html index feb239924c..f9e6e47633 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.html +++ b/frontend/src/app/modules/shared/components/table/table.component.html @@ -19,11 +19,9 @@ SPDX-License-Identifier: Apache-2.0 --> -
+

{{ tableHeader | i18n }}

@@ -44,8 +42,7 @@
--> - - + + class="table--header--row"> - + + + +
+
+ + + + data-testid="table-component--body-row"> - + mat-row> - - + - + @@ -101,6 +117,10 @@

{{ 'table.noResultFound' | i18n }}

+ + + + @@ -108,69 +128,83 @@

{{ 'table.noResultFound' | i18n }}

-
- + + + + + + + - - - + - -
+ + +
+
- build + build

{{ 'table.noResultFound' | i18n }}

{{ 'table.tryAgain' | i18n }}

@@ -92,7 +107,8 @@

{{ 'table.noResultFound' | i18n }}

-
- +
+ + data-testid="select-all--test-id"> - + class="table--select-all__dropdown"> keyboard_arrow_down
- - - + + +
- + + data-testid="select-one--test-id"> + * + + - * + + [attr.colspan]="tableConfig?.displayedColumns.length">
- - @@ -182,15 +216,17 @@

{{ 'table.noResultFound' | i18n }}

-
-
+
+
- @@ -199,10 +235,10 @@

{{ 'table.noResultFound' | i18n }}

{{ 'table.noResultFound' | i18n }} [disabled]="!tableConfig?.sortableColumns?.[column]" mat-header-cell class="table--header" - (click)="sortingEventTrigger(column)" - > -
+ (click)="sortingEventTrigger(column)"> + +
{{ tableConfig?.header?.[column] | i18n }}
- sorting arrow - + sorting arrow + class="table--header--cell--content--sort--dsc" />
-
- + filterIcon - + class="table--header--cell--filter--icon" /> + -
- + + [formControl]="filterFormGroup.controls[filter.filterKey]">
@@ -271,7 +299,8 @@

{{ 'table.noResultFound' | i18n }}

-
+
{{ i + 1 + '.' }}{{ item[1] === 'asc' ? '↑' : item[1] === 'desc' ? '↓' : '' }}
@@ -280,19 +309,20 @@

{{ 'table.noResultFound' | i18n }}

+ + [ngTemplateOutletContext]="{ value: element[column], row: element }">
- {{ 'table.noResultFound' | i18n }} [attr.aria-label]="'table.selectPageSize' | i18n" [attr.data-testId]="'table.selectPageSize' | i18n" (page)="onPaginationChange($event)" - showFirstLastButtons - > - + showFirstLastButtons> + -->
{{ pureColumn }} - + {{ value | autoFormat | i18n }} - + \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/table/table.component.scss b/frontend/src/app/modules/shared/components/table/table.component.scss index 76e9c8c7a9..f72d168294 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.scss +++ b/frontend/src/app/modules/shared/components/table/table.component.scss @@ -28,8 +28,14 @@ table { width: 100%; } -.settingsIcon { - padding-left: 13px; +.table-wrapper { + scrollbar-width: thin; + overflow-x: auto; +} + +.settings-icon { + margin-left: 50px; + margin-right: 25px; } .table--select-all { @@ -39,7 +45,7 @@ table { &__menu { background: rgba(0, 0, 0, 0.04); - & > .table--select-all__dropdown:hover { + &>.table--select-all__dropdown:hover { background: none; } } @@ -91,10 +97,12 @@ table { border: none !important; max-width: 332px; } + .table--header--cell { display: flex; justify-content: space-between; width: 100%; + &--content { display: flex; flex-direction: row; @@ -103,11 +111,13 @@ table { &--sort { width: 24px; height: 24px; + &--asc { width: 10px; transform: rotate(180deg); margin: 9.5px 7px; } + &--dsc { width: 10px; margin: 9.5px 7px; @@ -121,6 +131,7 @@ table { display: flex; justify-content: center; align-content: center; + &--icon { width: 12px; } @@ -130,6 +141,7 @@ table { border-radius: 10px; box-shadow: 0px 15px 25px 0px #3333330d, -10px -10px 15px 0px #ffffff1a, 0px 0px 25px 0px #33333308; } + .menu-input-field .mat-mdc-menu-content { padding: 0; } @@ -190,6 +202,7 @@ table { } } + .table--menu { &__mobile { display: none; @@ -250,6 +263,15 @@ table { padding-bottom: 8px; } +.menu-spacer { + margin-left: 25px; + margin-right: 25px; +} + +.empty-cell { + display: none; +} + .tableScroll { height: 547px !important; overflow: auto !important; @@ -274,6 +296,7 @@ table { white-space: pre-line; } } + .table--header--row .mdc-data-table__header-cell { padding: 0 12px 0 12px !important; } @@ -281,4 +304,4 @@ table { .table--body--row .mdc-data-table__cell { padding: 0 12px 0 12px !important; border-bottom-style: none !important; -} +} \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/table/table.component.spec.ts b/frontend/src/app/modules/shared/components/table/table.component.spec.ts index db3dc0c402..21a8f979e6 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.spec.ts +++ b/frontend/src/app/modules/shared/components/table/table.component.spec.ts @@ -22,11 +22,10 @@ import { Pagination } from '@core/model/pagination.model'; import { FilterOperator } from '@page/parts/model/parts.model'; import { TableComponent } from '@shared/components/table/table.component'; -import { FilterMethod, TableConfig, TableEventConfig, TableFilter } from '@shared/components/table/table.model'; +import { FilterMethod, TableConfig, TableEventConfig } from '@shared/components/table/table.model'; import { SharedModule } from '@shared/shared.module'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { getInputFromChildNodes, renderComponent } from '@tests/test-render.utils'; -import exp from 'constants'; describe('TableComponent', () => { const generateTableContent = (size: number) => { @@ -42,15 +41,19 @@ describe('TableComponent', () => { const data = { page: 0, pageSize: 10, totalItems: 100, content } as Pagination; const tableConfig: TableConfig = { displayedColumns, header }; + return renderComponent( - ``, + ``, { declarations: [TableComponent], imports: [SharedModule], componentProperties: { data, tableConfig, - selected, + selected }, }, ); @@ -83,7 +86,7 @@ describe('TableComponent', () => { const tableSize = 3; await renderTable(tableSize, ['name'], { name: 'Name for test' }); - expect(screen.getByText('Name for test')).toBeInTheDocument(); + expect(screen.getByText('table.column.name')).toBeInTheDocument(); }); it('should render select column', async () => { @@ -142,7 +145,7 @@ describe('TableComponent', () => { }, ); - const nameElement = screen.getByText('Name Sort'); + const nameElement = screen.getByText('table.column.name'); nameElement.click(); expect(configChange).toHaveBeenCalledWith({ @@ -198,6 +201,7 @@ describe('TableComponent', () => { }, ], }; + const { fixture } = await renderComponent(TableComponent, { declarations: [TableComponent], imports: [SharedModule], @@ -206,6 +210,7 @@ describe('TableComponent', () => { tableConfig, }, }); + const { componentInstance } = fixture; const tabelConfigRes: TableEventConfig = { page: 0, @@ -239,6 +244,7 @@ describe('TableComponent', () => { componentInstance.filterFormGroup.controls['description'].patchValue('value1'); componentInstance.filterFormGroup.controls['createdDate'].patchValue('2023-11-11'); + componentInstance.tableConfig.filterConfig[2].option[0].checked = true; componentInstance.triggerFilterAdding('description', false); diff --git a/frontend/src/app/modules/shared/components/table/table.component.ts b/frontend/src/app/modules/shared/components/table/table.component.ts index 6a46fe8f66..a34ac5d63a 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.ts +++ b/frontend/src/app/modules/shared/components/table/table.component.ts @@ -21,24 +21,24 @@ import { SelectionModel } from '@angular/cdk/collections'; import { Component, ElementRef, EventEmitter, Input, Output, ViewChild, ViewEncapsulation } from '@angular/core'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { FormControl, FormGroup } from '@angular/forms'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { MatSort, Sort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Pagination } from '@core/model/pagination.model'; import { RoleService } from '@core/user/role.service'; -import { FilterOperator } from '@page/parts/model/parts.model'; +import { TableSettingsService } from '@core/user/table-settings.service'; import { - MenuActionConfig, - TableConfig, - TableEventConfig, - TableFilter, + CreateHeaderFromColumns, MenuActionConfig, PartTableType, TableConfig, TableEventConfig, TableHeaderSort, TableFilter, FilterMethod, - TableHeaderSort, FilterInfo, SortingOptions, } from '@shared/components/table/table.model'; import { addSelectedValues, clearAllRows, clearCurrentRows, removeSelectedValues } from '@shared/helper/table-helper'; +import { TableViewConfig } from '../parts-table/table-view-config.model'; +import { TableSettingsComponent } from '../table-settings/table-settings.component'; +import { FilterOperator } from '@page/parts/model/parts.model'; @Component({ selector: 'app-table', @@ -60,17 +60,32 @@ export class TableComponent { return; } - const { menuActionsConfig: menuActions, displayedColumns: dc, columnRoles, hasPagination = true } = tableConfig; + const { menuActionsConfig: menuActions, displayedColumns: dc, columnRoles, hasPagination } = tableConfig; const displayedColumns = dc.filter(column => this.roleService.hasAccess(columnRoles?.[column] ?? 'user')); + const cellRenderers = this._tableConfig?.cellRenderers ?? tableConfig.cellRenderers; + + const viewDetailsLabel = 'actions.viewDetails'; + const viewDetailsMenuAction: MenuActionConfig = { - label: 'actions.viewDetails', + label: viewDetailsLabel, icon: 'remove_red_eye', action: (data: Record) => this.selected.emit(data), }; - const menuActionsConfig = menuActions ? [viewDetailsMenuAction, ...menuActions] : null; - this._tableConfig = { ...tableConfig, displayedColumns, hasPagination, menuActionsConfig }; + let menuActionsConfig: MenuActionConfig[] = null; + + if (menuActions) { + if (!menuActions.some((action) => { + return action.label === viewDetailsLabel + })) { + menuActionsConfig = [viewDetailsMenuAction, ...menuActions] + } else { + menuActionsConfig = menuActions; + } + } + + this._tableConfig = { ...tableConfig, cellRenderers, displayedColumns, hasPagination, menuActionsConfig }; } get tableConfig(): TableConfig { @@ -80,6 +95,7 @@ export class TableComponent { @Input() labelId: string; @Input() noShadow = false; @Input() showHover = true; + @Input() tableType: PartTableType; @Input() selectedPartsInfoLabel: string; @Input() selectedPartsActionLabel: string; @@ -135,23 +151,34 @@ export class TableComponent { public isMenuOpen: boolean; public sortingEvent: Record = {}; + public filterConfiguration: any[]; + public displayedColumns: string[]; + public defaultColumns: string[]; + public filterFormGroup = new FormGroup({}); + private pageSize: number; private sorting: TableHeaderSort; private filtering: TableFilter = { filterMethod: FilterMethod.AND }; private _tableConfig: TableConfig; + private tableViewConfig: TableViewConfig; - filterFormGroup = new FormGroup({}); - - constructor(private readonly roleService: RoleService) {} + constructor(private readonly roleService: RoleService, private readonly tableSettingsService: TableSettingsService, private dialog: MatDialog) { } ngOnInit() { + this.initializeTableViewSettings(); + if (this.tableConfig?.filterConfig?.length > 0) { this.setupFilterFormGroup(); } if (this.tableConfig?.sortableColumns) { this.setupSortingEvent(); } + + this.tableSettingsService.getEvent().subscribe(() => { + this.setupTableViewSettings(); + }) + this.setupTableViewSettings(); } setupSortingEvent(): void { @@ -212,6 +239,90 @@ export class TableComponent { } } + private setupTableViewSettings() { + if (!this.tableType) { + this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + return; + } + + const tableSettingsList = this.tableSettingsService.getStoredTableSettings(); + // check if there are table settings list + if (tableSettingsList) { + // if yes, check if there is a table-setting for this table type + if (tableSettingsList[this.tableType]) { + // if yes, get the effective displayedcolumns from the settings and set the tableconfig after it. + this.setupTableConfigurations(tableSettingsList[this.tableType].columnsForTable, tableSettingsList[this.tableType].filterColumnsForTable, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + } else { + // if no, create new a table setting for this.tabletype and put it into the list. Additionally, intitialize default table configuration + tableSettingsList[this.tableType] = this.createSettingsList(); + this.tableSettingsService.storeTableSettings(this.tableType, tableSettingsList); + this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + } + } else { + // if no, create new list and a settings entry for this.tabletype with default values and set correspondingly the tableconfig + const newTableSettingsList = { + [this.tableType]: { + columnsForDialog: this.tableViewConfig.displayedColumnsForTable, + columnSettingsOptions: this.getDefaultColumnVisibilityMap(), + columnsForTable: this.tableViewConfig.displayedColumnsForTable, + filterColumnsForTable: this.tableViewConfig.displayedColumns + } + } + this.tableSettingsService.storeTableSettings(this.tableType, newTableSettingsList); + this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + } + } + + private createSettingsList(): any { + return { + columnsForDialog: this.tableViewConfig.displayedColumnsForTable, + columnSettingsOptions: this.getDefaultColumnVisibilityMap(), + columnsForTable: this.tableViewConfig.displayedColumnsForTable, + filterColumnsForTable: this.tableViewConfig.displayedColumns + } + } + + private setupTableConfigurations(displayedColumnsForTable: string[], displayedColumns: string[], sortableColumns: Record, filterConfiguration: any[], filterFormGroup: any): any { + const headerKey = 'table.column'; + + this.tableConfig = { + hasPagination: this.tableConfig.hasPagination, + filterConfig: this.tableConfig.filterConfig, + menuActionsConfig: this.tableConfig.menuActionsConfig, + displayedColumns: displayedColumnsForTable, + header: CreateHeaderFromColumns(displayedColumnsForTable, headerKey), + sortableColumns: sortableColumns, + }; + + this.filterConfiguration = filterConfiguration; + this.displayedColumns = displayedColumns; + for (const controlName in filterFormGroup) { + if (filterFormGroup.hasOwnProperty(controlName)) { + this.filterFormGroup.addControl(controlName, filterFormGroup[controlName]); + } + } + } + + private getDefaultColumnVisibilityMap(): Map { + const initialColumnMap = new Map(); + for (const column of this.tableViewConfig.displayedColumnsForTable) { + initialColumnMap.set(column, true); + } + return initialColumnMap; + } + + private initializeTableViewSettings(): void { + if (this.tableConfig) { + this.tableViewConfig = { + displayedColumns: this.tableConfig.displayedColumns, + displayedColumnsForTable: this.tableConfig.displayedColumns, + filterConfiguration: this.tableConfig.filterConfig, + filterFormGroup: undefined, + sortableColumns: this.tableConfig.sortableColumns, + } + } + } + public triggerFilterAdding(filterName: string, isDate: boolean): void { //Should the filtering be reset every time? Else remove the following line: this.filtering = { filterMethod: FilterMethod.AND }; @@ -278,4 +389,17 @@ export class TableComponent { private removeSelectedValues(itemsToRemove: unknown[]): void { removeSelectedValues(this.selection, itemsToRemove); } + + openDialog(): void { + const config = new MatDialogConfig(); + config.autoFocus = false; + config.data = { + title: "table.tableSettings.title", + panelClass: "custom", + tableType: this.tableType, + defaultColumns: this.tableViewConfig.displayedColumnsForTable, + defaultFilterColumns: this.tableViewConfig.displayedColumns + }; + this.dialog.open(TableSettingsComponent, config) + } } diff --git a/frontend/src/app/modules/shared/components/table/table.model.ts b/frontend/src/app/modules/shared/components/table/table.model.ts index be5ea4b2d5..fd67f4340b 100644 --- a/frontend/src/app/modules/shared/components/table/table.model.ts +++ b/frontend/src/app/modules/shared/components/table/table.model.ts @@ -50,30 +50,34 @@ export interface Option { } export enum PartTableType { - AS_BUILT_OWN, - AS_PLANNED_OWN, - AS_BUILT_SUPPLIER, - AS_BUILT_CUSTOMER, - AS_PLANNED_SUPPLIER, - AS_PLANNED_CUSTOMER, - AS_DESIGNED_OWN, - AS_ORDERED_OWN, - AS_SUPPORTED_OWN, - AS_RECYCLED_OWN, - AS_DESIGNED_SUPPLIER, - AS_DESIGNED_CUSTOMER, - AS_ORDERED_SUPPLIER, - AS_ORDERED_CUSTOMER, - AS_SUPPORTED_SUPPLIER, - AS_SUPPORTED_CUSTOMER, - AS_RECYCLED_SUPPLIER, - AS_RECYCLED_CUSTOMER, + AS_BUILT_OWN = 'AS_BUILT_OWN', + AS_PLANNED_OWN = 'AS_PLANNED_OWN', + AS_BUILT_SUPPLIER = 'AS_BUILT_SUPPLIER', + AS_BUILT_CUSTOMER = 'AS_BUILT_CUSTOMER', + AS_PLANNED_SUPPLIER = 'AS_PLANNED_SUPPLIER', + AS_PLANNED_CUSTOMER = 'AS_PLANNED_CUSTOMER', + AS_DESIGNED_OWN = 'AS_DESIGNED_OWN', + AS_ORDERED_OWN = 'AS_ORDERED_OWN', + AS_SUPPORTED_OWN = 'AS_SUPPORTED_OWN', + AS_RECYCLED_OWN = 'AS_RECYCLED_OWN', + AS_DESIGNED_SUPPLIER = 'AS_DESIGNED_SUPPLIER', + AS_DESIGNED_CUSTOMER = 'AS_DESIGNED_CUSTOMER', + AS_ORDERED_SUPPLIER = 'AS_ORDERED_SUPPLIER', + AS_ORDERED_CUSTOMER = 'AS_ORDERED_CUSTOMER', + AS_SUPPORTED_SUPPLIER = 'AS_SUPPORTED_SUPPLIER', + AS_SUPPORTED_CUSTOMER = 'AS_SUPPORTED_CUSTOMER', + AS_RECYCLED_SUPPLIER = 'AS_RECYCLED_SUPPLIER', + AS_RECYCLED_CUSTOMER = 'AS_RECYCLED_CUSTOMER', + ALERTS_RECEIVED = 'ALERTS_RECEIVED', + ALERTS_SENT = 'ALERTS_SENT', + INVESTIGATIONS_RECEIVED = 'INVESTIGATIONS_RECEIVED', + INVESTIGATIONS_SENT = 'INVESTIGATIONS_SENT', } -export type DisplayColumns = 'select' | 'menu' | T; +export type DisplayColumns = 'select' | 'menu' | 'settings' | T; export const CreateHeaderFromColumns = (columns: string[], headerKey: string): Record => { - return columns.reduce((header, column) => ({ ...header, [column]: `${headerKey}.${column}` }), {}); + return columns?.reduce((header, column) => ({ ...header, [column]: `${headerKey}.${column}` }), {}); }; export interface TablePaginationEventConfig { diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html index 6624eed761..e0cfd47248 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.html @@ -21,10 +21,12 @@ - + {{ onItemCountChange(notifications.data.totalItems) }}
- + (filterChange)="onFilterChange()">
@@ -43,7 +44,8 @@
- + @@ -51,32 +53,41 @@ - -
+ +
{{ translationContext + '.status.' + status | i18n }}
- - + + - +

{{ text }}

- +

{{ text }}

- +

{{ text | formatDate : { dateStyle: 'short', timeStyle: 'short' } }}

- +

- +

-
+
\ No newline at end of file diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss index c7f1754c64..f0ece4f6a6 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.scss @@ -27,6 +27,10 @@ max-width: 500px; } +.notifications-tab { + overflow: auto; +} + .loader { margin: 0 auto; } diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts index e581d13c67..88b522588f 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts @@ -24,6 +24,7 @@ import { CreateHeaderFromColumns, DisplayColumns, MenuActionConfig, + PartTableType, TableConfig, TableEventConfig, TableHeaderSort, @@ -47,6 +48,7 @@ export class NotificationTabComponent implements AfterViewInit { @Input() sortableColumns: Record = {}; @Input() multiSortList: TableHeaderSort[] = []; @Input() enableScroll: boolean = true; + @Input() tableType: PartTableType; @Input() filterConfig: any[] = []; @Output() tableConfigChanged = new EventEmitter(); @@ -63,9 +65,11 @@ export class NotificationTabComponent implements AfterViewInit { public tableConfig: TableConfig; public filteredContent = false; + protected readonly PartTableType = PartTableType; + public ngAfterViewInit(): void { const defaultColumns: DisplayColumns[] = ['createdDate', 'description', 'status']; - const displayedColumns: DisplayColumns[] = [...defaultColumns, ...this.optionalColumns, 'menu']; + const displayedColumns: DisplayColumns[] = [...defaultColumns, ...this.optionalColumns, 'menu', 'settings']; const sortableColumns: Record = this.sortableColumns; const filterConfig: any[] = this.filterConfig; this.tableConfig = { diff --git a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html index 4365087721..40a598c3dc 100644 --- a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html +++ b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.html @@ -19,54 +19,52 @@ SPDX-License-Identifier: Apache-2.0 --> - + mat-align-tabs="start"> -
{{ translationContext + '.tabs.received' | i18n }} {{ itemCountString }}
+
{{ translationContext + '.tabs.received' | i18n }}
+
({{ itemCount[tabIndex$ | async] }})
- + (itemCount)="onItemCountChanged($event, 0)" + (selected)="selected.emit($event)"> +
- {{ translationContext + '.tabs.queuedAndRequested' | i18n }} {{ itemCountString }} + {{ translationContext + '.tabs.queuedAndRequested' | i18n }}
+
({{ itemCount[tabIndex$ | async] }})
- + (selected)="selected.emit($event)">
-
+ \ No newline at end of file diff --git a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts index 932f9d3317..b38c3aab8a 100644 --- a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts @@ -35,6 +35,7 @@ import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; import { buildMockInvestigations } from '../../../../../mocks/services/investigations-mock/investigations.test.model'; import { NotificationModule } from '../notification.module'; +import { PartTableType } from '@shared/components/table/table.model'; describe('NotificationsInboxComponent', () => { let clickHandler; @@ -70,9 +71,9 @@ describe('NotificationsInboxComponent', () => { [queuedAndRequestedNotifications$]='queuedAndRequestedNotifications$' [receivedNotifications$]='receivedNotifications$' [translationContext]="'commonInvestigation'" - [menuActionsConfig]="'menuActionsConfig'" - + [menuActionsConfig]='menuActionsConfig' (onReceivedPagination)='clickHandler($event)' + [tablesType]='tablesType' (onQueuedAndRequestedPagination)='clickHandler($event)' >`, { @@ -83,6 +84,7 @@ describe('NotificationsInboxComponent', () => { receivedNotifications$, clickHandler, menuActionsConfig, + tablesType: [PartTableType.INVESTIGATIONS_RECEIVED, PartTableType.INVESTIGATIONS_SENT] }, }, ); diff --git a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.ts b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.ts index 211d889ee8..d4a622c651 100644 --- a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.ts +++ b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.ts @@ -21,7 +21,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { MenuActionConfig, TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; +import { MenuActionConfig, PartTableType, TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; import { Notification, Notifications } from '@shared/model/notification.model'; import { View } from '@shared/model/view.model'; import { StaticIdService } from '@shared/service/staticId.service'; @@ -43,6 +43,7 @@ export class NotificationComponent { @Input() queuedAndRequestedSortableColumns: Record = {}; @Input() receivedMultiSortList: TableHeaderSort[] = []; @Input() queuedAndRequestedMultiSortList: TableHeaderSort[] = []; + @Input() tablesType: PartTableType[]; @Input() receivedFilterConfig: any[] = []; @Input() queuedAndRequestedFilterConfig: any[] = []; @@ -55,21 +56,21 @@ export class NotificationComponent { public readonly receivedTabLabelId = this.staticIdService.generateId('Notification.receivedTab'); public readonly queuedAndRequestedTabLabelId = this.staticIdService.generateId('Notification.queuedAndRequestedTab'); - public itemCount: number = 0; - public itemCountString: string; + public itemCount: number[] = []; + + protected readonly PartTableType = PartTableType; constructor( private readonly router: Router, private readonly route: ActivatedRoute, private readonly staticIdService: StaticIdService, - ) {} + ) { } public onTabChange(tabIndex: number): void { void this.router.navigate([], { queryParams: { tabIndex }, replaceUrl: true }); } - public onItemCountChanged(itemCount: number): void { - this.itemCount = itemCount; - this.itemCountString = `(${itemCount})`; + public onItemCountChanged(itemCount: number, tabIndex: number): void { + this.itemCount[tabIndex] = itemCount; } } diff --git a/frontend/src/app/modules/shared/shared.module.ts b/frontend/src/app/modules/shared/shared.module.ts index 478679c638..e93661e044 100644 --- a/frontend/src/app/modules/shared/shared.module.ts +++ b/frontend/src/app/modules/shared/shared.module.ts @@ -34,6 +34,7 @@ import { NotificationUserComponent } from '@shared/components/notification-user/ import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; import { SeveritySelectComponent } from '@shared/components/severity-select/severity-select.component'; import { SeverityComponent } from '@shared/components/severity/severity.component'; +import { TableSettingsComponent } from '@shared/components/table-settings/table-settings.component'; import { TextWithIconComponent } from '@shared/components/text-with-icon/text-with-icon.component'; import { NotificationModalContentComponent @@ -88,7 +89,7 @@ import { ViewSelectorComponent } from "@shared/components/view-selector/view-sel import { CountryFlagGeneratorComponent } from "@shared/components/country-flag-generator/country-flag-generator.component"; - +import { DragDropModule } from '@angular/cdk/drag-drop'; @NgModule({ declarations: [ ToastContainerComponent, @@ -97,6 +98,7 @@ import { ButtonComponent, TextWithIconComponent, TableComponent, + TableSettingsComponent, TooltipDirective, RoleDirective, I18nPipe, @@ -138,7 +140,7 @@ import { MultiSelectAutocompleteComponent, CountryFlagGeneratorComponent ], - imports: [TemplateModule, RouterModule, I18NextModule], + imports: [TemplateModule, RouterModule, I18NextModule, DragDropModule], exports: [ ToastContainerComponent, ToastMessageComponent, diff --git a/frontend/src/assets/images/drag_indicator.svg b/frontend/src/assets/images/drag_indicator.svg new file mode 100644 index 0000000000..c0635abb26 --- /dev/null +++ b/frontend/src/assets/images/drag_indicator.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/refresh_icon.svg b/frontend/src/assets/images/icons/refresh_icon.svg new file mode 100644 index 0000000000..c27d093633 --- /dev/null +++ b/frontend/src/assets/images/icons/refresh_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 548534d29f..5639c116e1 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -71,6 +71,12 @@ "shellDescriptorsFetchDelta": "Abruf-Delta", "endDate": "Enddatum" }, + "tableSettings": { + "title": "Einstellungen der Spalte", + "selectAll": "Alle", + "refreshTooltip": "Zurücksetzen der individuellen Einstellung der Tabellenspalten auf die Standardeinstellung", + "saveAction": "Speichern" + }, "column": { "id": "ID", "!": "!", @@ -311,4 +317,4 @@ "confirmationDialog": { "cancel": "Abbrechen" } -} +} \ No newline at end of file diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 64d96bbf35..2c0a0e715d 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -71,6 +71,12 @@ "shellDescriptorsFetchDelta": "Fetch Delta", "endDate": "End date" }, + "tableSettings": { + "title": "Column settings", + "selectAll": "All", + "refreshTooltip": "Reset individual table column settings to default", + "saveAction": "Save" + }, "column": { "id": "ID", "alertId": "Alert ID", @@ -315,4 +321,4 @@ "confirmationDialog": { "cancel": "Cancel" } -} +} \ No newline at end of file diff --git a/frontend/src/theme/base.scss b/frontend/src/theme/base.scss index 46b9b70d9d..4532ef2fb8 100644 --- a/frontend/src/theme/base.scss +++ b/frontend/src/theme/base.scss @@ -531,7 +531,7 @@ padding: 20px 20px 0; } - //scroll bar + //scroll bar - does not work on firefox /* width */ ::-webkit-scrollbar { width: 4.55px; From e324c4a150dd13dfb0c3bbbabc5b464bad0463ee Mon Sep 17 00:00:00 2001 From: Joel Dietz <145654504+JoelDietz@users.noreply.github.com> Date: Thu, 23 Nov 2023 09:42:33 +0100 Subject: [PATCH 03/11] [DO-1583][minor] Sorting shared parts (#55) * feature: changed the display of the multi sort and the tool tip * fix: alerts received not sorted correctly in the beginning. Also fixed the tooltip showing on the dashboard where sorting is not possible * fix: dashboard table takes the height of the my parts table. * fix: tests and duplication * fix: test * fix: test * fix: duplication * fix: more test things --- .../presentation/alerts.component.spec.ts | 105 +++---- .../dashboard/abstraction/dashboard.facade.ts | 5 +- .../investigations.component.spec.ts | 126 ++++---- .../customer-parts.component.spec.ts | 65 +--- .../supplier-parts.component.spec.ts | 58 +--- .../parts-table/parts-table.component.html | 207 ++++++------- .../parts-table/parts-table.component.scss | 20 +- .../parts-table/parts-table.component.ts | 109 ++++--- .../components/table/table.component.html | 278 +++++++++--------- .../components/table/table.component.scss | 57 ++-- .../components/table/table.component.ts | 109 ++++--- .../notification.component.spec.ts | 30 +- .../modules/shared/service/alerts.service.ts | 2 +- frontend/src/assets/locales/de/common.json | 1 + frontend/src/assets/locales/en/common.json | 1 + 15 files changed, 564 insertions(+), 609 deletions(-) diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts index b8ac2fcaca..9d832fc6a9 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts @@ -25,7 +25,6 @@ import { renderComponent } from '@tests/test-render.utils'; import { AlertsComponent } from './alerts.component'; - describe('AlertsComponent', () => { const renderAlerts = async () => { return await renderComponent(AlertsComponent, { @@ -51,14 +50,18 @@ describe('AlertsComponent', () => { const { fixture } = await renderAlerts(); const alertsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(alertsComponent, "setTableSortingList").and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status'); - await waitFor(() => { fireEvent.click(statusColumnHeader); }, { timeout: 3000 }); - + let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); + let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(statusColumnHeader); + }, + { timeout: 10000 }, + ); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "received"); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); - expect(alertsComponent['alertReceivedSortList']).toEqual([["status", "asc"]]); + expect(alertsComponent['alertReceivedSortList']).toEqual([['status', 'asc']]); }); it('should sort queued and requested alerts after column status', async () => { @@ -67,83 +70,61 @@ describe('AlertsComponent', () => { fireEvent.click(await waitFor(() => screen.getByText('commonAlert.tabs.queuedAndRequested'))); - let setTableFunctionSpy = spyOn(alertsComponent, "setTableSortingList").and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status'); - await waitFor(() => { fireEvent.click(statusColumnHeader); }, { timeout: 3000 }); - + let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); + let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(statusColumnHeader); + }, + { timeout: 10000 }, + ); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "queued-and-requested"); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'queued-and-requested'); - expect(alertsComponent['alertQueuedAndRequestedSortList']).toEqual([["status", "asc"]]); + expect(alertsComponent['alertQueuedAndRequestedSortList']).toEqual([['status', 'asc']]); }); - it('should multisort after column description and status', async () => { const { fixture } = await renderAlerts(); const alertsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(alertsComponent, "setTableSortingList").and.callThrough(); - let descriptionColumnHeader = await screen.findByText('table.column.description'); - await waitFor(() => { fireEvent.click(descriptionColumnHeader); }, { timeout: 3000 }); - let statusHeader = await screen.findByText('table.column.status') + let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); + let descriptionColumnHeader = await screen.findByText('table.column.description', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(descriptionColumnHeader); + }, + { timeout: 10000 }, + ); + let statusHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); await waitFor(() => { fireEvent.keyDown(statusHeader, { ctrlKey: true, - charCode: 17 - }) - }) + charCode: 17, + }); + }); expect(alertsComponent['ctrlKeyState']).toBeTruthy(); await waitFor(() => { - fireEvent.click(statusHeader) + fireEvent.click(statusHeader); }); await waitFor(() => { fireEvent.keyUp(statusHeader, { ctrlKey: true, - charCode: 17 - }) - }) - - await waitFor(() => { fireEvent.click(statusHeader) }); - - - expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], "received"); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "received"); - expect(alertsComponent['alertReceivedSortList']).toEqual([["description", "asc"], ["status", "desc"]]); - }); - - it('should reset sorting after third click', async () => { - const { fixture } = await renderAlerts(); - const alertsComponent = fixture.componentInstance; - - let descriptionColumnHeader = await screen.findByText('table.column.description'); - await waitFor(() => { fireEvent.click(descriptionColumnHeader); }, { timeout: 3000 }); - let statusColumnHeader = await screen.findByText('table.column.status') - - await waitFor(() => { - fireEvent.keyDown(statusColumnHeader, { - ctrlKey: true, - charCode: 17 - }) - }) - - await waitFor(() => { - fireEvent.click(statusColumnHeader) + charCode: 17, + }); }); await waitFor(() => { - fireEvent.keyUp(statusColumnHeader, { - ctrlKey: true, - charCode: 17 - }) - }) - - await waitFor(() => { fireEvent.click(statusColumnHeader) }); - - await waitFor(() => { fireEvent.click(statusColumnHeader) }); + fireEvent.click(statusHeader); + }); - expect(alertsComponent['alertReceivedSortList']).toEqual([]); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], 'received'); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); + expect(alertsComponent['alertReceivedSortList']).toEqual([ + ['description', 'asc'], + ['status', 'desc'], + ]); }); - }); diff --git a/frontend/src/app/modules/page/dashboard/abstraction/dashboard.facade.ts b/frontend/src/app/modules/page/dashboard/abstraction/dashboard.facade.ts index 043f911031..b143a15aa3 100644 --- a/frontend/src/app/modules/page/dashboard/abstraction/dashboard.facade.ts +++ b/frontend/src/app/modules/page/dashboard/abstraction/dashboard.facade.ts @@ -42,7 +42,7 @@ export class DashboardFacade { private readonly partsService: PartsService, private readonly investigationsService: InvestigationsService, private readonly alertsService: AlertsService, - ) { } + ) {} public get numberOfMyParts$(): Observable> { return this.dashboardState.numberOfMyParts$; @@ -87,7 +87,6 @@ export class DashboardFacade { this.dashboardState.setNumberOfOtherParts({ data: dashboardStats.otherParts }); this.dashboardState.setNumberOfInvestigations({ data: dashboardStats.investigations || 0 }); this.dashboardState.setNumberOfAlerts({ data: dashboardStats.alerts || 0 }); - }, error: error => { this.dashboardState.setNumberOfMyParts({ error }); @@ -113,7 +112,7 @@ export class DashboardFacade { private setAlerts(): void { this.alertSubscription?.unsubscribe(); - this.alertSubscription = this.alertsService.getReceivedAlerts(0, 5, null).subscribe({ + this.alertSubscription = this.alertsService.getReceivedAlerts(0, 5, []).subscribe({ next: data => this.dashboardState.setAlerts({ data }), error: (error: Error) => this.dashboardState.setAlerts({ error }), }); diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts index 46a0fbc3fb..41371dd3f9 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts @@ -49,7 +49,11 @@ describe('InvestigationsComponent', () => { it('should call change pagination of received investigations', async () => { await renderInvestigations(); - fireEvent.click(await waitFor(() => screen.getByLabelText('pagination.nextPageLabel', { selector: 'button' }))); + fireEvent.click( + await waitFor(() => screen.getByLabelText('pagination.nextPageLabel', { selector: 'button' }), { + timeout: 10000, + }), + ); expect(await waitFor(() => screen.getByText('Investigation No 84'))).toBeInTheDocument(); expect(await waitFor(() => screen.getByText('Investigation No 11'))).toBeInTheDocument(); @@ -58,7 +62,9 @@ describe('InvestigationsComponent', () => { it('should call change pagination of queued & requested investigations', async () => { await renderInvestigations(); - fireEvent.click(await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested'))); + fireEvent.click(await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested')), { + timeout: 10000, + }); fireEvent.click(await waitFor(() => screen.getByLabelText('pagination.nextPageLabel', { selector: 'button' }))); @@ -68,93 +74,83 @@ describe('InvestigationsComponent', () => { it('should sort received investigations after column status', async () => { const { fixture } = await renderInvestigations(); - const investigationComponent = fixture.componentInstance; + const investigationComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(investigationComponent, "setTableSortingList").and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status'); - await waitFor(() => {fireEvent.click(statusColumnHeader);}, {timeout: 3000}); + let setTableFunctionSpy = spyOn(investigationComponent, 'setTableSortingList').and.callThrough(); + let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(statusColumnHeader); + }, + { timeout: 10000 }, + ); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "received" ); - - expect(investigationComponent['investigationReceivedSortList']).toEqual([["status", "asc"]]); + expect(investigationComponent['investigationReceivedSortList']).toEqual([['status', 'asc']]); }); it('should sort queued and requested investigations after column status', async () => { const { fixture } = await renderInvestigations(); - const investigationComponent = fixture.componentInstance; + const investigationComponent = fixture.componentInstance; fireEvent.click(await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested'))); - let setTableFunctionSpy = spyOn(investigationComponent, "setTableSortingList").and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status'); - await waitFor(() => {fireEvent.click(statusColumnHeader);}, {timeout: 3000}); - + let setTableFunctionSpy = spyOn(investigationComponent, 'setTableSortingList').and.callThrough(); + let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(statusColumnHeader); + }, + { timeout: 10000 }, + ); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "queued-and-requested" ); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'queued-and-requested'); - expect(investigationComponent['investigationQueuedAndRequestedSortList']).toEqual([["status", "asc"]]); + expect(investigationComponent['investigationQueuedAndRequestedSortList']).toEqual([['status', 'asc']]); }); - it('should multisort after column description and status', async () => { const { fixture } = await renderInvestigations(); - const investigationsComponent = fixture.componentInstance; + const investigationsComponent = fixture.componentInstance; + + let setTableFunctionSpy = spyOn(investigationsComponent, 'setTableSortingList').and.callThrough(); + let descriptionColumnHeader = await screen.findByText('table.column.description', undefined, { timeout: 10000 }); + await waitFor( + () => { + fireEvent.click(descriptionColumnHeader); + }, + { timeout: 10000 }, + ); + let statusHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - let setTableFunctionSpy = spyOn(investigationsComponent, "setTableSortingList").and.callThrough(); - let descriptionColumnHeader = await screen.findByText('table.column.description'); - await waitFor(() => {fireEvent.click(descriptionColumnHeader);}, {timeout: 3000}); - let statusHeader = await screen.findByText('table.column.status') - - await waitFor(() => {fireEvent.keyDown(statusHeader, { - ctrlKey: true, - charCode: 17 - })}) + await waitFor(() => { + fireEvent.keyDown(statusHeader, { + ctrlKey: true, + charCode: 17, + }); + }); expect(investigationsComponent['ctrlKeyState']).toBeTruthy(); await waitFor(() => { - fireEvent.click(statusHeader) + fireEvent.click(statusHeader); }); - await waitFor(() => {fireEvent.keyUp(statusHeader, { - ctrlKey: true, - charCode: 17 - })}) - - await waitFor(() => {fireEvent.click(statusHeader)}); - - - expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], "received" ); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], "received" ); - expect(investigationsComponent['investigationReceivedSortList']).toEqual([["description", "asc"], ["status", "desc"]]); - }); - - it('should reset sorting after third click', async () => { - const { fixture } = await renderInvestigations(); - const investigationsComponent = fixture.componentInstance; - - let descriptionColumnHeader = await screen.findByText('table.column.description'); - await waitFor(() => {fireEvent.click(descriptionColumnHeader);}, {timeout: 3000}); - let statusColumnHeader = await screen.findByText('table.column.status') - - await waitFor(() => {fireEvent.keyDown(statusColumnHeader, { - ctrlKey: true, - charCode: 17 - })}) - await waitFor(() => { - fireEvent.click(statusColumnHeader) + fireEvent.keyUp(statusHeader, { + ctrlKey: true, + charCode: 17, + }); }); - await waitFor(() => {fireEvent.keyUp(statusColumnHeader, { - ctrlKey: true, - charCode: 17 - })}) - - await waitFor(() => {fireEvent.click(statusColumnHeader)}); - - await waitFor(() => {fireEvent.click(statusColumnHeader)}); + await waitFor(() => { + fireEvent.click(statusHeader); + }); - expect(investigationsComponent['investigationReceivedSortList']).toEqual([]); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], 'received'); + expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); + expect(investigationsComponent['investigationReceivedSortList']).toEqual([ + ['description', 'asc'], + ['status', 'desc'], + ]); }); - }); diff --git a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.spec.ts b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.spec.ts index 6d0973ab15..a7bdc66c7b 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.spec.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.spec.ts @@ -33,9 +33,9 @@ describe('CustomerPartsComponent', () => { const renderCustomerParts = () => renderComponent(CustomerPartsComponent, { - imports: [ OtherPartsModule ], - providers: [ { provide: OtherPartsState, useFactory: () => otherPartsState }, { provide: PartsState } ], - roles: [ 'admin', 'wip' ], + imports: [OtherPartsModule], + providers: [{ provide: OtherPartsState, useFactory: () => otherPartsState }, { provide: PartsState }], + roles: ['admin', 'wip'], componentInputs: { bomLifecycle: MainAspectType.AS_BUILT, }, @@ -61,8 +61,7 @@ describe('CustomerPartsComponent', () => { let nameHeader = await screen.findByText('table.column.name'); fireEvent.click(nameHeader); - expect(customerPartsComponent['tableCustomerAsBuiltSortList']).toEqual([ [ 'name', 'asc' ] ]); - + expect(customerPartsComponent['tableCustomerAsBuiltSortList']).toEqual([['name', 'asc']]); }); it('should multisort after column name and semanticModelId', async () => { @@ -94,47 +93,12 @@ describe('CustomerPartsComponent', () => { await waitFor(() => { fireEvent.click(semanticModelIdHeader); }); - expect(customerPartsComponent['tableCustomerAsBuiltSortList']).toEqual([ [ 'name', 'asc' ], [ 'semanticModelId', 'desc' ] ]); - }); - - it('should reset sorting on third click', async () => { - const { fixture } = await renderCustomerParts(); - const customerPartsComponent = fixture.componentInstance; - customerPartsComponent.bomLifecycle = MainAspectType.AS_BUILT; - fixture.detectChanges(); - - let nameHeader = await screen.findByText('table.column.name'); - fireEvent.click(nameHeader); - let semanticModelIdHeader = await screen.findByText('table.column.semanticModelId'); - - await waitFor(() => { - fireEvent.keyDown(semanticModelIdHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - expect(customerPartsComponent['ctrlKeyState']).toBeTruthy(); - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - - await waitFor(() => { - fireEvent.keyUp(semanticModelIdHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - expect(customerPartsComponent['tableCustomerAsBuiltSortList']).toEqual([]); + expect(customerPartsComponent['tableCustomerAsBuiltSortList']).toEqual([ + ['name', 'asc'], + ['semanticModelId', 'desc'], + ]); }); - it('should handle updateCustomerParts null', async () => { const { fixture } = await renderCustomerParts(); const customerPartsComponent = fixture.componentInstance; @@ -145,10 +109,8 @@ describe('CustomerPartsComponent', () => { customerPartsComponent.updateCustomerParts(); - expect(updateCustomerPartAsBuiltSpy).toHaveBeenCalledWith(); expect(updateCustomerPartAsPlannedSpy).toHaveBeenCalledWith(); - }); it('should handle updateCustomerParts including search', async () => { @@ -162,10 +124,13 @@ describe('CustomerPartsComponent', () => { const search = 'test'; customerPartsComponent.updateCustomerParts(search); - expect(updateCustomerPartAsBuiltSpy).toHaveBeenCalledWith(0, 50, [], toGlobalSearchAssetFilter(search, true), true); - expect(updateCustomerPartAsPlannedSpy).toHaveBeenCalledWith(0, 50, [], toGlobalSearchAssetFilter(search, false), true); - + expect(updateCustomerPartAsPlannedSpy).toHaveBeenCalledWith( + 0, + 50, + [], + toGlobalSearchAssetFilter(search, false), + true, + ); }); - }); diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.spec.ts b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.spec.ts index 576076cd52..23a0ace16f 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.spec.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.spec.ts @@ -17,7 +17,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ - import { OtherPartsState } from '@page/other-parts/core/other-parts.state'; import { OtherPartsModule } from '@page/other-parts/other-parts.module'; import { PartsState } from '@page/parts/core/parts.state'; @@ -117,7 +116,6 @@ describe('SupplierPartsComponent', () => { fireEvent.click(nameHeader); expect(supplierPartsComponent['tableSupplierAsBuiltSortList']).toEqual([['name', 'asc']]); - }); it('should multisort after column name and semanticModelId', async () => { @@ -149,45 +147,12 @@ describe('SupplierPartsComponent', () => { await waitFor(() => { fireEvent.click(semanticModelIdHeader); }); - expect(supplierPartsComponent['tableSupplierAsBuiltSortList']).toEqual([['name', 'asc'], ['semanticModelId', 'desc']]); + expect(supplierPartsComponent['tableSupplierAsBuiltSortList']).toEqual([ + ['name', 'asc'], + ['semanticModelId', 'desc'], + ]); }); - it('should reset sorting on third click', async () => { - const { fixture } = await renderSupplierParts({ roles: ['admin'] }); - const supplierPartsComponent = fixture.componentInstance; - - let nameHeader = await screen.findByText('table.column.name'); - fireEvent.click(nameHeader); - let semanticModelIdHeader = await screen.findByText('table.column.semanticModelId'); - - await waitFor(() => { - fireEvent.keyDown(semanticModelIdHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - expect(supplierPartsComponent['ctrlKeyState']).toBeTruthy(); - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - - await waitFor(() => { - fireEvent.keyUp(semanticModelIdHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - await waitFor(() => { - fireEvent.click(semanticModelIdHeader); - }); - expect(supplierPartsComponent['tableSupplierAsBuiltSortList']).toEqual([]); - }); - - it('should handle updateSupplierParts null', async () => { const { fixture } = await renderSupplierParts(); const supplierPartsComponent = fixture.componentInstance; @@ -198,10 +163,8 @@ describe('SupplierPartsComponent', () => { supplierPartsComponent.updateSupplierParts(); - expect(updateSupplierPartAsBuiltSpy).toHaveBeenCalledWith(); expect(updateSupplierPartAsPlannedSpy).toHaveBeenCalledWith(); - }); it('should handle updateCustomerParts including search', async () => { @@ -212,15 +175,16 @@ describe('SupplierPartsComponent', () => { const updateSupplierPartAsBuiltSpy = spyOn(otherPartsFacade, 'setSupplierPartsAsBuilt'); const updateSupplierPartAsPlannedSpy = spyOn(otherPartsFacade, 'setSupplierPartsAsPlanned'); - const search = 'test'; supplierPartsComponent.updateSupplierParts(search); - expect(updateSupplierPartAsBuiltSpy).toHaveBeenCalledWith(0, 50, [], toGlobalSearchAssetFilter(search, true), true); - expect(updateSupplierPartAsPlannedSpy).toHaveBeenCalledWith(0, 50, [], toGlobalSearchAssetFilter(search, false), true); - + expect(updateSupplierPartAsPlannedSpy).toHaveBeenCalledWith( + 0, + 50, + [], + toGlobalSearchAssetFilter(search, false), + true, + ); }); - - }); diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index b34d97f4b5..86f8f0c00b 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -25,16 +25,16 @@
- + variant="flat" + >
- Create alert icon + Create alert icon
{{ 'page.selectedParts.action' | i18n }}
@@ -46,26 +46,30 @@
- - + + class="table--header--row" + > - - + - + data-testid="table-component--body-row" + > - + mat-row + > - - + - + + @@ -125,56 +128,53 @@

{{ 'table.noResultFound' | i18n }}

-
- - - - + - -
- + + showFirstLastButtons + >
+
- build + build

{{ 'table.noResultFound' | i18n }}

-

{{ 'table.tryAgain' | i18n }}

- + * + (click)="openDialog()" + /> -
-
+
+
-
- +
+ + data-testid="select-all--test-id" + >
- + + data-testid="select-one--test-id" + > {{ 'table.noResultFound' | i18n }} mat-header-cell [ngClass]="{ hasQualityAlertsHeader: column === '!' }" class="table--header--id: column === 'id'" - (click)="sortingEventTrigger(column)"> + >
- {{ - tableConfig?.header?.[column] | i18n }} -
- sorting arrow - sorting arrow +
+ {{ + tableConfig?.header?.[column] | i18n }} +
+
+ + + +
+ {{ i + 1 + '.' }} +
+ sorting arrow +
+
-
- + filterIcon + class="table--header--row__menu--dropdown" + />
- + -
- + + [formControl]="filterFormGroup.controls[filter.filterKey]" + >
- - - -
- - {{ i + 1 + '.' }}{{ item[1] === 'asc' ? '↑' : item[1] === 'desc' ? '↓' : '' }} -
-
-
+
@@ -253,18 +260,19 @@

{{ 'table.noResultFound' | i18n }}

- + [ngTemplateOutletContext]="{ value: element[column], row: element }" + >
- +
!
@@ -280,7 +288,6 @@

{{ 'table.noResultFound' | i18n }}

{{ pureColumn }} - + {{ value | autoFormat | i18n }} - \ No newline at end of file + diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss index c5d6309d2d..85d4592431 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss @@ -38,7 +38,7 @@ &__menu { background: rgba(0, 0, 0, 0.04); - &>.table--select-all__dropdown:hover { + & > .table--select-all__dropdown:hover { background: none; } } @@ -304,7 +304,11 @@ tr.error { &--sort { width: 24px; - + height: 24px; + margin-right: 24px; + display: flex; + flex-direction: row; + align-items: center; &--asc { width: 10px; transform: rotate(180deg); @@ -315,6 +319,11 @@ tr.error { width: 10px; margin: 9.5px 7px; } + &--indicator { + display: flex; + align-items: center; + margin-left: 10px; + } } } } @@ -366,8 +375,8 @@ tr.error { padding: 0 !important; } -.checkbox+.mdc-data-table__header-cell, -.checkbox+.mdc-data-table__cell { +.checkbox + .mdc-data-table__header-cell, +.checkbox + .mdc-data-table__cell { @apply text-secondary; padding: 0 !important; } @@ -378,6 +387,7 @@ tr.error { } .table--header--tooltip { + white-space: pre-line; .mdc-tooltip__surface { background-color: rgb(48, 48, 48); @apply font-medium; @@ -391,4 +401,4 @@ tr.error { .mdc-tooltip--multiline { white-space: pre-line; } -} \ No newline at end of file +} diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index abbfea3f60..346efc93d8 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -40,7 +40,6 @@ import { MultiSelectAutocompleteComponent } from '@shared/components/multi-selec import { CreateHeaderFromColumns, PartTableType, - SortingOptions, TableConfig, TableEventConfig, TableHeaderSort, @@ -63,7 +62,8 @@ export class PartsTableComponent implements OnInit { @ViewChild(MatSort) sort: MatSort; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('tableElement', { read: ElementRef }) tableElementRef: ElementRef; - @ViewChildren(MultiSelectAutocompleteComponent) multiSelectAutocompleteComponents: QueryList; + @ViewChildren(MultiSelectAutocompleteComponent) + multiSelectAutocompleteComponents: QueryList; @Input() multiSelectActive = false; @Input() labelId: string; @@ -291,8 +291,6 @@ export class PartsTableComponent implements OnInit { public filterConfiguration: any[]; public displayedColumns: string[]; - public sortingEvent: Record = {}; - filterFormGroup = new FormGroup({}); public isDateElement(key: string) { @@ -356,7 +354,7 @@ export class PartsTableComponent implements OnInit { 'manufacturingCountry', 'activeAlerts', 'activeInvestigations', - 'menu' + 'menu', ]; private readonly displayedColumnsAsPlannedForTable: string[] = [ @@ -375,7 +373,7 @@ export class PartsTableComponent implements OnInit { 'catenaXSiteId', 'functionValidFrom', 'functionValidUntil', - 'menu' + 'menu', ]; private readonly sortableColumnsAsBuilt: Record = { @@ -538,16 +536,10 @@ export class PartsTableComponent implements OnInit { private pageSize: number; private sorting: TableHeaderSort; - ngAfterViewInit() { this.paginator._intl.itemsPerPageLabel = 'Show'; } - private setupSortingEvent(): void { - const sortingNames = Object.keys(this.tableConfig.sortableColumns); - sortingNames.forEach(sortName => (this.sortingEvent[sortName] = SortingOptions.NONE)); - } - public triggerFilterAdding(): void { const filterValues: any = { ...this.filterFormGroup.value }; const selectedSemanticDataModelOptions: string[] = []; @@ -560,29 +552,23 @@ export class PartsTableComponent implements OnInit { this.filterActivated.emit(filterValues); } - constructor(private readonly tableViewSettingsService: TableSettingsService, private dialog: MatDialog) { } + constructor(private readonly tableViewSettingsService: TableSettingsService, private dialog: MatDialog) {} public defaultColumns: string[]; private tableViewConfig: TableViewConfig; ngOnInit() { - this.initializeTableViewSettings() + this.initializeTableViewSettings(); this.tableViewSettingsService.getEvent().subscribe(() => { this.setupTableViewSettings(); - }) + }); this.setupTableViewSettings(); - - if (this.tableConfig.sortableColumns) { - this.setupSortingEvent(); - } } public getTooltip(column: string) { - return column === '!' - ? i18next.t('parts.openInvestigations') - : 'First click: sort in ascending order ↑ Second click: sort in descending order ↓ Third click: reset sorting'; + return column === '!' ? i18next.t('parts.openInvestigations') : i18next.t('table.sortTooltip'); } private setupTableConfigurations( @@ -615,12 +601,24 @@ export class PartsTableComponent implements OnInit { // if yes, check if there is a table-setting for this table type if (settingsList[this.tableType]) { // if yes, get the effective displayedcolumns from the settings and set the tableconfig after it. - this.setupTableConfigurations(settingsList[this.tableType].columnsForTable, settingsList[this.tableType].filterColumnsForTable, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + this.setupTableConfigurations( + settingsList[this.tableType].columnsForTable, + settingsList[this.tableType].filterColumnsForTable, + this.tableViewConfig.sortableColumns, + this.tableViewConfig.filterConfiguration, + this.tableViewConfig.filterFormGroup, + ); } else { // if no, create new a table setting for this.tabletype and put it into the list. Additionally, intitialize default table configuration settingsList[this.tableType] = this.getSettingsList(); this.tableViewSettingsService.storeTableSettings(this.tableType, settingsList); - this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + this.setupTableConfigurations( + this.tableViewConfig.displayedColumnsForTable, + this.tableViewConfig.displayedColumns, + this.tableViewConfig.sortableColumns, + this.tableViewConfig.filterConfiguration, + this.tableViewConfig.filterFormGroup, + ); } } else { // if no, create new list and a settings entry for this.tabletype with default values and set correspondingly the tableconfig @@ -629,11 +627,17 @@ export class PartsTableComponent implements OnInit { columnsForDialog: this.tableViewConfig.displayedColumnsForTable, columnSettingsOptions: this.getDefaultColumnVisibilityMap(), columnsForTable: this.tableViewConfig.displayedColumnsForTable, - filterColumnsForTable: this.tableViewConfig.displayedColumns - } - } + filterColumnsForTable: this.tableViewConfig.displayedColumns, + }, + }; this.tableViewSettingsService.storeTableSettings(this.tableType, newTableSettingsList); - this.setupTableConfigurations(this.tableViewConfig.displayedColumnsForTable, this.tableViewConfig.displayedColumns, this.tableViewConfig.sortableColumns, this.tableViewConfig.filterConfiguration, this.tableViewConfig.filterFormGroup); + this.setupTableConfigurations( + this.tableViewConfig.displayedColumnsForTable, + this.tableViewConfig.displayedColumns, + this.tableViewConfig.sortableColumns, + this.tableViewConfig.filterConfiguration, + this.tableViewConfig.filterFormGroup, + ); } } @@ -642,8 +646,8 @@ export class PartsTableComponent implements OnInit { columnsForDialog: this.tableViewConfig.displayedColumnsForTable, columnSettingsOptions: this.getDefaultColumnVisibilityMap(), columnsForTable: this.tableViewConfig.displayedColumnsForTable, - filterColumnsForTable: this.tableViewConfig.displayedColumns - } + filterColumnsForTable: this.tableViewConfig.displayedColumns, + }; } private getDefaultColumnVisibilityMap(): Map { @@ -662,8 +666,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsPlannedCustomerForTable, filterConfiguration: this.assetAsPlannedCustomerFilterConfiguration, filterFormGroup: this.assetAsPlannedCustomerFilterFormGroup, - sortableColumns: this.sortableColumnsAsPlannedCustomer - } + sortableColumns: this.sortableColumnsAsPlannedCustomer, + }; break; // TODO add other table view configurations when they are implemented @@ -677,8 +681,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsPlannedForTable, filterConfiguration: this.assetAsPlannedFilterConfiguration, filterFormGroup: this.assetAsPlannedFilterFormGroup, - sortableColumns: this.sortableColumnsAsPlanned - } + sortableColumns: this.sortableColumnsAsPlanned, + }; break; case PartTableType.AS_PLANNED_SUPPLIER: this.tableViewConfig = { @@ -686,8 +690,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsPlannedSupplierForTable, filterConfiguration: this.assetAsPlannedSupplierFilterConfiguration, filterFormGroup: this.assetAsPlannedSupplierFilterFormGroup, - sortableColumns: this.sortableColumnsAsPlannedSupplier - } + sortableColumns: this.sortableColumnsAsPlannedSupplier, + }; break; case PartTableType.AS_BUILT_OWN: this.tableViewConfig = { @@ -695,8 +699,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsBuiltForTable, filterConfiguration: this.assetAsBuiltFilterConfiguration, filterFormGroup: this.assetAsBuiltFilterFormGroup, - sortableColumns: this.sortableColumnsAsBuilt - } + sortableColumns: this.sortableColumnsAsBuilt, + }; break; case PartTableType.AS_BUILT_CUSTOMER: this.tableViewConfig = { @@ -704,8 +708,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsBuiltCustomerForTable, filterConfiguration: this.assetAsBuiltCustomerFilterConfiguration, filterFormGroup: this.assetAsBuiltCustomerFilterFormGroup, - sortableColumns: this.sortableColumnsAsBuiltCustomer - } + sortableColumns: this.sortableColumnsAsBuiltCustomer, + }; break; case PartTableType.AS_BUILT_SUPPLIER: this.tableViewConfig = { @@ -713,8 +717,8 @@ export class PartsTableComponent implements OnInit { displayedColumnsForTable: this.displayedColumnsAsBuiltSupplierForTable, filterConfiguration: this.assetAsBuiltSupplierFilterConfiguration, filterFormGroup: this.assetAsBuiltSupplierFilterFormGroup, - sortableColumns: this.sortableColumnsAsBuiltSupplier - } + sortableColumns: this.sortableColumnsAsBuiltSupplier, + }; break; } } @@ -877,13 +881,13 @@ export class PartsTableComponent implements OnInit { const config = new MatDialogConfig(); config.autoFocus = false; config.data = { - title: "table.tableSettings.title", - panelClass: "custom", + title: 'table.tableSettings.title', + panelClass: 'custom', tableType: this.tableType, defaultColumns: this.tableViewConfig.displayedColumnsForTable, - defaultFilterColumns: this.tableViewConfig.displayedColumns + defaultFilterColumns: this.tableViewConfig.displayedColumns, }; - this.dialog.open(TableSettingsComponent, config) + this.dialog.open(TableSettingsComponent, config); } public areAllRowsSelected(): boolean { @@ -947,19 +951,6 @@ export class PartsTableComponent implements OnInit { return !!this.selection.selected.find(data => JSON.stringify(data) === JSON.stringify(row)); } - public sortingEventTrigger(column: string): void { - if (!this.sortingEvent[column]) { - return; - } - if (this.sortingEvent[column] === SortingOptions.NONE) { - this.sortingEvent[column] = SortingOptions.ASC; - } else if (this.sortingEvent[column] === SortingOptions.ASC) { - this.sortingEvent[column] = SortingOptions.DSC; - } else { - this.sortingEvent[column] = SortingOptions.NONE; - } - } - private addSelectedValues(newData: unknown[]): void { addSelectedValues(this.selection, newData); } diff --git a/frontend/src/app/modules/shared/components/table/table.component.html b/frontend/src/app/modules/shared/components/table/table.component.html index f9e6e47633..bf8d9f70a0 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.html +++ b/frontend/src/app/modules/shared/components/table/table.component.html @@ -19,16 +19,18 @@ SPDX-License-Identifier: Apache-2.0 --> -
+
-

{{ tableHeader | i18n }}

+

{{ tableHeader | i18n }}

- - + + class="notification-table--header--row" + > -
-
+ - + data-testid="table-component--body-row" + > - - - - - - + - + + + + + @@ -128,37 +132,36 @@

{{ 'table.noResultFound' | i18n }}

-
- @@ -166,45 +169,49 @@

{{ 'table.noResultFound' | i18n }}

- - + [attr.colspan]="tableConfig?.displayedColumns.length" + > - - - + - -
- + + showFirstLastButtons + >
-
- build +
+
+ build

{{ 'table.noResultFound' | i18n }}

-

{{ 'table.tryAgain' | i18n }}

-
- +
+ + data-testid="select-all--test-id" + > - + class="notification-table--select-all__dropdown" + > keyboard_arrow_down
- - - + + +
- + + data-testid="select-one--test-id" + > - + * + (click)="openDialog()" + /> - - + -
- - @@ -216,17 +223,15 @@

{{ 'table.noResultFound' | i18n }}

-
-
+
+
- @@ -235,94 +240,100 @@

{{ 'table.noResultFound' | i18n }}

- -
-
+ class="notification-table--header" + > +
+
{{ tableConfig?.header?.[column] | i18n }} -
- sorting arrow - sorting arrow +
+ + + +
+ {{ i + 1 + '.' }} +
+ sorting arrow +
+
-
- + filterIcon - + class="notification-table--header--cell--filter--icon" + /> + -
- + + [formControl]="filterFormGroup.controls[filter.filterKey]" + >
- - - - -
- - {{ i + 1 + '.' }}{{ item[1] === 'asc' ? '↑' : item[1] === 'desc' ? '↓' : '' }} -
-
-
+ class="notification-table--body--cell" + data-testid="table-component--cell-data" + > + [ngTemplateOutletContext]="{ value: element[column], row: element }" + >
- -
- + + "> - - + + [tableType]="PartTableType.AS_BUILT_CUSTOMER">
-
- + + "> - - + + [tableType]="PartTableType.AS_PLANNED_CUSTOMER">
- + - + - + \ No newline at end of file diff --git a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html index cf37afcdc6..6748eabf76 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html +++ b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.html @@ -18,89 +18,98 @@ SPDX-License-Identifier: Apache-2.0 --> -
-
- -
-
- -
- - -

{{'page.asBuiltParts' | i18n }}

- - - - {{ 'pageOtherParts.tab.supplier' | i18n }} - - - + + +
+
+

+ My parts icon + {{ 'pageTitle.otherParts' | i18n }} +

+
+
+
+ +
+
- - - {{ 'pageOtherParts.tab.customer' | i18n }} - + +
+ +
+
+ + + + + +
{{ 'pageOtherParts.tab.supplier' | i18n }}
+
- -
-
-
- -

{{'page.asPlannedParts' | i18n }}

- - - - {{ 'pageOtherParts.tab.supplier' | i18n }} - + + - - + + +
{{ 'pageOtherParts.tab.customer' | i18n }}
+
- - - {{ 'pageOtherParts.tab.customer' | i18n }} - + + +
+
+ + + + +
{{ 'pageOtherParts.tab.supplier' | i18n }}
+
- -
-
-
-
+ + - -
+ + +
{{ 'pageOtherParts.tab.customer' | i18n }}
+
+ + +
+
+
+
+ + +
+ + \ No newline at end of file diff --git a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.scss b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.scss index 634bf8268f..3931fab437 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.scss +++ b/frontend/src/app/modules/page/other-parts/presentation/other-parts.component.scss @@ -19,46 +19,37 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -.other-parts-container { - @apply flex flex-col; - height: 72vh; -} - -.parts-search-input-wrapper { - padding: 20px; -} - -.app-bom-lifecycle-activator-container { - grid-column: 1 / span 2; /* This will make it span both columns */ - width: 100%; -} - .split-container { - height: auto; + height: 518px; } -.split-container > as-split-area { - overflow: hidden; +.split-container-wrapper { + overflow-y: hidden !important; } -.supplier-parts-as-built-table-wrapper { - max-height: 51vh; -} - -.supplier-parts-as-planned-table-wrapper { - max-height: 51vh; +.other-parts-mat-tab-group { + width: 100%; + overflow-y: hidden !important; } -.customer-parts-as-built-table-wrapper { - max-height: 51vh; +.app-bom-lifecycle-activator-container { + grid-column: 1 / span 2; + /* This will make it span both columns */ + width: 100%; + border-bottom: 1px solid; + @apply border-inactiveShape; } -.customer-parts-as-planned-table-wrapper { - max-height: 51vh; +.parts-table-wrapper { + @extend .parts-tab-wrapper; } -.other-parts-mat-tab-group { - margin: 16px; +.parts-tab-wrapper { + height: 100% !important; + overflow-y: auto !important; } - +.split-area { + overflow-y: hidden !important; + height: 405px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.html b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.html index 4692600434..8a80b4fc3d 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.html +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.html @@ -17,89 +17,85 @@ SPDX-License-Identifier: Apache-2.0 --> -
- + - - + "> + +
-
- + + "> - - + +
- - - - - - + - + - - + - - + (submitted)="submit()"> + \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/bom-lifecycle-activator/bom-lifecycle-activator.component.ts b/frontend/src/app/modules/shared/components/bom-lifecycle-activator/bom-lifecycle-activator.component.ts index f61c347d8d..09313af3ff 100644 --- a/frontend/src/app/modules/shared/components/bom-lifecycle-activator/bom-lifecycle-activator.component.ts +++ b/frontend/src/app/modules/shared/components/bom-lifecycle-activator/bom-lifecycle-activator.component.ts @@ -40,6 +40,8 @@ export class BomLifecycleActivatorComponent implements OnInit { public lifecycleCtrl = new FormControl(''); public selectedLifecycles: string[] = []; + private currentSelectedLifeCycles: string[] = []; + private unimplementedLifecycleTypes: string[] = [BomLifecycleType.AS_DESIGNED, BomLifecycleType.AS_ORDERED, BomLifecycleType.AS_SUPPORTED, BomLifecycleType.AS_RECYCLED]; constructor(public bomLifeCycleUserSetting: BomLifecycleSettingsService) { } @@ -138,6 +140,14 @@ export class BomLifecycleActivatorComponent implements OnInit { for (let i = 0; i < values.length; i++) { this.updateLifecycleConfig(values[i], true); } + + // sort the selection based on when an item was added + this.currentSelectedLifeCycles = this.currentSelectedLifeCycles.filter(item => values.includes(item)); + + const newItem = values.filter(item => !this.currentSelectedLifeCycles.includes(item)); + this.currentSelectedLifeCycles.push(...newItem); + this.selectedLifecycles = [...this.currentSelectedLifeCycles]; + } removeLifeCycle(lifeCycle: string): void { @@ -146,6 +156,8 @@ export class BomLifecycleActivatorComponent implements OnInit { if (index >= 0) { this.selectedLifecycles.splice(index, 1); this.selectedLifecycles = Array.from(this.selectedLifecycles); + + this.currentSelectedLifeCycles = [...this.selectedLifecycles]; } this.updateLifecycleConfig(lifeCycle, false); diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index 86f8f0c00b..556e3a416d 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -25,16 +25,16 @@
- + variant="flat">
- Create alert icon + Create alert icon
{{ 'page.selectedParts.action' | i18n }}
@@ -46,8 +46,7 @@
- - + + class="table--header--row"> - - + - + data-testid="table-component--body-row"> - + mat-row> - - + - + @@ -128,15 +127,16 @@

{{ 'table.noResultFound' | i18n }}

-
- @@ -144,35 +144,36 @@

{{ 'table.noResultFound' | i18n }}

- - - + - -
- + + showFirstLastButtons>
+
- build + build

{{ 'table.noResultFound' | i18n }}

- + * + (click)="openDialog()" /> +
-
- +
+ + data-testid="select-all--test-id">
- + + data-testid="select-one--test-id"> {{ 'table.noResultFound' | i18n }} [mat-sort-header]="tableConfig.sortableColumns?.[column] ? '' : null" [disabled]="!tableConfig.sortableColumns?.[column]" mat-header-cell - [ngClass]="{ hasQualityAlertsHeader: column === '!' }" - class="table--header--id: column === 'id'" - > + [ngClass]="{ hasQualityAlertsHeader: column === '!'}" + class="table--header--id: column === 'id'">
{{ - tableConfig?.header?.[column] | i18n }} + tableConfig?.header?.[column] | i18n }}
-
+
-
+
{{ i + 1 + '.' }}
- sorting arrow + " />
-
- + filterIcon + class="table--header--row__menu--dropdown" />
- + -
- + + [formControl]="filterFormGroup.controls[filter.filterKey]">
+
@@ -260,19 +254,18 @@

{{ 'table.noResultFound' | i18n }}

- + [ngTemplateOutletContext]="{ value: element[column], row: element }">
- +
!
@@ -288,6 +281,7 @@

{{ 'table.noResultFound' | i18n }}

{{ pureColumn }} - + {{ value | autoFormat | i18n }} - + \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss index 85d4592431..38fb30e16c 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.scss @@ -38,7 +38,7 @@ &__menu { background: rgba(0, 0, 0, 0.04); - & > .table--select-all__dropdown:hover { + &>.table--select-all__dropdown:hover { background: none; } } @@ -309,6 +309,7 @@ tr.error { display: flex; flex-direction: row; align-items: center; + &--asc { width: 10px; transform: rotate(180deg); @@ -319,6 +320,7 @@ tr.error { width: 10px; margin: 9.5px 7px; } + &--indicator { display: flex; align-items: center; @@ -352,11 +354,11 @@ tr.error { margin-left: 5px; } -.table--header--row .mdc-data-table__header-cell:first-child { +.select .table--header--row .mdc-data-table__header-cell:first-child { padding: 0 !important; } -.table--body--row .mdc-data-table__cell:first-child { +.select .table--body--row .mdc-data-table__cell:first-child { padding: 0 !important; border-bottom-style: none !important; } @@ -375,8 +377,8 @@ tr.error { padding: 0 !important; } -.checkbox + .mdc-data-table__header-cell, -.checkbox + .mdc-data-table__cell { +.checkbox+.mdc-data-table__header-cell, +.checkbox+.mdc-data-table__cell { @apply text-secondary; padding: 0 !important; } @@ -388,6 +390,7 @@ tr.error { .table--header--tooltip { white-space: pre-line; + .mdc-tooltip__surface { background-color: rgb(48, 48, 48); @apply font-medium; @@ -401,4 +404,4 @@ tr.error { .mdc-tooltip--multiline { white-space: pre-line; } -} +} \ No newline at end of file diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index 346efc93d8..e91d9ccb36 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -358,7 +358,6 @@ export class PartsTableComponent implements OnInit { ]; private readonly displayedColumnsAsPlannedForTable: string[] = [ - 'select', 'id', 'idShort', 'name', @@ -453,7 +452,6 @@ export class PartsTableComponent implements OnInit { ]; private readonly displayedColumnsAsBuiltCustomerForTable: string[] = [ - 'select', 'semanticDataModel', 'name', 'manufacturer', @@ -476,7 +474,6 @@ export class PartsTableComponent implements OnInit { }; private readonly displayedColumnsAsPlannedCustomerForTable: string[] = [ - 'select', 'semanticDataModel', 'name', 'manufacturer', @@ -517,7 +514,6 @@ export class PartsTableComponent implements OnInit { }; private readonly displayedColumnsAsPlannedSupplierForTable: string[] = [ - 'select', 'semanticDataModel', 'name', 'manufacturer', @@ -552,7 +548,7 @@ export class PartsTableComponent implements OnInit { this.filterActivated.emit(filterValues); } - constructor(private readonly tableViewSettingsService: TableSettingsService, private dialog: MatDialog) {} + constructor(private readonly tableViewSettingsService: TableSettingsService, private dialog: MatDialog) { } public defaultColumns: string[]; diff --git a/frontend/src/app/modules/shared/components/severity/severity.component.scss b/frontend/src/app/modules/shared/components/severity/severity.component.scss index 5ffc467a6a..131611df9d 100644 --- a/frontend/src/app/modules/shared/components/severity/severity.component.scss +++ b/frontend/src/app/modules/shared/components/severity/severity.component.scss @@ -26,6 +26,8 @@ display: flex; align-items: center; color: currentColor; + white-space: nowrap; + padding-right: 10px } &__MINOR { diff --git a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss index 43f9e92e6a..57915a1a2f 100644 --- a/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss +++ b/frontend/src/app/modules/shared/components/table-settings/table-settings.component.scss @@ -20,7 +20,7 @@ @tailwind base; .container { - width: 240px; + width: 200px; overflow: hidden !important; } @@ -66,7 +66,7 @@ .select-all-container { display: flex; justify-content: space-between; - padding-left: 16px; + padding-left: 21.5px; } .placeholder { @@ -122,6 +122,7 @@ .drag-icon { margin-left: 5px; + margin-right: 5px; opacity: 0; } diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 5c377cf2a0..4359558efb 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -16,7 +16,7 @@ "pageTitle": { "dashboard": "Dashboard", "parts": "My parts", - "otherParts": "Other parts", + "otherParts": "Shared parts", "about": "About Cofinity-X", "relations": "Part relations", "admin": "Administration Catena-X", diff --git a/frontend/src/theme/base.scss b/frontend/src/theme/base.scss index 4532ef2fb8..3d6d12b27c 100644 --- a/frontend/src/theme/base.scss +++ b/frontend/src/theme/base.scss @@ -258,7 +258,7 @@ border-radius: 50px !important; } - .mat-mdc-unelevated-button > .mdc-button__label { + .mat-mdc-unelevated-button>.mdc-button__label { @apply text-interactive; } @@ -310,7 +310,7 @@ @apply text-black; } - .mat-sort-header-sorted > .mat-sort-header-content { + .mat-sort-header-sorted>.mat-sort-header-content { @apply font-semiBold; } @@ -431,9 +431,7 @@ .mat-mdc-form-field.mat-accent .mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, .mat-mdc-form-field.mat-accent .mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, - .mat-mdc-form-field.mat-accent - .mdc-text-field--invalid:not(.mdc-text-field--disabled) - .mdc-notched-outline__trailing { + .mat-mdc-form-field.mat-accent .mdc-text-field--invalid:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { border-width: 2px !important; } @@ -443,8 +441,7 @@ line-height: 28px; } - .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field - .mdc-notched-outline__notch { + .mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field.mat-mdc-form-field .mdc-notched-outline__notch { border-right: none; border-left: none; } @@ -563,4 +560,4 @@ .mdc-text-field__input::-webkit-calendar-picker-indicator { display: flex !important; -} +} \ No newline at end of file From 6e8670b5a7f0287daf5db900d3d2cf25c943f456 Mon Sep 17 00:00:00 2001 From: Jan Kreutzfeld <100288948+ds-jkreutzfeld@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:23:10 +0100 Subject: [PATCH 07/11] chore(trivy):[DO-4229] Fix multiple Trivy security issues by adapting the Helm charts and updating dependencies * chore(trivy):[DO-4229] Fix security issues identified by Trivy * chore(trivy):[DO-4229] Fix imports after lib update * chore(trivy):[DO-4229] Update resilience4j core library to provide required method * chore(trivy):[DO-4229] Temporarily store sarif file for analysis * chore(trivy):[DO-4229] Fix trivy issues in Helm config * chore(trivy):[DO-4229] Update sub-dependencies to avoid CVEs * chore(trivy):[DO-4229] Revoke unnecessary changes * chore(trivy):[DO-4229] Move fsGroup config back to pod level * chore(trivy):[DO-4229] Remove unnecessary template * chore(trivy):[DO-4229] Provide emptyDir mounts for readOnlyFilesystem * chore(trivy):[DO-4229] Provide emptyDir mounts for readOnlyFilesystem * chore(trivy):[DO-4229] Make filesystem writable again, as required by the init scripts * chore(trivy):[DO-4229] Try to change nginx user id to be > 10000 * chore(trivy):[DO-4229] Add tmp volume for backend * chore(trivy):[DO-4229] Remove outdated link comment --- .github/workflows/helm-test.yaml | 2 +- .../charts/backend/templates/deployment.yaml | 6 +++++ .../charts/backend/values.yaml | 2 +- .../charts/frontend/values.yaml | 14 +++++++---- .../templates/edc-vault-secret.yaml | 14 ----------- charts/traceability-foss/values.yaml | 17 +++++++++----- frontend/Dockerfile | 4 +++- pom.xml | 2 +- tx-backend/pom.xml | 23 +++++++++++++++++++ .../repository/AssetAsBuildSpecification.java | 4 ++-- .../AssetAsPlannedSpecification.java | 4 ++-- .../alert/repository/AlertSpecification.java | 7 +++--- .../InvestigationSpecification.java | 9 ++++---- 13 files changed, 69 insertions(+), 39 deletions(-) delete mode 100644 charts/traceability-foss/templates/edc-vault-secret.yaml diff --git a/.github/workflows/helm-test.yaml b/.github/workflows/helm-test.yaml index 950748c519..4dc5255e5c 100644 --- a/.github/workflows/helm-test.yaml +++ b/.github/workflows/helm-test.yaml @@ -71,7 +71,7 @@ jobs: version: v3.9.3 - name: Set up chart-testing - uses: helm/chart-testing-action@v2.4.0 + uses: helm/chart-testing-action@v2.6.1 - name: Run chart-testing (list-changed) id: list-changed diff --git a/charts/traceability-foss/charts/backend/templates/deployment.yaml b/charts/traceability-foss/charts/backend/templates/deployment.yaml index 7170c3b973..0ce316f050 100644 --- a/charts/traceability-foss/charts/backend/templates/deployment.yaml +++ b/charts/traceability-foss/charts/backend/templates/deployment.yaml @@ -153,6 +153,9 @@ spec: {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - mountPath: /tmp + name: volume-tmp {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} @@ -165,3 +168,6 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + volumes: + - name: volume-tmp + emptyDir: { } diff --git a/charts/traceability-foss/charts/backend/values.yaml b/charts/traceability-foss/charts/backend/values.yaml index b2f5823ca6..0062fa856d 100644 --- a/charts/traceability-foss/charts/backend/values.yaml +++ b/charts/traceability-foss/charts/backend/values.yaml @@ -73,7 +73,7 @@ securityContext: capabilities: drop: - ALL - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true service: type: ClusterIP diff --git a/charts/traceability-foss/charts/frontend/values.yaml b/charts/traceability-foss/charts/frontend/values.yaml index 88eba1f40f..8e53913635 100644 --- a/charts/traceability-foss/charts/frontend/values.yaml +++ b/charts/traceability-foss/charts/frontend/values.yaml @@ -60,16 +60,22 @@ serviceAccount: podAnnotations: { } -podSecurityContext: { } -# fsGroup: 2000 +podSecurityContext: + fsGroup: 10100 # Following Catena-X Helm Best Practices @url: https://catenax-ng.github.io/docs/kubernetes-basics/helm # @url: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod securityContext: + seccompProfile: + type: RuntimeDefault allowPrivilegeEscalation: false runAsNonRoot: true - runAsUser: 101 - # runAsGroup: 3000 + runAsUser: 10100 + runAsGroup: 10100 + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false # Variables are written to the HTML files on startup, so this is not possible. service: type: ClusterIP diff --git a/charts/traceability-foss/templates/edc-vault-secret.yaml b/charts/traceability-foss/templates/edc-vault-secret.yaml deleted file mode 100644 index be46358d59..0000000000 --- a/charts/traceability-foss/templates/edc-vault-secret.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{ if .Values.install.edc.vault }} - -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Values.config.vault.secret.name | toString | quote }} - namespace: {{ .Release.Namespace | default "default" | quote }} -type: Opaque -data: - dapsCert: {{ .Files.Get "certs/edc.crt" | b64enc }} - dapsKey: {{ .Files.Get "certs/edc.key" | b64enc }} - dataEncryption: {{ .Values.config.vault.secret.dataEncryptionValue | toString | quote | b64enc }} - -{{ end }} diff --git a/charts/traceability-foss/values.yaml b/charts/traceability-foss/values.yaml index f94ae6f9cb..442314eafe 100644 --- a/charts/traceability-foss/values.yaml +++ b/charts/traceability-foss/values.yaml @@ -78,16 +78,21 @@ frontend: podAnnotations: {} - podSecurityContext: {} - # fsGroup: 2000 + podSecurityContext: + fsGroup: 10100 - # Following Catena-X Helm Best Practices @url: https://catenax-ng.github.io/docs/kubernetes-basics/helm # @url: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod securityContext: + seccompProfile: + type: RuntimeDefault allowPrivilegeEscalation: false runAsNonRoot: true - runAsUser: 101 - # runAsGroup: 3000 + runAsUser: 10100 + runAsGroup: 10100 + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false # Variables are written to the HTML files on startup, so this is not possible. service: type: ClusterIP @@ -206,7 +211,7 @@ backend: capabilities: drop: - ALL - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true service: type: ClusterIP diff --git a/frontend/Dockerfile b/frontend/Dockerfile index a1e6c6b6ef..0ff01a39cd 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -39,6 +39,8 @@ HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=10s \ USER root +RUN apk --no-cache add shadow=4.13-r4 && usermod -u 10100 -o nginx && groupmod -g 10100 -o nginx + RUN rm /usr/share/nginx/html/index.html && rm /etc/nginx/conf.d/default.conf # Copy project files from ‘builder’ stage copy over the artifacts in dist folder to default nginx public folder @@ -69,4 +71,4 @@ USER root RUN chown nginx:nginx /etc/nginx/nginx.conf RUN chown nginx:nginx /etc/nginx/security-headers.conf -USER 101 +USER 10100 diff --git a/pom.xml b/pom.xml index e603e884b9..4bea9f4ac6 100644 --- a/pom.xml +++ b/pom.xml @@ -99,7 +99,7 @@ SPDX-License-Identifier: Apache-2.0 2.15.2 5.9.3 3.0.0 - 1.2.1-SNAPSHOT + 1.4.1 jacoco reuseReports diff --git a/tx-backend/pom.xml b/tx-backend/pom.xml index 2f4854d80e..c2b01ba0a8 100644 --- a/tx-backend/pom.xml +++ b/tx-backend/pom.xml @@ -56,6 +56,12 @@ SPDX-License-Identifier: Apache-2.0 irs-registry-client ${irs-client-lib.version} + + + org.eclipse.jetty + jetty-xml + 11.0.18 + @@ -172,10 +178,22 @@ SPDX-License-Identifier: Apache-2.0 org.springframework.boot spring-boot-starter-web + + + org.apache.tomcat.embed + tomcat-embed-core + 10.1.16 + org.springframework.cloud spring-cloud-starter-openfeign + + + org.springframework.security + spring-security-rsa + 1.0.12.RELEASE + com.fasterxml.jackson.datatype jackson-datatype-jsr310 @@ -218,6 +236,11 @@ SPDX-License-Identifier: Apache-2.0 resilience4j-retry ${resilience4j.version} + + io.github.resilience4j + resilience4j-core + ${resilience4j.version} + io.github.resilience4j resilience4j-spring-boot2 diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asbuilt/repository/AssetAsBuildSpecification.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asbuilt/repository/AssetAsBuildSpecification.java index faedf2c737..bd50c93aeb 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asbuilt/repository/AssetAsBuildSpecification.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asbuilt/repository/AssetAsBuildSpecification.java @@ -28,10 +28,10 @@ import org.eclipse.tractusx.traceability.common.model.SearchCriteriaFilter; import org.eclipse.tractusx.traceability.common.model.SearchCriteriaOperator; import org.eclipse.tractusx.traceability.common.repository.BaseSpecification; -import org.glassfish.jersey.internal.guava.Lists; import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.domain.Specification; +import java.util.ArrayList; import java.util.List; @@ -46,7 +46,7 @@ public Predicate toPredicate(@NotNull Root root, @NotNull Cr } public static Specification toSpecification(final List allSpecifications, SearchCriteriaOperator searchCriteriaOperator) { - var specifications = Lists.newArrayList(allSpecifications); + var specifications = new ArrayList<>(allSpecifications); if (specifications.isEmpty()) { return Specification.allOf(); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asplanned/repository/AssetAsPlannedSpecification.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asplanned/repository/AssetAsPlannedSpecification.java index df4b2e42f3..2d0f98b10e 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asplanned/repository/AssetAsPlannedSpecification.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/assets/infrastructure/asplanned/repository/AssetAsPlannedSpecification.java @@ -28,10 +28,10 @@ import org.eclipse.tractusx.traceability.common.model.SearchCriteriaFilter; import org.eclipse.tractusx.traceability.common.model.SearchCriteriaOperator; import org.eclipse.tractusx.traceability.common.repository.BaseSpecification; -import org.glassfish.jersey.internal.guava.Lists; import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.domain.Specification; +import java.util.ArrayList; import java.util.List; @@ -47,7 +47,7 @@ public Predicate toPredicate(@NotNull Root root, @NotNull } public static Specification toSpecification(final List allSpecifications, SearchCriteriaOperator searchCriteriaOperator) { - var specifications = Lists.newArrayList(allSpecifications); + var specifications = new ArrayList<>(allSpecifications); if (specifications.isEmpty()) { return Specification.allOf(); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/repository/AlertSpecification.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/repository/AlertSpecification.java index 37c8ab6e91..247d40b204 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/repository/AlertSpecification.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/alert/repository/AlertSpecification.java @@ -27,19 +27,20 @@ import org.eclipse.tractusx.traceability.qualitynotification.domain.base.QualityNotificationSpecificationUtil; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; -import org.glassfish.jersey.internal.guava.Lists; import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.domain.Specification; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; public class AlertSpecification extends BaseSpecification implements Specification { private static final List ATTRIBUTES_IN_ALERT_ENTITY = List.of("description", "status", "createdDate"); + public AlertSpecification(SearchCriteriaFilter criteria) { super(criteria); } @@ -52,7 +53,7 @@ public Predicate toPredicate(@NotNull Root root, @NotNull CriteriaQ private Predicate createPredicateBasedOnJoin(SearchCriteriaFilter criteria, Root root, CriteriaBuilder builder) { Join alertJoin = root.join("notifications"); Path predicatePath = ATTRIBUTES_IN_ALERT_ENTITY.contains(criteria.getKey()) ? - root.get(criteria.getKey()):alertJoin.get(criteria.getKey()); + root.get(criteria.getKey()) : alertJoin.get(criteria.getKey()); if (criteria.getStrategy().equals(SearchStrategy.EQUAL)) { return builder.equal( predicatePath.as(String.class), @@ -75,7 +76,7 @@ private Predicate createPredicateBasedOnJoin(SearchCriteriaFilter criteria, Root } public static Specification toSpecification(final List allSpecifications, SearchCriteriaOperator searchCriteriaOperator) { - var specifications = Lists.newArrayList(allSpecifications); + var specifications = new ArrayList<>(allSpecifications); if (specifications.isEmpty()) { return Specification.allOf(); } diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/investigation/repository/InvestigationSpecification.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/investigation/repository/InvestigationSpecification.java index c090c39a0a..a915a3c1df 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/investigation/repository/InvestigationSpecification.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/qualitynotification/infrastructure/investigation/repository/InvestigationSpecification.java @@ -27,19 +27,20 @@ import org.eclipse.tractusx.traceability.qualitynotification.domain.base.QualityNotificationSpecificationUtil; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationNotificationEntity; -import org.glassfish.jersey.internal.guava.Lists; import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.domain.Specification; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; public class InvestigationSpecification extends BaseSpecification implements Specification { private static final List ATTRIBUTES_IN_INVESTIGATION_ENTITY = List.of("description", "status", "createdDate"); + public InvestigationSpecification(SearchCriteriaFilter criteria) { super(criteria); } @@ -50,9 +51,9 @@ public Predicate toPredicate(@NotNull Root root, @NotNull C } private Predicate createPredicateBasedOnJoin(SearchCriteriaFilter criteria, Root root, CriteriaBuilder builder) { - Join investigationJoin = root.join("notifications"); + Join investigationJoin = root.join("notifications"); Path predicatePath = ATTRIBUTES_IN_INVESTIGATION_ENTITY.contains(criteria.getKey()) ? - root.get(criteria.getKey()):investigationJoin.get(criteria.getKey()); + root.get(criteria.getKey()) : investigationJoin.get(criteria.getKey()); if (criteria.getStrategy().equals(SearchStrategy.EQUAL)) { return builder.equal( predicatePath.as(String.class), @@ -75,7 +76,7 @@ private Predicate createPredicateBasedOnJoin(SearchCriteriaFilter criteria, Root } public static Specification toSpecification(final List allSpecifications, SearchCriteriaOperator searchCriteriaOperator) { - var specifications = Lists.newArrayList(allSpecifications); + var specifications = new ArrayList<>(allSpecifications); if (specifications.isEmpty()) { return Specification.allOf(); } From b1fec28a2e6ba37f1cfe2b4dc015fbe795770515 Mon Sep 17 00:00:00 2001 From: Jan Kreutzfeld <100288948+ds-jkreutzfeld@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:37:59 +0100 Subject: [PATCH 08/11] chore(security):[DO-4230] Add missing security headers, replace calls to deprecated methods with new API design (#56) --- frontend/build/security-headers.conf | 1 + .../common/config/SecurityConfig.java | 44 +++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/frontend/build/security-headers.conf b/frontend/build/security-headers.conf index f6f3f636b8..489f4219ea 100644 --- a/frontend/build/security-headers.conf +++ b/frontend/build/security-headers.conf @@ -4,3 +4,4 @@ add_header X-Frame-Options "DENY" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin" always; add_header Feature-Policy "microphone 'none'; geolocation 'none'; camera 'none'" always; +add_header Permissions-Policy "microphone=(), geolocation=(), camera=()" always; diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java index e8338df155..8badb4f249 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/config/SecurityConfig.java @@ -26,13 +26,19 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.header.writers.PermissionsPolicyHeaderWriter; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.time.Duration; import java.util.List; @Configuration @@ -42,6 +48,9 @@ @RequiredArgsConstructor public class SecurityConfig { + private static final long HSTS_MAX_AGE_DAYS = 365; + + private static final String PERMISSION_POLICY = "microphone=(), geolocation=(), camera=()"; private static final String[] WHITELIST_PATHS = { "/swagger-ui.html", @@ -65,15 +74,27 @@ public class SecurityConfig { @Bean - SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity) throws Exception { + SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity, @Value("${cors.origins}") final List origins) throws Exception { + + httpSecurity.httpBasic(AbstractHttpConfigurer::disable); + httpSecurity.formLogin(AbstractHttpConfigurer::disable); + httpSecurity.logout(AbstractHttpConfigurer::disable); + httpSecurity.anonymous(AbstractHttpConfigurer::disable); + httpSecurity.csrf(AbstractHttpConfigurer::disable); + + httpSecurity.cors(customizer -> customizer.configurationSource(corsConfigurationSource(origins))); - httpSecurity.httpBasic().disable(); - httpSecurity.formLogin().disable(); - httpSecurity.logout().disable(); - httpSecurity.anonymous().disable(); - httpSecurity.csrf().disable(); - httpSecurity.cors(); + httpSecurity.headers(headers -> headers.httpStrictTransportSecurity( + httpStrictTransportSecurity -> + httpStrictTransportSecurity.maxAgeInSeconds(Duration.ofDays(HSTS_MAX_AGE_DAYS).toSeconds()) + .includeSubDomains(true) + .preload(true) + .requestMatcher(AnyRequestMatcher.INSTANCE))); + httpSecurity.headers(headers -> headers.addHeaderWriter(new ReferrerPolicyHeaderWriter( + ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN))); + + httpSecurity.headers(headers -> headers.addHeaderWriter(new PermissionsPolicyHeaderWriter(PERMISSION_POLICY))); httpSecurity.authorizeHttpRequests(auth -> auth .requestMatchers(WHITELIST_PATHS) @@ -81,17 +102,14 @@ SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity) throws .anyRequest() .authenticated()); - httpSecurity.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt() - .jwtAuthenticationConverter( - new JwtAuthenticationTokenConverter(resourceClient))) - .oauth2Client(); + httpSecurity.oauth2ResourceServer(server -> server.jwt(custom -> custom.jwtAuthenticationConverter(new JwtAuthenticationTokenConverter(resourceClient)))) + .oauth2Client(Customizer.withDefaults()); return httpSecurity.build(); } - @Bean - CorsConfigurationSource corsConfigurationSource(@Value("${cors.origins}") List origins) { + private CorsConfigurationSource corsConfigurationSource(List origins) { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(origins); configuration.setAllowedMethods(List.of("*")); From 0e0c8abffcbbeec861eaf7e54b1f46c92f621faf Mon Sep 17 00:00:00 2001 From: ramnarayan-cofinityx <147039699+ramnarayan-cofinityx@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:38:59 +0100 Subject: [PATCH 09/11] Feature/do 1578 and do 4345 sort quality investigations (#45) * Sort feature for Investigations * Fix for do-4345 * Added integration tests for sort --- .../common/request/OwnPageable.java | 4 + .../alert/ReadAlertsControllerIT.java | 642 ----------------- ...reatedAlertsInSortedOrderControllerIT.java | 224 ++++++ ...dAlertsWithSearchCriteriaControllerIT.java | 31 +- ...ceivedAlertsInSortedOrderControllerIT.java | 224 ++++++ ...dAlertsWithSearchCriteriaControllerIT.java | 83 +-- .../alert/ReceiverAlertsControllerIT.java | 14 +- ...vestigationsInSortedOrderControllerIT.java | 225 ++++++ ...gationsWithSearchCriteriaControllerIT.java | 35 +- .../ReadInvestigationsControllerIT.java | 643 +----------------- ...vestigationsInSortedOrderControllerIT.java | 225 ++++++ ...gationsWithSearchCriteriaControllerIT.java | 80 ++- .../testdata/AlertTestDataFactory.java | 173 ++++- .../InvestigationTestDataFactory.java | 177 ++++- 14 files changed, 1354 insertions(+), 1426 deletions(-) create mode 100644 tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsInSortedOrderControllerIT.java create mode 100644 tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsInSortedOrderControllerIT.java create mode 100644 tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsInSortedOrderControllerIT.java create mode 100644 tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsInSortedOrderControllerIT.java diff --git a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/request/OwnPageable.java b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/request/OwnPageable.java index 727fa4a0cf..5d62580fe9 100644 --- a/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/request/OwnPageable.java +++ b/tx-backend/src/main/java/org/eclipse/tractusx/traceability/common/request/OwnPageable.java @@ -82,6 +82,10 @@ private static String handleEnumColumns(final String column) { return switch(column) { case "status" -> "statusrank"; case "notifications_status" -> "notifications_statusrank"; + // Include the notification table based attributes + case "createdBy" -> "notifications.createdBy"; + case "targetDate" -> "notifications.targetDate"; + case "sendTo" -> "notifications.sendTo"; case "qualityAlertsInStatusActive" -> "noOfActiveAlerts"; case "qualityInvestigationsInStatusActive" -> "noOfActiveInvestigations"; default -> column; diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadAlertsControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadAlertsControllerIT.java index 304c923f92..48b4ad9559 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadAlertsControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadAlertsControllerIT.java @@ -24,7 +24,6 @@ import org.eclipse.tractusx.traceability.integration.common.support.AlertNotificationsSupport; import org.eclipse.tractusx.traceability.integration.common.support.AlertsSupport; import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; -import org.eclipse.tractusx.traceability.qualitynotification.domain.base.model.QualityNotificationSeverity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.model.NotificationSideBaseEntity; @@ -117,543 +116,6 @@ void shouldReturnNoCreatedAlerts() throws JoseException { .body("content", Matchers.hasSize(0)); } - @Test - void givenAlerts_whenGetAlerts_thenReturnSortedByCreationTime() throws JoseException { - // given - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(40L)) - .build(); - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .alert(secondAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .alert(thirdAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .alert(fourthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .alert(fifthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - // when/then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created") - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)); - } - - @Test - void givenSortByCreatedDateProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { - // given - String sortString = "createdDate,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .alert(secondAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .alert(thirdAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .alert(fourthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .alert(fifthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)); - } - - @Test - void givenSortByDescriptionProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { - // given - String sortString = "description,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .alert(secondAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .alert(thirdAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .alert(fourthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .alert(fifthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.description", Matchers.containsInRelativeOrder("5", "4", "3", "2", "1")); - } - - @Test - void givenSortByStatusProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { - // given - String sortString = "status,asc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .alert(secondAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .alert(thirdAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .alert(fourthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .alert(fifthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.status", Matchers.containsInRelativeOrder("CREATED", "CREATED", "SENT", "ACKNOWLEDGED", "CLOSED")); - } - - @Test - void givenSortBySeverityProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { - // given - String sortString = "severity,asc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .severity(QualityNotificationSeverity.CRITICAL) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .severity(QualityNotificationSeverity.MAJOR) - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .alert(secondAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .severity(QualityNotificationSeverity.LIFE_THREATENING) - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .alert(thirdAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .severity(QualityNotificationSeverity.MINOR) - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .alert(fourthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .severity(QualityNotificationSeverity.CRITICAL) - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .alert(fifthAlert) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "CRITICAL", "LIFE-THREATENING")); - } - - @Test - void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { - // given - String sortString = "createdDate,failure"; - - // when/then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(400) - .body("message", Matchers.is( - "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" - )); - } - @Test void shouldReturnPagedCreatedAlerts() throws JoseException { // given @@ -752,110 +214,6 @@ void shouldReturnProperlyPagedReceivedAlerts() throws JoseException { .body("totalItems", Matchers.is(100)); } - @Test - void shouldReturnReceivedAlertsSortedByCreationTime() throws JoseException { - // given - String sortString = "createdDate,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - AlertEntity firstAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .side(NotificationSideBaseEntity.RECEIVER) - .description("1") - .createdDate(now.minusSeconds(5L)) - .build(); - AlertEntity secondAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("2") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(2L)) - .build(); - AlertEntity thirdAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("3") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now) - .build(); - AlertEntity fourthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("4") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(20L)) - .build(); - AlertEntity fifthAlert = AlertEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - alertNotificationsSupport.storedAlertNotifications( - AlertNotificationEntity - .builder() - .id("1") - .alert(firstAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .id("2") - .alert(secondAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .id("3") - .alert(thirdAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .id("4") - .alert(fourthAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - AlertNotificationEntity - .builder() - .id("5") - .alert(fifthAlert) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - // then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/alerts/received?sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)) - .body("content.description", Matchers.containsInRelativeOrder("4", "2", "3", "1")) - .body("content.createdDate", Matchers.hasItems(isIso8601DateTime())); - } - @Test void givenNoAlertId_whenGetAlertById_thenReturnNotFound() throws JoseException { given() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsInSortedOrderControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsInSortedOrderControllerIT.java new file mode 100644 index 0000000000..37a8e5ca78 --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsInSortedOrderControllerIT.java @@ -0,0 +1,224 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.traceability.integration.qualitynotification.alert; + +import io.restassured.http.ContentType; +import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; +import org.eclipse.tractusx.traceability.integration.common.support.AlertNotificationsSupport; +import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; +import org.eclipse.tractusx.traceability.testdata.AlertTestDataFactory; +import org.hamcrest.Matchers; +import org.jose4j.lang.JoseException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static io.restassured.RestAssured.given; +import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; + +class ReadCreatedAlertsInSortedOrderControllerIT extends IntegrationTestSpecification { + + @Autowired + AlertNotificationsSupport alertNotificationsSupport; + + @Autowired + BpnSupport bpnSupport; + + @Test + void givenSortByCreatedDateProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "createdDate,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Second Alert on Asset2", "Fourth Alert on Asset4", + "Third Alert on Asset3", "First Alert on Asset1")); + } + + @Test + void givenSortByDescriptionProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "description,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Third Alert on Asset3", + "Second Alert on Asset2", "Fourth Alert on Asset4", "First Alert on Asset1")); + } + + @Test + void givenSortByStatusProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "status,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.status", Matchers.containsInRelativeOrder("CREATED", "SENT", "ACCEPTED", "ACCEPTED")); + } + + @Test + void givenSortBySeverityProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "severity,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "LIFE-THREATENING")); + } + + @Test + void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { + // given + String sortString = "createdDate,failure"; + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(400) + .body("message", Matchers.is( + "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" + )); + } + + @Test + void givenSortBySendToProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "sendTo,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendTo", Matchers.containsInRelativeOrder("BPNL000000000003", "BPNL000000000002", "BPNL000000000001", "BPNL000000000001")); + } + + @Test + void givenSortByTargetDateProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "targetDate,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendToName", Matchers.containsInRelativeOrder("OEM1", "OEM2", "OEM1", "OEM3")); + } +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsWithSearchCriteriaControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsWithSearchCriteriaControllerIT.java index 099e3a475f..52dd4f17d4 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsWithSearchCriteriaControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadCreatedAlertsWithSearchCriteriaControllerIT.java @@ -30,6 +30,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; + import static io.restassured.RestAssured.given; import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; @@ -47,7 +51,7 @@ void givenFilterBySendToProvided_whenGetAlerts_thenReturnCreatedAlertsFilteredBy String filterString = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -71,10 +75,13 @@ void givenFilterBySendToProvided_whenGetAlerts_thenReturnCreatedAlertsFilteredBy @Test void givenFilterByCreatedDateProvided_whenGetAlerts_thenReturnCreatedAlertsFilteredByCreatedDate() throws JoseException { // given - String filterString = "createdDate,AT_LOCAL_DATE,2023-11-09"; + Date myDate = Date.from(Instant.now()); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = formatter.format(myDate); + String filterString = "createdDate,AT_LOCAL_DATE," + formattedDate; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -90,8 +97,8 @@ void givenFilterByCreatedDateProvided_whenGetAlerts_thenReturnCreatedAlertsFilte .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(3)) - .body("totalItems", Matchers.is(3)); + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)); } @Test @@ -100,7 +107,7 @@ void givenFilterBySendToNameProvided_whenGetAlerts_thenReturnCreatedAlertsFilter String filterString = "sendToName,EQUAL,OEM2"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -127,7 +134,7 @@ void givenFilterByStatusProvided_whenGetAlerts_thenReturnCreatedAlertsFilteredBy String filterString = "status,EQUAL,ACCEPTED"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -154,7 +161,7 @@ void givenFilterBySeverityProvided_whenGetAlerts_thenReturnCreatedAlertsFiltered String filterString = "severity,EQUAL,3"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -181,7 +188,7 @@ void givenFilterByCreatedByProvided_whenGetAlerts_thenReturnCreatedAlertsFiltere String filterString = "createdBy,EQUAL,BPNL00000000000A"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -208,7 +215,7 @@ void givenFilterByDescriptionProvided_whenGetAlerts_thenReturnCreatedAlertsFilte String filterString = "description,STARTS_WITH,First"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -236,7 +243,7 @@ void givenFilterByDescriptionAndSendToProvided_whenGetAlerts_thenReturnCreatedAl String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -266,7 +273,7 @@ void givenFilterBySendToNameOrSendToProvided_whenGetAlerts_thenReturnCreatedAler String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createSenderMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsInSortedOrderControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsInSortedOrderControllerIT.java new file mode 100644 index 0000000000..8ae0d0e2ef --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsInSortedOrderControllerIT.java @@ -0,0 +1,224 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.traceability.integration.qualitynotification.alert; + +import io.restassured.http.ContentType; +import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; +import org.eclipse.tractusx.traceability.integration.common.support.AlertNotificationsSupport; +import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; +import org.eclipse.tractusx.traceability.testdata.AlertTestDataFactory; +import org.hamcrest.Matchers; +import org.jose4j.lang.JoseException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static io.restassured.RestAssured.given; +import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; + +class ReadReceivedAlertsInSortedOrderControllerIT extends IntegrationTestSpecification { + + @Autowired + AlertNotificationsSupport alertNotificationsSupport; + + @Autowired + BpnSupport bpnSupport; + + @Test + void givenSortByCreatedDateProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "createdDate,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Second Alert on Asset2", "Fourth Alert on Asset4", + "Third Alert on Asset3", "First Alert on Asset1")); + } + + @Test + void givenSortByDescriptionProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "description,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Third Alert on Asset3", + "Second Alert on Asset2", "Fourth Alert on Asset4", "First Alert on Asset1")); + } + + @Test + void givenSortByStatusProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "status,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.status", Matchers.containsInRelativeOrder("RECEIVED", "ACKNOWLEDGED", "ACCEPTED", "ACCEPTED")); + } + + @Test + void givenSortBySeverityProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "severity,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "LIFE-THREATENING")); + } + + @Test + void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { + // given + String sortString = "createdDate,failure"; + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(400) + .body("message", Matchers.is( + "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" + )); + } + + @Test + void givenSortBySendToProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "sendTo,desc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendTo", Matchers.containsInRelativeOrder("BPNL000000000003", "BPNL000000000002", "BPNL000000000001", "BPNL000000000001")); + } + + @Test + void givenSortByTargetDateProvided_whenGetAlerts_thenReturnAlertsProperlySorted() throws JoseException { + // given + String sortString = "targetDate,asc"; + String testBpn = bpnSupport.testBpn(); + + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); + alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/alerts/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendToName", Matchers.containsInRelativeOrder("OEM1", "OEM2", "OEM1", "OEM3")); + } +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsWithSearchCriteriaControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsWithSearchCriteriaControllerIT.java index a049bed9a4..a44edc3237 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsWithSearchCriteriaControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReadReceivedAlertsWithSearchCriteriaControllerIT.java @@ -30,6 +30,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; + import static io.restassured.RestAssured.given; import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; @@ -44,10 +48,10 @@ class ReadReceivedAlertsWithSearchCriteriaControllerIT extends IntegrationTestSp @Test void givenFilterBySendToProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredBySendTo() throws JoseException { // given - String filterString = "sendTo,EQUAL,BPNL000000000004"; + String filterString = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -63,18 +67,21 @@ void givenFilterBySendToProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredB .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) - .body("content.sendTo", Matchers.hasItems("BPNL000000000004")); + .body("content", Matchers.hasSize(2)) + .body("totalItems", Matchers.is(2)) + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")); } @Test void givenFilterByCreatedDateProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredByCreatedDate() throws JoseException { // given - String filterString = "createdDate,AT_LOCAL_DATE,2023-12-09"; + Date myDate = Date.from(Instant.now()); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = formatter.format(myDate); + String filterString = "createdDate,AT_LOCAL_DATE," + formattedDate; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -90,17 +97,17 @@ void givenFilterByCreatedDateProvided_whenGetAlerts_thenReturnReceivedAlertsFilt .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)); + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)); } @Test void givenFilterBySendToNameProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredBySendToName() throws JoseException { // given - String filterString = "sendToName,EQUAL,OEM4"; + String filterString = "sendToName,EQUAL,OEM2"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -118,16 +125,16 @@ void givenFilterBySendToNameProvided_whenGetAlerts_thenReturnReceivedAlertsFilte .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.sendToName", Matchers.hasItems("OEM4")); + .body("content.sendToName", Matchers.hasItems("OEM2")); } @Test void givenFilterByStatusProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredByStatus() throws JoseException { // given - String filterString = "status,EQUAL,CANCELED"; + String filterString = "status,EQUAL,ACCEPTED"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -143,18 +150,18 @@ void givenFilterByStatusProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredB .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) - .body("content.status", Matchers.hasItems("CANCELED")); + .body("content", Matchers.hasSize(2)) + .body("totalItems", Matchers.is(2)) + .body("content.status", Matchers.hasItems("ACCEPTED")); } @Test void givenFilterBySeverityProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredBySeverity() throws JoseException { // given - String filterString = "severity,EQUAL,0"; + String filterString = "severity,EQUAL,3"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -172,7 +179,7 @@ void givenFilterBySeverityProvided_whenGetAlerts_thenReturnReceivedAlertsFiltere .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.severity", Matchers.hasItems("MINOR")); + .body("content.severity", Matchers.hasItems("LIFE-THREATENING")); } @Test @@ -181,7 +188,7 @@ void givenFilterByCreatedByProvided_whenGetAlerts_thenReturnReceivedAlertsFilter String filterString = "createdBy,EQUAL,BPNL00000000000A"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -197,18 +204,18 @@ void givenFilterByCreatedByProvided_whenGetAlerts_thenReturnReceivedAlertsFilter .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) .body("content.createdBy", Matchers.hasItems("BPNL00000000000A")); } @Test void givenFilterByDescriptionProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredByDescription() throws JoseException { // given - String filterString = "description,STARTS_WITH,Fifth"; + String filterString = "description,STARTS_WITH,First"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -226,17 +233,17 @@ void givenFilterByDescriptionProvided_whenGetAlerts_thenReturnReceivedAlertsFilt .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.description", Matchers.hasItems("Fifth Alert on Asset5")); + .body("content.description", Matchers.hasItems("First Alert on Asset1")); } @Test void givenFilterByDescriptionAndSendToProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredByDescriptionAndSendTo() throws JoseException { // given - String filterString1 = "description,STARTS_WITH,Fifth"; - String filterString2 = "sendTo,EQUAL,BPNL000000000004"; + String filterString1 = "description,STARTS_WITH,First"; + String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -255,19 +262,18 @@ void givenFilterByDescriptionAndSendToProvided_whenGetAlerts_thenReturnReceivedA .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.sendTo", Matchers.hasItems("BPNL000000000004")) - .body("content.description", Matchers.hasItems("Fifth Alert on Asset5")); + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")) + .body("content.description", Matchers.hasItems("First Alert on Asset1")); } @Test void givenFilterBySendToNameOrSendToProvided_whenGetAlerts_thenReturnReceivedAlertsFilteredBySendToNameOrSendTo() throws JoseException { // given - String filterString1 = "sendToName,EQUAL,OEM4"; - // non-existent BPN but based on OR condition, OEM4 record would still be fetched - String filterString2 = "sendTo,EQUAL,BPNL000000000005"; + String filterString1 = "sendToName,EQUAL,OEM2"; + String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createAlertNotificationEntitiesTestData(testBpn); + AlertNotificationEntity[] alertNotificationEntities = AlertTestDataFactory.createReceiverMajorityAlertNotificationEntitiesTestData(testBpn); alertNotificationsSupport.storedAlertNotifications(alertNotificationEntities); given() @@ -284,8 +290,9 @@ void givenFilterBySendToNameOrSendToProvided_whenGetAlerts_thenReturnReceivedAle .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) - .body("content.sendToName", Matchers.hasItems("OEM4")); + .body("content", Matchers.hasSize(3)) + .body("totalItems", Matchers.is(3)) + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")) + .body("content.sendToName", Matchers.hasItems("OEM2")); } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReceiverAlertsControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReceiverAlertsControllerIT.java index 18fccbcf74..734f53e6e3 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReceiverAlertsControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/alert/ReceiverAlertsControllerIT.java @@ -21,7 +21,13 @@ import io.restassured.http.ContentType; import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; +import org.eclipse.tractusx.traceability.integration.common.support.AlertNotificationsSupport; import org.eclipse.tractusx.traceability.integration.common.support.AlertsSupport; +import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationNotificationEntity; +import org.eclipse.tractusx.traceability.testdata.AlertTestDataFactory; +import org.eclipse.tractusx.traceability.testdata.InvestigationTestDataFactory; import org.hamcrest.Matchers; import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; @@ -31,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import qualitynotification.base.request.UpdateQualityNotificationStatusRequest; +import java.time.Instant; import java.util.stream.Stream; import static io.restassured.RestAssured.given; @@ -43,6 +50,12 @@ class ReceiverAlertsControllerIT extends IntegrationTestSpecification { @Autowired AlertsSupport alertsSupport; + @Autowired + AlertNotificationsSupport alertNotificationsSupport; + + @Autowired + BpnSupport bpnSupport; + @Test void ShouldAcknowledgeReceivedAlert() throws JoseException { // given @@ -248,5 +261,4 @@ private static Stream invalidRequest() { """.replace("$status", UpdateQualityNotificationStatusRequest.DECLINED.name())) ); } - } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsInSortedOrderControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsInSortedOrderControllerIT.java new file mode 100644 index 0000000000..18862e286b --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsInSortedOrderControllerIT.java @@ -0,0 +1,225 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.traceability.integration.qualitynotification.investigation; + +import io.restassured.http.ContentType; +import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; +import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; +import org.eclipse.tractusx.traceability.integration.common.support.InvestigationNotificationsSupport; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationNotificationEntity; +import org.eclipse.tractusx.traceability.testdata.InvestigationTestDataFactory; +import org.hamcrest.Matchers; +import org.jose4j.lang.JoseException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static io.restassured.RestAssured.given; +import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; + +class ReadCreatedInvestigationsInSortedOrderControllerIT extends IntegrationTestSpecification { + + @Autowired + BpnSupport bpnSupport; + @Autowired + InvestigationNotificationsSupport investigationNotificationsSupport; + + @Test + void givenSortByCreatedDateProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "createdDate,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Second Investigation on Asset2", "Fourth Investigation on Asset4", + "Third Investigation on Asset3", "First Investigation on Asset1")); + } + + + @Test + void givenSortByDescriptionProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "description,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Third Investigation on Asset3", "Second Investigation on Asset2", + "Fourth Investigation on Asset4", "First Investigation on Asset1")); + } + + @Test + void givenSortByStatusProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "status,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.status", Matchers.containsInRelativeOrder( "CREATED", "SENT", "ACKNOWLEDGED", "ACCEPTED")); + } + + @Test + void givenSortBySeverityProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "severity,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "LIFE-THREATENING")); + } + + @Test + void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { + // given + String sortString = "createdDate,failure"; + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(400) + .body("message", Matchers.is( + "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" + )); + } + + @Test + void givenSortBySendToProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "sendTo,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendTo", Matchers.containsInRelativeOrder("BPNL000000000003", "BPNL000000000002", "BPNL000000000001", "BPNL000000000001")); + } + + @Test + void givenSortByTargetDateProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "targetDate,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/created") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendToName", Matchers.containsInRelativeOrder("OEM1", "OEM2", "OEM1", "OEM3")); + } +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsWithSearchCriteriaControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsWithSearchCriteriaControllerIT.java index 7c3ee1dac7..c939a316f6 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsWithSearchCriteriaControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadCreatedInvestigationsWithSearchCriteriaControllerIT.java @@ -30,6 +30,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; + import static io.restassured.RestAssured.given; import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; @@ -46,7 +50,7 @@ void givenFilterBySendToProvided_whenGetInvestigations_thenReturnCreatedInvestig String filterString = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -71,10 +75,13 @@ void givenFilterBySendToProvided_whenGetInvestigations_thenReturnCreatedInvestig @Test void givenFilterByCreatedDateProvided_whenGetInvestigations_thenReturnCreatedInvestigationsFilteredByCreatedDate() throws JoseException { // given - String filterString = "createdDate,AT_LOCAL_DATE,2023-11-09"; + Date myDate = Date.from(Instant.now()); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = formatter.format(myDate); + String filterString = "createdDate,AT_LOCAL_DATE," + formattedDate; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -90,8 +97,8 @@ void givenFilterByCreatedDateProvided_whenGetInvestigations_thenReturnCreatedInv .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(3)) - .body("totalItems", Matchers.is(3)); + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)); } @Test @@ -100,7 +107,7 @@ void givenFilterBySendToNameProvided_whenGetInvestigations_thenReturnCreatedInve String filterString = "sendToName,EQUAL,OEM2"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -129,7 +136,7 @@ void givenFilterByStatusProvided_whenGetInvestigations_thenReturnCreatedInvestig String filterString = "status,EQUAL,ACCEPTED"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -145,8 +152,8 @@ void givenFilterByStatusProvided_whenGetInvestigations_thenReturnCreatedInvestig .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(2)) - .body("totalItems", Matchers.is(2)) + .body("content", Matchers.hasSize(1)) + .body("totalItems", Matchers.is(1)) .body("content.status", Matchers.hasItems("ACCEPTED")); ; ; @@ -158,7 +165,7 @@ void givenFilterBySeverityProvided_whenGetInvestigations_thenReturnCreatedInvest String filterString = "severity,EQUAL,3"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -187,7 +194,7 @@ void givenFilterByCreatedByProvided_whenGetInvestigations_thenReturnCreatedInves String filterString = "createdBy,EQUAL,BPNL00000000000A"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -216,7 +223,7 @@ void givenFilterByDescriptionProvided_whenGetInvestigations_thenReturnCreatedInv String filterString = "description,STARTS_WITH,First"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -246,7 +253,7 @@ void givenFilterByDescriptionAndSendToProvided_whenGetInvestigations_thenReturnC String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -276,7 +283,7 @@ void givenFilterBySendToNameOrSendToProvided_whenGetInvestigations_thenReturnCre String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createSenderMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadInvestigationsControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadInvestigationsControllerIT.java index d0e9b692b6..e6e7fe5bc2 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadInvestigationsControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadInvestigationsControllerIT.java @@ -29,6 +29,7 @@ import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationNotificationEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.model.NotificationSideBaseEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.model.NotificationStatusBaseEntity; +import org.eclipse.tractusx.traceability.testdata.InvestigationTestDataFactory; import org.hamcrest.Matchers; import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; @@ -119,544 +120,6 @@ void shouldReturnNoCreatedInvestigations() throws JoseException { .body("content", Matchers.hasSize(0)); } - @Test - void givenInvestigations_whenGetInvestigations_thenReturnSortedByCreationTime() throws JoseException { - // given - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(40L)) - .build(); - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .investigation(secondInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .investigation(thirdInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .investigation(fourthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .investigation(fifthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - // then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created") - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)); - } - - @Test - void givenSortByCreatedDateProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { - // given - String sortString = "createdDate,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(40L)) - .build(); - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .investigation(secondInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .investigation(thirdInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .investigation(fourthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .investigation(fifthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - // when/then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)); - } - - - @Test - void givenSortByDescriptionProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { - // given - String sortString = "description,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .investigation(secondInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .investigation(thirdInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .investigation(fourthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .investigation(fifthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.description", Matchers.containsInRelativeOrder("5", "4", "3", "2", "1")); - } - - @Test - void givenSortByStatusProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { - // given - String sortString = "status,asc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .investigation(secondInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .investigation(thirdInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .investigation(fourthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .investigation(fifthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.status", Matchers.containsInRelativeOrder("CREATED", "CREATED", "SENT", "ACKNOWLEDGED", "CLOSED")); - } - - @Test - void givenSortBySeverityProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { - // given - String sortString = "severity,asc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.SENT) - .side(NotificationSideBaseEntity.SENDER) - .description("1") - .createdDate(now.minusSeconds(10L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("2") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(21L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CLOSED) - .description("3") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("4") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.ACKNOWLEDGED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .severity(QualityNotificationSeverity.CRITICAL) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .severity(QualityNotificationSeverity.MAJOR) - .status(NotificationStatusBaseEntity.CREATED) - .id("2") - .investigation(secondInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .severity(QualityNotificationSeverity.LIFE_THREATENING) - .status(NotificationStatusBaseEntity.CREATED) - .id("3") - .investigation(thirdInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .severity(QualityNotificationSeverity.MINOR) - .status(NotificationStatusBaseEntity.CREATED) - .id("4") - .investigation(fourthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .severity(QualityNotificationSeverity.CRITICAL) - .status(NotificationStatusBaseEntity.CREATED) - .id("5") - .investigation(fifthInvestigation) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(5)) - .body("totalItems", Matchers.is(5)) - .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "CRITICAL", "LIFE-THREATENING")); - } - - @Test - void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { - // given - String sortString = "createdDate,failure"; - - // when/then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/created?page=0&size=10&sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(400) - .body("message", Matchers.is( - "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" - )); - } - @Test void shouldReturnPagedCreatedInvestigations() throws JoseException { // given @@ -755,110 +218,6 @@ void shouldReturnProperlyPagedReceivedInvestigations() throws JoseException { .body("totalItems", Matchers.is(100)); } - @Test - void shouldReturnReceivedInvestigationsSortedByCreationTime() throws JoseException { - // given - String sortString = "createdDate,desc"; - Instant now = Instant.now(); - String testBpn = bpnSupport.testBpn(); - - InvestigationEntity firstInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .side(NotificationSideBaseEntity.RECEIVER) - .description("1") - .createdDate(now.minusSeconds(5L)) - .build(); - InvestigationEntity secondInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("2") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(2L)) - .build(); - InvestigationEntity thirdInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("3") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now) - .build(); - InvestigationEntity fourthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.RECEIVED) - .description("4") - .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(now.plusSeconds(20L)) - .build(); - InvestigationEntity fifthInvestigation = InvestigationEntity.builder() - .assets(Collections.emptyList()) - .bpn(testBpn) - .status(NotificationStatusBaseEntity.CREATED) - .description("5") - .side(NotificationSideBaseEntity.SENDER) - .createdDate(now.plusSeconds(40L)) - .build(); - - investigationNotificationsSupport.storedNotifications( - InvestigationNotificationEntity - .builder() - .id("1") - .investigation(firstInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .id("2") - .investigation(secondInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .id("3") - .investigation(thirdInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .id("4") - .investigation(fourthInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build(), - InvestigationNotificationEntity - .builder() - .id("5") - .investigation(fifthInvestigation) - .status(NotificationStatusBaseEntity.CREATED) - .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") - .build() - ); - - // then - given() - .header(oAuth2Support.jwtAuthorization(ADMIN)) - .param("page", "0") - .param("size", "10") - .contentType(ContentType.JSON) - .when() - .get("/api/investigations/received?sort=$sortString".replace("$sortString", sortString)) - .then() - .statusCode(200) - .body("page", Matchers.is(0)) - .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(4)) - .body("totalItems", Matchers.is(4)) - .body("content.description", Matchers.containsInRelativeOrder("4", "2", "3", "1")) - .body("content.createdDate", Matchers.hasItems(isIso8601DateTime())); - } - @Test void givenNoInvestigationId_whenGetInvestigationById_thenReturnNotFound() throws JoseException { given() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsInSortedOrderControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsInSortedOrderControllerIT.java new file mode 100644 index 0000000000..1260b2cffb --- /dev/null +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsInSortedOrderControllerIT.java @@ -0,0 +1,225 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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 limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.traceability.integration.qualitynotification.investigation; + +import io.restassured.http.ContentType; +import org.eclipse.tractusx.traceability.integration.IntegrationTestSpecification; +import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; +import org.eclipse.tractusx.traceability.integration.common.support.InvestigationNotificationsSupport; +import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.investigation.model.InvestigationNotificationEntity; +import org.eclipse.tractusx.traceability.testdata.InvestigationTestDataFactory; +import org.hamcrest.Matchers; +import org.jose4j.lang.JoseException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static io.restassured.RestAssured.given; +import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; + +class ReadReceivedInvestigationsInSortedOrderControllerIT extends IntegrationTestSpecification { + + @Autowired + BpnSupport bpnSupport; + @Autowired + InvestigationNotificationsSupport investigationNotificationsSupport; + + @Test + void givenSortByCreatedDateProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "createdDate,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Second Investigation on Asset2", "Fourth Investigation on Asset4", + "Third Investigation on Asset3", "First Investigation on Asset1")); + } + + + @Test + void givenSortByDescriptionProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "description,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.description", + Matchers.containsInRelativeOrder("Third Investigation on Asset3", "Second Investigation on Asset2", + "Fourth Investigation on Asset4", "First Investigation on Asset1")); + } + + @Test + void givenSortByStatusProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "status,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.status", Matchers.containsInRelativeOrder("RECEIVED", "ACKNOWLEDGED", "ACCEPTED", "CLOSED")); + } + + @Test + void givenSortBySeverityProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "severity,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.severity", Matchers.containsInRelativeOrder("MINOR", "MAJOR", "CRITICAL", "LIFE-THREATENING")); + } + + @Test + void givenInvalidSort_whenGetCreated_thenBadRequest() throws JoseException { + // given + String sortString = "createdDate,failure"; + + // when/then + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(400) + .body("message", Matchers.is( + "Invalid sort param provided sort=createdDate,failure expected format is following sort=parameter,order" + )); + } + + @Test + void givenSortBySendToProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "sendTo,desc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendTo", Matchers.containsInRelativeOrder("BPNL000000000003", "BPNL000000000002", "BPNL000000000001", "BPNL000000000001")); + } + + @Test + void givenSortByTargetDateProvided_whenGetInvestigations_thenReturnInvestigationsProperlySorted() throws JoseException { + // given + String sortString = "targetDate,asc"; + String testBpn = bpnSupport.testBpn(); + + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); + investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); + + given() + .header(oAuth2Support.jwtAuthorization(ADMIN)) + .param("page", "0") + .param("size", "10") + .param("sort", sortString) + .contentType(ContentType.JSON) + .when() + .get("/api/investigations/received") + .then() + .statusCode(200) + .body("page", Matchers.is(0)) + .body("pageSize", Matchers.is(10)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) + .body("content.sendToName", Matchers.containsInRelativeOrder("OEM1", "OEM2", "OEM1", "OEM3")); + } +} diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsWithSearchCriteriaControllerIT.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsWithSearchCriteriaControllerIT.java index 205cbfc97e..bf367d65ba 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsWithSearchCriteriaControllerIT.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/integration/qualitynotification/investigation/ReadReceivedInvestigationsWithSearchCriteriaControllerIT.java @@ -28,9 +28,12 @@ import org.hamcrest.Matchers; import org.jose4j.lang.JoseException; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; import org.springframework.beans.factory.annotation.Autowired; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; + import static io.restassured.RestAssured.given; import static org.eclipse.tractusx.traceability.common.security.JwtRole.ADMIN; @@ -44,10 +47,10 @@ class ReadReceivedInvestigationsWithSearchCriteriaControllerIT extends Integrati @Test void givenFilterBySendToProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredBySendTo() throws JoseException { // given - String filterString = "sendTo,EQUAL,BPNL000000000004"; + String filterString = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -63,18 +66,21 @@ void givenFilterBySendToProvided_whenGetInvestigations_thenReturnReceivedInvesti .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) - .body("content.sendTo", Matchers.hasItems("BPNL000000000004")); + .body("content", Matchers.hasSize(2)) + .body("totalItems", Matchers.is(2)) + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")); } @Test void givenFilterByCreatedDateProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredByCreatedDate() throws JoseException { // given - String filterString = "createdDate,AT_LOCAL_DATE,2023-12-09"; + Date myDate = Date.from(Instant.now()); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + String formattedDate = formatter.format(myDate); + String filterString = "createdDate,AT_LOCAL_DATE," + formattedDate; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -90,17 +96,17 @@ void givenFilterByCreatedDateProvided_whenGetInvestigations_thenReturnReceivedIn .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)); + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)); } @Test void givenFilterBySendToNameProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredBySendToName() throws JoseException { // given - String filterString = "sendToName,EQUAL,OEM4"; + String filterString = "sendToName,EQUAL,OEM2"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -118,16 +124,16 @@ void givenFilterBySendToNameProvided_whenGetInvestigations_thenReturnReceivedInv .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.sendToName", Matchers.hasItems("OEM4")); + .body("content.sendToName", Matchers.hasItems("OEM2")); } @Test void givenFilterByStatusProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredByStatus() throws JoseException { // given - String filterString = "status,EQUAL,CANCELED"; + String filterString = "status,EQUAL,RECEIVED"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -145,16 +151,16 @@ void givenFilterByStatusProvided_whenGetInvestigations_thenReturnReceivedInvesti .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.status", Matchers.hasItems("CANCELED")); + .body("content.status", Matchers.hasItems("RECEIVED")); } @Test void givenFilterBySeverityProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredBySeverity() throws JoseException { // given - String filterString = "severity,EQUAL,0"; + String filterString = "severity,EQUAL,3"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -172,7 +178,7 @@ void givenFilterBySeverityProvided_whenGetInvestigations_thenReturnReceivedInves .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.severity", Matchers.hasItems("MINOR")); + .body("content.severity", Matchers.hasItems("LIFE-THREATENING")); } @Test @@ -181,7 +187,7 @@ void givenFilterByCreatedByProvided_whenGetInvestigations_thenReturnReceivedInve String filterString = "createdBy,EQUAL,BPNL00000000000A"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -197,18 +203,18 @@ void givenFilterByCreatedByProvided_whenGetInvestigations_thenReturnReceivedInve .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) + .body("content", Matchers.hasSize(4)) + .body("totalItems", Matchers.is(4)) .body("content.createdBy", Matchers.hasItems("BPNL00000000000A")); } @Test void givenFilterByDescriptionProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredByDescription() throws JoseException { // given - String filterString = "description,STARTS_WITH,Fifth"; + String filterString = "description,STARTS_WITH,Second"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -226,17 +232,17 @@ void givenFilterByDescriptionProvided_whenGetInvestigations_thenReturnReceivedIn .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.description", Matchers.hasItems("Fifth Investigation on Asset5")); + .body("content.description", Matchers.hasItems("Second Investigation on Asset2")); } @Test void givenFilterByDescriptionAndSendToProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredByDescriptionAndSendTo() throws JoseException { // given - String filterString1 = "description,STARTS_WITH,Fifth"; - String filterString2 = "sendTo,EQUAL,BPNL000000000004"; + String filterString1 = "description,STARTS_WITH,Second"; + String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -255,19 +261,18 @@ void givenFilterByDescriptionAndSendToProvided_whenGetInvestigations_thenReturnR .body("pageSize", Matchers.is(10)) .body("content", Matchers.hasSize(1)) .body("totalItems", Matchers.is(1)) - .body("content.sendTo", Matchers.hasItems("BPNL000000000004")) - .body("content.description", Matchers.hasItems("Fifth Investigation on Asset5")); + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")) + .body("content.description", Matchers.hasItems("Second Investigation on Asset2")); } @Test void givenFilterBySendToNameOrSendToProvided_whenGetInvestigations_thenReturnReceivedInvestigationsFilteredBySendToNameOrSendTo() throws JoseException { // given - String filterString1 = "sendToName,EQUAL,OEM4"; - // non-existent BPN but based on OR condition, OEM4 record would still be fetched - String filterString2 = "sendTo,EQUAL,BPNL000000000005"; + String filterString1 = "sendToName,EQUAL,OEM2"; + String filterString2 = "sendTo,EQUAL,BPNL000000000001"; String testBpn = bpnSupport.testBpn(); - InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createInvestigationNotificationEntitiesTestData(testBpn); + InvestigationNotificationEntity[] investigationNotificationEntities = InvestigationTestDataFactory.createReceiverMajorityInvestigationNotificationEntitiesTestData(testBpn); investigationNotificationsSupport.storedNotifications(investigationNotificationEntities); given() @@ -284,8 +289,9 @@ void givenFilterBySendToNameOrSendToProvided_whenGetInvestigations_thenReturnRec .statusCode(200) .body("page", Matchers.is(0)) .body("pageSize", Matchers.is(10)) - .body("content", Matchers.hasSize(1)) - .body("totalItems", Matchers.is(1)) - .body("content.sendToName", Matchers.hasItems("OEM4")); + .body("content", Matchers.hasSize(3)) + .body("totalItems", Matchers.is(3)) + .body("content.sendTo", Matchers.hasItems("BPNL000000000001")) + .body("content.sendToName", Matchers.hasItems("OEM2")); } } diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/AlertTestDataFactory.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/AlertTestDataFactory.java index 0cf93ec80d..05b66be91d 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/AlertTestDataFactory.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/AlertTestDataFactory.java @@ -19,35 +19,23 @@ package org.eclipse.tractusx.traceability.testdata; -import org.eclipse.tractusx.traceability.common.model.*; -import org.eclipse.tractusx.traceability.integration.common.support.BpnSupport; -import org.eclipse.tractusx.traceability.qualitynotification.domain.base.model.*; +import org.eclipse.tractusx.traceability.qualitynotification.domain.base.model.QualityNotificationSeverity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.alert.model.AlertNotificationEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.model.NotificationSideBaseEntity; import org.eclipse.tractusx.traceability.qualitynotification.infrastructure.model.NotificationStatusBaseEntity; -import org.springframework.beans.factory.annotation.Autowired; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Collections; -import java.util.List; import java.util.Locale; public class AlertTestDataFactory { - public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { - String createdDateInNovString = "12:00 PM, Thu 11/9/2023"; - String createdDateInDecString = "12:00 PM, Sat 12/9/2023"; - String dateFormatter = "hh:mm a, EEE M/d/uuuu"; - Instant createdDateInNov = LocalDateTime.parse(createdDateInNovString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) - .atZone(ZoneId.of("Europe/Berlin")) - .toInstant(); - Instant createdDateInDec = LocalDateTime.parse(createdDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) - .atZone(ZoneId.of("Europe/Berlin")) - .toInstant(); + private static AlertEntity[] createSenderMajorityAlertEntitiesTestData(String senderBpn) { + Instant now = Instant.now(); AlertEntity firstAlert = AlertEntity.builder() .assets(Collections.emptyList()) @@ -55,7 +43,7 @@ public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { .status(NotificationStatusBaseEntity.CREATED) .description("First Alert on Asset1") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now.minusSeconds(10L)) .build(); AlertEntity secondAlert = AlertEntity.builder() .assets(Collections.emptyList()) @@ -63,7 +51,7 @@ public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { .status(NotificationStatusBaseEntity.SENT) .description("Second Alert on Asset2") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now.plusSeconds(21L)) .build(); AlertEntity thirdAlert = AlertEntity.builder() .assets(Collections.emptyList()) @@ -71,7 +59,7 @@ public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { .status(NotificationStatusBaseEntity.ACCEPTED) .description("Third Alert on Asset3") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now) .build(); AlertEntity fourthAlert = AlertEntity.builder() .assets(Collections.emptyList()) @@ -79,7 +67,7 @@ public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { .status(NotificationStatusBaseEntity.ACCEPTED) .description("Fourth Alert on Asset4") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInDec) + .createdDate(now.plusSeconds(20L)) .build(); AlertEntity fifthAlert = AlertEntity.builder() .assets(Collections.emptyList()) @@ -87,16 +75,25 @@ public static AlertEntity[] createAlertEntitiesTestData(String senderBpn) { .status(NotificationStatusBaseEntity.CANCELED) .description("Fifth Alert on Asset5") .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(createdDateInDec) + .createdDate(now.plusSeconds(40L)) .build(); AlertEntity[] alertEntities = {firstAlert, secondAlert, thirdAlert, fourthAlert, fifthAlert}; return alertEntities; } - public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData(String senderBpn) { + public static AlertNotificationEntity[] createSenderMajorityAlertNotificationEntitiesTestData(String senderBpn) { + String targetDateInNovString = "12:00 PM, Sun 11/9/2025"; + String targetDateInDecString = "12:00 PM, Tue 12/9/2025"; + String dateFormatter = "hh:mm a, EEE M/d/uuuu"; + Instant targetDateInNov = LocalDateTime.parse(targetDateInNovString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInDec = LocalDateTime.parse(targetDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); - AlertEntity[] alertEntities = createAlertEntitiesTestData(senderBpn); + AlertEntity[] alertEntities = createSenderMajorityAlertEntitiesTestData(senderBpn); AlertNotificationEntity[] alertNotificationEntities = { AlertNotificationEntity @@ -107,6 +104,7 @@ public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData( .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000001") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) .sendToName("OEM1") .severity(QualityNotificationSeverity.MAJOR) .build(), @@ -118,9 +116,137 @@ public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData( .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000001") .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM1") + .severity(QualityNotificationSeverity.CRITICAL) + .build(), + AlertNotificationEntity + .builder() + .id("3") + .alert(alertEntities[2]) + .status(NotificationStatusBaseEntity.ACCEPTED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000002") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) + .sendToName("OEM2") + .severity(QualityNotificationSeverity.LIFE_THREATENING) + .build(), + AlertNotificationEntity + .builder() + .id("4") + .alert(alertEntities[3]) + .status(NotificationStatusBaseEntity.ACCEPTED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000003") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM3") + .severity(QualityNotificationSeverity.MINOR) + .build(), + AlertNotificationEntity + .builder() + .id("5") + .alert(alertEntities[4]) + .status(NotificationStatusBaseEntity.ACKNOWLEDGED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000004") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) + .sendToName("OEM4") + .severity(QualityNotificationSeverity.MINOR) + .build() + }; + + return alertNotificationEntities; + } + + private static AlertEntity[] createReceiverMajorityAlertEntitiesTestData(String senderBpn) { + Instant now = Instant.now(); + + AlertEntity firstAlert = AlertEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.ACKNOWLEDGED) + .description("First Alert on Asset1") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.minusSeconds(10L)) + .build(); + AlertEntity secondAlert = AlertEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.RECEIVED) + .description("Second Alert on Asset2") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.plusSeconds(21L)) + .build(); + AlertEntity thirdAlert = AlertEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.ACCEPTED) + .description("Third Alert on Asset3") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now) + .build(); + AlertEntity fourthAlert = AlertEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.ACCEPTED) + .description("Fourth Alert on Asset4") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.plusSeconds(20L)) + .build(); + AlertEntity fifthAlert = AlertEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.CANCELED) + .description("Fifth Alert on Asset5") + .side(NotificationSideBaseEntity.SENDER) + .createdDate(now.plusSeconds(40L)) + .build(); + + AlertEntity[] alertEntities = {firstAlert, secondAlert, thirdAlert, fourthAlert, fifthAlert}; + return alertEntities; + } + + public static AlertNotificationEntity[] createReceiverMajorityAlertNotificationEntitiesTestData(String senderBpn) { + String targetDateInNovString = "12:00 PM, Sun 11/9/2025"; + String targetDateInDecString = "12:00 PM, Tue 12/9/2025"; + String dateFormatter = "hh:mm a, EEE M/d/uuuu"; + Instant targetDateInNov = LocalDateTime.parse(targetDateInNovString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInDec = LocalDateTime.parse(targetDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + + AlertEntity[] alertEntities = createReceiverMajorityAlertEntitiesTestData(senderBpn); + + AlertNotificationEntity[] alertNotificationEntities = { + AlertNotificationEntity + .builder() + .id("1") + .alert(alertEntities[0]) + .status(NotificationStatusBaseEntity.CREATED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000001") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) .sendToName("OEM1") .severity(QualityNotificationSeverity.MAJOR) .build(), + AlertNotificationEntity + .builder() + .id("2") + .alert(alertEntities[1]) + .status(NotificationStatusBaseEntity.SENT) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000001") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM1") + .severity(QualityNotificationSeverity.CRITICAL) + .build(), AlertNotificationEntity .builder() .id("3") @@ -129,6 +255,7 @@ public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData( .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000002") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) .sendToName("OEM2") .severity(QualityNotificationSeverity.LIFE_THREATENING) .build(), @@ -140,6 +267,7 @@ public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData( .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000003") .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) .sendToName("OEM3") .severity(QualityNotificationSeverity.MINOR) .build(), @@ -151,6 +279,7 @@ public static AlertNotificationEntity[] createAlertNotificationEntitiesTestData( .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000004") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov) .sendToName("OEM4") .severity(QualityNotificationSeverity.MINOR) .build() diff --git a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/InvestigationTestDataFactory.java b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/InvestigationTestDataFactory.java index 53ec080c4a..6c3d8d0cad 100644 --- a/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/InvestigationTestDataFactory.java +++ b/tx-backend/src/test/java/org/eclipse/tractusx/traceability/testdata/InvestigationTestDataFactory.java @@ -194,16 +194,8 @@ public static SearchCriteria createSearchCriteria() { return searchCriteria; } - public static InvestigationEntity[] createInvestigationEntitiesTestData(String senderBpn) { - String createdDateInNovString = "12:00 PM, Thu 11/9/2023"; - String createdDateInDecString = "12:00 PM, Sat 12/9/2023"; - String dateFormatter = "hh:mm a, EEE M/d/uuuu"; - Instant createdDateInNov = LocalDateTime.parse(createdDateInNovString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) - .atZone(ZoneId.of("Europe/Berlin")) - .toInstant(); - Instant createdDateInDec = LocalDateTime.parse(createdDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) - .atZone(ZoneId.of("Europe/Berlin")) - .toInstant(); + private static InvestigationEntity[] createSenderMajorityInvestigationEntitiesTestData(String senderBpn) { + Instant now = Instant.now(); InvestigationEntity firstInvestigation = InvestigationEntity.builder() .assets(Collections.emptyList()) @@ -211,7 +203,7 @@ public static InvestigationEntity[] createInvestigationEntitiesTestData(String s .status(NotificationStatusBaseEntity.CREATED) .description("First Investigation on Asset1") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now.minusSeconds(10L)) .build(); InvestigationEntity secondInvestigation = InvestigationEntity.builder() .assets(Collections.emptyList()) @@ -219,7 +211,7 @@ public static InvestigationEntity[] createInvestigationEntitiesTestData(String s .status(NotificationStatusBaseEntity.SENT) .description("Second Investigation on Asset2") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now.plusSeconds(21L)) .build(); InvestigationEntity thirdInvestigation = InvestigationEntity.builder() .assets(Collections.emptyList()) @@ -227,15 +219,15 @@ public static InvestigationEntity[] createInvestigationEntitiesTestData(String s .status(NotificationStatusBaseEntity.ACCEPTED) .description("Third Investigation on Asset3") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInNov) + .createdDate(now) .build(); InvestigationEntity fourthInvestigation = InvestigationEntity.builder() .assets(Collections.emptyList()) .bpn(senderBpn) - .status(NotificationStatusBaseEntity.ACCEPTED) + .status(NotificationStatusBaseEntity.ACKNOWLEDGED) .description("Fourth Investigation on Asset4") .side(NotificationSideBaseEntity.SENDER) - .createdDate(createdDateInDec) + .createdDate(now.plusSeconds(20L)) .build(); InvestigationEntity fifthInvestigation = InvestigationEntity.builder() .assets(Collections.emptyList()) @@ -243,16 +235,29 @@ public static InvestigationEntity[] createInvestigationEntitiesTestData(String s .status(NotificationStatusBaseEntity.CANCELED) .description("Fifth Investigation on Asset5") .side(NotificationSideBaseEntity.RECEIVER) - .createdDate(createdDateInDec) + .createdDate(now.plusSeconds(40L)) .build(); InvestigationEntity[] InvestigationEntities = {firstInvestigation, secondInvestigation, thirdInvestigation, fourthInvestigation, fifthInvestigation}; return InvestigationEntities; } - public static InvestigationNotificationEntity[] createInvestigationNotificationEntitiesTestData(String senderBpn) { + public static InvestigationNotificationEntity[] createSenderMajorityInvestigationNotificationEntitiesTestData(String senderBpn) { + String targetDateInNovString1 = "12:00 PM, Sun 11/9/2025"; + String targetDateInNovString2 = "12:00 PM, Mon 11/10/2025"; + String targetDateInDecString = "12:00 PM, Tue 12/9/2025"; + String dateFormatter = "hh:mm a, EEE M/d/uuuu"; + Instant targetDateInNov1 = LocalDateTime.parse(targetDateInNovString1, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInNov2 = LocalDateTime.parse(targetDateInNovString2, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInDec = LocalDateTime.parse(targetDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); - InvestigationEntity[] investigationEntities = createInvestigationEntitiesTestData(senderBpn); + InvestigationEntity[] investigationEntities = createSenderMajorityInvestigationEntitiesTestData(senderBpn); InvestigationNotificationEntity[] investigationNotificationEntities = { InvestigationNotificationEntity @@ -263,6 +268,7 @@ public static InvestigationNotificationEntity[] createInvestigationNotificationE .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000001") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov1) .sendToName("OEM1") .severity(QualityNotificationSeverity.MAJOR) .build(), @@ -274,9 +280,141 @@ public static InvestigationNotificationEntity[] createInvestigationNotificationE .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000001") .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM1") + .severity(QualityNotificationSeverity.CRITICAL) + .build(), + InvestigationNotificationEntity + .builder() + .id("3") + .investigation(investigationEntities[2]) + .status(NotificationStatusBaseEntity.ACCEPTED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000002") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov2) + .sendToName("OEM2") + .severity(QualityNotificationSeverity.LIFE_THREATENING) + .build(), + InvestigationNotificationEntity + .builder() + .id("4") + .investigation(investigationEntities[3]) + .status(NotificationStatusBaseEntity.ACCEPTED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000003") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM3") + .severity(QualityNotificationSeverity.MINOR) + .build(), + InvestigationNotificationEntity + .builder() + .id("5") + .investigation(investigationEntities[4]) + .status(NotificationStatusBaseEntity.ACKNOWLEDGED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000004") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov1) + .sendToName("OEM4") + .severity(QualityNotificationSeverity.MINOR) + .build() + }; + + return investigationNotificationEntities; + } + + private static InvestigationEntity[] createReceiverMajorityInvestigationEntitiesTestData(String senderBpn) { + Instant now = Instant.now(); + + InvestigationEntity firstInvestigation = InvestigationEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.RECEIVED) + .description("First Investigation on Asset1") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.minusSeconds(10L)) + .build(); + InvestigationEntity secondInvestigation = InvestigationEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.ACKNOWLEDGED) + .description("Second Investigation on Asset2") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.plusSeconds(21L)) + .build(); + InvestigationEntity thirdInvestigation = InvestigationEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.ACCEPTED) + .description("Third Investigation on Asset3") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now) + .build(); + InvestigationEntity fourthInvestigation = InvestigationEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.CLOSED) + .description("Fourth Investigation on Asset4") + .side(NotificationSideBaseEntity.RECEIVER) + .createdDate(now.plusSeconds(20L)) + .build(); + InvestigationEntity fifthInvestigation = InvestigationEntity.builder() + .assets(Collections.emptyList()) + .bpn(senderBpn) + .status(NotificationStatusBaseEntity.CANCELED) + .description("Fifth Investigation on Asset5") + .side(NotificationSideBaseEntity.SENDER) + .createdDate(now.plusSeconds(40L)) + .build(); + + InvestigationEntity[] InvestigationEntities = {firstInvestigation, secondInvestigation, thirdInvestigation, fourthInvestigation, fifthInvestigation}; + return InvestigationEntities; + } + + public static InvestigationNotificationEntity[] createReceiverMajorityInvestigationNotificationEntitiesTestData(String senderBpn) { + String targetDateInNovString1 = "12:00 PM, Sun 11/9/2025"; + String targetDateInNovString2 = "12:00 PM, Mon 11/10/2025"; + String targetDateInDecString = "12:00 PM, Tue 12/9/2025"; + String dateFormatter = "hh:mm a, EEE M/d/uuuu"; + Instant targetDateInNov1 = LocalDateTime.parse(targetDateInNovString1, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInNov2 = LocalDateTime.parse(targetDateInNovString2, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + Instant targetDateInDec = LocalDateTime.parse(targetDateInDecString, DateTimeFormatter.ofPattern(dateFormatter, Locale.US)) + .atZone(ZoneId.of("Europe/Berlin")) + .toInstant(); + + InvestigationEntity[] investigationEntities = createReceiverMajorityInvestigationEntitiesTestData(senderBpn); + + InvestigationNotificationEntity[] investigationNotificationEntities = { + InvestigationNotificationEntity + .builder() + .id("1") + .investigation(investigationEntities[0]) + .status(NotificationStatusBaseEntity.CREATED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000001") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov1) .sendToName("OEM1") .severity(QualityNotificationSeverity.MAJOR) .build(), + InvestigationNotificationEntity + .builder() + .id("2") + .investigation(investigationEntities[1]) + .status(NotificationStatusBaseEntity.RECEIVED) + .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") + .sendTo("BPNL000000000001") + .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) + .sendToName("OEM1") + .severity(QualityNotificationSeverity.CRITICAL) + .build(), InvestigationNotificationEntity .builder() .id("3") @@ -285,6 +423,7 @@ public static InvestigationNotificationEntity[] createInvestigationNotificationE .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000002") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov2) .sendToName("OEM2") .severity(QualityNotificationSeverity.LIFE_THREATENING) .build(), @@ -296,6 +435,7 @@ public static InvestigationNotificationEntity[] createInvestigationNotificationE .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000003") .createdBy("BPNL00000000000A") + .targetDate(targetDateInDec) .sendToName("OEM3") .severity(QualityNotificationSeverity.MINOR) .build(), @@ -307,6 +447,7 @@ public static InvestigationNotificationEntity[] createInvestigationNotificationE .edcNotificationId("cda2d956-fa91-4a75-bb4a-8e5ba39b268a") .sendTo("BPNL000000000004") .createdBy("BPNL00000000000A") + .targetDate(targetDateInNov1) .sendToName("OEM4") .severity(QualityNotificationSeverity.MINOR) .build() From 5b32b8d1ebcd8054b8a1e7f9ebca565554c9171e Mon Sep 17 00:00:00 2001 From: Joel Dietz <145654504+JoelDietz@users.noreply.github.com> Date: Fri, 24 Nov 2023 11:55:52 +0100 Subject: [PATCH 10/11] [DO- 4505][patch] sorting tests relocation (#61) * fix: changed the testing to only test what is in the component since the rendering could take some time in the tests. * chore: clean up of the old test that were commented out so that in case of errors the old test could be checked --- .../presentation/alerts.component.spec.ts | 121 +++++++-------- .../investigations.component.spec.ts | 142 ++++++------------ .../components/table/table.component.spec.ts | 82 +++++++++- .../notification.component.spec.ts | 42 +----- 4 files changed, 177 insertions(+), 210 deletions(-) diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts index c440b63b0d..90087feeb7 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts @@ -24,18 +24,9 @@ import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; import { AlertsComponent } from './alerts.component'; +import { TableEventConfig } from '@shared/components/table/table.model'; describe('AlertsComponent', () => { - var originalTimeout: number; - beforeEach(function () { - originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - }); - - afterEach(function () { - jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; - }); - const renderAlerts = async () => { return await renderComponent(AlertsComponent, { imports: [AlertsModule], @@ -56,85 +47,75 @@ describe('AlertsComponent', () => { // expect(spy).toHaveBeenCalledWith(['/alerts/id-84'], { queryParams: tabInformation }); // }); - it('should sort received alerts after column status', async () => { + it('should render the component', async () => { + await renderAlerts(); + const alertsHeader = screen.getByText('pageTitle.alerts'); + expect(alertsHeader).toBeInTheDocument(); + }); + + it('should multisort after column description and status', async () => { const { fixture } = await renderAlerts(); const alertsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(statusColumnHeader); - }, - { timeout: 10000 }, - ); + const paginationOne: TableEventConfig = { page: 0, pageSize: 50, sorting: ['description', 'asc'] }; + const paginationTwo: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'asc'] }; + const paginationThree: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'desc'] }; - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); + alertsComponent.onReceivedTableConfigChange(paginationOne); - expect(alertsComponent['alertReceivedSortList']).toEqual([['status', 'asc']]); - }); + expect(alertsComponent.alertReceivedSortList).toEqual([['description', 'asc']]); - it('should sort queued and requested alerts after column status', async () => { - const { fixture } = await renderAlerts(); - const alertsComponent = fixture.componentInstance; + const alertsHeader = screen.getByText('pageTitle.alerts'); + fireEvent.keyDown(alertsHeader, { + ctrlKey: true, + charCode: 17, + }); - fireEvent.click(await waitFor(() => screen.getByText('commonAlert.tabs.queuedAndRequested'))); + alertsComponent.onReceivedTableConfigChange(paginationTwo); - let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(statusColumnHeader); - }, - { timeout: 10000 }, - ); + expect(alertsComponent.alertReceivedSortList).toEqual([ + ['description', 'asc'], + ['status', 'asc'], + ]); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'queued-and-requested'); + alertsComponent.onReceivedTableConfigChange(paginationThree); - expect(alertsComponent['alertQueuedAndRequestedSortList']).toEqual([['status', 'asc']]); + expect(alertsComponent.alertReceivedSortList).toEqual([ + ['description', 'asc'], + ['status', 'desc'], + ]); }); - - it('should multisort after column description and status', async () => { + it('should reset the multisortList if a selection is done and the ctrl key is not pressed.', async () => { const { fixture } = await renderAlerts(); const alertsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(alertsComponent, 'setTableSortingList').and.callThrough(); - let descriptionColumnHeader = await screen.findByText('table.column.description', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(descriptionColumnHeader); - }, - { timeout: 10000 }, - ); - let statusHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - - await waitFor(() => { - fireEvent.keyDown(statusHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - expect(alertsComponent['ctrlKeyState']).toBeTruthy(); - await waitFor(() => { - fireEvent.click(statusHeader); - }); + const paginationOne: TableEventConfig = { page: 0, pageSize: 50, sorting: ['description', 'asc'] }; + const paginationTwo: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'asc'] }; - await waitFor(() => { - fireEvent.keyUp(statusHeader, { - ctrlKey: true, - charCode: 17, - }); - }); + alertsComponent.onReceivedTableConfigChange(paginationOne); - await waitFor(() => { - fireEvent.click(statusHeader); + expect(alertsComponent.alertReceivedSortList).toEqual([['description', 'asc']]); + + const alertsHeader = screen.getByText('pageTitle.alerts'); + fireEvent.keyDown(alertsHeader, { + ctrlKey: true, + charCode: 17, }); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], 'received'); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); - expect(alertsComponent['alertReceivedSortList']).toEqual([ + alertsComponent.onReceivedTableConfigChange(paginationTwo); + + expect(alertsComponent.alertReceivedSortList).toEqual([ ['description', 'asc'], - ['status', 'desc'], + ['status', 'asc'], ]); + + fireEvent.keyUp(alertsHeader, { + ctrlKey: false, + charCode: 17, + }); + + alertsComponent.onReceivedTableConfigChange(paginationOne); + + expect(alertsComponent.alertReceivedSortList).toEqual([['description', 'asc']]); }); }); diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts index 56f442edd1..4457e2bd67 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts @@ -21,22 +21,13 @@ import { InvestigationsModule } from '@page/investigations/investigations.module'; import { InvestigationsComponent } from '@page/investigations/presentation/investigations.component'; +import { TableEventConfig } from '@shared/components/table/table.model'; import { NotificationTabInformation } from '@shared/model/notification-tab-information'; import { InvestigationsService } from '@shared/service/investigations.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; describe('InvestigationsComponent', () => { - var originalTimeout: number; - beforeEach(function () { - originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - }); - - afterEach(function () { - jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; - }); - const renderInvestigations = async () => { return await renderComponent(InvestigationsComponent, { imports: [InvestigationsModule], @@ -57,110 +48,75 @@ describe('InvestigationsComponent', () => { // expect(spy).toHaveBeenCalledWith(['/investigations/id-84'], { queryParams: tabInformation } ); // }); - it('should call change pagination of received investigations', async () => { + it('should render the component', async () => { await renderInvestigations(); - fireEvent.click( - await waitFor(() => screen.getByLabelText('pagination.nextPageLabel', { selector: 'button' }), { - timeout: 10000, - }), - ); - - expect(await waitFor(() => screen.getByText('Investigation No 84'))).toBeInTheDocument(); - expect(await waitFor(() => screen.getByText('Investigation No 11'))).toBeInTheDocument(); + const investigationsHeader = screen.getByText('pageTitle.investigations'); + expect(investigationsHeader).toBeInTheDocument(); }); - it('should call change pagination of queued & requested investigations', async () => { - await renderInvestigations(); - - fireEvent.click(await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested')), { - timeout: 10000, - }); - - fireEvent.click(await waitFor(() => screen.getByLabelText('pagination.nextPageLabel', { selector: 'button' }))); - - expect(await waitFor(() => screen.getByText('Investigation No 84'))).toBeInTheDocument(); - expect(await waitFor(() => screen.getByText('Investigation No 11'))).toBeInTheDocument(); - }); - - it('should sort received investigations after column status', async () => { + it('should multisort after column description and status', async () => { const { fixture } = await renderInvestigations(); - const investigationComponent = fixture.componentInstance; + const investigationsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(investigationComponent, 'setTableSortingList').and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(statusColumnHeader); - }, - { timeout: 10000 }, - ); + const paginationOne: TableEventConfig = { page: 0, pageSize: 50, sorting: ['description', 'asc'] }; + const paginationTwo: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'asc'] }; + const paginationThree: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'desc'] }; - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); + investigationsComponent.onReceivedTableConfigChanged(paginationOne); - expect(investigationComponent['investigationReceivedSortList']).toEqual([['status', 'asc']]); - }); + expect(investigationsComponent.investigationReceivedSortList).toEqual([['description', 'asc']]); - it('should sort queued and requested investigations after column status', async () => { - const { fixture } = await renderInvestigations(); - const investigationComponent = fixture.componentInstance; + const investigationsHeader = screen.getByText('pageTitle.investigations'); + fireEvent.keyDown(investigationsHeader, { + ctrlKey: true, + charCode: 17, + }); - fireEvent.click(await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested'))); + investigationsComponent.onReceivedTableConfigChanged(paginationTwo); - let setTableFunctionSpy = spyOn(investigationComponent, 'setTableSortingList').and.callThrough(); - let statusColumnHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(statusColumnHeader); - }, - { timeout: 10000 }, - ); + expect(investigationsComponent.investigationReceivedSortList).toEqual([ + ['description', 'asc'], + ['status', 'asc'], + ]); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'queued-and-requested'); + investigationsComponent.onReceivedTableConfigChanged(paginationThree); - expect(investigationComponent['investigationQueuedAndRequestedSortList']).toEqual([['status', 'asc']]); + expect(investigationsComponent.investigationReceivedSortList).toEqual([ + ['description', 'asc'], + ['status', 'desc'], + ]); }); - - it('should multisort after column description and status', async () => { + it('should reset the multisortList if a selection is done and the ctrl key is not pressed.', async () => { const { fixture } = await renderInvestigations(); const investigationsComponent = fixture.componentInstance; - let setTableFunctionSpy = spyOn(investigationsComponent, 'setTableSortingList').and.callThrough(); - let descriptionColumnHeader = await screen.findByText('table.column.description', undefined, { timeout: 10000 }); - await waitFor( - () => { - fireEvent.click(descriptionColumnHeader); - }, - { timeout: 10000 }, - ); - let statusHeader = await screen.findByText('table.column.status', undefined, { timeout: 10000 }); - - await waitFor(() => { - fireEvent.keyDown(statusHeader, { - ctrlKey: true, - charCode: 17, - }); - }); - expect(investigationsComponent['ctrlKeyState']).toBeTruthy(); - await waitFor(() => { - fireEvent.click(statusHeader); - }); + const paginationOne: TableEventConfig = { page: 0, pageSize: 50, sorting: ['description', 'asc'] }; + const paginationTwo: TableEventConfig = { page: 0, pageSize: 50, sorting: ['status', 'asc'] }; - await waitFor(() => { - fireEvent.keyUp(statusHeader, { - ctrlKey: true, - charCode: 17, - }); - }); + investigationsComponent.onReceivedTableConfigChanged(paginationOne); + + expect(investigationsComponent.investigationReceivedSortList).toEqual([['description', 'asc']]); - await waitFor(() => { - fireEvent.click(statusHeader); + const investigationsHeader = screen.getByText('pageTitle.investigations'); + fireEvent.keyDown(investigationsHeader, { + ctrlKey: true, + charCode: 17, }); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['description', 'asc'], 'received'); - expect(setTableFunctionSpy).toHaveBeenCalledWith(['status', 'asc'], 'received'); - expect(investigationsComponent['investigationReceivedSortList']).toEqual([ + investigationsComponent.onReceivedTableConfigChanged(paginationTwo); + + expect(investigationsComponent.investigationReceivedSortList).toEqual([ ['description', 'asc'], - ['status', 'desc'], + ['status', 'asc'], ]); + + fireEvent.keyUp(investigationsHeader, { + ctrlKey: false, + charCode: 17, + }); + + investigationsComponent.onReceivedTableConfigChanged(paginationOne); + + expect(investigationsComponent.investigationReceivedSortList).toEqual([['description', 'asc']]); }); }); diff --git a/frontend/src/app/modules/shared/components/table/table.component.spec.ts b/frontend/src/app/modules/shared/components/table/table.component.spec.ts index 21a8f979e6..4fabdb9f69 100644 --- a/frontend/src/app/modules/shared/components/table/table.component.spec.ts +++ b/frontend/src/app/modules/shared/components/table/table.component.spec.ts @@ -53,7 +53,7 @@ describe('TableComponent', () => { componentProperties: { data, tableConfig, - selected + selected, }, }, ); @@ -161,13 +161,6 @@ describe('TableComponent', () => { sorting: ['name', 'desc'], filtering: Object({ filterMethod: 'AND' }), }); - nameElement.click(); - expect(configChange).toHaveBeenCalledWith({ - page: 0, - pageSize: 10, - sorting: ['name', 'desc'], - filtering: Object({ filterMethod: 'AND' }), - }); }); it('should select one item', async () => { @@ -260,4 +253,77 @@ describe('TableComponent', () => { fixture.detectChanges(); expect(componentInstance.configChanged.emit).toHaveBeenCalledWith(tabelConfigResThree); }); + + it('should fire the correct page change event for changing the page', async () => { + const tableSize = 3; + const content = generateTableContent(tableSize); + const paginationData = { page: 0, pageSize: 10, totalItems: 100, content } as Pagination; + + const tableConfig: TableConfig = { + displayedColumns: ['description', 'createdDate', 'status'], + header: { name: 'Name Sort' }, + }; + + const { fixture } = await renderComponent(TableComponent, { + declarations: [TableComponent], + imports: [SharedModule], + componentProperties: { + paginationData, + tableConfig, + }, + }); + const { componentInstance } = fixture; + + const tabelConfigRes: TableEventConfig = { + page: 1, + pageSize: 10, + sorting: undefined, + filtering: { + filterMethod: FilterMethod.AND, + }, + }; + + spyOn(componentInstance.configChanged, 'emit'); + + componentInstance.onPaginationChange({ pageIndex: 1, pageSize: 10, length: 0 }); + + fixture.detectChanges(); + expect(componentInstance.configChanged.emit).toHaveBeenCalledWith(tabelConfigRes); + }); + it('should fire the correct page change event for changing the number of shown items', async () => { + const tableSize = 3; + const content = generateTableContent(tableSize); + const paginationData = { page: 0, pageSize: 10, totalItems: 100, content } as Pagination; + + const tableConfig: TableConfig = { + displayedColumns: ['description', 'createdDate', 'status'], + header: { name: 'Name Sort' }, + }; + + const { fixture } = await renderComponent(TableComponent, { + declarations: [TableComponent], + imports: [SharedModule], + componentProperties: { + paginationData, + tableConfig, + }, + }); + const { componentInstance } = fixture; + + const tabelConfigRes: TableEventConfig = { + page: 0, + pageSize: 20, + sorting: undefined, + filtering: { + filterMethod: FilterMethod.AND, + }, + }; + + spyOn(componentInstance.configChanged, 'emit'); + + componentInstance.onPaginationChange({ pageIndex: 0, pageSize: 20, length: 0 }); + + fixture.detectChanges(); + expect(componentInstance.configChanged.emit).toHaveBeenCalledWith(tabelConfigRes); + }); }); diff --git a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts index ff9c43ffae..f2a606185e 100644 --- a/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/presentation/notification.component.spec.ts @@ -29,7 +29,7 @@ import { import { View } from '@shared/model/view.model'; import { SharedModule } from '@shared/shared.module'; import { TemplateModule } from '@shared/template.module'; -import { fireEvent, screen, within, waitFor } from '@testing-library/angular'; +import { screen } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; @@ -39,16 +39,9 @@ import { PartTableType } from '@shared/components/table/table.model'; describe('NotificationsInboxComponent', () => { let clickHandler; - var originalTimeout: number; - - afterEach(function () { - jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; - }); beforeEach(() => { clickHandler = jasmine.createSpy(); - originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }); const mapNotificationResponse = (data: NotificationResponse): Notification => { @@ -104,36 +97,7 @@ describe('NotificationsInboxComponent', () => { it('should render received notifications', async () => { const component = await renderNotificationsInbox(); component.detectChanges(); - expect(await screen.findByText('Investigation No 1', undefined, { timeout: 10000 })).toBeInTheDocument(); - }); - - it('should render received notifications with date and status', async () => { - await renderNotificationsInbox(); - const descriptionEl = await screen.findByText('Investigation No 1', undefined, { timeout: 10000 }); - const row = descriptionEl.closest('tr'); - - expect(within(row).getByText('commonInvestigation.status.RECEIVED')).toBeInTheDocument(); - }); - - it('should be able to change notifications page', async () => { - await renderNotificationsInbox(); - fireEvent.click( - await waitFor(() => screen.getByLabelText('pagination.nextPageLabel'), { - timeout: 10000, - }), - ); - - expect(await screen.findByText('Investigation No 51', undefined, { timeout: 10000 })).toBeInTheDocument(); - }); - - it('should render queued & requested notifications', async () => { - await renderNotificationsInbox(); - - fireEvent.click( - await waitFor(() => screen.getByText('commonInvestigation.tabs.queuedAndRequested'), { - timeout: 10000, - }), - ); - expect(await screen.findByText('Investigation No 1', undefined, { timeout: 10000 })).toBeInTheDocument(); + expect(await screen.getByText('commonInvestigation.tabs.received')).toBeInTheDocument(); + expect(await screen.getByText('commonInvestigation.tabs.queuedAndRequested')).toBeInTheDocument(); }); }); From 35faf2a15668a85c14fe05418c7bf4c252f2878c Mon Sep 17 00:00:00 2001 From: viannbreslerAccso <134606480+viannbreslerAccso@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:49:06 +0100 Subject: [PATCH 11/11] [DO-1581][minor] Implement quality investigations details * feature: [DO-1581] Implement quality details figma designs * chore: [DO-1581] Cleanup * chore: [DO-1581] Cleanup --- .../alerts/detail/alert-detail.component.html | 20 +- .../alerts/detail/alert-detail.component.scss | 22 +- .../investigation-detail.component.html | 383 +++++++++--------- .../investigation-detail.component.scss | 96 ++++- .../notification-modal-content.component.html | 4 +- .../icons/quality_investigation_link.svg | 4 + frontend/src/assets/locales/de/common.json | 12 +- frontend/src/assets/locales/en/common.json | 12 +- 8 files changed, 314 insertions(+), 239 deletions(-) create mode 100644 frontend/src/assets/images/icons/quality_investigation_link.svg diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html index c6f5971870..cf6257e351 100644 --- a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html @@ -30,9 +30,9 @@

< - +
{{ selectedAlert?.description }} - +

{{ 'pageAlert.reason' | i18n }}
-
-

- {{ 'pageAlert.subHeadline.' + (isReceived ? 'affectedParts' : 'supplierParts') | i18n }} -

-
+

+ {{ 'pageAlert.subHeadline.' + (isReceived ? 'affectedParts' : 'supplierParts') | i18n }} +

-
-

{{ 'pageAlert.subHeadline.supplierParts' | i18n }}

-
+

{{ 'pageAlert.subHeadline.supplierParts' | i18n }}

-
{{ row.semanticModelId }} + filter_none
diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss index 032a954991..a482dc0a1c 100644 --- a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss @@ -26,7 +26,7 @@ .header-description { @extend h3; - max-width: 600px; + max-width: 900px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; @@ -62,8 +62,12 @@ } } -.detail-content { - @apply col-span-12 lg:col-span-3; +.alert--semantic-model-id__icon { + width: 14px; + height: 14px; + font-size: 14px; + margin-left: 0.5rem; + padding-right: 20px; } .detail--table_wrapper--notification { @@ -78,13 +82,6 @@ padding: 10px; } -.alert--semantic-model-id__icon { - width: 14px; - height: 14px; - font-size: 14px; - margin-left: 0.5rem; -} - .section-details { padding-right: 20px; } @@ -117,4 +114,9 @@ display: flex; justify-content: flex-end; padding: 0 20px 20px 20px; +} + +.alert-copy-button { + overflow: hidden; + white-space: nowrap; } \ No newline at end of file diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html index 444c3def75..1b2717a9cb 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html @@ -18,222 +18,225 @@ SPDX-License-Identifier: Apache-2.0 --> + +
+
+ +

+ < +
+ {{ selectedInvestigation?.description }} +
+

+
+ X +
-
-
- -
- arrow_back - {{ 'actions.goBack' | i18n }} + + +
+
+
+
+

{{ 'pageInvestigation.info' | i18n }}

+
- +
+ +
+
+ +
+
+
+

{{ 'pageInvestigation.reason' | i18n }}

+
+
+
+ +
+
-
- -
- share - {{ 'actions.approve' | i18n }} + +
+
+
+

+ {{ 'pageInvestigation.subHeadline.' + (isReceived ? 'affectedParts' : 'supplierParts') | i18n }} +

- - + +
+
+ +
+
+

{{ 'pageInvestigation.subHeadline.supplierParts' | i18n }}

+
+
+ +
+
+
+ +
+ -
- cancel + [variant]="'flat'" + [color]="'accent'"> +
{{ 'actions.cancel' | i18n }}
- -
- close + [variant]="'flat'" + [color]="'accent'"> +
{{ 'actions.close' | i18n }}
- +
+ {{ 'actions.decline' | i18n }} +
+
+ + +
+ {{ 'actions.approve' | i18n }} +
+
+ + -
- assignment_turned_in + [variant]="'flat'" + [color]="'primary'"> +
{{ 'actions.accept' | i18n }}
- -
- work + [variant]="'flat'" + [color]="'primary'"> +
{{ 'actions.acknowledge' | i18n }}
+
- -
- assignment_late - {{ 'actions.decline' | i18n }} + + +
+ {{ row.semanticModelId }} + filter_none
-
-
- - - -
- - -

{{ 'pageInvestigation.info' | i18n }}

-
- - - -
- - - -

- {{ 'pageInvestigation.subHeadline.' + (isReceived ? 'affectedParts' : 'supplierParts') | i18n }} -

-
- - - -
-
- -
- - -

{{ 'pageInvestigation.reason' | i18n }}

-
- - - -
- - - -

{{ 'pageInvestigation.subHeadline.supplierParts' | i18n }}

-
- - - -
-
- - - -
- {{ row.semanticModelId }} - filter_none -
-
-
- - - - - - - - - - - - - - - - - - -

{{ 'dataLoading.error' | i18n }}

-

{{ view.error | json }}

-
- - - - - - - + + + + + + + + + + + + + + +

{{ 'dataLoading.error' | i18n }}

+

{{ view.error | json }}

+
+ + + + + + + + + + + + + - + (submitted)="isInvestigationOpen$.next(false)">
+
\ No newline at end of file diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.scss b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.scss index 6c2afafbb6..ca96615b28 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.scss +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.scss @@ -1,7 +1,5 @@ /******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,37 +16,107 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +@import '../../../../../theme/base.scss'; +@tailwind base; -.detail--header { +.header-link { + @apply text-textLink; + cursor: pointer; +} + +.header-description { + @extend h3; + max-width: 850px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.investigation--semantic-model-id__icon { + width: 14px; + height: 14px; + font-size: 14px; + margin-left: 0.5rem; + padding-right: 20px; +} + +.detail--header__title { display: flex; justify-content: space-between; + padding: 20px; +} + +.detail--header { + display: flex; +} + +.investigation-copy-button { + overflow: hidden; + white-space: nowrap; } .detail--wrapper { display: grid; - grid-template-columns: 30% 70%; + grid-template-columns: 35% 65%; + padding: 20px; @media (max-width: 1024px) { grid-template-columns: 100%; } } -.detail--wrapper__supplier { - margin-top: 1.5rem; +.detail--tables--wrapper { + display: grid; + grid-template-columns: 50% 50%; + padding: 20px; + + @media (max-width: 1024px) { + grid-template-columns: 100%; + } } .detail--table_wrapper--notification { - margin-left: 1.5rem; + overflow: auto; @media (max-width: 1024px) { margin-left: 0; - margin-top: 1.5rem; } } -.investigation--semantic-model-id__icon { - width: 14px; - height: 14px; - font-size: 14px; - margin-left: 0.5rem; +.detail--table_wrapper--content { + padding: 10px; +} + +.section-details { + padding-right: 20px; +} + +.section-status-content { + padding-right: 5px; + height: 225px; + overflow-y: auto; +} + + +.button-text { + @extend .label-large; + font-weight: 600; + line-height: 20px; + letter-spacing: 0.1px; + padding: 0 5px 0 5px; +} + +.button-text { + @extend .button-text; + @apply text-white; +} + +.action-button { + padding-left: 20px; } + +.buttons-container { + display: flex; + justify-content: flex-end; + padding: 0 20px 20px 20px; +} \ No newline at end of file diff --git a/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html b/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html index 43889a5828..a8a8674bb8 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html @@ -23,7 +23,7 @@
-

{{ 'pageAlert.info' | i18n }}

+

{{ 'page' + notification.notificationType + '.info' | i18n }}

@@ -35,7 +35,7 @@

{{ 'pageAlert.info' | i18n }}

-

{{ 'pageAlert.reason' | i18n }}

+

{{ 'page' + notification.notificationType + '.reason' | i18n }}

diff --git a/frontend/src/assets/images/icons/quality_investigation_link.svg b/frontend/src/assets/images/icons/quality_investigation_link.svg new file mode 100644 index 0000000000..a53673d7c4 --- /dev/null +++ b/frontend/src/assets/images/icons/quality_investigation_link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index b6a57e792f..578782af24 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -207,15 +207,15 @@ "DECLINED": "Abgelehnt" }, "modal": { - "acceptTitle": "Annehmen der Untersuchung", + "acceptTitle": "Untersuchung akzeptieren", "acceptReasonHint": "Geben Sie den Grund für die Annahme dieser Untersuchung ein.", - "acknowledgeTitle": "Bestätigung der Untersuchung", - "approvalTitle": "Genehmigung der Untersuchung", - "closeTitle": "Schließen der Untersuchung", + "acknowledgeTitle": "Untersuchung bestätigen", + "approvalTitle": "Untersuchung genehmigen", + "closeTitle": "Untersuchung schließen", "closeReasonHint": "Geben Sie den Grund für den Abschluss dieser Untersuchung ein", - "cancellationTitle": "Abbruch der Untersuchung", + "cancellationTitle": "Untersuchung abbrechen", "cancellationHint": "Geben Sie die ID der Untersuchung ein, um Ihre Abbruchanfrage zu bestätigen", - "declineTitle": "Ablehnung der Untersuchung", + "declineTitle": "Untersuchung ablehnen", "declineReasonHint": "Geben Sie den Grund für die Ablehnung dieser Untersuchung ein", "successfullyAccepted": "Untersuchung wurde erfolgreich angenommen.", "successfullyAcknowledged": "Untersuchung wurde erfolgreich bestätigt", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 4359558efb..cf2e5b843d 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -211,15 +211,15 @@ "DECLINED": "Declined" }, "modal": { - "acceptTitle": "Accept of investigation", + "acceptTitle": "Accept investigation", "acceptReasonHint": "Enter the reason for accepting this investigation.", - "acknowledgeTitle": "Acknowledgment of investigation", - "approvalTitle": "Approval of investigation", - "closeTitle": "Close of investigation", + "acknowledgeTitle": "Acknowledge investigation", + "approvalTitle": "Approve investigation", + "closeTitle": "Close investigation", "closeReasonHint": "Enter the reason for closing this investigation", - "cancellationTitle": "Cancellation of investigation", + "cancellationTitle": "Cancel investigation", "cancellationHint": "Enter the ID of the investigation to confirm your cancellation", - "declineTitle": "Decline of investigation", + "declineTitle": "Decline investigation", "declineReasonHint": "Enter the reason for declining this investigation", "successfullyAccepted": "Investigation was accepted successfully.", "successfullyAcknowledged": "Investigation was acknowledged successfully.",