From e4daf2b82580ed00aab83395e25fcf62ac689b7d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 25 Nov 2024 15:17:34 +0100 Subject: [PATCH 1/2] 120256: Ensure searchOptions$ is a SearchOptions and not a plain object --- .../search-facet-filter/search-facet-filter.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 994b488d9c7..0edbeea7ea1 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -118,7 +118,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.filterValues$ = new BehaviorSubject(createPendingRemoteDataObject()); this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); this.searchOptions$ = this.searchConfigService.searchOptions.pipe( - map((options: SearchOptions) => hasNoValue(this.scope) ? options : Object.assign({}, options, { + map((options: SearchOptions) => hasNoValue(this.scope) ? options : Object.assign(new SearchOptions(options), { scope: this.scope, })), ); From 70b855e7852f3b302e8c9ad0b5d71f9fc14e3a6d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 24 Nov 2024 22:42:05 +0100 Subject: [PATCH 2/2] 121534: Removed unauthorized metadata-export-search request on search page for non-admins --- .../search-export-csv.component.spec.ts | 19 +++++++----- .../search-export-csv.component.ts | 31 +++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts index 82c15feeace..f75f01afa2a 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.spec.ts @@ -6,7 +6,6 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati import { SearchExportCsvComponent } from './search-export-csv.component'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; -import { Script } from '../../../process-page/scripts/script.model'; import { Process } from '../../../process-page/processes/process.model'; import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; import { NotificationsService } from '../../notifications/notifications.service'; @@ -25,7 +24,6 @@ describe('SearchExportCsvComponent', () => { let notificationsService; let router; - const script = Object.assign(new Script(), {id: 'metadata-export-search', name: 'metadata-export-search'}); const process = Object.assign(new Process(), {processId: 5, scriptName: 'metadata-export-search'}); const searchConfig = new PaginatedSearchOptions({ @@ -41,7 +39,7 @@ describe('SearchExportCsvComponent', () => { function initBeforeEachAsync() { scriptDataService = jasmine.createSpyObj('scriptDataService', { - findById: createSuccessfulRemoteDataObject$(script), + scriptWithNameExistsAndCanExecute: observableOf(true), invoke: createSuccessfulRemoteDataObject$(process) }); authorizationDataService = jasmine.createSpyObj('authorizationService', { @@ -110,15 +108,22 @@ describe('SearchExportCsvComponent', () => { describe('when the metadata-export-search script is not present', () => { beforeEach(waitForAsync(() => { initBeforeEachAsync(); - (scriptDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not found', 404)); + (scriptDataService.scriptWithNameExistsAndCanExecute as jasmine.Spy).and.returnValue(observableOf(false)); })); - beforeEach(() => { - initBeforeEach(); - }); + it('should should not add the button', () => { + initBeforeEach(); + const debugElement = fixture.debugElement.query(By.css('button.export-button')); expect(debugElement).toBeNull(); }); + + it('should not call scriptWithNameExistsAndCanExecute when unauthorized', () => { + (authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + initBeforeEach(); + + expect(scriptDataService.scriptWithNameExistsAndCanExecute).not.toHaveBeenCalled(); + }); }); }); describe('export', () => { diff --git a/src/app/shared/search/search-export-csv/search-export-csv.component.ts b/src/app/shared/search/search-export-csv/search-export-csv.component.ts index 6ad105342f6..ac425214da8 100644 --- a/src/app/shared/search/search-export-csv/search-export-csv.component.ts +++ b/src/app/shared/search/search-export-csv/search-export-csv.component.ts @@ -1,8 +1,8 @@ import { Component, Input, OnInit } from '@angular/core'; -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { map } from 'rxjs/operators'; +import { map, switchMap, filter, startWith } from 'rxjs/operators'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { hasValue, isNotEmpty } from '../../empty.util'; @@ -13,6 +13,7 @@ import { NotificationsService } from '../../notifications/notifications.service' import { TranslateService } from '@ngx-translate/core'; import { Router } from '@angular/router'; import { PaginatedSearchOptions } from '../models/paginated-search-options.model'; +import { SearchFilter } from '../models/search-filter.model'; @Component({ selector: 'ds-search-export-csv', @@ -48,15 +49,11 @@ export class SearchExportCsvComponent implements OnInit { } ngOnInit(): void { - const scriptExists$ = this.scriptDataService.findById('metadata-export-search').pipe( - getFirstCompletedRemoteData(), - map((rd) => rd.isSuccess && hasValue(rd.payload)) - ); - - const isAuthorized$ = this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf); - - this.shouldShowButton$ = observableCombineLatest([scriptExists$, isAuthorized$]).pipe( - map(([scriptExists, isAuthorized]: [boolean, boolean]) => scriptExists && isAuthorized) + this.shouldShowButton$ = this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf).pipe( + filter((isAuthorized: boolean) => isAuthorized), + switchMap(() => this.scriptDataService.scriptWithNameExistsAndCanExecute('metadata-export-search')), + map((canExecute: boolean) => canExecute), + startWith(false), ); } @@ -76,19 +73,19 @@ export class SearchExportCsvComponent implements OnInit { parameters.push({name: '-c', value: this.searchConfig.configuration}); } if (isNotEmpty(this.searchConfig.filters)) { - this.searchConfig.filters.forEach((filter) => { - if (hasValue(filter.values)) { - filter.values.forEach((value) => { + this.searchConfig.filters.forEach((searchFilter: SearchFilter) => { + if (hasValue(searchFilter.values)) { + searchFilter.values.forEach((value: string) => { let operator; let filterValue; - if (hasValue(filter.operator)) { - operator = filter.operator; + if (hasValue(searchFilter.operator)) { + operator = searchFilter.operator; filterValue = value; } else { operator = value.substring(value.lastIndexOf(',') + 1); filterValue = value.substring(0, value.lastIndexOf(',')); } - const valueToAdd = `${filter.key.substring(2)},${operator}=${filterValue}`; + const valueToAdd = `${searchFilter.key.substring(2)},${operator}=${filterValue}`; parameters.push({name: '-f', value: valueToAdd}); }); }