diff --git a/src/consents/serializers.py b/src/consents/serializers.py index 8046b41d5f716b358fe2a9ae3689a0ac34950359..0853ae05f567f5a0e8d9d7cb1c784fbb1ef1d298 100644 --- a/src/consents/serializers.py +++ b/src/consents/serializers.py @@ -16,7 +16,8 @@ class ProviderSchemaSerializer(serializers.ModelSerializer): "name": obj.provider.name, "description": obj.provider.description, "verbs": VerbSerializer(obj.verbs(), many=True).data, - "essential_verbs": VerbSerializer(obj.essential_verbs(), many=True).data + "essential_verbs": VerbSerializer(obj.essential_verbs(), many=True).data, + "additional_lrs": obj.additional_lrs, } class Meta: @@ -117,50 +118,19 @@ class UserVerbSerializer(serializers.Serializer): class SaveUserConsentSerializer(serializers.Serializer): - def get_verb_objects_lookup(self, verb: Dict) -> Dict: - try: - object_consents = {} - objects_arr = json.loads(verb["objects"]) - for obj in objects_arr: - object_consents[obj["id"]] = obj["consented"] - return object_consents - except json.JSONDecodeError: - raise serializers.ValidationError( - f"Invalid json for objects in verb {verb['id']}" - ) def validate(self, data): """ - Validate verbs with same id have consistent consent (including objects) + Validate existence of submitted groups. """ - look_up = {} - for verb in data["verbs"]: - if look_up.get(verb["id"]) is None: - look_up[verb["id"]] = { - "consented": verb["consented"], - "object_consents": self.get_verb_objects_lookup(verb), - } - else: - if look_up[verb["id"]]["consented"] != verb["consented"]: - raise serializers.ValidationError( - f"Verb {verb['id']} contains ambiguous consent." - ) - if look_up[verb["id"]][ - "object_consents" - ] != self.get_verb_objects_lookup(verb): - raise serializers.ValidationError( - f"Verb {verb['id']} contains ambiguous object consents." - ) - + for group_id in data["groups"]: + if not ProviderVerbGroup.objects.filter(id=group_id): + raise serializers.ValidationError( + f"Group with id {group_id} does not exist." + ) return data - providerId = serializers.PrimaryKeyRelatedField( - queryset=Provider.objects.all(), required=False - ) - providerSchemaId = serializers.PrimaryKeyRelatedField( - queryset=ProviderSchema.objects.all(), required=False - ) - verbs = serializers.ListSerializer(child=UserVerbSerializer()) + groups = serializers.ListSerializer(child=serializers.IntegerField()) class GroupVerbSerializer(serializers.Serializer): diff --git a/src/consents/views.py b/src/consents/views.py index 71c395c860d30e4f86bf570fa9b8f05f86d51402..c13013651d77ec21b6cfff78da2c872db309b804 100644 --- a/src/consents/views.py +++ b/src/consents/views.py @@ -649,8 +649,7 @@ class CreateUserConsentViaConnectServiceView(APIView): class SaveUserConsentView(APIView): """ - Saves a user consent. - The provider schema id for each provider must always refer to the latest schema version of the respective provider, otherwise the request will be rejected + Saves a list of user consents, submitted as a list of group ids. """ permission_classes = (IsAuthenticated,) @@ -658,37 +657,34 @@ class SaveUserConsentView(APIView): def post(self, request): user = request.user - serializer = SaveUserConsentSerializer(data=request.data, many=True) + serializer = SaveUserConsentSerializer(data=request.data) serializer.is_valid(raise_exception=True) - # Validate each user provider setting refers to the latest provider schema id - for user_provider_setting in serializer.validated_data: - latest_provider_schema = ProviderSchema.objects.get( - provider=user_provider_setting["providerId"].id, superseded_by=None - ) - - if ( - user_provider_setting["providerSchemaId"].id - != latest_provider_schema.id - ): - return JsonResponse( - {"message": "provider schema id isn't latest provider schema id"}, - status=status.HTTP_400_BAD_REQUEST, + for group_id in serializer.validated_data["groups"]: + group = ProviderVerbGroup.objects.filter(pk=group_id).first() + for verb in group.verbs.all(): + object_consent_data = [] + for obj in verb.verbobject_set.all(): + object_consent_data.append({ + "id": obj.object_id, + "label": obj.label, + "defaultConsent": verb.default_consent, + "matching": obj.matching, + "definition": obj.definition, + "consented": True + }) + consent = UserConsents.objects.create( + user = user, + consented = True, + provider = group.provider, + verb_group = group, + verb = verb, + object = object_consent_data, + active = True ) - for user_provider_setting in serializer.validated_data: - user.accepted_provider_schemas.add( - user_provider_setting["providerSchemaId"] - ) - user.save() - save_user_consent( - user, - user_provider_setting["providerSchemaId"], - user_provider_setting["verbs"], - ) - return JsonResponse( - {"message": "user consent saved"}, + {"message": "user consents saved"}, status=status.HTTP_200_OK, ) diff --git a/src/frontend/src/app/app-routing.module.ts b/src/frontend/src/app/app-routing.module.ts index 2a6ceb3ae59e838083d49db00905e4b1644ecf4d..6948d653eeff0b55a1032f37f700f4432a2019f3 100644 --- a/src/frontend/src/app/app-routing.module.ts +++ b/src/frontend/src/app/app-routing.module.ts @@ -12,7 +12,7 @@ import { UserProfilComponent } from './user-profil/user-profil.component' import { DataDisclosureComponent } from './data-disclosure/data-disclosure.component' import { DataRemovalComponent } from './data-removal/data-removal.component' import { ApplicationTokensComponent } from './consent-management/application-tokens/application-tokens.component' -import { AnalyticsTokensComponent } from './consent-management/analytics-tokens/analytics-tokens.component' +import { AnalyticsTokensComponent } from './analytics-tokens/analytics-tokens.component' import { AnalyticsEngineComponent } from './analytics-engine/analytics-engine.component' import { ConsentHistoryComponent } from './consent-management/consent-history/consent-history.component' import { ControlCenterComponent } from './control-center/control-center.component' diff --git a/src/frontend/src/app/app.component.ts b/src/frontend/src/app/app.component.ts index ab8694344763bc4f518939f2310134157d39a194..84afd73ad3b7c8f7480dbd2a62d6877ff0c2a484 100644 --- a/src/frontend/src/app/app.component.ts +++ b/src/frontend/src/app/app.component.ts @@ -20,9 +20,7 @@ export class AppComponent implements OnInit, OnDestroy { loading = false isDarkModeOn: boolean = false loggedIn: Userinfo | null = null - dialogCloseSubscription?: Subscription privacyPolicyDialogCloseSubscription?: Subscription - wizardDialogRef: NzModalRef<WizardDialog> | null = null privacyPolicyDialogRef: NzModalRef<PrivacyPolicyDialog> | null = null constructor( @@ -40,13 +38,6 @@ export class AppComponent implements OnInit, OnDestroy { this.listenToLoading() this._authService.loggedIn.subscribe((userInfo) => { if (userInfo && !userInfo.isProvider) { - /*this._apiService.getUserConsentStatus().subscribe((userConsent) => { - if (Object.values(userConsent.result).length === 0) this.openWizard(null) - const isAnyOutdatedOrNone = !!Object.values(userConsent.result).find( - ({ status }) => status === 'outdated' || status === 'none' - ) - if (isAnyOutdatedOrNone) this.openWizard(userConsent) - })*/ this._apiService.getPrivacyPolicyStatus().subscribe(result => { if(!result?.status) { @@ -56,14 +47,13 @@ export class AppComponent implements OnInit, OnDestroy { } }) } - + this.loggedIn = userInfo }) } ngOnDestroy(): void { - this.dialogCloseSubscription?.unsubscribe() this.privacyPolicyDialogCloseSubscription?.unsubscribe() } @@ -75,26 +65,6 @@ export class AppComponent implements OnInit, OnDestroy { }) } - openWizard(userConsentStatus: UserConsentStatus | null): void { - if (!this.wizardDialogRef) { - this.wizardDialogRef = this._dialog.create({ - nzContent: WizardDialog, - nzMaskClosable: false, - nzClosable: false, - nzWidth: '90vw', - nzComponentParams: { - data: userConsentStatus - } - }) - - this.dialogCloseSubscription = this.wizardDialogRef.afterClose.subscribe((result) => { - this._router - .navigateByUrl('/', { skipLocationChange: true }) - .then(() => this._router.navigate(['/consent-management'])) - }) - } - } - openPrivacyPolicy(content: string): void { if (!this.privacyPolicyDialogRef && this.loggedIn != null) { this.privacyPolicyDialogRef = this._dialog.create({ diff --git a/src/frontend/src/app/app.module.ts b/src/frontend/src/app/app.module.ts index 1a6af97a01f7afcec8b3ee1e6ec54f628cb27e69..5cffa71bbd4090c32c4e8090002cc524bc62c417 100644 --- a/src/frontend/src/app/app.module.ts +++ b/src/frontend/src/app/app.module.ts @@ -31,7 +31,7 @@ import { DataDisclosureComponent } from './data-disclosure/data-disclosure.compo import { DataRemovalComponent } from './data-removal/data-removal.component' import { DeleteDialog } from './dialogs/delete-dialog/delete-dialog' import { ApplicationTokensComponent } from './consent-management/application-tokens/application-tokens.component' -import { AnalyticsTokensComponent } from './consent-management/analytics-tokens/analytics-tokens.component' +import { AnalyticsTokensComponent } from './analytics-tokens/analytics-tokens.component' import { SchemaChangeComponent } from './consent-management/schema-change/schema-change.component' import { ObjectChangesComponent } from './consent-management/schema-change/object-changes/object-changes.component' import { CreateTokenDialog } from './dialogs/create-token-dialog/create-token-dialog' diff --git a/src/frontend/src/app/consent-management/consent-history/consent-history.component.html b/src/frontend/src/app/consent-management/consent-history/consent-history.component.html index 4a536afbe6c0f59e5f296168345c3d760a252848..94d5fcef73b03b30ec11f6ea7245e606be0af3df 100644 --- a/src/frontend/src/app/consent-management/consent-history/consent-history.component.html +++ b/src/frontend/src/app/consent-management/consent-history/consent-history.component.html @@ -96,3 +96,9 @@ </nz-collapse-panel> <p></p> </nz-collapse> + +<p> + <button nz-button (click)="openWizard()"> + Einwilligungen erteilen + </button> +</p> diff --git a/src/frontend/src/app/consent-management/consent-history/consent-history.component.ts b/src/frontend/src/app/consent-management/consent-history/consent-history.component.ts index 5fb0afc4e78752954fe93679004f94d5f7f58507..1ae94ca0e1ecc5e87606568a6641a37d221e4ca8 100644 --- a/src/frontend/src/app/consent-management/consent-history/consent-history.component.ts +++ b/src/frontend/src/app/consent-management/consent-history/consent-history.component.ts @@ -1,8 +1,9 @@ import { Component } from '@angular/core' -import { ApiService, ConsentHistoryGroup, GroupedConsentHistory } from '../../services/api.service' +import { ApiService, ConsentHistoryGroup, GroupedConsentHistory, UserConsentStatus } from '../../services/api.service' import { faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' -import { NzModalService } from 'ng-zorro-antd/modal' +import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal' import { NzMessageService } from 'ng-zorro-antd/message' +import { WizardDialog, GroupConsentStatus } from '../wizard/wizard.component' type ActiveGroupsMap = Record<number, boolean>; @@ -72,5 +73,26 @@ export class ConsentHistoryComponent { }) } + + openWizard(): void { + let groupConsentStatus: GroupConsentStatus = {} + this.consentHistory.forEach((group: GroupedConsentHistory) => { + groupConsentStatus[group.group.id] = group.group.consented; + }) + let wizardDialogRef = this._dialog.create({ + nzContent: WizardDialog, + nzMaskClosable: false, + nzWidth: '90vw', + nzComponentParams: { + consentStatus: groupConsentStatus + } + }) + + wizardDialogRef.afterClose.subscribe((result) => { + this.updateConsentHistory(); + }) + } + protected readonly faTimesCircle = faTimesCircle + protected readonly open = open } diff --git a/src/frontend/src/app/consent-management/consent-management.component.ts b/src/frontend/src/app/consent-management/consent-management.component.ts index 42a9131425625e6abdc145d7a03e0f217027d641..bc05a948b5dbcf422e0ca5563776dfdf2bf1f5f4 100644 --- a/src/frontend/src/app/consent-management/consent-management.component.ts +++ b/src/frontend/src/app/consent-management/consent-management.component.ts @@ -81,10 +81,10 @@ export class ConsentManagementComponent implements OnInit, OnDestroy { } saveChanges(): void { - this._apiService.saveUserConsent(this.userConsents).subscribe((response) => { + /*this._apiService.saveUserConsent(this.userConsents).subscribe((response) => { this._messageService.success($localize`:@@toastMessageSaved:Saved`) this.getProviderUserConsents() - }) + })*/ } showPauseDialog(): void { diff --git a/src/frontend/src/app/consent-management/consentDeclaration.ts b/src/frontend/src/app/consent-management/consentDeclaration.ts index 013133fd39a4f178f8e3c4afc1e6d354a0e67cec..92efd5df6e842614df7795a36d4e3d9d7a51730c 100644 --- a/src/frontend/src/app/consent-management/consentDeclaration.ts +++ b/src/frontend/src/app/consent-management/consentDeclaration.ts @@ -31,17 +31,17 @@ export interface XApiVerbConsented extends XApiVerbSchema { } export interface XApiVerbGroupSchema { - id: string + id: number label: string description: string purposeOfCollection: string - requiresConsent: boolean + requires_consent: boolean provider_schema: number verbs: XApiVerbSchema[] } export interface XApiVerbGroupConsented extends XApiVerbGroupSchema { - id: string + id: number label: string description: string purposeOfCollection: string @@ -51,7 +51,7 @@ export interface XApiVerbGroupConsented extends XApiVerbGroupSchema { } export interface ConsentGroupConsented { - id: string + id: number label: string description: string purposeOfCollection: string diff --git a/src/frontend/src/app/consent-management/wizard/wizard.component.html b/src/frontend/src/app/consent-management/wizard/wizard.component.html index 67801a9a8737e09c1098cff17cbf585521660b6e..013677f8c1609f4964fcebe5f347b9317a62d680 100644 --- a/src/frontend/src/app/consent-management/wizard/wizard.component.html +++ b/src/frontend/src/app/consent-management/wizard/wizard.component.html @@ -3,8 +3,8 @@ </div> <div> - <div *ngIf="loading"> - <nz-spin></nz-spin> + <div class="loading-spinner" *ngIf="loading"> + <img ngSrc="assets/spinner_small.gif" alt="loading" height="100" width="100" /> </div> <div *ngIf="!loading"> @@ -14,163 +14,122 @@ The provider has not yet uploaded a consent form. </h3> - <div i18n="Guide | Guide Text @@guideText"> + <!-- <div i18n="Guide | Guide Text @@guideText"> <h2>Guide</h2> <ul> <li>Hint 1</li> <li>Hint 2</li> <li>Hint 3</li> </ul> - </div> + </div> --> <div *ngIf="!isSummary && selectedProviderId"> - <div class="user-consents" *ngIf="wizardUserConsents[selectedProviderId] as userData"> - <h2>{{ userData.provider.name }}</h2> - <div *ngIf="userData.acceptedProviderSchema as userAcceptedSchema"> - <div *ngIf="userAcceptedSchema.superseded_by !== null"> - <h3 i18n="Provider schema changed @@providerSchemaChanged"> - Since your last visit, the provider has made changes. For each option, - you will now see your previous consent on the far left, and on the - right, separated by an arrow, your current consent, which you can of - course change. - </h3> - <app-schema-change - [newSchema]="userData.provider.versions[0].definition" - [oldSchema]="userAcceptedSchema.definition"> - </app-schema-change - > - <br /> - - <app-provider-setting - [consentDeclaration]="userData.defaultConsent" - [previousUserConsent]="userData.userConsent" - (change)=" - changePrivacySetting($event, selectedProviderId) - "></app-provider-setting> - </div> + <h2> + {{ getSelectedProvider()?.name }} + </h2> + <div *ngFor="let group_data of getSelectedProvider()?.groups"> + <nz-collapse *ngIf="Object.keys(groupsToConsent).includes(String(group_data.id))"> + <nz-collapse-panel [nzHeader]="groupConsentPanelHeader"> + <ng-template #groupConsentPanelHeader> + <div class="consent-header"> + <span> + <span style="color: #000">{{ group_data.label }}</span> + </span> + <span> + <nz-switch + [(ngModel)]="groupsToConsent[group_data.id]" - <div *ngIf="userAcceptedSchema.superseded_by === null"> - <h2 i18n="Consent Declaration UpToDate @@consentDeclarationUpToDate"> - You do not need to make any changes to your existing consent form, as - the provider has not made any changes in the meantime. - </h2> - </div> - </div> + (click)="$event.stopPropagation()" + ></nz-switch> + </span> + </div> + </ng-template> - <div *ngIf="!userData.acceptedProviderSchema"> - <h3 i18n="First Consent Declaration @@firstConsentDeclaration"> - You have not yet given consent for provider {{ userData.provider.name }}. - </h3> - <app-provider-setting - [consentDeclaration]="userData.defaultConsent" - [previousUserConsent]="null" - (change)=" - changePrivacySetting($event, selectedProviderId) - "></app-provider-setting> - </div> + <p> + <markdown> + {{group_data.description}} + </markdown> + </p> + <p> + <b i18n="purpose of collection | @@purposeOfCollection">Purpose of collection:</b> + <br /> + <markdown> + {{group_data.purposeOfCollection}} + </markdown> + </p> + </nz-collapse-panel> + <p></p> + </nz-collapse> </div> + </div> <div *ngIf="isSummary && selectedProviderId"> - <div *ngFor="let providerId of providerIds"> - <nz-card [nzTitle]="wizardUserConsents[providerId].provider.name"> - <div class="user-consents" *ngIf="wizardUserConsents[providerId] as userData"> - <div *ngIf="userData.acceptedProviderSchema as userAcceptedSchema"> - <div *ngIf="userAcceptedSchema.superseded_by !== null"> - <h3 - i18n=" - Changed provider schema | Summary text - @@changedProviderSchemaSummaryText"> - The provider has made changes since your last consent - declaration which requires renewed consent from you. - </h3> - - <app-provider-setting - [consentDeclaration]="userData.defaultConsent" - [previousUserConsent]="userData.userConsent" - (change)=" - changePrivacySetting($event, selectedProviderId) - "></app-provider-setting> - </div> + <h3>Neue Einwilligungen:</h3> - <div *ngIf="userAcceptedSchema.superseded_by === null"> - <h3>Nothing changed here.</h3> - </div> - </div> + <div *ngFor="let provider of this.providers"> - <div *ngIf="!userData.acceptedProviderSchema"> - <h3 - i18n=" - First provider consent | Summary text - @@firstProviderConsentSummaryText"> - You have not previously given consent for this provider, this is - your first consent declaration. - </h3> - <app-provider-setting - [consentDeclaration]="userData.defaultConsent" - [previousUserConsent]="null" - (change)=" - changePrivacySetting($event, selectedProviderId) - "></app-provider-setting> - </div> + <div *ngFor="let group_data of provider.groups"> + <div *ngIf="Object.keys(groupsToConsent).includes(String(group_data.id)) && groupsToConsent[group_data.id]"> + <b>{{provider.name}}</b> - {{group_data.label}} </div> - </nz-card> - <br /> + </div> </div> </div> </div> </div> -<span class="cdk-visually-hidden" #translatedPrevProvider i18n="Previous Provider @@prevProvider">Previous Provider</span> -<span class="cdk-visually-hidden" #translatedNextProvider i18n="Next Provider @@nextProvider">Next Provider</span> -<span class="cdk-visually-hidden" #translatedSummary i18n="Summary @@summary">Summary</span> -<div class="action-btns" *ngIf="!isSummary"> - <button nz-button (click)="logout()" i18n="Logout @@logout">Logout</button> - <div class="actions-btns-end"> - <button - nz-button - i18n="Previous @@previousWithIcon" - (click)="prev()" - *ngIf="selectedProviderId && providerIds.indexOf(selectedProviderId) - 1 >= 0"> - <fa-icon [icon]="faArrowLeft"></fa-icon> - Previous - </button> - <button - nz-button - i18n="Next @@nextWithIcon" - (click)="next()" - *ngIf="selectedProviderId && providerIds.indexOf(selectedProviderId) < providerIds.length - 1; else summaryButton"> - Next - <fa-icon [icon]="faArrowRight"></fa-icon> - </button> - <ng-template #summaryButton> +<div *nzModalFooter> + <div class="action-btns" *ngIf="!isSummary"> + <span></span> + <div class="actions-btns-end"> <button nz-button - i18n="Summary @@summaryWithIcon" - (click)="showSummary()"> - Summary + i18n="Previous @@previousWithIcon" + (click)="prev()" + *ngIf="selectedProviderId && providerIds.indexOf(selectedProviderId) - 1 >= 0"> + <fa-icon [icon]="faArrowLeft"></fa-icon> + Previous + </button> + <button + nz-button + i18n="Next @@nextWithIcon" + (click)="next()" + *ngIf="selectedProviderId && providerIds.indexOf(selectedProviderId) < providerIds.length - 1; else summaryButton"> + Next <fa-icon [icon]="faArrowRight"></fa-icon> </button> - </ng-template> + <ng-template #summaryButton> + <button + [disabled]="!consentChanged()" + nz-button + i18n="Summary @@summaryWithIcon" + (click)="showSummary()"> + Summary + <fa-icon [icon]="faArrowRight"></fa-icon> + </button> + </ng-template> + </div> </div> -</div> -<div class="action-btns" *ngIf="isSummary"> - <button nz-button (click)="logout()" i18n="Logout @@logout">Logout</button> - <div class="actions-btns-end"> - <button - nz-button - (click)="prev()" - i18n="Previous @@previousWithIcon"> - <fa-icon [icon]="faArrowLeft"></fa-icon> - Previous - </button> - <button - nz-button - nzType="primary" - (click)="submit()" - i18n="Submit @@submit"> - Submit - </button> + <div class="action-btns" *ngIf="isSummary"> + <span></span> + <div class="actions-btns-end"> + <button + nz-button + (click)="prev()" + i18n="Previous @@previousWithIcon"> + <fa-icon [icon]="faArrowLeft"></fa-icon> + Previous + </button> + <button + [disabled]="!consentChanged()" + nz-button + nzType="primary" + (click)="submit()" + i18n="Submit @@submit"> + Submit + </button> + </div> </div> </div> diff --git a/src/frontend/src/app/consent-management/wizard/wizard.component.scss b/src/frontend/src/app/consent-management/wizard/wizard.component.scss index e0f4744ab5fe2ee24971eca8b52bcb723419bc0a..22921f68bb42e54cc4a7457e7f7f12b6ffbbfdf3 100644 --- a/src/frontend/src/app/consent-management/wizard/wizard.component.scss +++ b/src/frontend/src/app/consent-management/wizard/wizard.component.scss @@ -1,6 +1,18 @@ .wizard { padding: 20px; } +.loading-spinner { + img { + margin-left: 50%; + transform: translateX(-50%); + } +} +.consent-header { + display: flex; + justify-content: space-between; + width: 100%; +} + .group-name-hint { margin-left: 10px; opacity: 0.2; diff --git a/src/frontend/src/app/consent-management/wizard/wizard.component.ts b/src/frontend/src/app/consent-management/wizard/wizard.component.ts index 5521aeb5b5c7ec3140442db88305435fdc48f7bb..593cf0c04164996dec25fe9eae890fefc783040a 100644 --- a/src/frontend/src/app/consent-management/wizard/wizard.component.ts +++ b/src/frontend/src/app/consent-management/wizard/wizard.component.ts @@ -1,17 +1,25 @@ -import { Component, Input } from '@angular/core' +import { Component, Input, OnInit } from '@angular/core' import { UserConsent, providerSchemaToUserConsent, ProviderSchema, ProviderId, - UserConsentVerbs + UserConsentVerbs, XApiVerbGroupSchema } from '../consentDeclaration' -import { ApiService, Provider, UserConsentStatus } from 'src/app/services/api.service' +import { + ApiService, + ConsentHistoryGroup, + GroupedConsentHistory, + Provider, + UserConsentStatus +} from 'src/app/services/api.service' import { AuthService } from 'src/app/services/auth.service' import { zip } from 'rxjs' import { NzMessageService } from 'ng-zorro-antd/message' import { NzModalRef } from 'ng-zorro-antd/modal' -import { faArrowLeft, faArrowRight } from '@fortawesome/free-solid-svg-icons' +import { faArrowLeft, faArrowRight, faCheckCircle, faTimesCircle } from '@fortawesome/free-solid-svg-icons' + +export type GroupConsentStatus = Record<number, boolean> export const extractVerbs = (providerSchema: UserConsent, providerId: ProviderId): any[] => { return providerSchema.groups.flatMap((group) => { @@ -34,8 +42,8 @@ export const extractVerbs = (providerSchema: UserConsent, providerId: ProviderId templateUrl: 'wizard.component.html', styleUrls: ['./wizard.component.scss'] }) -export class WizardDialog { - @Input() data: UserConsentStatus | null = null +export class WizardDialog implements OnInit { + @Input() consentStatus: GroupConsentStatus = {} constructor( public dialogRef: NzModalRef<WizardDialog>, @@ -46,6 +54,7 @@ export class WizardDialog { } loading = false + providers: Provider[] = [] providerIds: number[] = [] providerUserConsent: Record< @@ -70,6 +79,29 @@ export class WizardDialog { selectedProviderId: number | null = null isSummary = false + groupsToConsent: GroupConsentStatus = {} + + _initProviderIds(): void { + this.providers.forEach(provider => { + let containsGroupToConsent: boolean = false + provider.groups?.forEach(group => { + if(group.requires_consent) + { + if(!Object.keys(this.consentStatus).includes(String(group.id)) || (Object.keys(this.consentStatus).includes(String(group.id)) && !this.consentStatus[group.id])) + { + this.groupsToConsent[group.id] = false + containsGroupToConsent = true; + } + } + }) + if(containsGroupToConsent) { + this.providerIds.push(provider.id); + if(this.selectedProviderId === null) { + this.selectedProviderId = provider.id; + } + } + }) + } ngOnInit(): void { this.loading = true this._apiService.getProviders().subscribe((providers) => { @@ -77,59 +109,20 @@ export class WizardDialog { if (providers.length === 0) this.loading = false - const userConsents$ = zip( - providers.map((provider) => this._apiService.getUserConsent(provider.id)) - ) - - userConsents$.subscribe((userConsents) => { - this.providerUserConsent = userConsents.reduce((acc, userConsent, idx) => { - acc[providers[idx].id] = { - provider: providers[idx], - userConsent: userConsent.consent, - acceptedProviderSchema: userConsent.provider_schema, - defaultConsent: providerSchemaToUserConsent(providers[idx].versions[0]) - } - - return acc - }, {} as Record<ProviderId, { - provider: Provider; - userConsent: UserConsent | null; - defaultConsent: UserConsent; - acceptedProviderSchema?: ProviderSchema - }>) - - this.wizardUserConsents = userConsents.reduce((acc, userConsent, idx) => { - // Skip providers, for which user already accepted the latest version - if (userConsent.provider_schema?.superseded_by === null) return acc - acc[providers[idx].id] = { - provider: providers[idx], - userConsent: userConsent.consent, - acceptedProviderSchema: userConsent.provider_schema, - defaultConsent: providerSchemaToUserConsent(providers[idx].versions[0]) - } - - return acc - }, {} as Record<ProviderId, { - provider: Provider; - userConsent: UserConsent | null; - defaultConsent: UserConsent; - acceptedProviderSchema?: ProviderSchema - }>) - - this.providerIds = Object.keys(this.wizardUserConsents).map(Number) - - this.selectedProviderId = +Object.keys(this.wizardUserConsents)[0] ?? null + // maybe load consents + if(Object.keys(this.consentStatus).length === 0) + { + this._apiService.getUserConsentHistory().subscribe(history => { + history.groups.forEach((group: GroupedConsentHistory) => { + this.consentStatus[group.group.id] = group.group.consented + }) + this._initProviderIds() + this.loading = false + }) + } else { + this._initProviderIds() this.loading = false - }) - - this.userConsents = this.providers.map((provider) => { - const latestSchema = providerSchemaToUserConsent(provider.versions[0]) - return { - providerId: provider.id, - providerSchemaId: latestSchema.id, - verbs: extractVerbs(latestSchema, provider.id) - } - }) + } }) } @@ -153,36 +146,41 @@ export class WizardDialog { } } - showSummary(): void { - this.isSummary = true + getSelectedProvider(): Provider | null { + let _selectedProvider : Provider | null = null + this.providers.forEach(provider => { + if(provider.id === this.selectedProviderId) { + _selectedProvider = provider; + } + }) + return _selectedProvider; } - changePrivacySetting(userConsent: UserConsent, providerId: number) { - let consent = this.userConsents?.find(ele => ele.providerId == providerId) - if (consent) - consent.verbs = extractVerbs(userConsent, providerId) - this.wizardUserConsents[providerId] = { - ...this.wizardUserConsents[providerId], - userConsent: userConsent + consentChanged() : boolean { + let changed = false; + for (let group_id in this.groupsToConsent) { + if(this.groupsToConsent[group_id]) { + changed = true; + } } + return changed; } + showSummary(): void { + this.isSummary = true + } + + submit(): void { - this._apiService.saveUserConsent(this.userConsents).subscribe((response) => { + this._apiService.saveUserConsent(this.groupsToConsent).subscribe((response) => { this._messageService.success($localize`:@@toastMessageSubmitted:Submitted`) this.dialogRef.close() }) } - logout(): void { - this._authService.logout() - this.dialogRef.close() - } - - providerSchemaToUserConsent(providerSchema: ProviderSchema): UserConsent { - return providerSchemaToUserConsent(providerSchema) - } protected readonly faArrowLeft = faArrowLeft protected readonly faArrowRight = faArrowRight + protected readonly Object = Object + protected readonly String = String } diff --git a/src/frontend/src/app/services/api.service.ts b/src/frontend/src/app/services/api.service.ts index e3b7cd4074bf8aad19bfe5f8f144659cfec8520c..e83199d3e79ee9a573f83e5bb2b26db005a8f47f 100644 --- a/src/frontend/src/app/services/api.service.ts +++ b/src/frontend/src/app/services/api.service.ts @@ -8,6 +8,7 @@ import { import { HttpClient, HttpHeaders } from '@angular/common/http' import { Observable } from 'rxjs' import { environment } from '../../environments/environment' +import { GroupConsentStatus } from '../consent-management/wizard/wizard.component' export interface UserConsentResponse { paused_data_recording: boolean @@ -275,10 +276,10 @@ export class ApiService { ) } - saveUserConsent(data: UserConsentVerbs[]): Observable<UserConsentResponse> { - return this.http.post<UserConsentResponse>( + saveUserConsent(data: GroupConsentStatus): Observable<{message: string}> { + return this.http.post<{message: string}>( `${environment.apiUrl}/api/v1/consents/user/save`, - data + {groups: Object.keys(data).filter(key => data[parseInt(key)]).map(key => parseInt(key))} // submit as a list of group ids ) } diff --git a/src/frontend/src/environments/environment.prod.ts b/src/frontend/src/environments/environment.prod.ts index 4309a07e8b79b1cbb84e4d00b9f55002394bd783..7eead7da323dd3cacb2b4d5d926e9504a3ba30df 100644 --- a/src/frontend/src/environments/environment.prod.ts +++ b/src/frontend/src/environments/environment.prod.ts @@ -2,10 +2,10 @@ export const environment = { production: true, apiUrl: '', pageVisibility: { - analyses: true, + analyses: false, consent_history: true, consent_management: false, - merge_data: true, + merge_data: false, data_disclosure: true, } }; diff --git a/src/static/provider_schema.schema.json b/src/static/provider_schema.schema.json index b2509dd7d0ba13cb7dc8a63ad5fd15879c14ed14..63142de1d499a7abf9ef2569c22ddd9e3b324df0 100644 --- a/src/static/provider_schema.schema.json +++ b/src/static/provider_schema.schema.json @@ -82,7 +82,7 @@ "token_type": { "type": "string", "description": "token to authenticate with", - "enum": ["Basic", "Bearer"], + "enum": ["Basic", "Bearer", "API-Key"], "default": "Bearer" } },