diff --git a/CHANGELOG.md b/CHANGELOG.md index c207819f2..80f5417a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ the detailed section referring to by linking pull requests or issues. - Implemented markdown for asset descriptions - Implemented list view for Broker UI +- Asset Metadata is now editable - Now displaying organization information on BrokerUI's `/connectors` page #### Patch diff --git a/package-lock.json b/package-lock.json index b7fba442e..29ec17bd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@ng-apimock/core": "^3.11.0", "@ngxs/store": "^3.8.1", "@sovity.de/broker-server-client": "0.20240116.140046-main-787cf146", - "@sovity.de/edc-client": "0.20240109.103441-main-c8323d18", + "@sovity.de/edc-client": "0.20240109.181620-main-78f67a69", "clean-deep": "^3.4.0", "date-fns": "^2.30.0", "dotenv": "^16.3.1", @@ -3579,9 +3579,9 @@ "integrity": "sha512-2DOelDSvMA/6qQLPdlPUtTaL0l2TJo1tAKyqUvIXYsR8iTfp6Uze+SDU6qOBeAZv8yW73RN38Xak+PK1YPHOCQ==" }, "node_modules/@sovity.de/edc-client": { - "version": "0.20240109.103441-main-c8323d18", - "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20240109.103441-main-c8323d18.tgz", - "integrity": "sha512-g8cAV1uvt0g07Tm11XxmlB0B6v43Qo2LAyB8lx1U9vekYd5hPUPVsNbuXGOUxfURCdi4eOgZwX5wAzpxmO4odQ==" + "version": "0.20240109.181620-main-78f67a69", + "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20240109.181620-main-78f67a69.tgz", + "integrity": "sha512-fWL2TXXZg05RS54Z+gEXaBUYgMOWRWOVen0lWC+7WIjkGNxtb/ybc/bd34IktHV0e0mxMvRFdMbe8cRxGF4Mqg==" }, "node_modules/@tailwindcss/typography": { "version": "0.5.10", @@ -16671,9 +16671,9 @@ "integrity": "sha512-2DOelDSvMA/6qQLPdlPUtTaL0l2TJo1tAKyqUvIXYsR8iTfp6Uze+SDU6qOBeAZv8yW73RN38Xak+PK1YPHOCQ==" }, "@sovity.de/edc-client": { - "version": "0.20240109.103441-main-c8323d18", - "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20240109.103441-main-c8323d18.tgz", - "integrity": "sha512-g8cAV1uvt0g07Tm11XxmlB0B6v43Qo2LAyB8lx1U9vekYd5hPUPVsNbuXGOUxfURCdi4eOgZwX5wAzpxmO4odQ==" + "version": "0.20240109.181620-main-78f67a69", + "resolved": "https://registry.npmjs.org/@sovity.de/edc-client/-/edc-client-0.20240109.181620-main-78f67a69.tgz", + "integrity": "sha512-fWL2TXXZg05RS54Z+gEXaBUYgMOWRWOVen0lWC+7WIjkGNxtb/ybc/bd34IktHV0e0mxMvRFdMbe8cRxGF4Mqg==" }, "@tailwindcss/typography": { "version": "0.5.10", diff --git a/package.json b/package.json index 195ebdb35..428ced59c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@ng-apimock/core": "^3.11.0", "@ngxs/store": "^3.8.1", "@sovity.de/broker-server-client": "0.20240116.140046-main-787cf146", - "@sovity.de/edc-client": "0.20240109.103441-main-c8323d18", + "@sovity.de/edc-client": "0.20240109.181620-main-78f67a69", "clean-deep": "^3.4.0", "date-fns": "^2.30.0", "dotenv": "^16.3.1", diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts index 2ef52f944..f6ac077c1 100644 --- a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service.ts @@ -3,7 +3,10 @@ import {DataOffer} from '../../../core/services/models/data-offer'; import {UiAssetMapped} from '../../../core/services/models/ui-asset-mapped'; import {CatalogDataOfferMapped} from '../../../routes/broker-ui/catalog-page/catalog-page/mapping/catalog-page-result-mapped'; import {ContractAgreementCardMapped} from '../../../routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-card-mapped'; -import {AssetDetailDialogData} from './asset-detail-dialog-data'; +import { + AssetDetailDialogData, + OnAssetEditClickFn, +} from './asset-detail-dialog-data'; import {AssetPropertyGridGroupBuilder} from './asset-property-grid-group-builder'; @Injectable() @@ -12,10 +15,7 @@ export class AssetDetailDialogDataService { private assetPropertyGridGroupBuilder: AssetPropertyGridGroupBuilder, ) {} - assetDetails( - asset: UiAssetMapped, - allowDelete: boolean, - ): AssetDetailDialogData { + assetDetailsReadonly(asset: UiAssetMapped): AssetDetailDialogData { const propertyGridGroups = [ this.assetPropertyGridGroupBuilder.buildAssetPropertiesGroup(asset, null), this.assetPropertyGridGroupBuilder.buildAdditionalPropertiesGroup(asset), @@ -24,11 +24,22 @@ export class AssetDetailDialogDataService { return { type: 'asset-details', asset, - showDeleteButton: allowDelete, propertyGridGroups, }; } + assetDetailsEditable( + asset: UiAssetMapped, + opts: {onAssetEditClick: OnAssetEditClickFn}, + ): AssetDetailDialogData { + return { + ...this.assetDetailsReadonly(asset), + showDeleteButton: true, + showEditButton: true, + onAssetEditClick: opts.onAssetEditClick, + }; + } + dataOfferDetails(dataOffer: DataOffer): AssetDetailDialogData { const asset = dataOffer.asset; const propertyGridGroups = [ diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts index a6c3c9fe3..66f8863b6 100644 --- a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.ts @@ -16,4 +16,14 @@ export interface AssetDetailDialogData { contractAgreement?: ContractAgreementCardMapped; brokerDataOffer?: CatalogDataOfferMapped; showDeleteButton?: boolean; + showEditButton?: boolean; + onAssetEditClick?: OnAssetEditClickFn; } + +export type OnAssetEditClickFn = ( + asset: UiAssetMapped, + /** + * Required so that after the editing the detail dialog can be updated again + */ + afterEditCb: (updatedDialogData: AssetDetailDialogData) => void, +) => void; diff --git a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html index 9055a1610..2c98ebac9 100644 --- a/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html +++ b/src/app/component-library/catalog/asset-detail-dialog/asset-detail-dialog.component.html @@ -1,30 +1,56 @@ -
- - upload - - - - - {{ - data.contractAgreement!!.direction === 'PROVIDING' ? 'upload' : 'download' - }} - - -
-
- {{ asset.title }} -
-
- {{ asset.creatorOrganizationName }} +
+
+ + upload + + + + + {{ + data.contractAgreement!!.direction === 'PROVIDING' + ? 'upload' + : 'download' + }} + + +
+
+ {{ asset.title }} +
+
+ {{ asset.creatorOrganizationName }} +
+
+ + +
@@ -76,15 +102,8 @@
- - -
@@ -37,16 +48,12 @@
-
diff --git a/src/app/component-library/json-dialog/json-dialog/json-dialog.component.ts b/src/app/component-library/json-dialog/json-dialog/json-dialog.component.ts index 376307ab6..edb4b7646 100644 --- a/src/app/component-library/json-dialog/json-dialog/json-dialog.component.ts +++ b/src/app/component-library/json-dialog/json-dialog/json-dialog.component.ts @@ -17,7 +17,7 @@ import {filter, finalize, takeUntil} from 'rxjs/operators'; import {NgxJsonViewerComponent} from 'ngx-json-viewer'; import {ConfirmationDialogComponent} from '../../confirmation-dialog/confirmation-dialog/confirmation-dialog.component'; import {cleanJson} from './clean-json'; -import {DialogButton, JsonDialogData} from './json-dialog.data'; +import {DialogToolbarButton, JsonDialogData} from './json-dialog.data'; @Component({ selector: 'app-json-dialog', @@ -53,7 +53,7 @@ export class JsonDialogComponent implements OnInit, AfterViewInit, OnDestroy { : this.data.objectForJson; } - onAction(button: DialogButton) { + onAction(button: DialogToolbarButton) { if (button.confirmation) { const ref = this.matDialog.open(ConfirmationDialogComponent, { maxWidth: '20%', @@ -69,7 +69,7 @@ export class JsonDialogComponent implements OnInit, AfterViewInit, OnDestroy { } } - doAction(button: DialogButton) { + doAction(button: DialogToolbarButton) { if (this.busy) { return; } diff --git a/src/app/component-library/json-dialog/json-dialog/json-dialog.data.ts b/src/app/component-library/json-dialog/json-dialog/json-dialog.data.ts index 7a91c39d9..b9e1f8df6 100644 --- a/src/app/component-library/json-dialog/json-dialog/json-dialog.data.ts +++ b/src/app/component-library/json-dialog/json-dialog/json-dialog.data.ts @@ -6,12 +6,12 @@ export interface JsonDialogData { subtitle: string; icon: string; objectForJson: unknown; - actionButton?: DialogButton; + toolbarButton?: DialogToolbarButton; } -export interface DialogButton { +export interface DialogToolbarButton { text: string; - color: 'accent' | 'warn' | 'primary'; + icon: string; action: () => Observable | any; confirmation?: ConfirmDialogModel; } diff --git a/src/app/component-library/json-dialog/json-dialog/json-dialog.service.ts b/src/app/component-library/json-dialog/json-dialog/json-dialog.service.ts index a69767db3..65b54a61e 100644 --- a/src/app/component-library/json-dialog/json-dialog/json-dialog.service.ts +++ b/src/app/component-library/json-dialog/json-dialog/json-dialog.service.ts @@ -18,6 +18,11 @@ export class JsonDialogService { data: JsonDialogData, until$: Observable = NEVER, ): Observable { - return showDialogUntil(this.dialog, JsonDialogComponent, {data}, until$); + return showDialogUntil( + this.dialog, + JsonDialogComponent, + {data, autoFocus: 'first-tabbable'}, + until$, + ); } } diff --git a/src/app/component-library/pipes-and-directives/directives/external-link.directive.ts b/src/app/component-library/pipes-and-directives/directives/external-link.directive.ts new file mode 100644 index 000000000..c47bcbbc2 --- /dev/null +++ b/src/app/component-library/pipes-and-directives/directives/external-link.directive.ts @@ -0,0 +1,14 @@ +import {AfterViewInit, Directive, ElementRef} from '@angular/core'; + +@Directive({ + selector: '[externalLink]', +}) +export class ExternalLinkDirective implements AfterViewInit { + constructor(private elementRef: ElementRef) {} + + ngAfterViewInit() { + const element = this.elementRef.nativeElement as HTMLAnchorElement; + element.setAttribute('target', '_blank'); + element.setAttribute('rel', 'noopener noreferrer'); + } +} diff --git a/src/app/component-library/pipes-and-directives/pipes-and-directives.module.ts b/src/app/component-library/pipes-and-directives/pipes-and-directives.module.ts index 3be8f45d5..1af7a09de 100644 --- a/src/app/component-library/pipes-and-directives/pipes-and-directives.module.ts +++ b/src/app/component-library/pipes-and-directives/pipes-and-directives.module.ts @@ -3,6 +3,7 @@ import {NgModule} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {AutofocusDirective} from './directives/autofocus.direcitive'; +import {ExternalLinkDirective} from './directives/external-link.directive'; import {RemoveClassDirective} from './directives/remove-class.directive'; import {TrackByFieldDirective} from './directives/track-by-field.directive'; import {CompareByFieldPipe} from './pipes/compare-by-field.pipe'; @@ -21,6 +22,7 @@ import {ValuesPipe} from './pipes/values.pipe'; declarations: [ AutofocusDirective, CompareByFieldPipe, + ExternalLinkDirective, IsActiveFeaturePipe, RemoveClassDirective, TrackByFieldDirective, @@ -29,6 +31,7 @@ import {ValuesPipe} from './pipes/values.pipe'; exports: [ AutofocusDirective, CompareByFieldPipe, + ExternalLinkDirective, IsActiveFeaturePipe, RemoveClassDirective, TrackByFieldDirective, diff --git a/src/app/component-library/property-grid/property-grid.module.ts b/src/app/component-library/property-grid/property-grid.module.ts index 925171602..fb27846b9 100644 --- a/src/app/component-library/property-grid/property-grid.module.ts +++ b/src/app/component-library/property-grid/property-grid.module.ts @@ -5,6 +5,7 @@ import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import {MatTooltipModule} from '@angular/material/tooltip'; +import {PipesAndDirectivesModule} from '../pipes-and-directives/pipes-and-directives.module'; import {UiElementsModule} from '../ui-elements/ui-elements.module'; import {PropertyGridGroupComponent} from './property-grid-group/property-grid-group.component'; import {PropertyGridComponent} from './property-grid/property-grid.component'; @@ -24,6 +25,7 @@ import {PropertyGridComponent} from './property-grid/property-grid.component'; MatTooltipModule, // EDC UI Feature Modules + PipesAndDirectivesModule, UiElementsModule, ], declarations: [PropertyGridComponent, PropertyGridGroupComponent], diff --git a/src/app/component-library/property-grid/property-grid/property-grid.component.html b/src/app/component-library/property-grid/property-grid/property-grid.component.html index eab8edd6d..82a1f8944 100644 --- a/src/app/component-library/property-grid/property-grid/property-grid.component.html +++ b/src/app/component-library/property-grid/property-grid/property-grid.component.html @@ -27,8 +27,7 @@ {{ prop.text }} { + return from( + this.edcClient.uiApi.editAssetMetadata({ + assetId, + uiAssetEditMetadataRequest, + }), + ); + } + deleteAsset(assetId: string): Observable { return from(this.edcClient.uiApi.deleteAsset({assetId})); } diff --git a/src/app/core/services/api/fake-backend/connector-fake-impl/asset-fake-service.ts b/src/app/core/services/api/fake-backend/connector-fake-impl/asset-fake-service.ts index 424666c56..4e3fe0e53 100644 --- a/src/app/core/services/api/fake-backend/connector-fake-impl/asset-fake-service.ts +++ b/src/app/core/services/api/fake-backend/connector-fake-impl/asset-fake-service.ts @@ -3,7 +3,9 @@ import { IdResponseDto, UiAsset, UiAssetCreateRequest, + UiAssetEditMetadataRequest, } from '@sovity.de/edc-client'; +import {Patcher, patchObj} from '../../../../utils/object-utils'; import {TestAssets} from './data/test-assets'; let assets: UiAsset[] = [TestAssets.full, TestAssets.boring, TestAssets.short]; @@ -17,27 +19,21 @@ export const assetPage = (): AssetPage => { export const getAssetById = (id: string) => assets.find((it) => it.assetId === id); +function patchAsset(assetId: string, patcher: Patcher): UiAsset { + assets = assets.map((it) => + it.assetId === assetId ? patchObj(it, patcher) : it, + ); + return getAssetById(assetId)!; +} + export const createAsset = (asset: UiAssetCreateRequest): IdResponseDto => { + const assetId = asset.id; assets.push({ - assetId: asset.id, - title: asset.title ?? asset.id, + assetId, + ...createAssetMetadata(assetId, asset), connectorEndpoint: 'https://my-connector/api/dsp', participantId: 'MDSL1234XX.C1234XX', - description: asset.description, creatorOrganizationName: 'My Org', - publisherHomepage: asset.publisherHomepage, - licenseUrl: asset.licenseUrl, - version: asset.version, - keywords: asset.keywords, - mediaType: asset.mediaType, - landingPageUrl: asset.landingPageUrl, - dataCategory: asset.dataCategory, - dataSubcategory: asset.dataSubcategory, - dataModel: asset.dataModel, - geoReferenceMethod: asset.geoReferenceMethod, - transportMode: asset.transportMode, - additionalProperties: asset.additionalProperties, - privateProperties: asset.privateProperties, }); return { id: asset.id, @@ -45,6 +41,55 @@ export const createAsset = (asset: UiAssetCreateRequest): IdResponseDto => { }; }; +export const editAssetMetadata = ( + assetId: string, + request: UiAssetEditMetadataRequest, +): IdResponseDto => { + const asset = patchAsset(assetId, () => + createAssetMetadata(assetId, request), + ); + + return { + id: asset.assetId, + lastUpdatedDate: new Date(), + }; +}; + +function createAssetMetadata( + assetId: string, + request: UiAssetCreateRequest | UiAssetEditMetadataRequest, +): Omit< + UiAsset, + | 'assetId' + | 'assetJsonLd' + | 'connectorEndpoint' + | 'creatorOrganizationName' + | 'httpDatasourceHintsProxyBody' + | 'httpDatasourceHintsProxyMethod' + | 'httpDatasourceHintsProxyPath' + | 'httpDatasourceHintsProxyQueryParams' + | 'participantId' +> { + return { + title: request.title ?? assetId, + description: request.description, + descriptionShortText: request.description, + publisherHomepage: request.publisherHomepage, + licenseUrl: request.licenseUrl, + version: request.version, + keywords: request.keywords, + mediaType: request.mediaType, + landingPageUrl: request.landingPageUrl, + dataCategory: request.dataCategory, + dataSubcategory: request.dataSubcategory, + dataModel: request.dataModel, + geoReferenceMethod: request.geoReferenceMethod, + transportMode: request.transportMode, + additionalProperties: request.additionalProperties, + privateProperties: request.privateProperties, + }; +} + export const deleteAsset = (id: string): IdResponseDto => { assets = assets.filter((it) => it.assetId !== id); return {id, lastUpdatedDate: new Date()}; diff --git a/src/app/core/services/api/fake-backend/edc-fake-backend.ts b/src/app/core/services/api/fake-backend/edc-fake-backend.ts index 3dc4b078b..e505c5e3d 100644 --- a/src/app/core/services/api/fake-backend/edc-fake-backend.ts +++ b/src/app/core/services/api/fake-backend/edc-fake-backend.ts @@ -12,6 +12,7 @@ import { PolicyDefinitionPageToJSON, TransferHistoryPageToJSON, UiAssetCreateRequestFromJSON, + UiAssetEditMetadataRequestFromJSON, UiAssetToJSON, UiContractNegotiationToJSON, UiDataOfferToJSON, @@ -20,6 +21,7 @@ import { assetPage, createAsset, deleteAsset, + editAssetMetadata, } from './connector-fake-impl/asset-fake-service'; import {getCatalogPageDataOffers} from './connector-fake-impl/catalog-fake-service'; import { @@ -99,6 +101,13 @@ export const EDC_FAKE_BACKEND: FetchAPI = async ( return ok(IdResponseDtoToJSON(deleted)); }) + .url('pages/asset-page/assets/*/metadata') + .on('PUT', (assetId) => { + const editRequest = UiAssetEditMetadataRequestFromJSON(body); + const created = editAssetMetadata(assetId, editRequest); + return ok(IdResponseDtoToJSON(created)); + }) + .url('pages/policy-page') .on('GET', () => { const page = policyDefinitionPage(); diff --git a/src/app/core/services/asset-create-request-builder.ts b/src/app/core/services/asset-create-request-builder.ts index 1a9b6a7c2..6f0641b94 100644 --- a/src/app/core/services/asset-create-request-builder.ts +++ b/src/app/core/services/asset-create-request-builder.ts @@ -1,6 +1,9 @@ import {Injectable} from '@angular/core'; -import {UiAssetCreateRequest} from '@sovity.de/edc-client'; -import {AssetEditorDialogFormValue} from '../../routes/connector-ui/asset-page/asset-create-dialog/asset-editor-dialog-form-model'; +import { + UiAssetCreateRequest, + UiAssetEditMetadataRequest, +} from '@sovity.de/edc-client'; +import {AssetEditorDialogFormValue} from '../../routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-editor-dialog-form-model'; import {DataAddressMapper} from './data-address-mapper'; @Injectable() @@ -11,12 +14,32 @@ export class AssetCreateRequestBuilder { * Build {@link UiAssetCreateRequest} from {@link AssetEditorDialogFormValue} * * @param formValue form value - * @return asset create dto + * @return {@link UiAssetCreateRequest} */ buildAssetCreateRequest( formValue: AssetEditorDialogFormValue, ): UiAssetCreateRequest { const id = formValue.metadata?.id!; + const metadata = this.buildEditMetadataRequest(formValue); + const dataAddressProperties = + this.dataAddressMapper.buildDataAddressProperties(formValue.datasource); + + return { + id, + ...metadata, + dataAddressProperties, + }; + } + + /** + * Build {@link UiAssetEditMetadataRequest} from {@link AssetEditorDialogFormValue} + * + * @param formValue form value + * @return {@link UiAssetEditMetadataRequest} + */ + buildEditMetadataRequest( + formValue: AssetEditorDialogFormValue, + ): UiAssetEditMetadataRequest { const title = formValue.metadata?.title!; const version = formValue.metadata?.version; const description = formValue.metadata?.description; @@ -33,10 +56,7 @@ export class AssetCreateRequestBuilder { const geoReferenceMethod = formValue.advanced?.geoReferenceMethod; const dataModel = formValue.advanced?.dataModel; - const dataAddressProperties = - this.dataAddressMapper.buildDataAddressProperties(formValue.datasource); return { - id, title, language, description, @@ -51,7 +71,6 @@ export class AssetCreateRequestBuilder { dataModel, geoReferenceMethod, transportMode, - dataAddressProperties, }; } } diff --git a/src/app/core/services/data-address-mapper.ts b/src/app/core/services/data-address-mapper.ts index 9e097d12e..8823f4370 100644 --- a/src/app/core/services/data-address-mapper.ts +++ b/src/app/core/services/data-address-mapper.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {AssetDatasourceFormValue} from '../../routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-model'; +import {AssetDatasourceFormValue} from '../../routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-datasource-form-model'; import {ContractAgreementTransferDialogFormValue} from '../../routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model'; import {HttpRequestParamsMapper} from './http-params-mapper.service'; diff --git a/src/app/core/services/http-params-mapper.service.ts b/src/app/core/services/http-params-mapper.service.ts index f5e5f3794..84337cdc4 100644 --- a/src/app/core/services/http-params-mapper.service.ts +++ b/src/app/core/services/http-params-mapper.service.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; -import {AssetDatasourceFormValue} from '../../routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-model'; -import {HttpDatasourceHeaderFormValue} from '../../routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-header-form-model'; -import {HttpDatasourceQueryParamFormValue} from '../../routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-query-param-form-model'; +import {AssetDatasourceFormValue} from '../../routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-datasource-form-model'; +import {HttpDatasourceHeaderFormValue} from '../../routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-header-form-model'; +import {HttpDatasourceQueryParamFormValue} from '../../routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-query-param-form-model'; import {ContractAgreementTransferDialogFormValue} from '../../routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model'; import {mapKeys, removeNullValues} from '../utils/record-utils'; import {everythingAfter, everythingBefore} from '../utils/string-utils'; diff --git a/src/app/core/utils/angular-utils.ts b/src/app/core/utils/angular-utils.ts new file mode 100644 index 000000000..f529b667c --- /dev/null +++ b/src/app/core/utils/angular-utils.ts @@ -0,0 +1,35 @@ +import {Input, SimpleChanges} from '@angular/core'; +import {Subject} from 'rxjs'; + +/** + * A type-safe version of {@link SimpleChanges}. + * + * Does not contain all {@link Input}s, but only simple fields. + */ +export type SimpleChangesTyped< + Component extends object, + Props = ExcludeFunctions, +> = { + [Key in keyof Props]: SimpleChangeTyped; +}; + +export type SimpleChangeTyped = { + previousValue: T; + currentValue: T; + firstChange: boolean; + isFirstChange(): boolean; +}; + +type MarkFunctionPropertyNames = { + [Key in keyof Component]: Component[Key] extends Function | Subject + ? never + : Key; +}; + +type ExcludeFunctionPropertyNames = + MarkFunctionPropertyNames[keyof T]; + +type ExcludeFunctions = Pick< + T, + ExcludeFunctionPropertyNames +>; diff --git a/src/app/routes/broker-ui/catalog-page/filter-box/filter-box.component.ts b/src/app/routes/broker-ui/catalog-page/filter-box/filter-box.component.ts index 7bd3393b3..246a411a0 100644 --- a/src/app/routes/broker-ui/catalog-page/filter-box/filter-box.component.ts +++ b/src/app/routes/broker-ui/catalog-page/filter-box/filter-box.component.ts @@ -7,11 +7,11 @@ import { OnDestroy, OnInit, Output, - SimpleChanges, } from '@angular/core'; import {FormControl} from '@angular/forms'; import {Subject} from 'rxjs'; import {map} from 'rxjs/operators'; +import {SimpleChangesTyped} from '../../../../core/utils/angular-utils'; import {FilterBoxItem} from './filter-box-item'; import {FilterBoxVisibleState} from './filter-box-visible-state'; @@ -42,7 +42,7 @@ export class FilterBoxComponent implements OnInit, OnChanges, OnDestroy { }); } - ngOnChanges(changes: SimpleChanges) { + ngOnChanges(changes: SimpleChangesTyped) { if (changes.state) { const selectedItems = this.formControl.value ?? []; if (!this.state.isEqualSelectedItems(selectedItems)) { diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-form.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-form.ts deleted file mode 100644 index dd4773b31..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-form.ts +++ /dev/null @@ -1,121 +0,0 @@ -import {Injectable} from '@angular/core'; -import {FormBuilder, FormGroup} from '@angular/forms'; -import {DataAddressType} from '../../../../component-library/data-address/data-address-type-select/data-address-type'; -import {ActiveFeatureSet} from '../../../../core/config/active-feature-set'; -import {DataCategorySelectItem} from '../data-category-select/data-category-select-item'; -import { - AssetEditorDialogFormModel, - AssetEditorDialogFormValue, -} from './asset-editor-dialog-form-model'; -import {AssetAdvancedFormBuilder} from './model/asset-advanced-form-builder'; -import {AssetAdvancedFormModel} from './model/asset-advanced-form-model'; -import {AssetDatasourceFormBuilder} from './model/asset-datasource-form-builder'; -import {AssetDatasourceFormModel} from './model/asset-datasource-form-model'; -import {AssetMetadataFormBuilder} from './model/asset-metadata-form-builder'; -import {AssetMetadataFormModel} from './model/asset-metadata-form-model'; - -/** - * Handles AngularForms for AssetEditorDialog - */ -@Injectable() -export class AssetCreateDialogForm { - all = this.buildFormGroup(); - - /** - * FormGroup for stepper step "Metadata" - */ - metadata = this.all.controls.metadata; - - /** - * FormGroup for stepper step "Advanced" - */ - advanced = this.all.controls.advanced!!; - - /** - * FormGroup for stepper step "Data Source" - */ - datasource = this.all.controls.datasource; - - /** - * Quick access to selected data address type - */ - get dataAddressType(): DataAddressType | null { - return this.datasource.controls.dataAddressType.value; - } - - /** - * Quick access to selected data category - */ - get dataCategory(): DataCategorySelectItem | null { - return this.advanced!!.controls.dataCategory.value; - } - - /** - * Quick access to full value - */ - get value(): AssetEditorDialogFormValue { - return this.all.value; - } - - get proxyMethod(): boolean { - return this.datasource.controls.httpProxyMethod.value; - } - - get proxyPath(): boolean { - return this.datasource.controls.httpProxyPath.value; - } - - get proxyQueryParams(): boolean { - return this.datasource.controls.httpProxyQueryParams.value; - } - - constructor( - private formBuilder: FormBuilder, - private activeFeatureSet: ActiveFeatureSet, - private assetMetadataFormBuilder: AssetMetadataFormBuilder, - private assetAdvancedFormBuilder: AssetAdvancedFormBuilder, - private assetDatasourceFormBuilder: AssetDatasourceFormBuilder, - ) {} - - buildFormGroup(): FormGroup { - const metadata: FormGroup = - this.assetMetadataFormBuilder.buildFormGroup(); - - const datasource: FormGroup = - this.assetDatasourceFormBuilder.buildFormGroup(); - - const formGroup: FormGroup = - this.formBuilder.nonNullable.group({ - metadata, - datasource, - }); - - if (this.activeFeatureSet.hasMdsFields()) { - const advanced: FormGroup = - this.assetAdvancedFormBuilder.buildFormGroup(); - formGroup.addControl('advanced', advanced); - } - - return formGroup; - } - - onHttpHeadersAddClick() { - this.datasource.controls.httpHeaders.push( - this.assetDatasourceFormBuilder.buildHeaderFormGroup(), - ); - } - - onHttpHeadersRemoveClick(index: number) { - this.datasource.controls.httpHeaders.removeAt(index); - } - - onHttpQueryParamsAddClick() { - this.datasource.controls.httpQueryParams.push( - this.assetDatasourceFormBuilder.buildQueryParamFormGroup(), - ); - } - - onHttpQueryParamsRemoveClick(index: number) { - this.datasource.controls.httpQueryParams.removeAt(index); - } -} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-result.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-result.ts deleted file mode 100644 index ff78023d9..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog-result.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface AssetCreateDialogResult { - /** - * When creating an asset, update asset list - */ - refreshList: boolean; -} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.ts deleted file mode 100644 index ef2c24a73..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Component, OnDestroy} from '@angular/core'; -import {MatDialogRef} from '@angular/material/dialog'; -import {Subject} from 'rxjs'; -import {finalize, takeUntil} from 'rxjs/operators'; -import {EdcApiService} from '../../../../core/services/api/edc-api.service'; -import {AssetCreateRequestBuilder} from '../../../../core/services/asset-create-request-builder'; -import {NotificationService} from '../../../../core/services/notification.service'; -import {ValidationMessages} from '../../../../core/validators/validation-messages'; -import {AssetCreateDialogForm} from './asset-create-dialog-form'; -import {AssetCreateDialogResult} from './asset-create-dialog-result'; -import {AssetAdvancedFormBuilder} from './model/asset-advanced-form-builder'; -import {AssetDatasourceFormBuilder} from './model/asset-datasource-form-builder'; -import {AssetMetadataFormBuilder} from './model/asset-metadata-form-builder'; - -@Component({ - selector: 'asset-create-dialog', - templateUrl: './asset-create-dialog.component.html', - providers: [ - AssetAdvancedFormBuilder, - AssetDatasourceFormBuilder, - AssetCreateDialogForm, - AssetCreateRequestBuilder, - AssetMetadataFormBuilder, - ], -}) -export class AssetCreateDialogComponent implements OnDestroy { - loading = false; - - methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; - - constructor( - private edcApiService: EdcApiService, - public form: AssetCreateDialogForm, - public validationMessages: ValidationMessages, - private assetEntryBuilder: AssetCreateRequestBuilder, - private notificationService: NotificationService, - private dialogRef: MatDialogRef, - ) {} - - onSave() { - const formValue = this.form.value; - const assetCreateRequest = - this.assetEntryBuilder.buildAssetCreateRequest(formValue); - - this.form.all.disable(); - this.loading = true; - this.edcApiService - .createAsset(assetCreateRequest) - .pipe( - takeUntil(this.ngOnDestroy$), - finalize(() => { - this.form.all.enable(); - this.loading = false; - }), - ) - .subscribe({ - complete: () => { - this.notificationService.showInfo('Successfully created asset'); - this.close({refreshList: true}); - }, - error: (error) => { - console.error('Failed creating asset!', error); - this.notificationService.showError('Failed creating asset!'); - }, - }); - } - - private close(params: AssetCreateDialogResult) { - this.dialogRef.close(params); - } - - ngOnDestroy$ = new Subject(); - - ngOnDestroy(): void { - this.ngOnDestroy$.next(null); - this.ngOnDestroy$.complete(); - } -} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-editor-dialog-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-editor-dialog-form-model.ts deleted file mode 100644 index a0ee34c21..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-editor-dialog-form-model.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {FormGroup, ɵFormGroupValue} from '@angular/forms'; -import {AssetAdvancedFormModel} from './model/asset-advanced-form-model'; -import {AssetDatasourceFormModel} from './model/asset-datasource-form-model'; -import {AssetMetadataFormModel} from './model/asset-metadata-form-model'; - -/** - * Form Model for AssetEditorDialog - */ -export interface AssetEditorDialogFormModel { - metadata: FormGroup; - advanced?: FormGroup; - datasource: FormGroup; -} - -/** - * Form Value for AssetEditorDialog - */ -export type AssetEditorDialogFormValue = - ɵFormGroupValue; diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-builder.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-builder.ts deleted file mode 100644 index 07c6d1fcc..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-builder.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {Injectable} from '@angular/core'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {DataCategorySelectItem} from '../../data-category-select/data-category-select-item'; -import {DataSubcategorySelectItem} from '../../data-subcategory-select/data-subcategory-select-item'; -import {TransportModeSelectItem} from '../../transport-mode-select/transport-mode-select-item'; -import {AssetAdvancedFormModel} from './asset-advanced-form-model'; - -@Injectable() -export class AssetAdvancedFormBuilder { - constructor(private formBuilder: FormBuilder) {} - - buildFormGroup(): FormGroup { - return this.formBuilder.nonNullable.group({ - dataModel: '', - dataCategory: [ - null as DataCategorySelectItem | null, - Validators.required, - ], - dataSubcategory: null as DataSubcategorySelectItem | null, - transportMode: null as TransportModeSelectItem | null, - geoReferenceMethod: '', - }); - } -} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-model.ts deleted file mode 100644 index 8cb0b1c0f..000000000 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-advanced-form-model.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {FormControl} from '@angular/forms'; -import {DataCategorySelectItem} from '../../data-category-select/data-category-select-item'; -import {DataSubcategorySelectItem} from '../../data-subcategory-select/data-subcategory-select-item'; -import {TransportModeSelectItem} from '../../transport-mode-select/transport-mode-select-item'; - -/** - * Form Model for AssetEditorDialog > Advanced - * (MDS Properties) - */ -export interface AssetAdvancedFormModel { - dataCategory: FormControl; - dataSubcategory: FormControl; - dataModel: FormControl; - geoReferenceMethod: FormControl; - transportMode: FormControl; -} diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-data.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-data.ts new file mode 100644 index 000000000..35e7e625d --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-data.ts @@ -0,0 +1,5 @@ +import {AssetEditorDialogFormValue} from './form/model/asset-editor-dialog-form-model'; + +export interface AssetEditDialogData { + initialFormValue: AssetEditorDialogFormValue; +} diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-mode.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-mode.ts new file mode 100644 index 000000000..3a5aab106 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-mode.ts @@ -0,0 +1 @@ +export type AssetEditDialogMode = 'CREATE' | 'EDIT_METADATA'; diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-result.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-result.ts new file mode 100644 index 000000000..af21e6881 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog-result.ts @@ -0,0 +1,13 @@ +import {UiAssetMapped} from '../../../../core/services/models/ui-asset-mapped'; + +export interface AssetEditDialogResult { + /** + * Updated asset list for the asset page + */ + refreshedList: UiAssetMapped[]; + + /** + * The updated / created asset + */ + asset: UiAssetMapped; +} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.html b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.html similarity index 97% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.html rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.html index 6daa7936b..d3c66dd90 100644 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/asset-create-dialog.component.html +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.html @@ -1,4 +1,6 @@ -

Create New Asset

+

+ {{ form.mode === 'CREATE' ? 'Create New Asset' : 'Edit Asset' }} +

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

Create New Asset

class="grow"> Asset ID @@ -61,7 +63,10 @@

Create New Asset

"> The description uses - Markdown syntax @@ -179,7 +184,7 @@

Create New Asset

- +
Datasource Information
Datasource
@@ -517,7 +522,11 @@

Create New Asset

- @@ -526,6 +535,6 @@

Create New Asset

color="primary" [disabled]="!form.all.valid || loading" (click)="onSave()"> - Create + {{ form.mode === 'CREATE' ? 'Create' : 'Update' }}
diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.ts new file mode 100644 index 000000000..93549a415 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.component.ts @@ -0,0 +1,125 @@ +import {Component, Inject, OnDestroy} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {EMPTY, Observable, Subject, switchMap} from 'rxjs'; +import {catchError, finalize, map, takeUntil, tap} from 'rxjs/operators'; +import {IdResponseDto} from '@sovity.de/edc-client'; +import {EdcApiService} from '../../../../core/services/api/edc-api.service'; +import {AssetCreateRequestBuilder} from '../../../../core/services/asset-create-request-builder'; +import {AssetService} from '../../../../core/services/asset.service'; +import {NotificationService} from '../../../../core/services/notification.service'; +import {ValidationMessages} from '../../../../core/validators/validation-messages'; +import {AssetEditDialogData} from './asset-edit-dialog-data'; +import {AssetEditDialogResult} from './asset-edit-dialog-result'; +import {AssetAdvancedFormBuilder} from './form/asset-advanced-form-builder'; +import {AssetDatasourceFormBuilder} from './form/asset-datasource-form-builder'; +import {AssetEditDialogForm} from './form/asset-edit-dialog-form'; +import {AssetMetadataFormBuilder} from './form/asset-metadata-form-builder'; +import {DATA_SOURCE_HTTP_METHODS} from './form/http-methods'; +import {AssetEditorDialogFormValue} from './form/model/asset-editor-dialog-form-model'; + +@Component({ + selector: 'asset-edit-dialog', + templateUrl: './asset-edit-dialog.component.html', + providers: [ + AssetAdvancedFormBuilder, + AssetDatasourceFormBuilder, + AssetEditDialogForm, + AssetCreateRequestBuilder, + AssetMetadataFormBuilder, + ], +}) +export class AssetEditDialogComponent implements OnDestroy { + loading = false; + + methods = DATA_SOURCE_HTTP_METHODS; + + constructor( + private edcApiService: EdcApiService, + private assetService: AssetService, + public form: AssetEditDialogForm, + public validationMessages: ValidationMessages, + private assetEntryBuilder: AssetCreateRequestBuilder, + private notificationService: NotificationService, + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) private data: AssetEditDialogData, + ) { + this.form.reset(this.data.initialFormValue); + } + + onSave() { + const formValue = this.form.value; + + // Workaround around disabled controls not being included in the form value + if (formValue.mode !== 'CREATE') { + formValue.metadata!.id = this.form.metadata.controls.id.getRawValue(); + } + + this.form.all.disable(); + this.loading = true; + this._saveRequest(formValue) + .pipe( + // Save Asset + takeUntil(this.ngOnDestroy$), + tap(() => { + this.notificationService.showInfo('Successfully saved asset'); + }), + catchError((error) => { + console.error('Failed saving asset!', error); + this.notificationService.showError('Failed saving asset!'); + this.form.all.enable(); + return EMPTY; + }), + switchMap(() => this.assetService.fetchAssets()), + map( + (assets): AssetEditDialogResult => ({ + refreshedList: assets, + asset: assets?.find( + (it) => it.assetId === this.form.value.metadata?.id, + )!, + }), + ), + finalize(() => { + this.loading = false; + }), + ) + .subscribe({ + next: (result: AssetEditDialogResult) => this.close(result), + error: (error) => { + console.error('Failed refreshing asset list!', error); + this.notificationService.showError('Failed refreshing asset list!'); + }, + }); + } + + private _saveRequest( + formValue: AssetEditorDialogFormValue, + ): Observable { + const mode = formValue.mode; + + if (mode === 'CREATE') { + const createRequest = + this.assetEntryBuilder.buildAssetCreateRequest(formValue); + return this.edcApiService.createAsset(createRequest); + } + + if (mode === 'EDIT_METADATA') { + const assetId = formValue.metadata?.id!; + const editRequest = + this.assetEntryBuilder.buildEditMetadataRequest(formValue); + return this.edcApiService.editAssetMetadata(assetId, editRequest); + } + + throw new Error(`Unsupported mode: ${mode}`); + } + + private close(params: AssetEditDialogResult) { + this.dialogRef.close(params); + } + + ngOnDestroy$ = new Subject(); + + ngOnDestroy(): void { + this.ngOnDestroy$.next(null); + this.ngOnDestroy$.complete(); + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.service.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.service.ts new file mode 100644 index 000000000..bd7875203 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/asset-edit-dialog.service.ts @@ -0,0 +1,45 @@ +import {Injectable} from '@angular/core'; +import {MatDialog} from '@angular/material/dialog'; +import {NEVER, Observable} from 'rxjs'; +import {UiAssetMapped} from '../../../../core/services/models/ui-asset-mapped'; +import {showDialogUntil} from '../../../../core/utils/mat-dialog-utils'; +import {AssetEditDialogData} from './asset-edit-dialog-data'; +import {AssetEditDialogResult} from './asset-edit-dialog-result'; +import {AssetEditDialogComponent} from './asset-edit-dialog.component'; +import {AssetEditDialogFormMapper} from './form/asset-edit-dialog-form-mapper'; + +@Injectable() +export class AssetEditDialogService { + constructor( + private dialog: MatDialog, + private assetEditDialogFormMapper: AssetEditDialogFormMapper, + ) {} + + showCreateDialog( + until$: Observable = NEVER, + ): Observable { + const initialFormValue = this.assetEditDialogFormMapper.forCreate(); + return this._open({initialFormValue}, until$); + } + + showEditDialog( + asset: UiAssetMapped, + until$: Observable = NEVER, + ): Observable { + const initialFormValue = + this.assetEditDialogFormMapper.forEditMetadata(asset); + return this._open({initialFormValue}, until$); + } + + private _open( + data: AssetEditDialogData, + until$: Observable, + ): Observable { + return showDialogUntil( + this.dialog, + AssetEditDialogComponent, + {data}, + until$, + ); + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/assets-id-validator-builder.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/assets-id-validator-builder.ts similarity index 100% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/assets-id-validator-builder.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/assets-id-validator-builder.ts diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-advanced-form-builder.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-advanced-form-builder.ts new file mode 100644 index 000000000..fb6de4c2f --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-advanced-form-builder.ts @@ -0,0 +1,21 @@ +import {Injectable} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {AssetAdvancedFormModel} from './model/asset-advanced-form-model'; +import {AssetEditorDialogFormValue} from './model/asset-editor-dialog-form-model'; + +@Injectable() +export class AssetAdvancedFormBuilder { + constructor(private formBuilder: FormBuilder) {} + + buildFormGroup( + initial: AssetEditorDialogFormValue['advanced'], + ): FormGroup { + return this.formBuilder.nonNullable.group({ + dataModel: initial?.dataModel!, + dataCategory: [initial?.dataCategory || null, Validators.required], + dataSubcategory: initial?.dataSubcategory || null, + transportMode: initial?.transportMode || null, + geoReferenceMethod: initial?.geoReferenceMethod!, + }); + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-builder.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-datasource-form-builder.ts similarity index 50% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-builder.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-datasource-form-builder.ts index 10e1f1cf0..4a75e3a34 100644 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-builder.ts +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-datasource-form-builder.ts @@ -1,47 +1,67 @@ import {Injectable} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {DataAddressType} from '../../../../../component-library/data-address/data-address-type-select/data-address-type'; import {switchDisabledControls} from '../../../../../core/utils/form-group-utils'; import {jsonValidator} from '../../../../../core/validators/json-validator'; import {urlValidator} from '../../../../../core/validators/url-validator'; import { AssetDatasourceFormModel, AssetDatasourceFormValue, -} from './asset-datasource-form-model'; -import {HttpDatasourceAuthHeaderType} from './http-datasource-auth-header-type'; -import {HttpDatasourceHeaderFormModel} from './http-datasource-header-form-model'; -import {HttpDatasourceQueryParamFormModel} from './http-datasource-query-param-form-model'; +} from './model/asset-datasource-form-model'; +import { + HttpDatasourceHeaderFormModel, + HttpDatasourceHeaderFormValue, +} from './model/http-datasource-header-form-model'; +import { + HttpDatasourceQueryParamFormModel, + HttpDatasourceQueryParamFormValue, +} from './model/http-datasource-query-param-form-model'; @Injectable() export class AssetDatasourceFormBuilder { constructor(private formBuilder: FormBuilder) {} - buildFormGroup(): FormGroup { + buildFormGroup( + initial: AssetDatasourceFormValue, + ): FormGroup { const datasource: FormGroup = this.formBuilder.nonNullable.group({ - dataAddressType: 'Http' as DataAddressType, - dataDestination: ['', [Validators.required, jsonValidator]], + dataAddressType: initial?.dataAddressType!, + dataDestination: [ + initial?.dataDestination!, + [Validators.required, jsonValidator], + ], // Http Datasource Fields - httpUrl: ['', [Validators.required, urlValidator]], - httpMethod: ['GET', Validators.required], - - httpAuthHeaderType: ['None' as HttpDatasourceAuthHeaderType], - httpAuthHeaderName: ['', Validators.required], - httpAuthHeaderValue: ['', Validators.required], - httpAuthHeaderSecretName: ['', Validators.required], + httpUrl: [initial?.httpUrl!, [Validators.required, urlValidator]], + httpMethod: [initial?.httpMethod!, Validators.required], + + httpAuthHeaderType: [initial?.httpAuthHeaderType!], + httpAuthHeaderName: [initial?.httpAuthHeaderName!, Validators.required], + httpAuthHeaderValue: [ + initial?.httpAuthHeaderValue!, + Validators.required, + ], + httpAuthHeaderSecretName: [ + initial?.httpAuthHeaderSecretName!, + Validators.required, + ], httpQueryParams: this.formBuilder.array( - new Array>(), + initial?.httpQueryParams?.map( + (param: HttpDatasourceQueryParamFormValue) => + this.buildQueryParamFormGroup(param), + ) ?? [], ), - httpDefaultPath: [''], - httpProxyMethod: [false], - httpProxyPath: [false], - httpProxyQueryParams: [false], - httpProxyBody: [false], + httpDefaultPath: [initial?.httpDefaultPath!], + httpProxyMethod: [initial?.httpProxyMethod!], + httpProxyPath: [initial?.httpProxyPath!], + httpProxyQueryParams: [initial?.httpProxyQueryParams!], + httpProxyBody: [initial?.httpProxyBody!], httpHeaders: this.formBuilder.array( - new Array>(), + initial?.httpHeaders?.map((header: HttpDatasourceHeaderFormValue) => + this.buildHeaderFormGroup(header), + ) ?? [], ), }); @@ -87,17 +107,21 @@ export class AssetDatasourceFormBuilder { return datasource; } - buildHeaderFormGroup(): FormGroup { + buildHeaderFormGroup( + initial: HttpDatasourceHeaderFormValue, + ): FormGroup { return this.formBuilder.nonNullable.group({ - headerName: ['', Validators.required], - headerValue: ['', Validators.required], + headerName: [initial.headerName!, Validators.required], + headerValue: [initial.headerValue!, Validators.required], }); } - buildQueryParamFormGroup(): FormGroup { + buildQueryParamFormGroup( + initial: HttpDatasourceQueryParamFormValue, + ): FormGroup { return this.formBuilder.nonNullable.group({ - paramName: ['', Validators.required], - paramValue: [''], + paramName: [initial.paramName!, Validators.required], + paramValue: [initial.paramValue!], }); } } diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form-mapper.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form-mapper.ts new file mode 100644 index 000000000..ab0efd433 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form-mapper.ts @@ -0,0 +1,83 @@ +import {Injectable} from '@angular/core'; +import {UiAssetMapped} from '../../../../../core/services/models/ui-asset-mapped'; +import {LanguageSelectItemService} from '../../language-select/language-select-item.service'; +import {AssetEditorDialogFormValue} from './model/asset-editor-dialog-form-model'; + +/** + * Handles AngularForms for AssetEditorDialog + */ +@Injectable() +export class AssetEditDialogFormMapper { + constructor(private languageSelectItemService: LanguageSelectItemService) {} + + forCreate(): AssetEditorDialogFormValue { + return { + mode: 'CREATE', + metadata: { + id: '', + title: '', + version: '', + contentType: '', + description: '', + keywords: [], + language: this.languageSelectItemService.english(), + publisher: '', + standardLicense: '', + endpointDocumentation: '', + }, + advanced: { + dataModel: '', + dataCategory: null, + dataSubcategory: null, + transportMode: null, + geoReferenceMethod: '', + }, + datasource: { + dataAddressType: 'Http', + dataDestination: '', + + httpUrl: '', + httpMethod: 'GET', + httpAuthHeaderType: 'None', + httpAuthHeaderName: '', + httpAuthHeaderValue: '', + httpAuthHeaderSecretName: '', + httpQueryParams: [], + + httpDefaultPath: '', + httpProxyMethod: false, + httpProxyPath: false, + httpProxyQueryParams: false, + httpProxyBody: false, + + httpHeaders: [], + }, + }; + } + + forEditMetadata(asset: UiAssetMapped): AssetEditorDialogFormValue { + return { + mode: 'EDIT_METADATA', + metadata: { + id: asset.assetId, + title: asset.title, + version: asset.version, + contentType: asset.mediaType, + description: asset.description, + keywords: asset.keywords, + language: asset.language, + publisher: asset.publisherHomepage, + standardLicense: asset.licenseUrl, + endpointDocumentation: asset.landingPageUrl, + }, + advanced: { + dataModel: asset.dataModel, + dataCategory: asset.dataCategory, + dataSubcategory: asset.dataSubcategory, + transportMode: asset.transportMode, + geoReferenceMethod: asset.geoReferenceMethod, + }, + datasource: this.forCreate().datasource, + }; + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form.ts new file mode 100644 index 000000000..eeb134a34 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-edit-dialog-form.ts @@ -0,0 +1,150 @@ +import {Injectable} from '@angular/core'; +import {FormBuilder, FormGroup} from '@angular/forms'; +import {DataAddressType} from '../../../../../component-library/data-address/data-address-type-select/data-address-type'; +import {ActiveFeatureSet} from '../../../../../core/config/active-feature-set'; +import {DataCategorySelectItem} from '../../data-category-select/data-category-select-item'; +import {AssetEditDialogMode} from '../asset-edit-dialog-mode'; +import {AssetAdvancedFormBuilder} from './asset-advanced-form-builder'; +import {AssetDatasourceFormBuilder} from './asset-datasource-form-builder'; +import {AssetMetadataFormBuilder} from './asset-metadata-form-builder'; +import {AssetAdvancedFormModel} from './model/asset-advanced-form-model'; +import {AssetDatasourceFormModel} from './model/asset-datasource-form-model'; +import { + AssetEditorDialogFormModel, + AssetEditorDialogFormValue, +} from './model/asset-editor-dialog-form-model'; +import {AssetMetadataFormModel} from './model/asset-metadata-form-model'; + +/** + * Handles AngularForms for AssetEditorDialog + */ +@Injectable() +export class AssetEditDialogForm { + all!: FormGroup; + + /** + * FormGroup for stepper step "Metadata" + */ + metadata!: AssetEditorDialogFormModel['metadata']; + + /** + * FormGroup for stepper step "Advanced" + */ + advanced!: AssetEditorDialogFormModel['advanced']; + + /** + * FormGroup for stepper step "Data Source" + */ + datasource!: AssetEditorDialogFormModel['datasource']; + + /** + * Quick access to selected data address type + */ + get dataAddressType(): DataAddressType | null { + return this.datasource!.controls.dataAddressType.value; + } + + /** + * Quick access to selected data category + */ + get dataCategory(): DataCategorySelectItem | null { + return this.advanced!!.controls.dataCategory.value; + } + + /** + * Quick Access to Dialog Mode + */ + get mode(): AssetEditDialogMode { + return this.all.controls.mode.value; + } + + /** + * Quick access to full value + */ + get value(): AssetEditorDialogFormValue { + return this.all.value; + } + + get proxyMethod(): boolean { + return this.datasource!.controls.httpProxyMethod.value; + } + + get proxyPath(): boolean { + return this.datasource!.controls.httpProxyPath.value; + } + + get proxyQueryParams(): boolean { + return this.datasource!.controls.httpProxyQueryParams.value; + } + + constructor( + private formBuilder: FormBuilder, + private activeFeatureSet: ActiveFeatureSet, + private assetMetadataFormBuilder: AssetMetadataFormBuilder, + private assetAdvancedFormBuilder: AssetAdvancedFormBuilder, + private assetDatasourceFormBuilder: AssetDatasourceFormBuilder, + ) {} + + reset(initial: AssetEditorDialogFormValue) { + this.all = this.buildFormGroup(initial); + this.metadata = this.all.controls.metadata; + this.advanced = this.all.controls.advanced; + this.datasource = this.all.controls.datasource; + } + + buildFormGroup( + initial: AssetEditorDialogFormValue, + ): FormGroup { + const metadata: FormGroup = + this.assetMetadataFormBuilder.buildFormGroup( + initial.metadata!, + initial.mode!, + ); + + const formGroup: FormGroup = + this.formBuilder.nonNullable.group({ + mode: [initial.mode as AssetEditDialogMode], + metadata, + }); + + if (this.activeFeatureSet.hasMdsFields()) { + const advanced: FormGroup = + this.assetAdvancedFormBuilder.buildFormGroup(initial.advanced); + formGroup.addControl('advanced', advanced); + } + + if (initial.mode !== 'EDIT_METADATA') { + const datasource: FormGroup = + this.assetDatasourceFormBuilder.buildFormGroup(initial.datasource!); + formGroup.addControl('datasource', datasource); + } + + return formGroup; + } + + onHttpHeadersAddClick() { + this.datasource!.controls.httpHeaders.push( + this.assetDatasourceFormBuilder.buildHeaderFormGroup({ + headerName: '', + headerValue: '', + }), + ); + } + + onHttpHeadersRemoveClick(index: number) { + this.datasource!.controls.httpHeaders.removeAt(index); + } + + onHttpQueryParamsAddClick() { + this.datasource!.controls.httpQueryParams.push( + this.assetDatasourceFormBuilder.buildQueryParamFormGroup({ + paramName: '', + paramValue: '', + }), + ); + } + + onHttpQueryParamsRemoveClick(index: number) { + this.datasource!.controls.httpQueryParams.removeAt(index); + } +} diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-builder.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-metadata-form-builder.ts similarity index 67% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-builder.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-metadata-form-builder.ts index c8c8af5ba..573355703 100644 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-builder.ts +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/asset-metadata-form-builder.ts @@ -5,45 +5,52 @@ import {map} from 'rxjs/operators'; import {value$} from '../../../../../core/utils/form-group-utils'; import {noWhitespacesOrColonsValidator} from '../../../../../core/validators/no-whitespaces-or-colons-validator'; import {urlValidator} from '../../../../../core/validators/url-validator'; -import {LanguageSelectItem} from '../../language-select/language-select-item'; -import {LanguageSelectItemService} from '../../language-select/language-select-item.service'; +import {AssetEditDialogMode} from '../asset-edit-dialog-mode'; import {AssetsIdValidatorBuilder} from '../assets-id-validator-builder'; -import {AssetMetadataFormModel} from './asset-metadata-form-model'; +import { + AssetMetadataFormModel, + AssetMetadataFormValue, +} from './model/asset-metadata-form-model'; @Injectable() export class AssetMetadataFormBuilder { constructor( private formBuilder: FormBuilder, - private languageSelectItemService: LanguageSelectItemService, private assetsIdValidatorBuilder: AssetsIdValidatorBuilder, ) {} - buildFormGroup(): FormGroup { + buildFormGroup( + initial: AssetMetadataFormValue, + mode: AssetEditDialogMode, + ): FormGroup { const metadata: FormGroup = this.formBuilder.nonNullable.group({ id: [ - '', + initial?.id!, [Validators.required, noWhitespacesOrColonsValidator], [this.assetsIdValidatorBuilder.assetIdDoesNotExistsValidator()], ], - title: ['', Validators.required], - version: '', - contentType: '', - description: '', - keywords: [new Array()], - language: - this.languageSelectItemService.english() as LanguageSelectItem | null, - publisher: ['', urlValidator], - standardLicense: ['', urlValidator], - endpointDocumentation: ['', urlValidator], + title: [initial?.title!, Validators.required], + version: [initial?.version!], + contentType: [initial?.contentType!], + description: [initial?.description!], + keywords: [initial?.keywords!], + language: [initial?.language ?? null], + publisher: [initial?.publisher!, urlValidator], + standardLicense: [initial?.standardLicense!, urlValidator], + endpointDocumentation: [initial?.endpointDocumentation!, urlValidator], }); // generate id from name and version(if available) - this.initIdGeneration( - metadata.controls.id, - metadata.controls.title, - metadata.controls.version, - ); + if (mode === 'CREATE') { + this.initIdGeneration( + metadata.controls.id, + metadata.controls.title, + metadata.controls.version, + ); + } else { + metadata.controls.id.disable(); + } return metadata; } diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/http-methods.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/http-methods.ts new file mode 100644 index 000000000..ffd3b35d2 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/http-methods.ts @@ -0,0 +1,12 @@ +export const DATA_SOURCE_HTTP_METHODS = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS', + 'HEAD', +]; +export const DATA_SINK_HTTP_METHODS = DATA_SOURCE_HTTP_METHODS.filter( + (it) => it !== 'GET', +); diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-advanced-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-advanced-form-model.ts new file mode 100644 index 000000000..7576831e8 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-advanced-form-model.ts @@ -0,0 +1,21 @@ +import {FormControl, ɵFormGroupValue} from '@angular/forms'; +import {DataCategorySelectItem} from '../../../data-category-select/data-category-select-item'; +import {DataSubcategorySelectItem} from '../../../data-subcategory-select/data-subcategory-select-item'; +import {TransportModeSelectItem} from '../../../transport-mode-select/transport-mode-select-item'; + +/** + * Form Model for AssetEditorDialog > Advanced + * (MDS Properties) + */ +export interface AssetAdvancedFormModel { + dataCategory: FormControl; + dataSubcategory: FormControl; + dataModel: FormControl; + geoReferenceMethod: FormControl; + transportMode: FormControl; +} + +/** + * Form Value for AssetEditorDialog > Advanced + */ +export type AssetAdvancedFormValue = ɵFormGroupValue; diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-datasource-form-model.ts similarity index 91% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-model.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-datasource-form-model.ts index 361ecda63..9cf3e6abe 100644 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-datasource-form-model.ts +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-datasource-form-model.ts @@ -4,7 +4,7 @@ import { FormGroup, ɵFormGroupValue, } from '@angular/forms'; -import {DataAddressType} from '../../../../../component-library/data-address/data-address-type-select/data-address-type'; +import {DataAddressType} from '../../../../../../component-library/data-address/data-address-type-select/data-address-type'; import {HttpDatasourceAuthHeaderType} from './http-datasource-auth-header-type'; import {HttpDatasourceHeaderFormModel} from './http-datasource-header-form-model'; import {HttpDatasourceQueryParamFormModel} from './http-datasource-query-param-form-model'; diff --git a/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-editor-dialog-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-editor-dialog-form-model.ts new file mode 100644 index 000000000..cbc11c372 --- /dev/null +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-editor-dialog-form-model.ts @@ -0,0 +1,21 @@ +import {FormControl, FormGroup, ɵFormGroupValue} from '@angular/forms'; +import {AssetEditDialogMode} from '../../asset-edit-dialog-mode'; +import {AssetAdvancedFormModel} from './asset-advanced-form-model'; +import {AssetDatasourceFormModel} from './asset-datasource-form-model'; +import {AssetMetadataFormModel} from './asset-metadata-form-model'; + +/** + * Form Model for AssetEditorDialog + */ +export interface AssetEditorDialogFormModel { + mode: FormControl; + metadata: FormGroup; + advanced?: FormGroup; + datasource?: FormGroup; +} + +/** + * Form Value for AssetEditorDialog + */ +export type AssetEditorDialogFormValue = + ɵFormGroupValue; diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-metadata-form-model.ts similarity index 62% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-model.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-metadata-form-model.ts index e335d96d0..cef83fbf2 100644 --- a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/asset-metadata-form-model.ts +++ b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/asset-metadata-form-model.ts @@ -1,5 +1,5 @@ -import {FormControl} from '@angular/forms'; -import {LanguageSelectItem} from '../../language-select/language-select-item'; +import {FormControl, ɵFormGroupValue} from '@angular/forms'; +import {LanguageSelectItem} from '../../../language-select/language-select-item'; /** * Form Model for AssetEditorDialog > Metadata @@ -16,3 +16,8 @@ export interface AssetMetadataFormModel { standardLicense: FormControl; endpointDocumentation: FormControl; } + +/** + * Form Value for AssetEditorDialog > Metadata + */ +export type AssetMetadataFormValue = ɵFormGroupValue; diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-auth-header-type.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-auth-header-type.ts similarity index 100% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-auth-header-type.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-auth-header-type.ts diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-header-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-header-form-model.ts similarity index 100% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-header-form-model.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-header-form-model.ts diff --git a/src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-query-param-form-model.ts b/src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-query-param-form-model.ts similarity index 100% rename from src/app/routes/connector-ui/asset-page/asset-create-dialog/model/http-datasource-query-param-form-model.ts rename to src/app/routes/connector-ui/asset-page/asset-edit-dialog/form/model/http-datasource-query-param-form-model.ts diff --git a/src/app/routes/connector-ui/asset-page/asset-page.module.ts b/src/app/routes/connector-ui/asset-page/asset-page.module.ts index 90a4bfb63..371521f0c 100644 --- a/src/app/routes/connector-ui/asset-page/asset-page.module.ts +++ b/src/app/routes/connector-ui/asset-page/asset-page.module.ts @@ -24,7 +24,9 @@ import {DataAddressModule} from '../../../component-library/data-address/data-ad import {PipesAndDirectivesModule} from '../../../component-library/pipes-and-directives/pipes-and-directives.module'; import {UiElementsModule} from '../../../component-library/ui-elements/ui-elements.module'; import {AssetCardsComponent} from './asset-cards/asset-cards.component'; -import {AssetCreateDialogComponent} from './asset-create-dialog/asset-create-dialog.component'; +import {AssetEditDialogComponent} from './asset-edit-dialog/asset-edit-dialog.component'; +import {AssetEditDialogService} from './asset-edit-dialog/asset-edit-dialog.service'; +import {AssetEditDialogFormMapper} from './asset-edit-dialog/form/asset-edit-dialog-form-mapper'; import {AssetPageComponent} from './asset-page/asset-page.component'; import {DataCategorySelectComponent} from './data-category-select/data-category-select.component'; import {DataSubcategoryItemsPipe} from './data-subcategory-select/data-subcategory-items.pipe'; @@ -68,7 +70,7 @@ import {TransportModeSelectComponent} from './transport-mode-select/transport-mo ], declarations: [ AssetCardsComponent, - AssetCreateDialogComponent, + AssetEditDialogComponent, AssetPageComponent, DataCategorySelectComponent, DataSubcategorySelectComponent, @@ -78,5 +80,6 @@ import {TransportModeSelectComponent} from './transport-mode-select/transport-mo TransportModeSelectComponent, ], exports: [AssetPageComponent], + providers: [AssetEditDialogService, AssetEditDialogFormMapper], }) export class AssetPageModule {} diff --git a/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts b/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts index 9781f0478..3b7d4a961 100644 --- a/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts +++ b/src/app/routes/connector-ui/asset-page/asset-page/asset-page.component.ts @@ -1,14 +1,14 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; -import {MatDialog} from '@angular/material/dialog'; -import {BehaviorSubject, Subject} from 'rxjs'; +import {BehaviorSubject, Subject, merge} from 'rxjs'; import {filter, map, switchMap} from 'rxjs/operators'; +import {OnAssetEditClickFn} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data'; import {AssetDetailDialogDataService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog-data.service'; import {AssetDetailDialogService} from '../../../../component-library/catalog/asset-detail-dialog/asset-detail-dialog.service'; import {AssetService} from '../../../../core/services/asset.service'; import {Fetched} from '../../../../core/services/models/fetched'; import {UiAssetMapped} from '../../../../core/services/models/ui-asset-mapped'; -import {AssetCreateDialogResult} from '../asset-create-dialog/asset-create-dialog-result'; -import {AssetCreateDialogComponent} from '../asset-create-dialog/asset-create-dialog.component'; +import {filterNotNull} from '../../../../core/utils/rxjs-utils'; +import {AssetEditDialogService} from '../asset-edit-dialog/asset-edit-dialog.service'; export interface AssetList { filteredAssets: UiAssetMapped[]; @@ -22,34 +22,36 @@ export interface AssetList { }) export class AssetPageComponent implements OnInit, OnDestroy { assetList: Fetched = Fetched.empty(); + assetListUpdater$ = new Subject(); searchText = ''; private fetch$ = new BehaviorSubject(null); constructor( private assetServiceMapped: AssetService, + private assetEditDialogService: AssetEditDialogService, private assetDetailDialogDataService: AssetDetailDialogDataService, private assetDetailDialogService: AssetDetailDialogService, - private dialog: MatDialog, ) {} ngOnInit(): void { - this.fetch$ + merge( + this.fetch$.pipe( + switchMap(() => this.assetServiceMapped.fetchAssets()), + Fetched.wrap({ + failureMessage: 'Failed fetching asset list.', + }), + ), + this.assetListUpdater$.pipe(map(Fetched.ready)), + ) .pipe( - switchMap(() => { - return this.assetServiceMapped.fetchAssets().pipe( - map( - (assets): AssetList => ({ - filteredAssets: assets.filter((asset) => - asset.title?.includes(this.searchText), - ), - numTotalAssets: assets.length, - }), + Fetched.map( + (assets): AssetList => ({ + filteredAssets: assets.filter((asset) => + asset.title?.includes(this.searchText), ), - Fetched.wrap({ - failureMessage: 'Failed fetching asset list.', - }), - ); - }), + numTotalAssets: assets.length, + }), + ), ) .subscribe((assetList) => (this.assetList = assetList)); } @@ -59,16 +61,32 @@ export class AssetPageComponent implements OnInit, OnDestroy { } onCreate() { - const ref = this.dialog.open(AssetCreateDialogComponent); - ref.afterClosed().subscribe((result: AssetCreateDialogResult) => { - if (result?.refreshList) { - this.refresh(); - } - }); + this.assetEditDialogService + .showCreateDialog(this.ngOnDestroy$) + .subscribe((result) => { + if (result?.refreshedList) { + this.assetListUpdater$.next(result.refreshedList); + } + }); } onAssetClick(asset: UiAssetMapped) { - const data = this.assetDetailDialogDataService.assetDetails(asset, true); + const onAssetEditClick: OnAssetEditClickFn = (asset, onAssetUpdated) => { + this.assetEditDialogService + .showEditDialog(asset, this.ngOnDestroy$) + .pipe(filterNotNull()) + .subscribe((result) => { + this.assetListUpdater$.next(result.refreshedList); + onAssetUpdated(buildDialogData(result.asset)); + }); + }; + + const buildDialogData = (asset: UiAssetMapped) => + this.assetDetailDialogDataService.assetDetailsEditable(asset, { + onAssetEditClick, + }); + + const data = buildDialogData(asset); this.assetDetailDialogService .open(data, this.ngOnDestroy$) .pipe(filter((it) => !!it?.refreshList)) @@ -80,7 +98,6 @@ export class AssetPageComponent implements OnInit, OnDestroy { } ngOnDestroy$ = new Subject(); - ngOnDestroy() { this.ngOnDestroy$.next(null); this.ngOnDestroy$.complete(); diff --git a/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-fetch-detail-dialog/catalog-browser-fetch-detail-dialog.component.html b/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-fetch-detail-dialog/catalog-browser-fetch-detail-dialog.component.html index 39a4602b0..d4298a605 100644 --- a/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-fetch-detail-dialog/catalog-browser-fetch-detail-dialog.component.html +++ b/src/app/routes/connector-ui/catalog-browser-page/catalog-browser-fetch-detail-dialog/catalog-browser-fetch-detail-dialog.component.html @@ -23,7 +23,7 @@
- + diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-cards.component.html b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-cards.component.html index 1f99738f2..3003e2655 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-cards.component.html +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-cards/contract-agreement-cards.component.html @@ -52,7 +52,7 @@
{{ card.counterPartyAddress }} diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model.ts index 84cc72c72..40985f508 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model.ts +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form-model.ts @@ -5,7 +5,7 @@ import { ɵFormGroupValue, } from '@angular/forms'; import {DataAddressType} from '../../../../component-library/data-address/data-address-type-select/data-address-type'; -import {HttpDatasourceQueryParamFormModel} from '../../asset-page/asset-create-dialog/model/http-datasource-query-param-form-model'; +import {HttpDatasourceQueryParamFormModel} from '../../asset-page/asset-edit-dialog/form/model/http-datasource-query-param-form-model'; import {HttpDatasinkAuthHeaderType} from './http-datasink-auth-header-type'; import {HttpDatasinkHeaderFormModel} from './http-datasink-header-form-model'; diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form.ts index 4dcd91335..22797443c 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form.ts +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog-form.ts @@ -4,8 +4,8 @@ import {DataAddressType} from '../../../../component-library/data-address/data-a import {switchDisabledControls} from '../../../../core/utils/form-group-utils'; import {jsonValidator} from '../../../../core/validators/json-validator'; import {urlValidator} from '../../../../core/validators/url-validator'; -import {HttpDatasourceAuthHeaderType} from '../../asset-page/asset-create-dialog/model/http-datasource-auth-header-type'; -import {HttpDatasourceQueryParamFormModel} from '../../asset-page/asset-create-dialog/model/http-datasource-query-param-form-model'; +import {HttpDatasourceAuthHeaderType} from '../../asset-page/asset-edit-dialog/form/model/http-datasource-auth-header-type'; +import {HttpDatasourceQueryParamFormModel} from '../../asset-page/asset-edit-dialog/form/model/http-datasource-query-param-form-model'; import { ContractAgreementTransferDialogFormModel, ContractAgreementTransferDialogFormValue, diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.html b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.html index b74b31e0d..e25bc4173 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.html +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.html @@ -356,7 +356,11 @@

Initiate Transfer

- diff --git a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.ts b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.ts index 2d0e40949..9e9eccd2d 100644 --- a/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.ts +++ b/src/app/routes/connector-ui/contract-agreement-page/contract-agreement-transfer-dialog/contract-agreement-transfer-dialog.component.ts @@ -12,6 +12,10 @@ import {DataAddressMapper} from '../../../../core/services/data-address-mapper'; import {HttpRequestParamsMapper} from '../../../../core/services/http-params-mapper.service'; import {NotificationService} from '../../../../core/services/notification.service'; import {ValidationMessages} from '../../../../core/validators/validation-messages'; +import { + DATA_SINK_HTTP_METHODS, + DATA_SOURCE_HTTP_METHODS, +} from '../../asset-page/asset-edit-dialog/form/http-methods'; import {ContractAgreementTransferDialogData} from './contract-agreement-transfer-dialog-data'; import {ContractAgreementTransferDialogForm} from './contract-agreement-transfer-dialog-form'; import {ContractAgreementTransferDialogFormValue} from './contract-agreement-transfer-dialog-form-model'; @@ -25,8 +29,8 @@ import {ContractAgreementTransferDialogResult} from './contract-agreement-transf export class ContractAgreementTransferDialogComponent implements OnDestroy { loading = false; - dataSinkMethods = ['POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; - dataSourceMethods = ['GET', ...this.dataSinkMethods]; + dataSinkMethods = DATA_SINK_HTTP_METHODS; + dataSourceMethods = DATA_SOURCE_HTTP_METHODS; get proxyMethod(): boolean { return ( diff --git a/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts b/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts index 3ce9ddbe5..02059d090 100644 --- a/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts +++ b/src/app/routes/connector-ui/contract-definition-page/asset-select/asset-select.component.ts @@ -25,7 +25,7 @@ export class AssetSelectComponent implements OnDestroy { ) {} onAssetClick(asset: UiAssetMapped) { - const data = this.assetDetailDialogDataService.assetDetails(asset, false); + const data = this.assetDetailDialogDataService.assetDetailsReadonly(asset); this.assetDetailDialogService.open(data, this.ngOnDestroy$); } diff --git a/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts b/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts index 6185f27b9..5494059e6 100644 --- a/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts +++ b/src/app/routes/connector-ui/contract-definition-page/contract-definition-cards/contract-definition-cards.component.ts @@ -58,7 +58,7 @@ export class ContractDefinitionCardsComponent implements OnDestroy { } onAssetClick(asset: UiAssetMapped) { - const data = this.assetDetailDialogDataService.assetDetails(asset, false); + const data = this.assetDetailDialogDataService.assetDetailsReadonly(asset); this.assetDetailDialogService .open(data, this.ngOnDestroy$) .pipe(filter((it) => !!it?.refreshList)) @@ -72,9 +72,9 @@ export class ContractDefinitionCardsComponent implements OnDestroy { subtitle: 'Contract Definition', icon: 'policy', objectForJson: card.detailJsonObj, - actionButton: { + toolbarButton: { text: 'Delete', - color: 'warn', + icon: 'delete', confirmation: ConfirmDialogModel.forDelete( 'contract definition', card.id, diff --git a/src/app/routes/connector-ui/contract-definition-page/contract-definition-editor-dialog/contract-definition-editor-dialog.component.html b/src/app/routes/connector-ui/contract-definition-page/contract-definition-editor-dialog/contract-definition-editor-dialog.component.html index cb82a2378..a7c43daa4 100644 --- a/src/app/routes/connector-ui/contract-definition-page/contract-definition-editor-dialog/contract-definition-editor-dialog.component.html +++ b/src/app/routes/connector-ui/contract-definition-page/contract-definition-editor-dialog/contract-definition-editor-dialog.component.html @@ -36,7 +36,9 @@

Create New Contract Definition

- +
diff --git a/src/app/routes/connector-ui/policy-definition-page/new-policy-dialog/new-policy-dialog.component.html b/src/app/routes/connector-ui/policy-definition-page/new-policy-dialog/new-policy-dialog.component.html index 1a5d08fcb..892946e91 100644 --- a/src/app/routes/connector-ui/policy-definition-page/new-policy-dialog/new-policy-dialog.component.html +++ b/src/app/routes/connector-ui/policy-definition-page/new-policy-dialog/new-policy-dialog.component.html @@ -61,7 +61,9 @@

Create New Policy

- +