diff --git a/src/app/component-library/edit-asset-form/edit-asset-form-label/edit-asset-form-label.component.ts b/src/app/component-library/edit-asset-form/edit-asset-form-label/edit-asset-form-label.component.ts index 71eccf219..c469a05f1 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form-label/edit-asset-form-label.component.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form-label/edit-asset-form-label.component.ts @@ -1,5 +1,5 @@ import {Component, Input} from '@angular/core'; -import {FormControl, Validators} from '@angular/forms'; +import {AbstractControl, Validators} from '@angular/forms'; @Component({ selector: 'edit-asset-form-label', @@ -8,7 +8,7 @@ import {FormControl, Validators} from '@angular/forms'; export class EditAssetFormLabelComponent { @Input() label!: string; @Input() htmlFor?: string; - @Input() ctrl?: FormControl; + @Input() ctrl?: AbstractControl; isRequired(): boolean { return this.ctrl?.hasValidator(Validators.required) || false; diff --git a/src/app/component-library/edit-asset-form/edit-asset-form.module.ts b/src/app/component-library/edit-asset-form/edit-asset-form.module.ts index 2f1bb2a37..cef836df3 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form.module.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form.module.ts @@ -23,6 +23,7 @@ import {MatTooltipModule} from '@angular/material/tooltip'; import {RouterModule} from '@angular/router'; import {CatalogModule} from '../catalog/catalog.module'; import {PipesAndDirectivesModule} from '../pipes-and-directives/pipes-and-directives.module'; +import {PolicyEditorModule} from '../policy-editor/policy-editor.module'; import {UiElementsModule} from '../ui-elements/ui-elements.module'; import {DataCategorySelectComponent} from './data-category-select/data-category-select.component'; import {DataSubcategoryItemsPipe} from './data-subcategory-select/data-subcategory-items.pipe'; @@ -67,6 +68,7 @@ import {TransportModeSelectComponent} from './transport-mode-select/transport-mo // EDC UI Modules CatalogModule, PipesAndDirectivesModule, + PolicyEditorModule, UiElementsModule, ], declarations: [ diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/assets-id-validator-builder.ts b/src/app/component-library/edit-asset-form/edit-asset-form/assets-id-validator-builder.ts deleted file mode 100644 index 15983dc7b..000000000 --- a/src/app/component-library/edit-asset-form/edit-asset-form/assets-id-validator-builder.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import { - AbstractControl, - AsyncValidatorFn, - ValidationErrors, -} from '@angular/forms'; -import {Observable} from 'rxjs'; -import {map} from 'rxjs/operators'; -import {AssetService} from 'src/app/core/services/asset.service'; - -@Injectable({ - providedIn: 'root', -}) -export class AssetsIdValidatorBuilder { - constructor(private assetServiceMapped: AssetService) {} - - assetIdDoesNotExistsValidator(): AsyncValidatorFn { - return (control: AbstractControl): Observable => { - return this.fetchAssetIds().pipe( - map((assetIds) => { - if (assetIds.has(control.value)) { - return {idAlreadyExists: true}; - } - return null; - }), - ); - }; - } - - private fetchAssetIds(): Observable> { - return this.assetServiceMapped - .fetchAssets() - .pipe(map((assets) => new Set(assets.map((asset) => asset.assetId)))); - } -} diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.html b/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.html index 086bcd8c3..82f9c2d2e 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.html +++ b/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.html @@ -475,14 +475,14 @@ type="text" placeholder="my-asset" [formControl]="ctrl" /> - + {{ validationMessages.invalidWhitespacesOrColonsMessage }} - + {{ validationMessages.invalidPrefix('ID', 'urn:artifact') }} - - {{ validationMessages.idExistsErrorMessage }} + + {{ ctrl.errors?.exists }} @@ -926,13 +926,56 @@ + + + +
+ + + + Publish unrestricted + + + Publish restricted + + + Do not publish + + +
+ + +
+ + +
+
+ diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.ts b/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.ts index 59477a806..2e8e396cf 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/edit-asset-form.component.ts @@ -1,12 +1,12 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; import {ValidationMessages} from 'src/app/core/validators/validation-messages'; +import {ExpressionFormHandler} from '../../policy-editor/editor/expression-form-handler'; import {EditAssetForm} from './form/edit-asset-form'; import {DATA_SOURCE_HTTP_METHODS} from './form/http-methods'; @Component({ selector: 'edit-asset-form', templateUrl: './edit-asset-form.component.html', - providers: [], }) export class EditAssetFormComponent { @Input() isLoading!: boolean; @@ -17,5 +17,6 @@ export class EditAssetFormComponent { constructor( public form: EditAssetForm, public validationMessages: ValidationMessages, + public expressionFormHandler: ExpressionFormHandler, ) {} } diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/asset-general-form-builder.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/asset-general-form-builder.ts index 9b0e3492a..3eaf5c591 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/form/asset-general-form-builder.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/asset-general-form-builder.ts @@ -5,7 +5,7 @@ import {map} from 'rxjs/operators'; import {ActiveFeatureSet} from 'src/app/core/config/active-feature-set'; import {value$} from 'src/app/core/utils/form-group-utils'; import {noWhitespacesOrColonsValidator} from 'src/app/core/validators/no-whitespaces-or-colons-validator'; -import {AssetsIdValidatorBuilder} from '../assets-id-validator-builder'; +import {EditAssetFormValidators} from './edit-asset-form-validators'; import {AssetEditDialogMode} from './model/asset-edit-dialog-mode'; import { AssetGeneralFormModel, @@ -16,8 +16,8 @@ import { export class AssetGeneralFormBuilder { constructor( private formBuilder: FormBuilder, - private assetsIdValidatorBuilder: AssetsIdValidatorBuilder, private activeFeatureSet: ActiveFeatureSet, + private editAssetFormValidators: EditAssetFormValidators, ) {} buildFormGroup( @@ -29,7 +29,7 @@ export class AssetGeneralFormBuilder { id: [ initial.id!, [Validators.required, noWhitespacesOrColonsValidator], - [this.assetsIdValidatorBuilder.assetIdDoesNotExistsValidator()], + this.editAssetFormValidators.isValidId(), ], name: [initial.name!, Validators.required], description: [initial.description!], @@ -78,6 +78,8 @@ export class AssetGeneralFormBuilder { .subscribe(([previousId, currentId]) => { if (!idCtrl.value || idCtrl.value === previousId) { idCtrl.setValue(currentId); + idCtrl.markAsTouched(); + idCtrl.updateValueAndValidity(); } }); } diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-initializer.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-initializer.ts index 1a5de70af..f9613b9ac 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-initializer.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-initializer.ts @@ -14,6 +14,7 @@ export class EditAssetFormInitializer { forCreate(): EditAssetFormValue { return { mode: 'CREATE', + publishMode: 'PUBLISH_UNRESTRICTED', general: { id: '', name: '', @@ -50,6 +51,7 @@ export class EditAssetFormInitializer { forEdit(asset: UiAssetMapped): EditAssetFormValue { return { mode: 'EDIT', + publishMode: 'DO_NOT_PUBLISH', general: { id: asset.assetId, name: asset.title, diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-validators.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-validators.ts new file mode 100644 index 000000000..d68297003 --- /dev/null +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-validators.ts @@ -0,0 +1,101 @@ +import {Injectable} from '@angular/core'; +import { + AbstractControl, + AsyncValidatorFn, + ValidationErrors, +} from '@angular/forms'; +import {Observable, combineLatest, of} from 'rxjs'; +import {catchError, map} from 'rxjs/operators'; +import {IdAvailabilityResponse} from '@sovity.de/edc-client'; +import {EdcApiService} from 'src/app/core/services/api/edc-api.service'; +import {ALWAYS_TRUE_POLICY_ID} from './model/always-true-policy-id'; +import {EditAssetFormValue} from './model/edit-asset-form-model'; + +/** + * Handles AngularForms for Edit Asset Form + */ +@Injectable({providedIn: 'root'}) +export class EditAssetFormValidators { + constructor(private edcApiService: EdcApiService) {} + + /** + * Use on asset control, reset asset control on publish mode changes, accesses parent form + */ + isValidId(): AsyncValidatorFn { + return (control: AbstractControl): Observable => { + const value = control?.parent?.parent?.value as EditAssetFormValue | null; + if (value?.mode !== 'CREATE') { + return of(null); + } + + const assetId = control.value! as string; + if (value.publishMode === 'PUBLISH_UNRESTRICTED') { + return combineLatest([ + this.assetIdExistsErrorMessage(assetId), + this.contractDefinitionIdErrorMessage(assetId), + this.policyIdExistsErrorMessage(ALWAYS_TRUE_POLICY_ID).pipe( + map((errorMessage) => + // We want to throw an error if always-true was not found + errorMessage ? null : 'Always True Policy does not exist.', + ), + ), + ]).pipe( + map((errorMessages) => this.buildValidationErrors(errorMessages)), + ); + } else if (value.publishMode === 'PUBLISH_RESTRICTED') { + return combineLatest([ + this.assetIdExistsErrorMessage(assetId), + this.contractDefinitionIdErrorMessage(assetId), + this.policyIdExistsErrorMessage(assetId), + ]).pipe( + map((errorMessages) => this.buildValidationErrors(errorMessages)), + ); + } else { + return this.assetIdExistsErrorMessage(assetId).pipe( + map((result) => this.buildValidationErrors([result])), + ); + } + + return of(null); + }; + } + + private assetIdExistsErrorMessage(id: string): Observable { + return this.edcApiService.isAssetIdAvailable(id).pipe( + catchError(() => of({id, available: false})), + map((it) => (it.available ? null : 'Asset already exists.')), + ); + } + + private contractDefinitionIdErrorMessage( + id: string, + ): Observable { + return this.edcApiService.isContractDefinitionIdAvailable(id).pipe( + catchError(() => of({id, available: false})), + map((it) => + it.available ? null : 'Contract Definition already exists.', + ), + ); + } + + private policyIdExistsErrorMessage(id: string): Observable { + return this.edcApiService.isPolicyIdAvailable(id).pipe( + catchError(() => of({id, available: false})), + map((it) => (it.available ? null : 'Policy already exists.')), + ); + } + + private buildValidationErrors( + errorMessages: (string | null)[], + ): ValidationErrors | null { + const errors = errorMessages.filter((it) => it); + if (!errors.length) { + return null; + } + + const message = + errors.length === 3 ? 'Data Offer already exists.' : errors.join(' '); + + return {exists: message}; + } +} diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form.ts index 6834e127c..8fe810fca 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form.ts @@ -1,6 +1,9 @@ import {Injectable} from '@angular/core'; import {FormBuilder, FormGroup} from '@angular/forms'; +import {delay} from 'rxjs/operators'; +import {ExpressionFormControls} from 'src/app/component-library/policy-editor/editor/expression-form-controls'; import {ActiveFeatureSet} from 'src/app/core/config/active-feature-set'; +import {switchDisabledControls} from 'src/app/core/utils/form-group-utils'; import {DataCategorySelectItem} from '../../data-category-select/data-category-select-item'; import {AssetAdvancedFormBuilder} from './asset-advanced-form-builder'; import {AssetDatasourceFormBuilder} from './asset-datasource-form-builder'; @@ -10,6 +13,7 @@ import {AssetDatasourceFormModel} from './model/asset-datasource-form-model'; import {AssetEditDialogMode} from './model/asset-edit-dialog-mode'; import {AssetGeneralFormModel} from './model/asset-general-form-model'; import {DataAddress} from './model/data-address'; +import {DataOfferPublishMode} from './model/data-offer-publish-mode'; import { EditAssetFormModel, EditAssetFormValue, @@ -62,6 +66,7 @@ export class EditAssetForm { private assetDatasourceFormBuilder: AssetDatasourceFormBuilder, private assetAdvancedFormBuilder: AssetAdvancedFormBuilder, private activeFeatureSet: ActiveFeatureSet, + private expressionFormControls: ExpressionFormControls, ) {} reset(initial: EditAssetFormValue) { @@ -84,16 +89,31 @@ export class EditAssetForm { const formGroup: FormGroup = this.formBuilder.nonNullable.group({ mode: [initial.mode as AssetEditDialogMode], + publishMode: [initial.publishMode as DataOfferPublishMode], + policyControls: this.expressionFormControls.formGroup, general, datasource, }); + formGroup.controls.publishMode.valueChanges + .pipe(delay(0)) + .subscribe(() => general.controls.id.updateValueAndValidity()); + if (this.activeFeatureSet.hasMdsFields()) { const advanced: FormGroup = this.assetAdvancedFormBuilder.buildFormGroup(initial.advanced!); formGroup.addControl('advanced', advanced); } + switchDisabledControls(formGroup, (value) => ({ + policyControls: value.publishMode === 'PUBLISH_RESTRICTED', + mode: true, + publishMode: true, + advanced: true, + general: true, + datasource: true, + })); + return formGroup; } diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/always-true-policy-id.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/always-true-policy-id.ts new file mode 100644 index 000000000..8a082e831 --- /dev/null +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/always-true-policy-id.ts @@ -0,0 +1 @@ +export const ALWAYS_TRUE_POLICY_ID = 'always-true'; diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/asset-datasource-form-enabled-ctrls.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/asset-datasource-form-enabled-ctrls.ts index 11b6830b5..7f347421f 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/asset-datasource-form-enabled-ctrls.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/asset-datasource-form-enabled-ctrls.ts @@ -10,8 +10,9 @@ export const assetDatasourceFormEnabledCtrls = ( value.dataAddressType === 'Custom-Data-Address-Json'; const onRequest = value.dataSourceAvailability === 'On-Request'; + const datasource = value.dataSourceAvailability === 'Datasource'; - const http = value.dataAddressType === 'Http' && !onRequest; + const http = value.dataAddressType === 'Http' && datasource; const httpAuth = value.httpAuthHeaderType !== 'None'; const httpAuthByValue = value.httpAuthHeaderType === 'Value'; const httpAuthByVault = value.httpAuthHeaderType === 'Vault-Secret'; @@ -24,10 +25,10 @@ export const assetDatasourceFormEnabledCtrls = ( contactEmail: onRequest, contactPreferredEmailSubject: onRequest, - dataAddressType: !onRequest, + dataAddressType: datasource, // Custom Datasource JSON - dataDestination: !onRequest && customDataAddressJson, + dataDestination: datasource && customDataAddressJson, // Http Datasource Fields httpUrl: http, diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/data-offer-publish-mode.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/data-offer-publish-mode.ts new file mode 100644 index 000000000..229aa7d2c --- /dev/null +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/data-offer-publish-mode.ts @@ -0,0 +1,4 @@ +export type DataOfferPublishMode = + | 'DO_NOT_PUBLISH' + | 'PUBLISH_UNRESTRICTED' + | 'PUBLISH_RESTRICTED'; diff --git a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/edit-asset-form-model.ts b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/edit-asset-form-model.ts index 73e6599c3..dff348313 100644 --- a/src/app/component-library/edit-asset-form/edit-asset-form/form/model/edit-asset-form-model.ts +++ b/src/app/component-library/edit-asset-form/edit-asset-form/form/model/edit-asset-form-model.ts @@ -1,14 +1,22 @@ -import {FormControl, FormGroup, ɵFormGroupValue} from '@angular/forms'; +import { + FormControl, + FormGroup, + UntypedFormGroup, + ɵFormGroupValue, +} from '@angular/forms'; import {AssetAdvancedFormModel} from './asset-advanced-form-model'; import {AssetDatasourceFormModel} from './asset-datasource-form-model'; import {AssetEditDialogMode} from './asset-edit-dialog-mode'; import {AssetGeneralFormModel} from './asset-general-form-model'; +import {DataOfferPublishMode} from './data-offer-publish-mode'; /** * Form Model for Edit Asset Form */ export interface EditAssetFormModel { mode: FormControl; + publishMode: FormControl; + policyControls: UntypedFormGroup; general: FormGroup; datasource: FormGroup; advanced?: FormGroup; diff --git a/src/app/core/services/api/edc-api.service.ts b/src/app/core/services/api/edc-api.service.ts index 6405ead04..fcd3be5cb 100644 --- a/src/app/core/services/api/edc-api.service.ts +++ b/src/app/core/services/api/edc-api.service.ts @@ -1,5 +1,5 @@ import {Inject, Injectable} from '@angular/core'; -import {Observable, from} from 'rxjs'; +import {Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import { AssetPage, @@ -12,6 +12,7 @@ import { DashboardPage, EdcClient, GetContractAgreementPageRequest, + IdAvailabilityResponse, IdResponseDto, InitiateCustomTransferRequest, InitiateTransferRequest, @@ -28,7 +29,7 @@ import { buildEdcClient, } from '@sovity.de/edc-client'; import {APP_CONFIG, AppConfig} from '../../config/app-config'; -import {throwIfNull} from '../../utils/rxjs-utils'; +import {throwIfNull, toObservable} from '../../utils/rxjs-utils'; import {EDC_FAKE_BACKEND} from './fake-backend/edc-fake-backend'; @Injectable({providedIn: 'root'}) @@ -46,24 +47,26 @@ export class EdcApiService { } getDashboardPage(): Observable { - return from(this.edcClient.uiApi.getDashboardPage()); + return toObservable(() => this.edcClient.uiApi.getDashboardPage()); } getAssetPage(): Observable { - return from(this.edcClient.uiApi.getAssetPage()); + return toObservable(() => this.edcClient.uiApi.getAssetPage()); } createAsset( uiAssetCreateRequest: UiAssetCreateRequest, ): Observable { - return from(this.edcClient.uiApi.createAsset({uiAssetCreateRequest})); + return toObservable(() => + this.edcClient.uiApi.createAsset({uiAssetCreateRequest}), + ); } editAsset( assetId: string, uiAssetEditRequest: UiAssetEditRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.editAsset({ assetId, uiAssetEditRequest, @@ -72,17 +75,17 @@ export class EdcApiService { } deleteAsset(assetId: string): Observable { - return from(this.edcClient.uiApi.deleteAsset({assetId})); + return toObservable(() => this.edcClient.uiApi.deleteAsset({assetId})); } getPolicyDefinitionPage(): Observable { - return from(this.edcClient.uiApi.getPolicyDefinitionPage()); + return toObservable(() => this.edcClient.uiApi.getPolicyDefinitionPage()); } createPolicyDefinition( policyDefinitionCreateRequest: PolicyDefinitionCreateRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.createPolicyDefinition({ policyDefinitionCreateRequest, }), @@ -92,7 +95,7 @@ export class EdcApiService { createPolicyDefinitionV2( policyDefinitionCreateDto: PolicyDefinitionCreateDto, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.createPolicyDefinitionV2({ policyDefinitionCreateDto, }), @@ -100,17 +103,19 @@ export class EdcApiService { } deletePolicyDefinition(policyId: string): Observable { - return from(this.edcClient.uiApi.deletePolicyDefinition({policyId})); + return toObservable(() => + this.edcClient.uiApi.deletePolicyDefinition({policyId}), + ); } getContractDefinitionPage(): Observable { - return from(this.edcClient.uiApi.getContractDefinitionPage()); + return toObservable(() => this.edcClient.uiApi.getContractDefinitionPage()); } createContractDefinition( contractDefinitionRequest: ContractDefinitionRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.createContractDefinition({ contractDefinitionRequest, }), @@ -120,7 +125,7 @@ export class EdcApiService { deleteContractDefinition( contractDefinitionId: string, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.deleteContractDefinition({contractDefinitionId}), ); } @@ -128,7 +133,7 @@ export class EdcApiService { getCatalogPageDataOffers( connectorEndpoint: string, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.getCatalogPageDataOffers({connectorEndpoint}), ); } @@ -136,7 +141,7 @@ export class EdcApiService { initiateContractNegotiation( contractNegotiationRequest: ContractNegotiationRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.initiateContractNegotiation({ contractNegotiationRequest, }), @@ -146,7 +151,7 @@ export class EdcApiService { terminateContractAgreement( terminateContractAgreementRequest: TerminateContractAgreementRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.terminateContractAgreement( terminateContractAgreementRequest, ), @@ -156,7 +161,7 @@ export class EdcApiService { getContractNegotiation( contractNegotiationId: string, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.getContractNegotiation({contractNegotiationId}), ); } @@ -164,7 +169,7 @@ export class EdcApiService { getContractAgreementPage( getContractAgreementPageRequest: GetContractAgreementPageRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.getContractAgreementPage( getContractAgreementPageRequest, ), @@ -191,7 +196,7 @@ export class EdcApiService { initiateTransfer( initiateTransferRequest: InitiateTransferRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.initiateTransfer({initiateTransferRequest}), ); } @@ -199,7 +204,7 @@ export class EdcApiService { initiateCustomTransfer( initiateCustomTransferRequest: InitiateCustomTransferRequest, ): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.initiateCustomTransfer({ initiateCustomTransferRequest, }), @@ -207,16 +212,40 @@ export class EdcApiService { } getTransferHistoryPage(): Observable { - return from(this.edcClient.uiApi.getTransferHistoryPage()); + return toObservable(() => this.edcClient.uiApi.getTransferHistoryPage()); } getTransferProcessAsset(transferProcessId: string): Observable { - return from( + return toObservable(() => this.edcClient.uiApi.getTransferProcessAsset({transferProcessId}), ); } getEnterpriseEditionConnectorLimits(): Observable { - return from(this.edcClient.enterpriseEditionApi.connectorLimits()); + return toObservable(() => + this.edcClient.enterpriseEditionApi.connectorLimits(), + ); + } + + isAssetIdAvailable(assetId: string): Observable { + return toObservable(() => + this.edcClient.uiApi.isAssetIdAvailable({assetId}), + ); + } + + isPolicyIdAvailable(policyId: string): Observable { + return toObservable(() => + this.edcClient.uiApi.isPolicyIdAvailable({policyId}), + ); + } + + isContractDefinitionIdAvailable( + contractDefinitionId: string, + ): Observable { + return toObservable(() => + this.edcClient.uiApi.isContractDefinitionIdAvailable({ + contractDefinitionId, + }), + ); } } 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 7dd4e44e0..c6195fd68 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 @@ -1,5 +1,6 @@ import { AssetPage, + IdAvailabilityResponse, IdResponseDto, UiAsset, UiAssetCreateRequest, @@ -22,6 +23,13 @@ export const assetPage = (): AssetPage => { }; }; +export const assetIdAvailable = (assetId: string): IdAvailabilityResponse => { + return { + id: assetId, + available: !assets.some((it) => it.assetId === assetId), + }; +}; + export const getAssetById = (id: string) => assets.find((it) => it.assetId === id); diff --git a/src/app/core/services/api/fake-backend/connector-fake-impl/contract-definition-fake-service.ts b/src/app/core/services/api/fake-backend/connector-fake-impl/contract-definition-fake-service.ts index 2d6b8567c..e864a7d74 100644 --- a/src/app/core/services/api/fake-backend/connector-fake-impl/contract-definition-fake-service.ts +++ b/src/app/core/services/api/fake-backend/connector-fake-impl/contract-definition-fake-service.ts @@ -2,6 +2,7 @@ import { ContractDefinitionEntry, ContractDefinitionPage, ContractDefinitionRequest, + IdAvailabilityResponse, IdResponseDto, } from '@sovity.de/edc-client'; import {AssetProperty} from '../../../models/asset-properties'; @@ -27,6 +28,17 @@ export const contractDefinitionPage = (): ContractDefinitionPage => { }; }; +export const contractDefinitionIdAvailable = ( + contractDefinitionId: string, +): IdAvailabilityResponse => { + return { + id: contractDefinitionId, + available: !contractDefinitions.some( + (it) => it.contractDefinitionId === contractDefinitionId, + ), + }; +}; + export const createContractDefinition = ( request: ContractDefinitionRequest, ): IdResponseDto => { diff --git a/src/app/core/services/api/fake-backend/connector-fake-impl/data/test-policies.ts b/src/app/core/services/api/fake-backend/connector-fake-impl/data/test-policies.ts index 21420df9c..508c336c5 100644 --- a/src/app/core/services/api/fake-backend/connector-fake-impl/data/test-policies.ts +++ b/src/app/core/services/api/fake-backend/connector-fake-impl/data/test-policies.ts @@ -49,4 +49,8 @@ export namespace TestPolicies { export const failedMapping: UiPolicy = policy({ type: 'EMPTY', }); + + export const unrestricted: UiPolicy = policy({ + type: 'EMPTY', + }); } diff --git a/src/app/core/services/api/fake-backend/connector-fake-impl/policy-definition-fake-service.ts b/src/app/core/services/api/fake-backend/connector-fake-impl/policy-definition-fake-service.ts index e66e91f65..6de17bd83 100644 --- a/src/app/core/services/api/fake-backend/connector-fake-impl/policy-definition-fake-service.ts +++ b/src/app/core/services/api/fake-backend/connector-fake-impl/policy-definition-fake-service.ts @@ -1,4 +1,5 @@ import { + IdAvailabilityResponse, IdResponseDto, PolicyDefinitionCreateDto, PolicyDefinitionCreateRequest, @@ -6,6 +7,7 @@ import { PolicyDefinitionPage, UiPolicyExpression, } from '@sovity.de/edc-client'; +import {ALWAYS_TRUE_POLICY_ID} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/model/always-true-policy-id'; import {TestPolicies} from './data/test-policies'; let policyDefinitions: PolicyDefinitionDto[] = [ @@ -21,11 +23,26 @@ let policyDefinitions: PolicyDefinitionDto[] = [ policyDefinitionId: 'test-policy-definition-3', policy: TestPolicies.failedMapping, }, + { + policyDefinitionId: ALWAYS_TRUE_POLICY_ID, + policy: TestPolicies.unrestricted, + }, ]; export const policyDefinitionPage = (): PolicyDefinitionPage => { return {policies: policyDefinitions}; }; +export const policyDefinitionIdAvailable = ( + policyDefinitionId: string, +): IdAvailabilityResponse => { + return { + id: policyDefinitionId, + available: !policyDefinitions.some( + (it) => it.policyDefinitionId === policyDefinitionId, + ), + }; +}; + export const getPolicyDefinitionByJsonLd = (jsonLd: string) => policyDefinitions.find((it) => it.policy.policyJsonLd === jsonLd)?.policy; 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 f8c7cca65..c58c18218 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 @@ -9,6 +9,7 @@ import { ContractTerminationRequestFromJSON, DashboardPageToJSON, FetchAPI, + IdAvailabilityResponseToJSON, IdResponseDtoToJSON, InitiateTransferRequestFromJSON, PolicyDefinitionCreateDtoFromJSON, @@ -22,6 +23,7 @@ import { UiDataOfferToJSON, } from '@sovity.de/edc-client'; import { + assetIdAvailable, assetPage, createAsset, deleteAsset, @@ -33,6 +35,7 @@ import { contractAgreementPage, } from './connector-fake-impl/contract-agreement-fake-service'; import { + contractDefinitionIdAvailable, contractDefinitionPage, createContractDefinition, deleteContractDefinition, @@ -48,6 +51,7 @@ import { createPolicyDefinition, createPolicyDefinitionV2, deletePolicyDefinition, + policyDefinitionIdAvailable, policyDefinitionPage, } from './connector-fake-impl/policy-definition-fake-service'; import { @@ -222,5 +226,23 @@ export const EDC_FAKE_BACKEND: FetchAPI = async ( return ok(ConnectorLimitsToJSON(limits)); }) + .url('ui/pages/data-offer-page/validate-asset-id/*') + .on('GET', (assetId) => { + const response = assetIdAvailable(assetId); + return ok(IdAvailabilityResponseToJSON(response)); + }) + + .url('ui/pages/data-offer-page/validate-policy-id/*') + .on('GET', (policyId) => { + const response = policyDefinitionIdAvailable(policyId); + return ok(IdAvailabilityResponseToJSON(response)); + }) + + .url('ui/pages/data-offer-page/validate-contract-definition-id/*') + .on('GET', (contractDefinitionId) => { + const response = contractDefinitionIdAvailable(contractDefinitionId); + return ok(IdAvailabilityResponseToJSON(response)); + }) + .tryMatch(); }; diff --git a/src/app/core/utils/rxjs-utils.ts b/src/app/core/utils/rxjs-utils.ts index e982658d8..47d47339c 100644 --- a/src/app/core/utils/rxjs-utils.ts +++ b/src/app/core/utils/rxjs-utils.ts @@ -1,4 +1,4 @@ -import {OperatorFunction} from 'rxjs'; +import {Observable, OperatorFunction, defer, from} from 'rxjs'; import {filter, tap} from 'rxjs/operators'; /** @@ -19,3 +19,6 @@ export function throwIfNull( } }) as OperatorFunction; } + +export const toObservable = (fn: () => Promise): Observable => + defer(() => from(fn())); diff --git a/src/app/routes/connector-ui/asset-edit-page/asset-edit-page/asset-edit-page.component.ts b/src/app/routes/connector-ui/asset-edit-page/asset-edit-page/asset-edit-page.component.ts index 2d0e56a70..d8821f41e 100644 --- a/src/app/routes/connector-ui/asset-edit-page/asset-edit-page/asset-edit-page.component.ts +++ b/src/app/routes/connector-ui/asset-edit-page/asset-edit-page/asset-edit-page.component.ts @@ -1,19 +1,24 @@ import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; -import {EMPTY, Observable, catchError, finalize, tap} from 'rxjs'; -import {IdResponseDto} from '@sovity.de/edc-client'; +import {EMPTY, Observable, catchError, concat, finalize, tap} from 'rxjs'; +import {IdResponseDto, UiCriterionLiteralType} from '@sovity.de/edc-client'; import {AssetAdvancedFormBuilder} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/asset-advanced-form-builder'; import {AssetDatasourceFormBuilder} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/asset-datasource-form-builder'; import {AssetGeneralFormBuilder} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/asset-general-form-builder'; import {EditAssetForm} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form'; import {EditAssetFormInitializer} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/edit-asset-form-initializer'; +import {ALWAYS_TRUE_POLICY_ID} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/model/always-true-policy-id'; import {EditAssetFormValue} from 'src/app/component-library/edit-asset-form/edit-asset-form/form/model/edit-asset-form-model'; +import {ExpressionFormControls} from 'src/app/component-library/policy-editor/editor/expression-form-controls'; +import {ExpressionFormHandler} from 'src/app/component-library/policy-editor/editor/expression-form-handler'; import {EdcApiService} from 'src/app/core/services/api/edc-api.service'; import {AssetRequestBuilder} from 'src/app/core/services/asset-request-builder'; import {AssetService} from 'src/app/core/services/asset.service'; +import {AssetProperty} from 'src/app/core/services/models/asset-properties'; import {Fetched} from 'src/app/core/services/models/fetched'; import {UiAssetMapped} from 'src/app/core/services/models/ui-asset-mapped'; import {NotificationService} from 'src/app/core/services/notification.service'; +import {PolicyDefinitionCreatePageForm} from '../../policy-definition-create-page/policy-definition-create-page/policy-definition-create-page-form'; @Component({ selector: 'asset-edit-page', @@ -24,6 +29,9 @@ import {NotificationService} from 'src/app/core/services/notification.service'; AssetGeneralFormBuilder, AssetDatasourceFormBuilder, AssetAdvancedFormBuilder, + ExpressionFormHandler, + ExpressionFormControls, + PolicyDefinitionCreatePageForm, ], }) export class AssetEditPageComponent implements OnInit { @@ -43,6 +51,7 @@ export class AssetEditPageComponent implements OnInit { private notificationService: NotificationService, private router: Router, private route: ActivatedRoute, + private expressionFormHandler: ExpressionFormHandler, ) {} ngOnInit(): void { @@ -108,16 +117,34 @@ export class AssetEditPageComponent implements OnInit { private _saveRequest( formValue: EditAssetFormValue, ): Observable { + const assetId = formValue.general!.id!; const mode = this.form.mode; + const publishMode = formValue.publishMode!; if (mode === 'CREATE') { - const createRequest = + const assetCreateRequest = this.assetRequestBuilder.buildAssetCreateRequest(formValue); - return this.edcApiService.createAsset(createRequest); + + if (publishMode === 'PUBLISH_UNRESTRICTED') { + return concat( + this.edcApiService.createAsset(assetCreateRequest), + this.createContractDefinition(assetId, ALWAYS_TRUE_POLICY_ID), + ); + } else if (publishMode === 'PUBLISH_RESTRICTED') { + return concat( + this.edcApiService.createAsset(assetCreateRequest), + this.edcApiService.createPolicyDefinitionV2({ + policyDefinitionId: assetId, + expression: this.expressionFormHandler.toUiPolicyExpression(), + }), + this.createContractDefinition(assetId, assetId), + ); + } else { + return this.edcApiService.createAsset(assetCreateRequest); + } } if (mode === 'EDIT') { - const assetId = formValue.general?.id!; const editRequest = this.assetRequestBuilder.buildAssetEditRequest(formValue); return this.edcApiService.editAsset(assetId, editRequest); @@ -125,4 +152,25 @@ export class AssetEditPageComponent implements OnInit { throw new Error(`Unsupported mode: ${mode}`); } + + private createContractDefinition( + assetId: string, + policyId: string, + ): Observable { + return this.edcApiService.createContractDefinition({ + accessPolicyId: policyId, + contractPolicyId: policyId, + contractDefinitionId: assetId, + assetSelector: [ + { + operandLeft: AssetProperty.id, + operator: 'IN', + operandRight: { + type: UiCriterionLiteralType.ValueList, + valueList: [assetId], + }, + }, + ], + }); + } }