From 7eb8f2522403b129ecceff3e8e3b87fe4833098f Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Mon, 13 Apr 2026 11:42:19 -0400 Subject: [PATCH 01/10] fixed ORCID null issue that prevented display of DMP contributors --- oar-dmp/src/app/app.module.ts | 4 ++-- .../src/app/form-components/personel/personel.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 97fe476..8e7d171 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - AuthModule, + // AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - // { provide: AuthenticationService, useClass:MockAuthenticationService } + { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent] diff --git a/oar-dmp/src/app/form-components/personel/personel.component.ts b/oar-dmp/src/app/form-components/personel/personel.component.ts index 998e13a..94b3aa0 100644 --- a/oar-dmp/src/app/form-components/personel/personel.component.ts +++ b/oar-dmp/src/app/form-components/personel/personel.component.ts @@ -346,7 +346,7 @@ export class PersonelComponent implements OnInit { this.contribOrcidWarn = ''; personel.contributors.forEach( (dmpContributor, index) => { - if (dmpContributor.orcid.length === 0){ + if (!dmpContributor.orcid){ this.contribOrcidWarn = PersonelComponent.ORCID_WARNING; } this.dmpContributors.push({ From 90650c3dad8b3f4c1c743af3f5ab61bb0d65eb46 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Mon, 13 Apr 2026 11:46:45 -0400 Subject: [PATCH 02/10] disabled mock authentication --- oar-dmp/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 8e7d171..97fe476 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - // AuthModule, + AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - { provide: AuthenticationService, useClass:MockAuthenticationService } + // { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent] From b58101a511b8949b1a727c4dfa1ec58198886707 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Wed, 15 Apr 2026 18:33:06 -0400 Subject: [PATCH 03/10] optimized code to not include id and isEdit tags to contributors when auto updating and saving metadata from people service --- oar-dmp/src/app/app.module.ts | 4 +- .../personel/personel.component.ts | 83 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 97fe476..8e7d171 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - AuthModule, + // AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - // { provide: AuthenticationService, useClass:MockAuthenticationService } + { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent] diff --git a/oar-dmp/src/app/form-components/personel/personel.component.ts b/oar-dmp/src/app/form-components/personel/personel.component.ts index 94b3aa0..075f29b 100644 --- a/oar-dmp/src/app/form-components/personel/personel.component.ts +++ b/oar-dmp/src/app/form-components/personel/personel.component.ts @@ -494,7 +494,8 @@ export class PersonelComponent implements OnInit { if (this.NISTPersonMetaChanged){ console.info(`Metadata for ${dmpContributor.firstName} ${dmpContributor.lastName} has been been updated to reflect most recent info found in the NIST people service database.`) // add changes to the form values if any changes were made to NIST contributors metadata - this.personelForm.value['contributors'] = this.dmpContributors; + this.RePopulateTable(); + // this.personelForm.value['contributors'] = this.dmpContributors; // patch value to indicate that the form has changed this.personelForm.patchValue({ @@ -834,49 +835,55 @@ export class PersonelComponent implements OnInit { this.resetContributorFields(); this.resetWarningAndErrorMessages(); } + + RePopulateTable(){ + this.dmpContributors = this.dmpContributors.filter((u: any) => !u.isSelected); + this.resetTable(); + this.contribOrcidWarn = ""; + this.dmpContributors.forEach((element)=>{ + if (element.orcid.length == 0){ + this.contribOrcidWarn = PersonelComponent.ORCID_WARNING; + } + // re populate contributors array + this.personelForm.value['contributors'].push({ + + firstName:element.firstName, + lastName:element.lastName, + orcid: element.orcid, + emailAddress: element.emailAddress, + + groupOrgID:element.groupOrgID, + groupNumber:element.groupNumber, + groupName:element.groupName, + + divisionOrgID:element.divisionOrgID, + divisionNumber:element.divisionNumber, + divisionName:element.divisionName, + + ouOrgID:element.ouOrgID, + ouNumber:element.ouNumber, + ouName:element.ouName, + + primary_contact: element.primary_contact, + institution: element.institution, + role: element.role + }); + }); + if (this.dmpContributors.length === 0){ + // If the table is empty disable clear and remove buttons + this.disableClear=true; + this.disableRemove=true; + } + + } removeSelectedRows() { const result = confirmDialog("Are you sure you want to delete selected contributor(s) for this DMP?"); if (result) { - this.dmpContributors = this.dmpContributors.filter((u: any) => !u.isSelected); - this.resetTable(); - this.contribOrcidWarn = ""; - this.dmpContributors.forEach((element)=>{ - if (element.orcid.length == 0){ - this.contribOrcidWarn = PersonelComponent.ORCID_WARNING; - } - // re populate contributors array - this.personelForm.value['contributors'].push({ - - firstName:element.firstName, - lastName:element.lastName, - orcid: element.orcid, - emailAddress: element.emailAddress, - - groupOrgID:element.groupOrgID, - groupNumber:element.groupNumber, - groupName:element.groupName, - - divisionOrgID:element.divisionOrgID, - divisionNumber:element.divisionNumber, - divisionName:element.divisionName, - - ouOrgID:element.ouOrgID, - ouNumber:element.ouNumber, - ouName:element.ouName, - - primary_contact: element.primary_contact, - institution: element.institution, - role: element.role - }); - }); - if (this.dmpContributors.length === 0){ - // If the table is empty disable clear and remove buttons - this.disableClear=true; - this.disableRemove=true; - } + this.RePopulateTable(); + } } From 7df38009391a5c968be9d55d0c170e2cbde15ca2 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Thu, 16 Apr 2026 23:18:41 -0400 Subject: [PATCH 04/10] added capability for keywords input to create chips even if user does not hit Enter key but rather clicks with a mousepointer elswehere. this was done by creating a mock event to fire off on a blur event --- .../keywords/keywords.component.html | 4 ++ .../keywords/keywords.component.ts | 53 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/oar-dmp/src/app/form-components/keywords/keywords.component.html b/oar-dmp/src/app/form-components/keywords/keywords.component.html index 06d747c..30e4d04 100644 --- a/oar-dmp/src/app/form-components/keywords/keywords.component.html +++ b/oar-dmp/src/app/form-components/keywords/keywords.component.html @@ -18,9 +18,13 @@ } diff --git a/oar-dmp/src/app/form-components/keywords/keywords.component.ts b/oar-dmp/src/app/form-components/keywords/keywords.component.ts index f2f2d10..f92a359 100644 --- a/oar-dmp/src/app/form-components/keywords/keywords.component.ts +++ b/oar-dmp/src/app/form-components/keywords/keywords.component.ts @@ -1,6 +1,6 @@ -import { Component, Input, Output } from '@angular/core'; +import { Component, Input, Output, ViewChild, ElementRef } from '@angular/core'; import { UntypedFormBuilder } from '@angular/forms'; -import { MatChipInputEvent } from '@angular/material/chips'; +import { MatChipInputEvent, MatChipInput} from '@angular/material/chips'; import { defer, map, of, startWith } from 'rxjs'; import { DMP_Meta } from '../../types/DMP.types'; import { ChipsSplitterService } from 'src/app/shared/chips-splitter.service'; @@ -21,7 +21,13 @@ export class KeywordsComponent { } ); - reactiveKeywords = signal(['']); + reactiveKeywords = signal(['']); + keywordsInputVal = ''; + // Reference the HTML input element that uses chips matching the #chipInput in the HTML + @ViewChild('chipInput') chipInputEl!: ElementRef; + + // This finds the MatChipInput directive inside that same element + @ViewChild(MatChipInput) chipInputDirective!: MatChipInput; constructor(private fb: UntypedFormBuilder, private spChips: ChipsSplitterService) { // console.log("Keywords Component"); @@ -114,11 +120,18 @@ export class KeywordsComponent { } addReactiveKeyword(event: MatChipInputEvent): void { - const chips = (this.spChips.splitChips(event.value.trim()) || '') + // To clean up chips array and ensure no empty strings or "just whitespace" items make it through, + // we should make fall back to an empty array [] and use the JavaScript .filter() method. + const chips = (this.spChips.splitChips(event.value.trim()) || []) + .filter(chip => chip.trim().length > 0); // Add our keyword if (chips) { - this.reactiveKeywords.update(keywords => [...keywords, ...chips]); + this.reactiveKeywords.update(keywords => { + // Combine both arrays into a Set to force uniqueness, + // then spread it back into a standard array. + return [...new Set([...keywords, ...chips])]; + }); chips.forEach((chip)=>{ this.keyWordsForm.patchValue({ keywords: chip @@ -130,6 +143,36 @@ export class KeywordsComponent { event.chipInput!.clear(); } + onBlur(event: FocusEvent) { + // this is called if user did not hit enter on keyboard to add chips but has rather pressed + // elsewhere with a mouse + + // Trigger event if input is not empty + if (this.keywordsInputVal !== ''){ + this.triggerAddChip(); + } + } + + onInputChange(value: string){ + // console.log('onInputChange', value); + this.keywordsInputVal = value; + + } + + triggerAddChip() { + //Construct the mock event + const mockEvent: MatChipInputEvent = { + input: this.chipInputEl.nativeElement, + value: this.keywordsInputVal.trim(), + chipInput: this.chipInputDirective + }; + + // Manually call your existing add function + this.addReactiveKeyword(mockEvent); + // Clear input value + this.keywordsInputVal = ''; + } + } From 063e68e40ad979032a2d1565280e06db08366511 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Thu, 16 Apr 2026 23:44:33 -0400 Subject: [PATCH 05/10] added capability for needed equipment input to create chips even if user does not hit Enter key but rather clicks with a mousepointer elswehere. this was done by creating a mock event to fire off on a blur event --- .../technical-requirements.component.html | 5 +- .../technical-requirements.component.ts | 53 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.html b/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.html index b082ee4..7084fd6 100644 --- a/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.html +++ b/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.html @@ -118,9 +118,12 @@ } diff --git a/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.ts b/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.ts index eb4c199..a27ddb8 100644 --- a/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.ts +++ b/oar-dmp/src/app/form-components/technical-requirements/technical-requirements.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, ChangeDetectionStrategy, inject, signal } from '@angular/core'; +import { Component, Input, Output, ChangeDetectionStrategy, signal, ViewChild, ElementRef } from '@angular/core'; import { confirmDialog } from 'src/app/shared/dmp.service'; import { DropDownSelectService } from '../../shared/drop-down-select.service'; //resources service to talk between two components @@ -12,7 +12,7 @@ import { DMP_Meta } from '../../types/DMP.types'; import { SoftwareDevelopment } from '../../types/software-development.type'; import { Subscription } from 'rxjs'; -import { MatChipInputEvent } from '@angular/material/chips'; +import { MatChipInputEvent, MatChipInput } from '@angular/material/chips'; import { ChipsSplitterService } from 'src/app/shared/chips-splitter.service'; interface InstrTblRow { @@ -81,6 +81,12 @@ export class StorageNeedsComponent { separatorExp: RegExp = /,|;/; reactiveInstruments = signal(['']); + instrumentsInputVal = ''; + // Reference the HTML input element that uses chips matching the #equipmentChips in the HTML + @ViewChild('equipmentChips') chipInputEl!: ElementRef; + + // This finds the MatChipInput directive inside that same element + @ViewChild(MatChipInput) chipInputDirective!: MatChipInput; // This mimics the technical-requirements type interface from // types/technical-requirements.type.ts @@ -654,11 +660,18 @@ export class StorageNeedsComponent { } addReactiveInstruments(event: MatChipInputEvent): void { - const chips = (this.spChips.splitChips(event.value.trim()) || '') + // To clean up chips array and ensure no empty strings or "just whitespace" items make it through, + // we should make fall back to an empty array [] and use the JavaScript .filter() method. + const chips = (this.spChips.splitChips(event.value.trim()) || []) + .filter(chip => chip.trim().length > 0); - // Add our keyword + // Add our instrument if (chips) { - this.reactiveInstruments.update(technicalResources => [...technicalResources, ...chips]); + this.reactiveInstruments.update(technicalResources => { + // Combine both arrays into a Set to force uniqueness, + // then spread it back into a standard array. + return [...new Set([...technicalResources, ...chips])]; + }); chips.forEach((chip)=>{ this.technicalRequirementsForm.patchValue({ technicalResources: chip @@ -671,6 +684,36 @@ export class StorageNeedsComponent { event.chipInput!.clear(); } + onBlur(event: FocusEvent) { + // this is called if user did not hit enter on keyboard to add chips but has rather pressed + // elsewhere with a mouse + + // Trigger event if input is not empty + if (this.instrumentsInputVal !== ''){ + this.triggerAddChip(); + } + } + + onInputChange(value: string){ + // console.log('onInputChange', value); + this.instrumentsInputVal = value; + + } + + triggerAddChip() { + //Construct the mock event + const mockEvent: MatChipInputEvent = { + input: this.chipInputEl.nativeElement, + value: this.instrumentsInputVal.trim(), + chipInput: this.chipInputDirective + }; + + // Manually call your existing add function + this.addReactiveInstruments(mockEvent); + // Clear input value + this.instrumentsInputVal = ''; + } + } From 9a19f34f4f6a5da5cabcbc683e255c3096b6b7b1 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Fri, 17 Apr 2026 00:17:49 -0400 Subject: [PATCH 06/10] added capability for paths and URLs input to create chips even if user does not hit Enter key but rather clicks with a mousepointer elswehere. this was done by creating a mock event to fire off on a blur event --- .../data-preservation.component.html | 5 +- .../data-preservation.component.ts | 53 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.html b/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.html index 1741e88..e552799 100644 --- a/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.html +++ b/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.html @@ -37,9 +37,12 @@ } diff --git a/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.ts b/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.ts index 37870e9..e50ef25 100644 --- a/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.ts +++ b/oar-dmp/src/app/form-components/data-preservation/data-preservation.component.ts @@ -1,8 +1,8 @@ -import { Component, Input, Output, ChangeDetectionStrategy, inject, signal } from '@angular/core'; +import { Component, Input, Output, ChangeDetectionStrategy, signal, ViewChild, ElementRef } from '@angular/core'; import { UntypedFormBuilder } from '@angular/forms'; import { defer, map, of, startWith } from 'rxjs'; -import { MatChipInputEvent } from '@angular/material/chips'; +import { MatChipInputEvent, MatChipInput } from '@angular/material/chips'; import { ChipsSplitterService } from 'src/app/shared/chips-splitter.service'; @@ -18,6 +18,12 @@ export class DataPreservationComponent { separatorExp: RegExp = /,|;/; reactivePathsURLs = signal(['']); + pathsInputVal = ''; + // Reference the HTML input element that uses chips matching the #pathInput in the HTML + @ViewChild('pathInput') chipInputEl!: ElementRef; + + // This finds the MatChipInput directive inside that same element + @ViewChild(MatChipInput) chipInputDirective!: MatChipInput; preservationForm = this.fb.group( { @@ -145,11 +151,18 @@ export class DataPreservationComponent { } addReactivePathsURLs(event: MatChipInputEvent): void { - const chips = (this.spChips.splitChips(event.value.trim()) || '') + // To clean up chips array and ensure no empty strings or "just whitespace" items make it through, + // we should make fall back to an empty array [] and use the JavaScript .filter() method. + const chips = (this.spChips.splitChips(event.value.trim()) || []) + .filter(chip => chip.trim().length > 0); - // Add our keyword + // Add our path if (chips) { - this.reactivePathsURLs.update(pathsURLs => [...pathsURLs, ...chips]); + this.reactivePathsURLs.update(pathsURLs => { + // Combine both arrays into a Set to force uniqueness, + // then spread it back into a standard array. + return [...new Set([...pathsURLs, ...chips])]; + }); chips.forEach((chip)=>{ this.preservationForm.patchValue({ pathsURLs: chip @@ -162,4 +175,34 @@ export class DataPreservationComponent { event.chipInput!.clear(); } + onBlur(event: FocusEvent) { + // this is called if user did not hit enter on keyboard to add chips but has rather pressed + // elsewhere with a mouse + + // Trigger event if input is not empty + if (this.pathsInputVal !== ''){ + this.triggerAddChip(); + } + } + + onInputChange(value: string){ + // console.log('onInputChange', value); + this.pathsInputVal = value; + + } + + triggerAddChip() { + //Construct the mock event + const mockEvent: MatChipInputEvent = { + input: this.chipInputEl.nativeElement, + value: this.pathsInputVal.trim(), + chipInput: this.chipInputDirective + }; + + // Manually call your existing add function + this.addReactivePathsURLs(mockEvent); + // Clear input value + this.pathsInputVal = ''; + } + } From b01a1c1c92ba19ae611578308501cb949d446899 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Fri, 17 Apr 2026 12:10:10 -0400 Subject: [PATCH 07/10] disabled mock authentication --- oar-dmp/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 8e7d171..97fe476 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - // AuthModule, + AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - { provide: AuthenticationService, useClass:MockAuthenticationService } + // { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent] From 7c465ecdbc8b832d844e6bac002f4a19dc96ca88 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Mon, 27 Apr 2026 23:38:14 -0400 Subject: [PATCH 08/10] initial implementation of checking whether the user has write privileges and based on that whether changes made to DMP can be saved as well as disabling auto updated of personel and OUs if the user does not have write privileges --- oar-dmp/src/app/app.module.ts | 4 +- .../src/app/dmp-form/dmp-form.component.ts | 43 +++++++++++++++---- .../personel/personel.component.ts | 2 +- oar-dmp/src/app/shared/dmp.service.ts | 17 ++++++++ 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 97fe476..8e7d171 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - AuthModule, + // AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - // { provide: AuthenticationService, useClass:MockAuthenticationService } + { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent] diff --git a/oar-dmp/src/app/dmp-form/dmp-form.component.ts b/oar-dmp/src/app/dmp-form/dmp-form.component.ts index c3630a5..c911180 100644 --- a/oar-dmp/src/app/dmp-form/dmp-form.component.ts +++ b/oar-dmp/src/app/dmp-form/dmp-form.component.ts @@ -127,6 +127,8 @@ export class DmpFormComponent implements OnInit{ OUsUpdate: UpdateIndicator = {numUpdates:0, isUpdated:false}; OUsTotalUpdates:number = 0; + canWrite:boolean = false; + constructor( private fb: UntypedFormBuilder, private dmp_Service: DmpService, @@ -138,7 +140,7 @@ export class DmpFormComponent implements OnInit{ private updateOUs: UpdateNistContributorService ) { - // console.log("constructor"); + console.log("constructor"); afterNextRender(() => { // used for one-time initialisation: // subscribe to track if the form has been changed by performing @@ -185,7 +187,7 @@ export class DmpFormComponent implements OnInit{ getFromDB:boolean = false; ngOnInit(): void { - // console.log("dmp-form.component ngOnInit") + console.log("dmp-form.component ngOnInit") // const elementToObserve = document.getElementById("footer"); @@ -205,6 +207,23 @@ export class DmpFormComponent implements OnInit{ this.formButtonSubscribe(); this.formExportFormatSubscribe(); this.id = this.route.snapshot.paramMap.get('id') + + if (this.id !==null){ + // if record ID exists then check if the user has write permissions + this.dmp_Service.writePermission(this.id).subscribe( + { + next: permission => { + this.canWrite = permission; + }, + error: error => { + console.log(error.message); + this.router.navigate(['error', { dmpError: this.buildErrorMessage(error) }]); + + } + } + ); + } + this.route.data.subscribe(data => { this.action = data["action"] ; if (this.action === "edit"){ @@ -260,7 +279,13 @@ export class DmpFormComponent implements OnInit{ this.resetDmp(); } else if (this.formButtonMessage === "Save"){ - this.saveDraft(); + if (this.canWrite){ + this.saveDraft(); + } + else{ + alert("Can not save changes to DMP because you don't have write privileges on this record."); + } + } else if (this.formButtonMessage === "Download"){ if (this.dmpExportFormatType === ""){ @@ -290,14 +315,14 @@ export class DmpFormComponent implements OnInit{ if (!this.formContributorsSubscription) { //subscribe if not already subscribed this.formContributorsSubscription = this.updateContributor.updateNISTContrib$.subscribe({ - next: (hasChanged:UpdateIndicator) => { + next: (hasChanged:UpdateIndicator) => { if (hasChanged.isUpdated){ // change this flag to indicate that we need to display alert about auto update of NIST contributors metadata from people service this.contributorsUpdate.isUpdated = hasChanged.isUpdated; // update this counter to indicate how many contributors have been updated this.contribTotalUpdates = hasChanged.numUpdates; this.saveDraft(); - } + } } }); } @@ -309,7 +334,7 @@ export class DmpFormComponent implements OnInit{ if (!this.formOUsSubscription) { //subscribe if not already subscribed this.formOUsSubscription = this.updateOUs.updateOUs$.subscribe({ - next: (hasChanged:UpdateIndicator) => { + next: (hasChanged:UpdateIndicator) => { if (hasChanged.isUpdated){ // change this flag to indicate that we need to display alert about auto update of NIST contributors metadata from people service this.OUsUpdate.isUpdated = hasChanged.isUpdated; @@ -406,8 +431,10 @@ export class DmpFormComponent implements OnInit{ // arr1 = [...arr1, ...arr2]; // arr1 is now [0, 1, 2, 3, 4, 5] this.dmp = { ...this.dmp, ...patch }; - this.contributorsSubscribe(); - this.OUsSubscribe(); + if (this.canWrite){ + this.contributorsSubscribe(); + this.OUsSubscribe(); + } } enableSaveButton(){ diff --git a/oar-dmp/src/app/form-components/personel/personel.component.ts b/oar-dmp/src/app/form-components/personel/personel.component.ts index 075f29b..10a37f8 100644 --- a/oar-dmp/src/app/form-components/personel/personel.component.ts +++ b/oar-dmp/src/app/form-components/personel/personel.component.ts @@ -492,7 +492,7 @@ export class PersonelComponent implements OnInit { }, complete: () => { if (this.NISTPersonMetaChanged){ - console.info(`Metadata for ${dmpContributor.firstName} ${dmpContributor.lastName} has been been updated to reflect most recent info found in the NIST people service database.`) + console.info(`Metadata for ${dmpContributor.firstName} ${dmpContributor.lastName} does not match most recent info found in the NIST people service database.`) // add changes to the form values if any changes were made to NIST contributors metadata this.RePopulateTable(); // this.personelForm.value['contributors'] = this.dmpContributors; diff --git a/oar-dmp/src/app/shared/dmp.service.ts b/oar-dmp/src/app/shared/dmp.service.ts index 83a71d9..2039a66 100644 --- a/oar-dmp/src/app/shared/dmp.service.ts +++ b/oar-dmp/src/app/shared/dmp.service.ts @@ -162,6 +162,23 @@ export class DmpService { } + writePermission(recordID:string|null) { + /** + * get DMP write permissions from API + */ + let apiAddress:string = this.configService.getConfig().PDRDMP; //this.PDR_API; + if (recordID !==null){ + apiAddress += "/" + recordID +"/acls/write/:user"; + } + return this.authService.getCredentials().pipe( + switchMap(creds => { + if (! creds) + return throwError(() => new Error('Authentication Failed')); + return this.http.get(apiAddress, this.getHttpOptions(creds)) + }) + ); + } + } export function confirmDialog(message: string): boolean { From 405795faad22adcda5844b9f2230068e70139573 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Wed, 29 Apr 2026 23:26:53 -0400 Subject: [PATCH 09/10] modified function for retreival of permissions so that any kind of permission can be checked, and modified logic for retreiving permissions and dmp data --- .../src/app/dmp-form/dmp-form.component.ts | 63 +++++++++++-------- oar-dmp/src/app/shared/dmp.service.ts | 4 +- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/oar-dmp/src/app/dmp-form/dmp-form.component.ts b/oar-dmp/src/app/dmp-form/dmp-form.component.ts index c911180..9966dce 100644 --- a/oar-dmp/src/app/dmp-form/dmp-form.component.ts +++ b/oar-dmp/src/app/dmp-form/dmp-form.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild, afterNextRender } from '@angular/core'; -import { ObservedValueOf, Subscription } from "rxjs"; +import { ObservedValueOf, Subscription, forkJoin, switchMap, of, EMPTY } from "rxjs"; import { UntypedFormBuilder } from '@angular/forms'; import { BasicInfoComponent } from '../form-components/basic-info/basic-info.component'; import { PersonelComponent } from '../form-components/personel/personel.component'; @@ -127,7 +127,9 @@ export class DmpFormComponent implements OnInit{ OUsUpdate: UpdateIndicator = {numUpdates:0, isUpdated:false}; OUsTotalUpdates:number = 0; - canWrite:boolean = false; + canWrite:boolean = false; + isAdmin:boolean = false; + canDelete:boolean = false; constructor( private fb: UntypedFormBuilder, @@ -206,23 +208,7 @@ export class DmpFormComponent implements OnInit{ this.formButtonSubscribe(); this.formExportFormatSubscribe(); - this.id = this.route.snapshot.paramMap.get('id') - - if (this.id !==null){ - // if record ID exists then check if the user has write permissions - this.dmp_Service.writePermission(this.id).subscribe( - { - next: permission => { - this.canWrite = permission; - }, - error: error => { - console.log(error.message); - this.router.navigate(['error', { dmpError: this.buildErrorMessage(error) }]); - - } - } - ); - } + this.id = this.route.snapshot.paramMap.get('id'); this.route.data.subscribe(data => { this.action = data["action"] ; @@ -236,24 +222,47 @@ export class DmpFormComponent implements OnInit{ } }); - // Fetch initial data from the backend - this.dmp_Service.fetchDMP(this.action, this.id).subscribe( + // 1. Start with the permission check + this.dmp_Service.aclsPermission(this.id, 'read').pipe( + switchMap(hasReadAccess => { + if (hasReadAccess) { + // 2. If true, move to the next "link" in the chain: fetch everything else + return forkJoin({ + dmpData: this.dmp_Service.fetchDMP(this.action, this.id), // Fetch initial data from the backend + writePerm: this.dmp_Service.aclsPermission(this.id, 'write'), + adminPerm: this.dmp_Service.aclsPermission(this.id, 'admin'), + deletePerm: this.dmp_Service.aclsPermission(this.id, 'delete'), + }); + } + else { + // 3. If false, stop the chain and handle the lack of access + this.router.navigate(['error', { dmpError: "You do not have read privileges for this record." }]); + return EMPTY; // Effectively kills the stream so 'next' isn't called + } + }) + ).subscribe( { - next: data => { + next: (result) => { + const { dmpData, writePerm, adminPerm, deletePerm } = result; if (this.id !==null){ // fetch DMP data from the backend - this.initialDMP = data.data; - this.dmp = data.data; - this.name.setValue(data.name); + this.initialDMP = dmpData.data; + this.dmp = dmpData.data; + this.name.setValue(dmpData.name); this.getFromDB = true; } else{ // empty new form for creating new DMP - this.initialDMP = data; - this.dmp = data; + this.initialDMP = dmpData; + this.dmp = dmpData; // disable save button by default until user has made a change on the form this.disableSaveButton(); } + + // Set permission flags + this.canWrite = writePerm; + this.isAdmin = adminPerm; + this.canDelete = deletePerm }, error: error => { console.log(error.message); diff --git a/oar-dmp/src/app/shared/dmp.service.ts b/oar-dmp/src/app/shared/dmp.service.ts index 2039a66..b20be41 100644 --- a/oar-dmp/src/app/shared/dmp.service.ts +++ b/oar-dmp/src/app/shared/dmp.service.ts @@ -162,13 +162,13 @@ export class DmpService { } - writePermission(recordID:string|null) { + aclsPermission(recordID:string|null, permissionType:string) { /** * get DMP write permissions from API */ let apiAddress:string = this.configService.getConfig().PDRDMP; //this.PDR_API; if (recordID !==null){ - apiAddress += "/" + recordID +"/acls/write/:user"; + apiAddress += "/" + recordID +"/acls/" + permissionType + "/:user"; } return this.authService.getCredentials().pipe( switchMap(creds => { From e5e5d16fbba99d28c73a584776865ad4a1b8e8b2 Mon Sep 17 00:00:00 2001 From: Niksa Blonder Date: Wed, 29 Apr 2026 23:35:43 -0400 Subject: [PATCH 10/10] disabled mock authentication --- oar-dmp/src/app/app.module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oar-dmp/src/app/app.module.ts b/oar-dmp/src/app/app.module.ts index 8e7d171..97fe476 100644 --- a/oar-dmp/src/app/app.module.ts +++ b/oar-dmp/src/app/app.module.ts @@ -66,7 +66,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv BrowserModule, FormsModule, HttpClientModule, - // AuthModule, + AuthModule, ConfigModule, StaffDirModule, FrameModule, @@ -94,7 +94,7 @@ import { SecurityAndPrivacyComponent } from './form-components/security-and-priv providers: [ { provide: RELEASE_INFO, useValue: RELEASE }, { provide: CONFIG_URL, useValue: environment.configUrl }, - { provide: AuthenticationService, useClass:MockAuthenticationService } + // { provide: AuthenticationService, useClass:MockAuthenticationService } ], bootstrap: [AppComponent]