diff --git a/angular.json b/angular.json index 26b42f19709..a032f797530 100644 --- a/angular.json +++ b/angular.json @@ -40,7 +40,12 @@ "aot": true, "assets": [ "src/assets", - "src/robots.txt" + "src/robots.txt", + { + "glob": "favicon.ico", + "input": "src/themes/datashare/assets/images", + "output": "/" + } ], "styles": [ "src/styles/startup.scss", diff --git a/config/config.example.yml b/config/config.example.yml index 8ea58b96e40..154b0dea47a 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -465,6 +465,10 @@ info: enableEndUserAgreement: true enablePrivacyStatement: true enableCOARNotifySupport: true + # Only enable Google Analytics on installations that configure a `google.analytics.key` + # property on the backend. When false (default) the UI does not probe for that property, + # avoiding a 404 request on every page. + enableGoogleAnalytics: false # Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/) # display in supported metadata fields. By default, only dc.description.abstract is supported. diff --git a/src/app/datashare/datashare-submission-form-section-container.service.ts b/src/app/datashare/datashare-submission-form-section-container.service.ts index 90c6a9e8720..4963dd6f3e5 100644 --- a/src/app/datashare/datashare-submission-form-section-container.service.ts +++ b/src/app/datashare/datashare-submission-form-section-container.service.ts @@ -19,7 +19,6 @@ export class DatashareSubmissionFormSectionContainerService { * @param id The ID of the panel to open, or null to close all panels */ setOpenPanelId(id: string | null): void { - console.log('Setting open panel ID to:', id); this.openPanelId.set(id); } } diff --git a/src/app/datashare/datashare-submission.service.ts b/src/app/datashare/datashare-submission.service.ts index e6e7de66b4b..63a4d10484d 100644 --- a/src/app/datashare/datashare-submission.service.ts +++ b/src/app/datashare/datashare-submission.service.ts @@ -23,14 +23,12 @@ export class DatashareSubmissionService { constructor(private notificationsService: NotificationsService, private translate: TranslateService, ) { - console.log('DatashareSubmissionService created'); } /** * Update the deposit button visibility state */ updatehasUploadFilesErrors(show: boolean): void { - console.log('Updating hasUploadFilesErrors to:', show); this._hasUploadFilesErrorsSignal.set(show); } diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.ts b/src/app/item-page/simple/field-components/file-section/file-section.component.ts index 6d630ff70f5..09131f6d997 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.ts +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.ts @@ -154,10 +154,6 @@ export class FileSectionComponent implements OnInit { this.notificationsService.error(this.translateService.get('file-section.error.header'), `${bitstreamsRD.statusCode} ${bitstreamsRD.errorMessage}`); } else if (hasValue(bitstreamsRD.payload)) { const current: Bitstream[] = this.bitstreams$.getValue(); - // For debugging. - bitstreamsRD.payload.page.forEach(bitstream => { - console.log('Bitstream:', bitstream); - }); this.bitstreams$.next([...current, ...bitstreamsRD.payload.page]); this.isLoading = false; this.isLastPage = this.currentPage === bitstreamsRD.payload.totalPages; @@ -169,10 +165,6 @@ export class FileSectionComponent implements OnInit { this.notificationsService.error(this.translateService.get('file-section.error.header'), `${licenseRD.statusCode} ${licenseRD.errorMessage}`); } else if (hasValue(licenseRD.payload)) { const updated: Bitstream[] = this.bitstreams$.getValue(); - // For debugging. - licenseRD.payload.page.forEach(bitstream => { - console.log('Bitstream:', bitstream); - }); this.bitstreams$.next([...updated, ...licenseRD.payload.page]); } this.isLoading = false; diff --git a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts index 16141b0359b..d007ffd5907 100644 --- a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts +++ b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts @@ -13,6 +13,7 @@ import { SplitPipe } from 'src/app/shared/utils/split.pipe'; import { APP_DATA_SERVICES_MAP } from '../../../../config/app-config.interface'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { RequestService } from '../../../core/data/request.service'; import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model'; import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service'; @@ -28,6 +29,7 @@ describe('QaEventNotificationComponent', () => { let component: QaEventNotificationComponent; let fixture: ComponentFixture; let qualityAssuranceSourceDataServiceStub: any; + let authorizationServiceStub: any; const obj = Object.assign(new QualityAssuranceSourceObject(), { id: 'sourceName:target', @@ -41,7 +43,10 @@ describe('QaEventNotificationComponent', () => { beforeEach(async () => { qualityAssuranceSourceDataServiceStub = { - getSourcesByTarget: () => objPL, + getSourcesByTarget: jasmine.createSpy('getSourcesByTarget').and.returnValue(objPL), + }; + authorizationServiceStub = { + isAuthorized: jasmine.createSpy('isAuthorized').and.returnValue(of(true)), }; await TestBed.configureTestingModule({ imports: [CommonModule, TranslateModule.forRoot(), QaEventNotificationComponent, SplitPipe], @@ -49,6 +54,7 @@ describe('QaEventNotificationComponent', () => { { provide: APP_DATA_SERVICES_MAP, useValue: {} }, { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, { provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub }, + { provide: AuthorizationDataService, useValue: authorizationServiceStub }, { provide: RequestService, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: HALEndpointService, useValue: new HALEndpointServiceStub('test') }, @@ -57,6 +63,12 @@ describe('QaEventNotificationComponent', () => { provideMockStore({}), ], }) + // The component declares QualityAssuranceSourceDataService in its own `providers`, + // which would otherwise shadow the stub above with a real instance. Override it so + // the component uses the stub we can assert against. + .overrideComponent(QaEventNotificationComponent, { + set: { providers: [{ provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub }] }, + }) .compileComponents(); fixture = TestBed.createComponent(QaEventNotificationComponent); component = fixture.componentInstance; @@ -78,4 +90,22 @@ describe('QaEventNotificationComponent', () => { const route = component.getQualityAssuranceRoute(); expect(route).toBe('/notifications/quality-assurance'); }); + + it('should request QA sources when the user is authorized to see QA', () => { + authorizationServiceStub.isAuthorized.and.returnValue(of(true)); + qualityAssuranceSourceDataServiceStub.getSourcesByTarget.calls.reset(); + let result: QualityAssuranceSourceObject[]; + component.getQualityAssuranceSources$().subscribe((sources) => result = sources); + expect(qualityAssuranceSourceDataServiceStub.getSourcesByTarget).toHaveBeenCalled(); + expect(result).toEqual([obj]); + }); + + it('should NOT request QA sources when the user is not authorized', () => { + authorizationServiceStub.isAuthorized.and.returnValue(of(false)); + qualityAssuranceSourceDataServiceStub.getSourcesByTarget.calls.reset(); + let result: QualityAssuranceSourceObject[]; + component.getQualityAssuranceSources$().subscribe((sources) => result = sources); + expect(qualityAssuranceSourceDataServiceStub.getSourcesByTarget).not.toHaveBeenCalled(); + expect(result).toEqual([]); + }); }); diff --git a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts index a428f7feb4f..bef2d7adb99 100644 --- a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts +++ b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts @@ -12,14 +12,20 @@ import { } from '@angular/core'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; +import { + Observable, + of as observableOf, +} from 'rxjs'; import { catchError, map, + switchMap, } from 'rxjs/operators'; import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths'; import { RequestParam } from '../../../core/cache/models/request-param.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { FindListOptions } from '../../../core/data/find-list-options.model'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; @@ -61,6 +67,7 @@ export class QaEventNotificationComponent implements OnChanges { constructor( private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService, + private authorizationService: AuthorizationDataService, ) {} /** @@ -77,20 +84,31 @@ export class QaEventNotificationComponent implements OnChanges { * Note: sourceId is composed as: id: "sourceName:" */ getQualityAssuranceSources$(): Observable { - const findListTopicOptions: FindListOptions = { - searchParams: [new RequestParam('target', this.item.uuid)], - }; - return this.qualityAssuranceSourceDataService.getSourcesByTarget(findListTopicOptions, false) - .pipe( - getFirstCompletedRemoteData(), - map((data: RemoteData>) => { - if (data.hasSucceeded) { - return data.payload.page; - } - return []; - }), - catchError(() => []), - ); + // Quality Assurance sources are only available to authorized users. Checking the + // authorization first avoids issuing the `qualityassurancesources/search/byTarget` + // request for anonymous/unauthorized users, which would otherwise return a 401 that + // shows up as a failed request in the browser. Authorized users keep the same behavior. + return this.authorizationService.isAuthorized(FeatureID.CanSeeQA).pipe( + switchMap((canSeeQA: boolean) => { + if (!canSeeQA) { + return observableOf([] as QualityAssuranceSourceObject[]); + } + const findListTopicOptions: FindListOptions = { + searchParams: [new RequestParam('target', this.item.uuid)], + }; + return this.qualityAssuranceSourceDataService.getSourcesByTarget(findListTopicOptions, false) + .pipe( + getFirstCompletedRemoteData(), + map((data: RemoteData>) => { + if (data.hasSucceeded) { + return data.payload.page; + } + return []; + }), + ); + }), + catchError(() => observableOf([] as QualityAssuranceSourceObject[])), + ); } /** diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index bb84aee35d9..d3fde59bcac 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -98,11 +98,6 @@ export class AccessControlFormContainerComponent impleme * Will be used from a parent component to read the value of the form */ getFormValue() { - console.log({ - bitstream: this.bitstreamAccessCmp.getValue(), - item: this.itemAccessCmp.getValue(), - state: this.state, - }); return { bitstream: this.bitstreamAccessCmp.getValue(), item: this.itemAccessCmp.getValue(), @@ -137,9 +132,7 @@ export class AccessControlFormContainerComponent impleme this.bulkAccessControlService.executeScript( [ this.itemRD.payload.uuid ], file, - ).pipe(take(1)).subscribe((res) => { - console.log('success', res); - }); + ).pipe(take(1)).subscribe(); } /** diff --git a/src/app/shared/access-control-form-container/bulk-access-control.service.ts b/src/app/shared/access-control-form-container/bulk-access-control.service.ts index f3c284e3580..b4a4a91dc70 100644 --- a/src/app/shared/access-control-form-container/bulk-access-control.service.ts +++ b/src/app/shared/access-control-form-container/bulk-access-control.service.ts @@ -59,8 +59,6 @@ export class BulkAccessControlService { * @param file */ executeScript(uuids: string[], file: File): Observable { - console.log('execute', { uuids, file }); - const params: ProcessParameter[] = [ { name: '-f', value: file.name }, ]; diff --git a/src/app/shared/cookies/browser-klaro.service.spec.ts b/src/app/shared/cookies/browser-klaro.service.spec.ts index 5cdd5275cd3..9b31e853333 100644 --- a/src/app/shared/cookies/browser-klaro.service.spec.ts +++ b/src/app/shared/cookies/browser-klaro.service.spec.ts @@ -6,6 +6,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; +import { environment } from '../../../environments/environment'; import { AuthService } from '../../core/auth/auth.service'; import { RestResponse } from '../../core/cache/response.models'; import { ConfigurationDataService } from '../../core/data/configuration-data.service'; @@ -310,6 +311,9 @@ describe('BrowserKlaroService', () => { let GOOGLE_ANALYTICS_KEY; let REGISTRATION_VERIFICATION_ENABLED_KEY; beforeEach(() => { + // The Google Analytics key is only probed when GA is enabled for the installation; + // these tests exercise that probe-and-filter path. + environment.info.enableGoogleAnalytics = true; GOOGLE_ANALYTICS_KEY = clone((service as any).GOOGLE_ANALYTICS_KEY); REGISTRATION_VERIFICATION_ENABLED_KEY = clone((service as any).REGISTRATION_VERIFICATION_ENABLED_KEY); spyOn((service as any), 'getUser$').and.returnValue(observableOf(user)); @@ -320,6 +324,10 @@ describe('BrowserKlaroService', () => { configurationDataService.findByPropertyName = findByPropertyName; }); + afterEach(() => { + environment.info.enableGoogleAnalytics = false; + }); + it('should not filter googleAnalytics when servicesToHide are empty', () => { const filteredConfig = (service as any).filterConfigServices([]); expect(filteredConfig).toContain(jasmine.objectContaining({ name: googleAnalytics })); @@ -401,5 +409,19 @@ describe('BrowserKlaroService', () => { service.initialize(); expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); + it('should hide googleAnalytics without requesting the key when GA is disabled', () => { + environment.info.enableGoogleAnalytics = false; + const findByPropertyNameSpy = jasmine.createSpy('findByPropertyName').and.returnValue( + createSuccessfulRemoteDataObject$({ + ...new ConfigurationProperty(), + name: trackingIdTestValue, + values: ['false'], + }), + ); + configurationDataService.findByPropertyName = findByPropertyNameSpy; + service.initialize(); + expect(findByPropertyNameSpy).not.toHaveBeenCalledWith(GOOGLE_ANALYTICS_KEY); + expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); + }); }); }); diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index ea660d9dd6d..d9b0000b32e 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -114,10 +114,15 @@ export class BrowserKlaroService extends KlaroService { this.klaroConfig.translations.zy.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy'; } - const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( - getFirstCompletedRemoteData(), - map(remoteData => !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)), - ); + // Only probe the backend for the Google Analytics key when GA is enabled for this + // installation. When it is disabled we hide the GA service from the consent UI without + // making a request that would otherwise 404 (the `google.analytics.key` property is unset). + const hideGoogleAnalytics$ = environment.info?.enableGoogleAnalytics + ? this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( + getFirstCompletedRemoteData(), + map(remoteData => !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)), + ) + : observableOf(true); const hideRegistrationVerification$ = this.configService.findByPropertyName(this.REGISTRATION_VERIFICATION_ENABLED_KEY).pipe( getFirstCompletedRemoteData(), 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 f0672637123..7fd98a15a56 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 @@ -140,6 +140,27 @@ describe('SearchExportCsvComponent', () => { }); }); }); + describe('bulkedit.export.max.items probe', () => { + beforeEach(waitForAsync(() => { + initBeforeEachAsync(); + })); + + it('should NOT probe bulkedit.export.max.items when the export button is not shown', () => { + // Non-admin: the button (and therefore the export-limit warning) is never rendered, + // so the config property must not be requested - this is what avoids the per-page 404. + (authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + (configurationDataService.findByPropertyName as jasmine.Spy).calls.reset(); + initBeforeEach(); + expect(configurationDataService.findByPropertyName).not.toHaveBeenCalledWith('bulkedit.export.max.items'); + }); + + it('should probe bulkedit.export.max.items once the export button is shown', () => { + // Admin with the export script available: the warning is evaluated, so the limit is fetched. + (configurationDataService.findByPropertyName as jasmine.Spy).calls.reset(); + initBeforeEach(); + expect(configurationDataService.findByPropertyName).toHaveBeenCalledWith('bulkedit.export.max.items'); + }); + }); describe('export', () => { beforeEach(waitForAsync(() => { initBeforeEachAsync(); 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 2727c6cce7c..a4baf28839d 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 @@ -19,6 +19,7 @@ import { Observable } from 'rxjs'; import { filter, map, + shareReplay, startWith, switchMap, } from 'rxjs/operators'; @@ -92,16 +93,34 @@ export class SearchExportCsvComponent implements OnInit, OnChanges { switchMap(() => this.scriptDataService.scriptWithNameExistsAndCanExecute('metadata-export-search')), map((canExecute: boolean) => canExecute), startWith(false), + shareReplay({ bufferSize: 1, refCount: true }), ); - this.shouldShowWarning$ = this.itemExceeds(); + this.shouldShowWarning$ = this.buildShouldShowWarning$(); } ngOnChanges(changes: SimpleChanges): void { - if (changes.total) { - this.shouldShowWarning$ = this.itemExceeds(); + // ngOnChanges runs before ngOnInit, so shouldShowButton$ may not exist on the first + // change; in that case ngOnInit builds the warning observable with the current total. + if (changes.total && hasValue(this.shouldShowButton$)) { + this.shouldShowWarning$ = this.buildShouldShowWarning$(); } } + /** + * Build the observable that decides whether to show the export-limit warning. + * The backend `bulkedit.export.max.items` property is only requested once the export + * button is shown (i.e. the user is an administrator who can run the export script), so + * anonymous and non-admin users never trigger that request (which would otherwise 404 + * when the property is unset). + */ + private buildShouldShowWarning$(): Observable { + return this.shouldShowButton$.pipe( + filter((canShow: boolean) => canShow), + switchMap(() => this.itemExceeds()), + startWith(false), + ); + } + /** * Checks if the export limit has been exceeded and updates the tooltip accordingly */ diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index eb35750c4ca..63c4b969f14 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -4,6 +4,7 @@ import { } from 'angulartics2'; import { of } from 'rxjs'; +import { environment } from '../../environments/environment'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { KlaroService } from '../shared/cookies/klaro.service'; @@ -40,6 +41,10 @@ describe('GoogleAnalyticsService', () => { }); beforeEach(() => { + // These tests exercise the active Google Analytics tracking path, which is only + // reached when GA is enabled for the installation. + environment.info.enableGoogleAnalytics = true; + googleAnalyticsSpy = jasmine.createSpyObj('Angulartics2GoogleAnalytics', [ 'startTracking', ]); @@ -80,11 +85,33 @@ describe('GoogleAnalyticsService', () => { service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy ); }); + afterEach(() => { + environment.info.enableGoogleAnalytics = false; + }); + it('should be created', () => { expect(service).toBeTruthy(); }); describe('addTrackingIdToPage()', () => { + describe('when Google Analytics is disabled by config', () => { + beforeEach(() => { + environment.info.enableGoogleAnalytics = false; + }); + + it(`should NOT request the ${trackingIdProp} property`, () => { + service.addTrackingIdToPage(); + expect(configSpy.findByPropertyName).not.toHaveBeenCalled(); + }); + + it('should NOT add a script or start tracking', () => { + service.addTrackingIdToPage(); + expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(0); + }); + }); + it(`should request the ${trackingIdProp} property`, () => { service.addTrackingIdToPage(); expect(configSpy.findByPropertyName).toHaveBeenCalledTimes(1); diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index 508297c3b9a..d1c3499c3a2 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -9,6 +9,7 @@ import { } from 'angulartics2'; import { combineLatest } from 'rxjs'; +import { environment } from '../../environments/environment'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { KlaroService } from '../shared/cookies/klaro.service'; @@ -38,6 +39,13 @@ export class GoogleAnalyticsService { * page and starts tracking. */ addTrackingIdToPage(): void { + // Skip Google Analytics entirely when it is not enabled for this installation. + // This avoids a 404 request to the `google.analytics.key` configuration property + // (and any tracking setup) on installations that do not use Google Analytics. + if (!environment.info?.enableGoogleAnalytics) { + return; + } + const googleKey$ = this.configService.findByPropertyName('google.analytics.key').pipe( getFirstCompletedRemoteData(), ); diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 375f9bfd1a3..ffbc8d5ca97 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -92,8 +92,6 @@ export class SubmissionFormFooterComponent implements OnChanges { private restService: SubmissionRestService, private submissionService: SubmissionService, private datashareSubmissionService: DatashareSubmissionService) { - // Debug: Log the signal value changes - console.log('Footer component created, initial signal value:', this.hasUploadFileErrorsSignal()); } // DATASHARE - End @@ -110,10 +108,6 @@ export class SubmissionFormFooterComponent implements OnChanges { this.processingDepositStatus = this.submissionService.getSubmissionDepositProcessingStatus(this.submissionId); this.showDepositAndDiscard = observableOf(this.submissionService.getSubmissionScope() === SubmissionScopeType.WorkspaceItem); this.hasUnsavedModification = this.submissionService.hasUnsavedModification(); - // DATASHARE - Start - // Debug: Log signal value on changes - console.log('Footer ngOnChanges, signal value:', this.hasUploadFileErrorsSignal()); - // DATASHARE - End } } diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index eb4e49885be..cd7b7327f56 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -133,7 +133,6 @@ export class SubmissionSectionContainerComponent implements OnInit { * @returns {string | null} The ID of the currently open panel, or null if no panel is open */ get openPanelId() { - console.log('Open panel ID:', this.datashareSubmissionFormSectionContainerService.openPanelId()); return this.datashareSubmissionFormSectionContainerService.openPanelId(); } @@ -155,7 +154,6 @@ export class SubmissionSectionContainerComponent implements OnInit { */ isSectionOpen(): boolean { const openPanelId = this.datashareSubmissionFormSectionContainerService.openPanelId(); - console.log('Checking if section is open:', this.sectionData.id === openPanelId); return this.sectionData.id === openPanelId; } // DATASHARE - end diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index efbf83280cd..dc7cfd724df 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -268,10 +268,6 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent { // IMPORTANT: Update the shared service signal const hasDuplicates = this.datashareSubmissionService.getDuplicateFileNames(this.fileNames).length > 0; - console.log('File names:', this.fileNames); - console.log('Has duplicates:', hasDuplicates); - console.log('Should show deposit button:', !hasDuplicates); - console.log('this.isTotalUploadedFilesSizeExceeded: ', this.isTotalUploadedFilesSizeExceeded); // Update the service signal this.datashareSubmissionService.updatehasUploadFilesErrors(!hasDuplicates || this.isTotalUploadedFilesSizeExceeded); diff --git a/src/config/config.util.ts b/src/config/config.util.ts index 005606e70ff..08a63db82b9 100644 --- a/src/config/config.util.ts +++ b/src/config/config.util.ts @@ -17,7 +17,6 @@ import { */ const extendEnvironmentWithAppConfig = (env: any, appConfig: AppConfig): void => { mergeConfig(env, appConfig); - console.log(`Environment extended with app config`); }; /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index fb4f5a427a1..fcc8eb1b86c 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -483,6 +483,10 @@ export class DefaultAppConfig implements AppConfig { enablePrivacyStatement: true, enableCOARNotifySupport: true, enableCookieConsentPopup: true, + // Disabled by default: when no Google Analytics key is configured on the backend, + // probing for it only produces a 404 on every page. Enable this on installations + // that actually configure a Google Analytics tracking id. + enableGoogleAnalytics: false, }; // Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/) diff --git a/src/config/info-config.interface.ts b/src/config/info-config.interface.ts index 0adf559a481..bcf7b5d7ab9 100644 --- a/src/config/info-config.interface.ts +++ b/src/config/info-config.interface.ts @@ -5,4 +5,12 @@ export interface InfoConfig extends Config { enablePrivacyStatement: boolean; enableCOARNotifySupport: boolean; enableCookieConsentPopup: boolean; + /** + * When false, the UI will not probe the backend for the `google.analytics.key` + * property and will not attempt to load Google Analytics. Set this to true only + * on installations that actually configure a Google Analytics tracking id on the + * backend. Keeping it false avoids a 404 request to + * `/server/api/config/properties/google.analytics.key` on every page. + */ + enableGoogleAnalytics: boolean; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 89be0daf6ae..6c09521da74 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -342,6 +342,7 @@ export const environment: BuildConfig = { enablePrivacyStatement: true, enableCOARNotifySupport: true, enableCookieConsentPopup: true, + enableGoogleAnalytics: false, }, markdown: { enabled: false,