response = executeHttpGet(urlPrefix + "/" + urlPath, requestEntity, Void.class);
+
+ // assert
+ assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
+ }
+}
diff --git a/code/gms-backend/src/test/java/io/github/gms/job/MessageCleanupJobTest.java b/code/gms-backend/src/test/java/io/github/gms/job/MessageCleanupJobTest.java
index a40cee7a..6f32f69e 100644
--- a/code/gms-backend/src/test/java/io/github/gms/job/MessageCleanupJobTest.java
+++ b/code/gms-backend/src/test/java/io/github/gms/job/MessageCleanupJobTest.java
@@ -12,6 +12,7 @@
import io.github.gms.util.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.slf4j.MDC;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Clock;
@@ -58,6 +59,8 @@ public void setup() {
ReflectionTestUtils.setField(job, "systemAttributeRepository", systemAttributeRepository);
addAppender(MessageCleanupJob.class);
+
+ MDC.clear();
}
@Test
diff --git a/code/gms-backend/src/test/java/io/github/gms/job/SecretRotationJobTest.java b/code/gms-backend/src/test/java/io/github/gms/job/SecretRotationJobTest.java
index 80c647a0..36255576 100644
--- a/code/gms-backend/src/test/java/io/github/gms/job/SecretRotationJobTest.java
+++ b/code/gms-backend/src/test/java/io/github/gms/job/SecretRotationJobTest.java
@@ -17,6 +17,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
+import org.slf4j.MDC;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.*;
@@ -62,6 +63,8 @@ public void setup() {
ReflectionTestUtils.setField(job, "systemAttributeRepository", systemAttributeRepository);
addAppender(SecretRotationJob.class);
+
+ MDC.clear();
}
@Test
diff --git a/code/gms-backend/src/test/java/io/github/gms/job/UserAnonymizationJobTest.java b/code/gms-backend/src/test/java/io/github/gms/job/UserAnonymizationJobTest.java
index e6faab71..4979a1c3 100644
--- a/code/gms-backend/src/test/java/io/github/gms/job/UserAnonymizationJobTest.java
+++ b/code/gms-backend/src/test/java/io/github/gms/job/UserAnonymizationJobTest.java
@@ -3,15 +3,16 @@
import io.github.gms.abstraction.AbstractLoggingUnitTest;
import io.github.gms.common.enums.SystemProperty;
import io.github.gms.common.enums.SystemStatus;
-import io.github.gms.functions.maintenance.user.UserAnonymizationService;
import io.github.gms.functions.maintenance.job.JobEntity;
import io.github.gms.functions.maintenance.job.JobRepository;
+import io.github.gms.functions.maintenance.user.UserAnonymizationService;
import io.github.gms.functions.setup.SystemAttributeRepository;
import io.github.gms.functions.system.SystemService;
import io.github.gms.functions.systemproperty.SystemPropertyService;
import io.github.gms.util.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.slf4j.MDC;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Clock;
@@ -57,6 +58,8 @@ public void setup() {
ReflectionTestUtils.setField(job, "jobRepository", jobRepository);
ReflectionTestUtils.setField(job, "systemAttributeRepository", systemAttributeRepository);
addAppender(UserAnonymizationJob.class);
+
+ MDC.clear();
}
@Test
diff --git a/code/gms-backend/src/test/java/io/github/gms/job/UserDeletionJobTest.java b/code/gms-backend/src/test/java/io/github/gms/job/UserDeletionJobTest.java
index 9d4e91a2..253e438e 100644
--- a/code/gms-backend/src/test/java/io/github/gms/job/UserDeletionJobTest.java
+++ b/code/gms-backend/src/test/java/io/github/gms/job/UserDeletionJobTest.java
@@ -13,6 +13,7 @@
import io.github.gms.util.TestUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.slf4j.MDC;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Clock;
@@ -61,6 +62,8 @@ public void setup() {
ReflectionTestUtils.setField(job, "jobRepository", jobRepository);
ReflectionTestUtils.setField(job, "systemAttributeRepository", systemAttributeRepository);
addAppender(UserDeletionJob.class);
+
+ MDC.clear();
}
@Test
diff --git a/code/gms-frontend/src/app/common/components/nav-back/nav-back.component.ts b/code/gms-frontend/src/app/common/components/nav-back/nav-back.component.ts
index 87d5d708..abd858ba 100644
--- a/code/gms-frontend/src/app/common/components/nav-back/nav-back.component.ts
+++ b/code/gms-frontend/src/app/common/components/nav-back/nav-back.component.ts
@@ -1,10 +1,9 @@
-import { NgFor, NgIf } from '@angular/common';
import { Component, Input } from '@angular/core';
import { RouterLink } from '@angular/router';
import { AngularMaterialModule } from '../../../angular-material-module';
import { NavButtonVisibilityPipe } from '../pipes/nav-button-visibility.pipe';
-import { ButtonConfig } from './button-config';
import { TranslatorModule } from '../pipes/translator/translator.module';
+import { ButtonConfig } from './button-config';
/**
* @author Peter Szrnka
@@ -14,7 +13,6 @@ import { TranslatorModule } from '../pipes/translator/translator.module';
imports: [
AngularMaterialModule,
NavButtonVisibilityPipe,
- NgIf, NgFor,
RouterLink,
TranslatorModule
],
diff --git a/code/gms-frontend/src/app/components/job/job-detail-list.component.html b/code/gms-frontend/src/app/components/job/job-detail-list.component.html
index 7cdcbee6..0ed939d3 100644
--- a/code/gms-frontend/src/app/components/job/job-detail-list.component.html
+++ b/code/gms-frontend/src/app/components/job/job-detail-list.component.html
@@ -3,58 +3,72 @@ {{ 'job.title' | translate }}
+
+
+ {{ 'job.execution.info' | translate }}
+ @for (item of job_execution_config; track $index) {
+
+ }
+
+ @if (authMode$ | async; as authMode) {
+
+ }
+
+
+
@if (error) {
- {{ 'messages.error' | translate }}: {{error}}
+ {{ 'messages.error' | translate }}: {{error}}
}
@if (!error) {
-
-
-
- {{ 'tables.filter' | translate }}
-
-
+
+
+
+ {{ 'tables.filter' | translate }}
+
+
-
-
-
-
- ID |
- {{element.id}} |
-
-
- {{ 'tables.name' | translate }} |
- {{element.name}} |
-
-
- Correlation Id |
- {{element.correlationId}} |
-
-
- {{ 'tables.status' | translate }} |
-
- {{element.status}}
- |
-
-
- {{ 'job.duration' | translate }} |
-
- {{element.duration}}
- |
-
-
- {{ 'tables.creationDate' | translate }} |
- {{element.creationDate | momentPipe:'yyyy.MM.DD. HH:mm:ss'}} |
-
-
- {{ 'messages.message' | translate }} |
-
- {{element.message}}
- |
-
-
-
+
+
+
+ ID |
+ {{element.id}} |
+
+
+ {{ 'tables.name' | translate }} |
+ {{element.name}} |
+
+
+ Correlation Id |
+ {{element.correlationId}} |
+
+
+ {{ 'tables.status' | translate }} |
+
+ {{element.status}}
+ |
+
+
+ {{ 'job.duration' | translate }} |
+
+ {{element.duration}}
+ |
+
+
+ {{ 'tables.creationDate' | translate }} |
+ {{element.creationDate | momentPipe:'yyyy.MM.DD.
+ HH:mm:ss'}} |
+
+
+ {{ 'messages.message' | translate }} |
+
+ {{element.message}}
+ |
+
+
+
-
-
+
+
}
\ No newline at end of file
diff --git a/code/gms-frontend/src/app/components/job/job-detail-list.component.spec.ts b/code/gms-frontend/src/app/components/job/job-detail-list.component.spec.ts
index a0598c9d..2a8b0b2d 100644
--- a/code/gms-frontend/src/app/components/job/job-detail-list.component.spec.ts
+++ b/code/gms-frontend/src/app/components/job/job-detail-list.component.spec.ts
@@ -1,14 +1,18 @@
import { HttpErrorResponse } from "@angular/common/http";
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from "@angular/compiler";
import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { MatSnackBar } from "@angular/material/snack-bar";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { ActivatedRoute, Data, Router } from "@angular/router";
-import { of, throwError } from "rxjs";
+import { of, ReplaySubject, throwError } from "rxjs";
import { AngularMaterialModule } from "../../angular-material-module";
import { MomentPipe } from "../../common/components/pipes/date-formatter.pipe";
+import { TranslatorModule } from "../../common/components/pipes/translator/translator.module";
+import { SharedDataService } from "../../common/service/shared-data-service";
+import { TranslatorService } from "../../common/service/translator-service";
import { JobDetailListComponent } from "./job-detail-list.component";
import { JobDetail } from "./model/job-detail.model";
-import { TranslatorModule } from "../../common/components/pipes/translator/translator.module";
+import { JobDetailService } from "./service/job-detail.service";
/**
* @author Peter Szrnka
@@ -19,6 +23,10 @@ describe('JobDetailListComponent', () => {
// Injected services
let router: any;
let activatedRoute : any = {};
+ let jobDetailService: any;
+ let snackbar: any;
+ let translatorService: any;
+ let authModeSubject = new ReplaySubject();
const configureTestBed = () => {
TestBed.configureTestingModule({
@@ -26,13 +34,18 @@ describe('JobDetailListComponent', () => {
schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA ],
providers: [
{ provide : Router, useValue : router },
- { provide : ActivatedRoute, useClass : activatedRoute }
+ { provide : ActivatedRoute, useClass : activatedRoute },
+ { provide : JobDetailService, useValue : jobDetailService },
+ { provide : MatSnackBar, useValue : snackbar },
+ { provide : SharedDataService, useValue : { authModeSubject$: authModeSubject } },
+ { provide : TranslatorService, useValue : translatorService }
]
});
fixture = TestBed.createComponent(JobDetailListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
+ authModeSubject.next('db');
};
@@ -55,6 +68,16 @@ describe('JobDetailListComponent', () => {
queryParams : {}
}
};
+
+ jobDetailService = {
+ startManualExecution : jest.fn().mockReturnValue(of({}))
+ };
+ snackbar = {
+ open : jest.fn()
+ };
+ translatorService = {
+ translate : jest.fn().mockReturnValue('Job executed successfully')
+ };
});
it('Should handle resolver error', () => {
@@ -84,4 +107,21 @@ describe('JobDetailListComponent', () => {
expect(component).toBeTruthy();
expect(router.navigateByUrl).toHaveBeenCalled();
});
+
+ it('executeJob when jobUrl is provided', () => {
+ configureTestBed();
+
+ component.executeJob('generated_keystore_cleanup');
+
+ expect(jobDetailService.startManualExecution).toHaveBeenCalled();
+ });
+
+ it('executeJob when jobUrl is invalid then handle error', () => {
+ jobDetailService.startManualExecution = jest.fn().mockReturnValue(throwError(() => new HttpErrorResponse({ error : new Error("OOPS!"), status : 500, statusText: "OOPS!"})));
+ configureTestBed();
+
+ component.executeJob('invalid_job_url');
+
+ expect(jobDetailService.startManualExecution).toHaveBeenCalled();
+ });
});
\ No newline at end of file
diff --git a/code/gms-frontend/src/app/components/job/job-detail-list.component.ts b/code/gms-frontend/src/app/components/job/job-detail-list.component.ts
index 603c9da0..c84b9d32 100644
--- a/code/gms-frontend/src/app/components/job/job-detail-list.component.ts
+++ b/code/gms-frontend/src/app/components/job/job-detail-list.component.ts
@@ -1,27 +1,44 @@
+import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
+import { MatSnackBar } from "@angular/material/snack-bar";
import { MatTableDataSource } from "@angular/material/table";
import { ActivatedRoute, Router } from "@angular/router";
-import { catchError } from "rxjs";
+import { catchError, Observable } from "rxjs";
import { AngularMaterialModule } from "../../angular-material-module";
import { NavBackComponent } from "../../common/components/nav-back/nav-back.component";
import { MomentPipe } from "../../common/components/pipes/date-formatter.pipe";
-import { JobDetail } from "./model/job-detail.model";
import { TranslatorModule } from "../../common/components/pipes/translator/translator.module";
+import { SharedDataService } from "../../common/service/shared-data-service";
+import { TranslatorService } from "../../common/service/translator-service";
+import { JobDetail } from "./model/job-detail.model";
+import { JobDetailService } from "./service/job-detail.service";
+
+const MANUAL_JOB_EXECUTION_CONFIG = [
+ { label: 'job.button.event.maintenance', url : 'event_maintenance' },
+ { label: 'job.button.keystore.cleanup', url : 'generated_keystore_cleanup' },
+ { label: 'job.button.old.job.log.cleanup', url : 'job_maintenance' },
+ { label: 'job.button.message.cleanup', url : 'message_cleanup' },
+ { label: 'job.button.secret.rotation', url : 'secret_rotation' },
+ { label: 'job.button.user.anonymization', url : 'user_anonymization' },
+ { label: 'job.button.user.deletion', url : 'user_deletion' }
+];
/**
* @author Peter Szrnka
*/
@Component({
standalone: true,
- imports: [AngularMaterialModule, NavBackComponent, MomentPipe, TranslatorModule],
+ imports: [AngularMaterialModule, CommonModule, NavBackComponent, MomentPipe, TranslatorModule],
selector: 'job-detail-list',
templateUrl: './job-detail-list.component.html'
})
export class JobDetailListComponent implements OnInit {
columns: string[] = ['id', 'name', 'correlationId', 'status', 'duration', 'creationDate', 'message'];
+ job_execution_config = MANUAL_JOB_EXECUTION_CONFIG;
loading = true;
+ authMode$: Observable = this.sharedData.authModeSubject$;
public datasource: MatTableDataSource;
public error?: string;
@@ -33,7 +50,11 @@ export class JobDetailListComponent implements OnInit {
constructor(
private readonly router: Router,
- private readonly activatedRoute: ActivatedRoute) {
+ private readonly activatedRoute: ActivatedRoute,
+ private readonly sharedData: SharedDataService,
+ private readonly jobDetailService: JobDetailService,
+ private readonly snackbar : MatSnackBar,
+ private readonly translatorService: TranslatorService) {
}
ngOnInit(): void {
@@ -48,6 +69,13 @@ export class JobDetailListComponent implements OnInit {
});
}
+ executeJob(jobUrl: string) {
+ this.jobDetailService.startManualExecution(jobUrl).subscribe({
+ next: () => this.snackbar.open(this.translatorService.translate('job.manual.execution.success')),
+ error: () => this.snackbar.open(this.translatorService.translate('job.manual.execution.error'))
+ });
+ }
+
private initDefaultDataTable() {
this.datasource = new MatTableDataSource([]);
}
diff --git a/code/gms-frontend/src/app/components/job/service/job-detail.service.spec.ts b/code/gms-frontend/src/app/components/job/service/job-detail.service.spec.ts
index 55476abb..4febad0b 100644
--- a/code/gms-frontend/src/app/components/job/service/job-detail.service.spec.ts
+++ b/code/gms-frontend/src/app/components/job/service/job-detail.service.spec.ts
@@ -45,4 +45,19 @@ describe("JobDetailService", () => {
req.flush(request);
httpMock.verify();
});
+
+ it('startManualExecution when called then return http 200', () => {
+ // arrange
+ const expectedUrl = environment.baseUrl + "secure/job_execution/generated_keystore_cleanup";
+ const mockResponse = {};
+
+ // act
+ service.startManualExecution('generated_keystore_cleanup').subscribe((res) => expect(res).toBe(mockResponse));
+
+ // assert
+ const req = httpMock.expectOne(expectedUrl);
+ expect(req.request.method).toBe('GET');
+ req.flush(mockResponse);
+ httpMock.verify();
+ });
});
\ No newline at end of file
diff --git a/code/gms-frontend/src/app/components/job/service/job-detail.service.ts b/code/gms-frontend/src/app/components/job/service/job-detail.service.ts
index 986abd34..7fa7296a 100644
--- a/code/gms-frontend/src/app/components/job/service/job-detail.service.ts
+++ b/code/gms-frontend/src/app/components/job/service/job-detail.service.ts
@@ -17,4 +17,8 @@ export class JobDetailService {
return this.http.get(environment.baseUrl + `secure/job/list?direction=${paging.direction}&property=${paging.property}&page=${paging.page}&size=${paging.size}`,
{ withCredentials: true, headers : getHeaders() });
}
+
+ startManualExecution(jobName: string): Observable {
+ return this.http.get(environment.baseUrl + `secure/job_execution/${jobName}`, { withCredentials: true, headers : getHeaders() });
+ }
}
\ No newline at end of file
diff --git a/code/gms-frontend/src/app/components/settings/settings-summary.component.ts b/code/gms-frontend/src/app/components/settings/settings-summary.component.ts
index 783f835c..9329aaf0 100644
--- a/code/gms-frontend/src/app/components/settings/settings-summary.component.ts
+++ b/code/gms-frontend/src/app/components/settings/settings-summary.component.ts
@@ -13,17 +13,7 @@ export interface PasswordSettings {
oldCredential: string | undefined,
newCredential1: string | undefined,
newCredential2: string | undefined
-}
-
-/*const LANGUAGE_SETTINGS_EN = [
- { key: 'en', value: 'English' },
- { key: 'hu', value: 'Hungarian' }
-];
-
-const LANGUAGE_SETTINGS_HU = [
- { key: 'en', value: 'Angol' },
- { key: 'hu', value: 'Magyar' }
-];*/
+};
/**
* @author Peter Szrnka
diff --git a/code/gms-frontend/src/assets/i18n/translations.json b/code/gms-frontend/src/assets/i18n/translations.json
index 012b2b4c..853eb8c3 100644
--- a/code/gms-frontend/src/assets/i18n/translations.json
+++ b/code/gms-frontend/src/assets/i18n/translations.json
@@ -194,6 +194,18 @@
"job.title" : "Jobs",
"job.duration" : "Duration (ms)",
+ "job.manual.execution.toggle": "Toggle manual job execution panel",
+ "job.execution.info": "Please select a job that you want to run manually.",
+ "job.button.event.maintenance" : "Event maintenance",
+ "job.button.keystore.cleanup" : "Generated keystore cleanup",
+ "job.button.old.job.log.cleanup" : "Delete old job log entries",
+ "job.button.message.cleanup" : "Message cleanup",
+ "job.button.secret.rotation" : "Secret rotation",
+ "job.button.user.anonymization" : "User anonymization",
+ "job.button.user.deletion" : "User deletion",
+ "job.button.ldapsync" : "LDAP user sync",
+ "job.manual.execution.success": "Job executed successfully!",
+ "job.manual.execution.error": "Job execution failed!",
"systemProperties.category": "Category",
@@ -490,6 +502,18 @@
"job.title" : "Munkafolyamatok",
"job.duration" : "Futásidő (ms)",
+ "job.manual.execution.toggle": "Kézi munkafolyamat végrehajtás panel",
+ "job.execution.info": "Kérjük válasszon egy munkafolyamatot, amelyet kézzel szeretne futtatni.",
+ "job.button.event.maintenance" : "Esemény karbantartás",
+ "job.button.keystore.cleanup" : "Generált kulcstárak törlése",
+ "job.button.old.job.log.cleanup" : "Régi munkafolyamat napló bejegyzések törlése",
+ "job.button.message.cleanup" : "Üzenetek törlése",
+ "job.button.secret.rotation" : "Secret rotálás",
+ "job.button.user.anonymization" : "Felhasználó anonimizálás",
+ "job.button.user.deletion" : "Felhasználó törlés",
+ "job.button.ldapsync" : "LDAP felhasználó szinkronizálás",
+ "job.manual.execution.success": "Munkafolyamat sikeresen végrehajtva!",
+ "job.manual.execution.error": "Munkafolyamat végrehajtása sikertelen!",
"systemProperties.category": "Kategória",