From 58bc117fff5e7cb2662402b124bcc2532009955a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 11:00:35 +0100 Subject: [PATCH 01/19] #708 - WIP --- .../java/ch/puzzle/okr/OkrApplication.java | 4 + .../controller/ClientConfigController.java | 7 +- .../ch/puzzle/okr/dto/ClientConfigDto.java | 13 ++++ .../okr/service/ClientConfigService.java | 29 -------- .../clientconfig/ClientConfigProperties.java | 18 +++++ .../clientconfig/ClientConfigService.java | 34 +++++++++ .../resources/application-staging.properties | 1 + .../src/main/resources/application.properties | 4 + .../controller/ClientConfigControllerIT.java | 2 +- .../okr/service/ClientConfigServiceIT.java | 10 +-- .../application-top-bar.component.html | 2 +- .../application-top-bar.component.scss | 2 +- .../application-top-bar.component.ts | 7 +- frontend/src/app/config.service.ts | 8 +- .../shared/services/customization.service.ts | 73 +++++++++++++++++++ .../app/shared/types/model/ClientConfig.ts | 14 ++++ frontend/src/index.html | 2 +- frontend/src/style/_variables.scss | 2 + 18 files changed, 184 insertions(+), 48 deletions(-) create mode 100644 backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java delete mode 100644 backend/src/main/java/ch/puzzle/okr/service/ClientConfigService.java create mode 100644 backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java create mode 100644 backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java create mode 100644 frontend/src/app/shared/services/customization.service.ts create mode 100644 frontend/src/app/shared/types/model/ClientConfig.ts diff --git a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java index ead1b1fb23..1219e9af06 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java @@ -1,11 +1,15 @@ package ch.puzzle.okr; +import ch.puzzle.okr.service.clientconfig.ClientConfigProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling +@EnableConfigurationProperties(ClientConfigProperties.class) public class OkrApplication { public static void main(String[] args) { SpringApplication.run(OkrApplication.class, args); diff --git a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java index 1f449278ca..218da02f8a 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java @@ -1,14 +1,13 @@ package ch.puzzle.okr.controller; -import ch.puzzle.okr.service.ClientConfigService; +import ch.puzzle.okr.dto.ClientConfigDto; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; - @RestController @RequestMapping("/config") public class ClientConfigController { @@ -20,7 +19,7 @@ public ClientConfigController(ClientConfigService configService) { } @GetMapping - public ResponseEntity> getConfig() { + public ResponseEntity getConfig() { return ResponseEntity.status(HttpStatus.OK).body(configService.getConfigBasedOnActiveEnv()); } } diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java new file mode 100644 index 0000000000..5501587c12 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java @@ -0,0 +1,13 @@ +package ch.puzzle.okr.dto; + +import java.util.HashMap; + +public record ClientConfigDto( + String activeProfile, + String issuer, + String clientId, + String favicon, + String logo, + HashMap customStyles +) { +} diff --git a/backend/src/main/java/ch/puzzle/okr/service/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/ClientConfigService.java deleted file mode 100644 index 3763be0d72..0000000000 --- a/backend/src/main/java/ch/puzzle/okr/service/ClientConfigService.java +++ /dev/null @@ -1,29 +0,0 @@ -package ch.puzzle.okr.service; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; - -@Service -public class ClientConfigService { - - @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") - private String issuer; - - @Value("${spring.profiles.active}") - private String activeProfile; - - @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") - private String clientId; - - public Map getConfigBasedOnActiveEnv() { - HashMap env = new HashMap<>(); - env.put("activeProfile", activeProfile); - env.put("issuer", issuer); - env.put("clientId", clientId); - return env; - } - -} diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java new file mode 100644 index 0000000000..c65580041b --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java @@ -0,0 +1,18 @@ +package ch.puzzle.okr.service.clientconfig; + +import ch.puzzle.okr.dto.ClientConfigDto; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Service; + +import java.util.HashMap; + +@ConfigurationProperties("okr.clientconfig") +public class ClientConfigProperties { + private String favicon; + + private String logo; + + private HashMap customStyles; + +} diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java new file mode 100644 index 0000000000..3754879b12 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -0,0 +1,34 @@ +package ch.puzzle.okr.service.clientconfig; + +import ch.puzzle.okr.dto.ClientConfigDto; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.HashMap; + +@Service +public class ClientConfigService { + + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuer; + + @Value("${spring.profiles.active}") + private String activeProfile; + + @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") + private String clientId; + + @Value("${okr.clientconfig.favicon}") + private String favicon; + + @Value("${okr.clientconfig.logo}") + private String logo; + + @Value("${okr.clientconfig.customStyles}") + private HashMap customStyles; + + public ClientConfigDto getConfigBasedOnActiveEnv() { + return new ClientConfigDto(activeProfile, issuer, clientId, favicon, logo, customStyles); + } + +} diff --git a/backend/src/main/resources/application-staging.properties b/backend/src/main/resources/application-staging.properties index 805e7beec6..80c9a109ff 100644 --- a/backend/src/main/resources/application-staging.properties +++ b/backend/src/main/resources/application-staging.properties @@ -4,3 +4,4 @@ logging.level.org.springframework=debug spring.security.oauth2.resourceserver.opaquetoken.client-id=pitc_okr_staging okr.user.champion.usernames=peggimann +okr.clientconfig.customStyles.okr-topbar-background-color=#ab31ad \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 11729ab8a3..bbc811a955 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -50,3 +50,7 @@ okr.jwt.user.username=preferred_username okr.jwt.user.firstname=given_name okr.jwt.user.lastname=family_name okr.jwt.user.email=email + +okr.clientconfig.favicon=assets/favicon.png +okr.clientconfig.logo=assets/images/okr-logo.svg +okr.clientconfig.customStyles.okr-topbar-background-color=#ff0000 \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java index 51daa45cd8..0be1e46dbe 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java @@ -1,6 +1,6 @@ package ch.puzzle.okr.controller; -import ch.puzzle.okr.service.ClientConfigService; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; diff --git a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java index f0f3b31be7..a11a62c71e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java @@ -1,11 +1,11 @@ package ch.puzzle.okr.service; +import ch.puzzle.okr.dto.ClientConfigDto; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Map; - import static org.junit.jupiter.api.Assertions.assertEquals; @SpringIntegrationTest @@ -16,10 +16,10 @@ class ClientConfigServiceIT { @Test void saveKeyResultShouldSaveNewKeyResult() { - Map configMap = clientConfigService.getConfigBasedOnActiveEnv(); + ClientConfigDto clientConfig = clientConfigService.getConfigBasedOnActiveEnv(); - assertEquals("prod", configMap.get("activeProfile")); - assertEquals("http://localhost:8544/realms/pitc", configMap.get("issuer")); + assertEquals("prod", clientConfig.activeProfile()); + assertEquals("http://localhost:8544/realms/pitc", clientConfig.issuer()); } } diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.html b/frontend/src/app/application-top-bar/application-top-bar.component.html index f95279203a..27d2589865 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.html +++ b/frontend/src/app/application-top-bar/application-top-bar.component.html @@ -1,7 +1,7 @@
- okr-logo + okr-logo
diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.scss b/frontend/src/app/application-top-bar/application-top-bar.component.scss index b2c8d632f6..ae740966bf 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.scss +++ b/frontend/src/app/application-top-bar/application-top-bar.component.scss @@ -12,7 +12,7 @@ z-index: 102; height: inherit; justify-content: space-between; - background-color: $pz-dark-blue; + background-color: var(--okr-topbar-background-color) } .topBarEntry { diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 800520390a..1d771ec829 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; -import { map, ReplaySubject } from 'rxjs'; +import { BehaviorSubject, map, ReplaySubject } from 'rxjs'; import { ConfigService } from '../config.service'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TeamManagementComponent } from '../shared/dialog/team-management/team-management.component'; @@ -22,6 +22,7 @@ export class ApplicationTopBarComponent implements OnInit { @Input() hasAdminAccess!: ReplaySubject; private dialogRef!: MatDialogRef | undefined; + logoSrc$ = new BehaviorSubject(undefined); constructor( private oauthService: OAuthService, @@ -35,9 +36,7 @@ export class ApplicationTopBarComponent implements OnInit { this.configService.config$ .pipe( map((config) => { - if (config.activeProfile === 'staging') { - document.getElementById('okrTopbar')!.style.backgroundColor = '#ab31ad'; - } + this.logoSrc$.next(config.logo); }), ) .subscribe(); diff --git a/frontend/src/app/config.service.ts b/frontend/src/app/config.service.ts index b311b6b04e..bc2b71a370 100644 --- a/frontend/src/app/config.service.ts +++ b/frontend/src/app/config.service.ts @@ -1,14 +1,18 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, shareReplay } from 'rxjs'; +import { ClientConfig } from "./shared/types/model/ClientConfig"; + @Injectable({ providedIn: 'root', }) export class ConfigService { - public config$: Observable; + public config$: Observable; constructor(private httpClient: HttpClient) { - this.config$ = this.httpClient.get('/config').pipe(shareReplay()); + this.config$ = this.httpClient + .get('/config') + .pipe(shareReplay()); } } diff --git a/frontend/src/app/shared/services/customization.service.ts b/frontend/src/app/shared/services/customization.service.ts new file mode 100644 index 0000000000..d7dc70e991 --- /dev/null +++ b/frontend/src/app/shared/services/customization.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@angular/core'; +import { ConfigService } from "../../config.service"; +import { CustomizationConfig } from "../types/model/ClientConfig"; + +@Injectable({ + providedIn: 'root', +}) +export class CustomizationService { + private currentConfig?: CustomizationConfig; + + constructor(private configService: ConfigService) { + configService.config$.subscribe(config => { + this.updateCustomizations(config); + }) + } + + + private updateCustomizations(config: CustomizationConfig) { + this.setFavicon(config.favicon); + this.setStyleCustomizations(config.customStyles); + + this.currentConfig = config; + } + + private setFavicon(favicon: string) { + if (!favicon || this.currentConfig?.favicon === favicon) { + return; + } + + document.getElementById("favicon")?.setAttribute("src", favicon); + } + + private setStyleCustomizations(customStylesMap: Map) { + if (!customStylesMap || this.areStylesTheSame(customStylesMap)) { + return; + } + + this.removeStyles(this.currentConfig?.customStyles) + this.setStyles(customStylesMap); + } + + private areStylesTheSame(customStylesMap: Map) { + return JSON.stringify(this.currentConfig?.customStyles) === JSON.stringify(customStylesMap); + } + + private removeStyles(customStylesMap: Map | undefined) { + if (!customStylesMap) { + return; + } + const styles = document?.getElementById("html")!.style; + if (!styles) { + return + } + + Array.from(customStylesMap.entries()).forEach(([varName, varValue]) => { + styles.setProperty(`--${varName}`, varValue); + }); + } + + private setStyles(customStylesMap: Map) { + if (!customStylesMap) { + return; + } + const styles = document?.getElementById("html")!.style; + if (!styles) { + return + } + + Array.from(customStylesMap.keys()).forEach((varName) => { + styles.removeProperty(`--${varName}`); + }); + } +} diff --git a/frontend/src/app/shared/types/model/ClientConfig.ts b/frontend/src/app/shared/types/model/ClientConfig.ts new file mode 100644 index 0000000000..fadb7ae552 --- /dev/null +++ b/frontend/src/app/shared/types/model/ClientConfig.ts @@ -0,0 +1,14 @@ + +export interface AuthConfig { + activeProfile: string; + issuer: string; + clientId: string; +} + +export interface CustomizationConfig { + favicon: string; + logo: string; + customStyles: Map; +} +export interface ClientConfig extends AuthConfig, CustomizationConfig { +} diff --git a/frontend/src/index.html b/frontend/src/index.html index c5a7a864f9..929342f919 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -5,7 +5,7 @@ Puzzle OKR - + diff --git a/frontend/src/style/_variables.scss b/frontend/src/style/_variables.scss index 6cb19bf774..5b91985d73 100644 --- a/frontend/src/style/_variables.scss +++ b/frontend/src/style/_variables.scss @@ -47,4 +47,6 @@ $pz-dark-blue-palette: ( --mdc-text-button-label-text-tracking: normal; --mdc-filled-button-label-text-tracking: normal; --mdc-outlined-button-label-text-tracking: normal; + + --okr-topbar-background-color: $pz-dark-blue; } From 689e9903b41c0acc9c712e5d13db0dd6eeff834d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 16:34:29 +0100 Subject: [PATCH 02/19] #708 - Add logic to send back frontend configuration and customizations. --- .../ch/puzzle/okr/dto/ClientConfigDto.java | 1 + .../clientconfig/ClientConfigProperties.java | 38 +++++++++++-- .../clientconfig/ClientConfigService.java | 25 +++++---- .../resources/application-staging.properties | 2 +- .../src/main/resources/application.properties | 2 +- frontend/src/app/app.module.ts | 5 +- .../application-top-bar.component.html | 2 +- .../application-top-bar.component.scss | 7 ++- .../application-top-bar.component.ts | 7 ++- frontend/src/app/config.service.ts | 7 +-- .../shared/services/customization.service.ts | 53 ++++++++++++------- .../app/shared/types/model/ClientConfig.ts | 11 ++-- frontend/src/index.html | 2 +- frontend/src/style/_variables.scss | 2 +- 14 files changed, 112 insertions(+), 52 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java index 5501587c12..3648fce74d 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java @@ -8,6 +8,7 @@ public record ClientConfigDto( String clientId, String favicon, String logo, + String title, HashMap customStyles ) { } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java index c65580041b..3dc9860d71 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java @@ -1,18 +1,46 @@ package ch.puzzle.okr.service.clientconfig; -import ch.puzzle.okr.dto.ClientConfigDto; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Service; import java.util.HashMap; + @ConfigurationProperties("okr.clientconfig") public class ClientConfigProperties { private String favicon; - private String logo; + private String title; + private HashMap customStyles = new HashMap<>(); + + public void setCustomStyles(HashMap customStyles) { + this.customStyles = customStyles; + } + + public String getFavicon() { + return favicon; + } + + public void setFavicon(String favicon) { + this.favicon = favicon; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public HashMap getCustomStyles() { + return customStyles; + } - private HashMap customStyles; + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java index 3754879b12..35725967c9 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -1,7 +1,9 @@ package ch.puzzle.okr.service.clientconfig; import ch.puzzle.okr.dto.ClientConfigDto; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -9,6 +11,7 @@ @Service public class ClientConfigService { + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") private String issuer; @@ -17,18 +20,22 @@ public class ClientConfigService { @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private String clientId; + private final ClientConfigProperties clientConfigProperties; - @Value("${okr.clientconfig.favicon}") - private String favicon; - - @Value("${okr.clientconfig.logo}") - private String logo; - - @Value("${okr.clientconfig.customStyles}") - private HashMap customStyles; + public ClientConfigService(ClientConfigProperties clientConfigProperties) { + this.clientConfigProperties = clientConfigProperties; + } public ClientConfigDto getConfigBasedOnActiveEnv() { - return new ClientConfigDto(activeProfile, issuer, clientId, favicon, logo, customStyles); + return new ClientConfigDto( + activeProfile, + issuer, + clientId, + this.clientConfigProperties.getFavicon(), + this.clientConfigProperties.getLogo(), + this.clientConfigProperties.getTitle(), + this.clientConfigProperties.getCustomStyles() + ); } } diff --git a/backend/src/main/resources/application-staging.properties b/backend/src/main/resources/application-staging.properties index 80c9a109ff..8aabb902fd 100644 --- a/backend/src/main/resources/application-staging.properties +++ b/backend/src/main/resources/application-staging.properties @@ -4,4 +4,4 @@ logging.level.org.springframework=debug spring.security.oauth2.resourceserver.opaquetoken.client-id=pitc_okr_staging okr.user.champion.usernames=peggimann -okr.clientconfig.customStyles.okr-topbar-background-color=#ab31ad \ No newline at end of file +okr.clientconfig.customstyles.okr-topbar-background-color=#ab31ad \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index bbc811a955..528f21be38 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -53,4 +53,4 @@ okr.jwt.user.email=email okr.clientconfig.favicon=assets/favicon.png okr.clientconfig.logo=assets/images/okr-logo.svg -okr.clientconfig.customStyles.okr-topbar-background-color=#ff0000 \ No newline at end of file +okr.clientconfig.title=Puzzle OKR \ No newline at end of file diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d11efee938..fa25ac45eb 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -71,6 +71,7 @@ import { ActionPlanComponent } from './action-plan/action-plan.component'; import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; import { TeamManagementComponent } from './shared/dialog/team-management/team-management.component'; import { KeyresultDialogComponent } from './shared/dialog/keyresult-dialog/keyresult-dialog.component'; +import { CustomizationService } from './shared/services/customization.service'; function initOauthFactory(configService: ConfigService, oauthService: OAuthService) { return async () => { @@ -202,4 +203,6 @@ export const MY_FORMATS = { bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], }) -export class AppModule {} +export class AppModule { + constructor(customizationService: CustomizationService) {} +} diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.html b/frontend/src/app/application-top-bar/application-top-bar.component.html index 27d2589865..9ceb0f491e 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.html +++ b/frontend/src/app/application-top-bar/application-top-bar.component.html @@ -1,7 +1,7 @@
- okr-logo + okr-logo
diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.scss b/frontend/src/app/application-top-bar/application-top-bar.component.scss index ae740966bf..63082ca229 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.scss +++ b/frontend/src/app/application-top-bar/application-top-bar.component.scss @@ -12,7 +12,12 @@ z-index: 102; height: inherit; justify-content: space-between; - background-color: var(--okr-topbar-background-color) + background-color: var(--okr-topbar-background-color); + + img { + max-height: calc(100% - 16px); + width: auto; + } } .topBarEntry { diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 1d771ec829..fa723334ff 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -21,8 +21,8 @@ export class ApplicationTopBarComponent implements OnInit { @Input() hasAdminAccess!: ReplaySubject; + logoSrc$ = new BehaviorSubject('assets/images/okr-logo.svg'); private dialogRef!: MatDialogRef | undefined; - logoSrc$ = new BehaviorSubject(undefined); constructor( private oauthService: OAuthService, @@ -36,7 +36,9 @@ export class ApplicationTopBarComponent implements OnInit { this.configService.config$ .pipe( map((config) => { - this.logoSrc$.next(config.logo); + if (config.logo) { + this.logoSrc$.next(config.logo); + } }), ) .subscribe(); @@ -45,6 +47,7 @@ export class ApplicationTopBarComponent implements OnInit { this.username.next(this.oauthService.getIdentityClaims()['name']); } } + logOut() { const currentUrlTree = this.router.createUrlTree([], { queryParams: {} }); this.router.navigateByUrl(currentUrlTree).then(() => { diff --git a/frontend/src/app/config.service.ts b/frontend/src/app/config.service.ts index bc2b71a370..27faea76e1 100644 --- a/frontend/src/app/config.service.ts +++ b/frontend/src/app/config.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, shareReplay } from 'rxjs'; -import { ClientConfig } from "./shared/types/model/ClientConfig"; - +import { ClientConfig } from './shared/types/model/ClientConfig'; @Injectable({ providedIn: 'root', @@ -11,8 +10,6 @@ export class ConfigService { public config$: Observable; constructor(private httpClient: HttpClient) { - this.config$ = this.httpClient - .get('/config') - .pipe(shareReplay()); + this.config$ = this.httpClient.get('/config').pipe(shareReplay()); } } diff --git a/frontend/src/app/shared/services/customization.service.ts b/frontend/src/app/shared/services/customization.service.ts index d7dc70e991..1af88f3a3a 100644 --- a/frontend/src/app/shared/services/customization.service.ts +++ b/frontend/src/app/shared/services/customization.service.ts @@ -1,6 +1,7 @@ -import { Injectable } from '@angular/core'; -import { ConfigService } from "../../config.service"; -import { CustomizationConfig } from "../types/model/ClientConfig"; +import { Inject, Injectable } from '@angular/core'; +import { ConfigService } from '../../config.service'; +import { CustomizationConfig, CustomStyles } from '../types/model/ClientConfig'; +import { DOCUMENT } from '@angular/common'; @Injectable({ providedIn: 'root', @@ -8,14 +9,17 @@ import { CustomizationConfig } from "../types/model/ClientConfig"; export class CustomizationService { private currentConfig?: CustomizationConfig; - constructor(private configService: ConfigService) { - configService.config$.subscribe(config => { + constructor( + configService: ConfigService, + @Inject(DOCUMENT) private document: Document, + ) { + configService.config$.subscribe((config) => { this.updateCustomizations(config); - }) + }); } - private updateCustomizations(config: CustomizationConfig) { + this.setTitle(config.title); this.setFavicon(config.favicon); this.setStyleCustomizations(config.customStyles); @@ -27,46 +31,55 @@ export class CustomizationService { return; } - document.getElementById("favicon")?.setAttribute("src", favicon); + this.document.getElementById('favicon')?.setAttribute('href', favicon); + } + + private setTitle(title: string) { + if (!title || this.currentConfig?.title === title) { + return; + } + debugger; + this.document.querySelector('title')!.innerHTML = title; } - private setStyleCustomizations(customStylesMap: Map) { + private setStyleCustomizations(customStylesMap: CustomStyles) { if (!customStylesMap || this.areStylesTheSame(customStylesMap)) { return; } - this.removeStyles(this.currentConfig?.customStyles) + this.removeStyles(this.currentConfig?.customStyles); this.setStyles(customStylesMap); } - private areStylesTheSame(customStylesMap: Map) { + private areStylesTheSame(customStylesMap: CustomStyles) { return JSON.stringify(this.currentConfig?.customStyles) === JSON.stringify(customStylesMap); } - private removeStyles(customStylesMap: Map | undefined) { + private setStyles(customStylesMap: CustomStyles | undefined) { if (!customStylesMap) { return; } - const styles = document?.getElementById("html")!.style; + + const styles = this.document.querySelector('html')!.style; if (!styles) { - return + return; } - Array.from(customStylesMap.entries()).forEach(([varName, varValue]) => { + Object.entries(customStylesMap).forEach(([varName, varValue]) => { styles.setProperty(`--${varName}`, varValue); }); } - private setStyles(customStylesMap: Map) { + private removeStyles(customStylesMap: CustomStyles | undefined) { if (!customStylesMap) { return; } - const styles = document?.getElementById("html")!.style; + + const styles = this.document.querySelector('html')!.style; if (!styles) { - return + return; } - - Array.from(customStylesMap.keys()).forEach((varName) => { + Object.keys(customStylesMap).forEach((varName) => { styles.removeProperty(`--${varName}`); }); } diff --git a/frontend/src/app/shared/types/model/ClientConfig.ts b/frontend/src/app/shared/types/model/ClientConfig.ts index fadb7ae552..031e4bb55a 100644 --- a/frontend/src/app/shared/types/model/ClientConfig.ts +++ b/frontend/src/app/shared/types/model/ClientConfig.ts @@ -1,14 +1,17 @@ - export interface AuthConfig { activeProfile: string; issuer: string; clientId: string; } +export interface CustomStyles { + [key: string]: string; +} + export interface CustomizationConfig { + title: string; favicon: string; logo: string; - customStyles: Map; -} -export interface ClientConfig extends AuthConfig, CustomizationConfig { + customStyles: CustomStyles; } +export interface ClientConfig extends AuthConfig, CustomizationConfig {} diff --git a/frontend/src/index.html b/frontend/src/index.html index 929342f919..a368b64e9b 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -2,7 +2,7 @@ - Puzzle OKR + OKR Tool diff --git a/frontend/src/style/_variables.scss b/frontend/src/style/_variables.scss index 5b91985d73..8fe6a1340c 100644 --- a/frontend/src/style/_variables.scss +++ b/frontend/src/style/_variables.scss @@ -48,5 +48,5 @@ $pz-dark-blue-palette: ( --mdc-filled-button-label-text-tracking: normal; --mdc-outlined-button-label-text-tracking: normal; - --okr-topbar-background-color: $pz-dark-blue; + --okr-topbar-background-color: #1e5a96; } From 76a82cdc55c9aba9b45ebd43e7ba09bae3ca6564 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 21 Mar 2024 15:35:05 +0000 Subject: [PATCH 03/19] [FM] Automated formating backend --- .../java/ch/puzzle/okr/dto/ClientConfigDto.java | 11 ++--------- .../clientconfig/ClientConfigProperties.java | 1 - .../service/clientconfig/ClientConfigService.java | 13 +++---------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java index 3648fce74d..876be7aa0f 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java @@ -2,13 +2,6 @@ import java.util.HashMap; -public record ClientConfigDto( - String activeProfile, - String issuer, - String clientId, - String favicon, - String logo, - String title, - HashMap customStyles -) { +public record ClientConfigDto(String activeProfile, String issuer, String clientId, String favicon, String logo, + String title, HashMap customStyles) { } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java index 3dc9860d71..7e4f067620 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java @@ -4,7 +4,6 @@ import java.util.HashMap; - @ConfigurationProperties("okr.clientconfig") public class ClientConfigProperties { private String favicon; diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java index 35725967c9..c8262d2615 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -11,7 +11,6 @@ @Service public class ClientConfigService { - @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") private String issuer; @@ -27,15 +26,9 @@ public ClientConfigService(ClientConfigProperties clientConfigProperties) { } public ClientConfigDto getConfigBasedOnActiveEnv() { - return new ClientConfigDto( - activeProfile, - issuer, - clientId, - this.clientConfigProperties.getFavicon(), - this.clientConfigProperties.getLogo(), - this.clientConfigProperties.getTitle(), - this.clientConfigProperties.getCustomStyles() - ); + return new ClientConfigDto(activeProfile, issuer, clientId, this.clientConfigProperties.getFavicon(), + this.clientConfigProperties.getLogo(), this.clientConfigProperties.getTitle(), + this.clientConfigProperties.getCustomStyles()); } } From 11c8bfb8a8046d7a91ed03ab80982b7673e87f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 16:43:19 +0100 Subject: [PATCH 04/19] Remove debugger --- frontend/src/app/shared/services/customization.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/shared/services/customization.service.ts b/frontend/src/app/shared/services/customization.service.ts index 1af88f3a3a..75e34cdb3d 100644 --- a/frontend/src/app/shared/services/customization.service.ts +++ b/frontend/src/app/shared/services/customization.service.ts @@ -38,7 +38,6 @@ export class CustomizationService { if (!title || this.currentConfig?.title === title) { return; } - debugger; this.document.querySelector('title')!.innerHTML = title; } From aa4d9047ce519ac8bdbceded4c85ef8dea2c7e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 17:04:47 +0100 Subject: [PATCH 05/19] Add customization.service.spec.ts --- .../services/chustomization.service.spec.ts | 38 +++++++++++++++++++ .../shared/services/customization.service.ts | 17 +++++++++ 2 files changed, 55 insertions(+) create mode 100644 frontend/src/app/shared/services/chustomization.service.spec.ts diff --git a/frontend/src/app/shared/services/chustomization.service.spec.ts b/frontend/src/app/shared/services/chustomization.service.spec.ts new file mode 100644 index 0000000000..d209fe0ecd --- /dev/null +++ b/frontend/src/app/shared/services/chustomization.service.spec.ts @@ -0,0 +1,38 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { CustomizationService } from "./customization.service"; +import { DOCUMENT } from "@angular/common"; +import { ConfigService } from "../../config.service"; +import { Observable, of } from "rxjs"; + +describe('CustomizationService', () => { + let service: CustomizationService; + + const body = { + title: "title", + favicon: "favicon", + logo: "logo", + customStyles: {cssVar1: "foo"} + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: DOCUMENT, useValue: undefined}, + { provide: ConfigService, useValue: { + config$: of(body) + }} + ] + }); + service = TestBed.inject(CustomizationService); + }); + + it('should be created', () => { + const currentConfig = service.getCurrentConfig(); + expect(currentConfig?.title).toBe(body.title) + expect(currentConfig?.logo).toBe(body.logo) + expect(currentConfig?.favicon).toBe(body.favicon) + expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']) + }); +}); diff --git a/frontend/src/app/shared/services/customization.service.ts b/frontend/src/app/shared/services/customization.service.ts index 75e34cdb3d..e96215cc12 100644 --- a/frontend/src/app/shared/services/customization.service.ts +++ b/frontend/src/app/shared/services/customization.service.ts @@ -18,6 +18,10 @@ export class CustomizationService { }); } + public getCurrentConfig() { + return this.currentConfig; + } + private updateCustomizations(config: CustomizationConfig) { this.setTitle(config.title); this.setFavicon(config.favicon); @@ -31,6 +35,10 @@ export class CustomizationService { return; } + if (!this.document) { + return; + } + this.document.getElementById('favicon')?.setAttribute('href', favicon); } @@ -38,6 +46,11 @@ export class CustomizationService { if (!title || this.currentConfig?.title === title) { return; } + + if (!this.document) { + return; + } + this.document.querySelector('title')!.innerHTML = title; } @@ -59,6 +72,10 @@ export class CustomizationService { return; } + if (!this.document) { + return; + } + const styles = this.document.querySelector('html')!.style; if (!styles) { return; From d99875330fdc5b58623ae8c5f76cdb44bed927a0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Thu, 21 Mar 2024 16:05:34 +0000 Subject: [PATCH 06/19] [FM] Automated formating frontend --- .../services/chustomization.service.spec.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/shared/services/chustomization.service.spec.ts b/frontend/src/app/shared/services/chustomization.service.spec.ts index d209fe0ecd..2a5701e1a4 100644 --- a/frontend/src/app/shared/services/chustomization.service.spec.ts +++ b/frontend/src/app/shared/services/chustomization.service.spec.ts @@ -1,38 +1,41 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { CustomizationService } from "./customization.service"; -import { DOCUMENT } from "@angular/common"; -import { ConfigService } from "../../config.service"; -import { Observable, of } from "rxjs"; +import { CustomizationService } from './customization.service'; +import { DOCUMENT } from '@angular/common'; +import { ConfigService } from '../../config.service'; +import { Observable, of } from 'rxjs'; describe('CustomizationService', () => { let service: CustomizationService; const body = { - title: "title", - favicon: "favicon", - logo: "logo", - customStyles: {cssVar1: "foo"} + title: 'title', + favicon: 'favicon', + logo: 'logo', + customStyles: { cssVar1: 'foo' }, }; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ - { provide: DOCUMENT, useValue: undefined}, - { provide: ConfigService, useValue: { - config$: of(body) - }} - ] + { provide: DOCUMENT, useValue: undefined }, + { + provide: ConfigService, + useValue: { + config$: of(body), + }, + }, + ], }); service = TestBed.inject(CustomizationService); }); it('should be created', () => { const currentConfig = service.getCurrentConfig(); - expect(currentConfig?.title).toBe(body.title) - expect(currentConfig?.logo).toBe(body.logo) - expect(currentConfig?.favicon).toBe(body.favicon) - expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']) + expect(currentConfig?.title).toBe(body.title); + expect(currentConfig?.logo).toBe(body.logo); + expect(currentConfig?.favicon).toBe(body.favicon); + expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']); }); }); From 016bb2f5bb365bb10791ff548f841f38f345f3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 17:10:34 +0100 Subject: [PATCH 07/19] Improve naming to be mor specific --- .../java/ch/puzzle/okr/OkrApplication.java | 5 +-- .../controller/ClientConfigController.java | 2 +- .../ClientConfigService.java | 18 ++++---- .../ClientCustomizationProperties.java} | 6 +-- .../resources/application-staging.properties | 2 +- .../src/main/resources/application.properties | 6 +-- .../controller/ClientConfigControllerIT.java | 2 +- .../okr/service/ClientConfigServiceIT.java | 2 +- .../services/chustomization.service.spec.ts | 38 ----------------- .../services/customization.service.spec.ts | 41 +++++++++++++++++++ 10 files changed, 60 insertions(+), 62 deletions(-) rename backend/src/main/java/ch/puzzle/okr/service/{clientconfig => clientcustomization}/ClientConfigService.java (50%) rename backend/src/main/java/ch/puzzle/okr/service/{clientconfig/ClientConfigProperties.java => clientcustomization/ClientCustomizationProperties.java} (85%) delete mode 100644 frontend/src/app/shared/services/chustomization.service.spec.ts create mode 100644 frontend/src/app/shared/services/customization.service.spec.ts diff --git a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java index 1219e9af06..f9273892a7 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java @@ -1,15 +1,14 @@ package ch.puzzle.okr; -import ch.puzzle.okr.service.clientconfig.ClientConfigProperties; +import ch.puzzle.okr.service.clientcustomization.ClientCustomizationProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling -@EnableConfigurationProperties(ClientConfigProperties.class) +@EnableConfigurationProperties(ClientCustomizationProperties.class) public class OkrApplication { public static void main(String[] args) { SpringApplication.run(OkrApplication.class, args); diff --git a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java index 218da02f8a..43b9f9f50f 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java @@ -1,7 +1,7 @@ package ch.puzzle.okr.controller; import ch.puzzle.okr.dto.ClientConfigDto; -import ch.puzzle.okr.service.clientconfig.ClientConfigService; +import ch.puzzle.okr.service.clientcustomization.ClientConfigService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java similarity index 50% rename from backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java rename to backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java index c8262d2615..f7e48dd497 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java @@ -1,13 +1,9 @@ -package ch.puzzle.okr.service.clientconfig; +package ch.puzzle.okr.service.clientcustomization; import ch.puzzle.okr.dto.ClientConfigDto; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; -import java.util.HashMap; - @Service public class ClientConfigService { @@ -19,16 +15,16 @@ public class ClientConfigService { @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private String clientId; - private final ClientConfigProperties clientConfigProperties; + private final ClientCustomizationProperties clientCustomizationProperties; - public ClientConfigService(ClientConfigProperties clientConfigProperties) { - this.clientConfigProperties = clientConfigProperties; + public ClientConfigService(ClientCustomizationProperties clientCustomizationProperties) { + this.clientCustomizationProperties = clientCustomizationProperties; } public ClientConfigDto getConfigBasedOnActiveEnv() { - return new ClientConfigDto(activeProfile, issuer, clientId, this.clientConfigProperties.getFavicon(), - this.clientConfigProperties.getLogo(), this.clientConfigProperties.getTitle(), - this.clientConfigProperties.getCustomStyles()); + return new ClientConfigDto(activeProfile, issuer, clientId, this.clientCustomizationProperties.getFavicon(), + this.clientCustomizationProperties.getLogo(), this.clientCustomizationProperties.getTitle(), + this.clientCustomizationProperties.getCustomStyles()); } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java similarity index 85% rename from backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java rename to backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java index 7e4f067620..094ff5918f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java @@ -1,11 +1,11 @@ -package ch.puzzle.okr.service.clientconfig; +package ch.puzzle.okr.service.clientcustomization; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.HashMap; -@ConfigurationProperties("okr.clientconfig") -public class ClientConfigProperties { +@ConfigurationProperties("okr.clientcustomization") +public class ClientCustomizationProperties { private String favicon; private String logo; private String title; diff --git a/backend/src/main/resources/application-staging.properties b/backend/src/main/resources/application-staging.properties index 8aabb902fd..a4f58cfba7 100644 --- a/backend/src/main/resources/application-staging.properties +++ b/backend/src/main/resources/application-staging.properties @@ -4,4 +4,4 @@ logging.level.org.springframework=debug spring.security.oauth2.resourceserver.opaquetoken.client-id=pitc_okr_staging okr.user.champion.usernames=peggimann -okr.clientconfig.customstyles.okr-topbar-background-color=#ab31ad \ No newline at end of file +okr.clientcustomization.customstyles.okr-topbar-background-color=#ab31ad \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 528f21be38..d0c0ad39de 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -51,6 +51,6 @@ okr.jwt.user.firstname=given_name okr.jwt.user.lastname=family_name okr.jwt.user.email=email -okr.clientconfig.favicon=assets/favicon.png -okr.clientconfig.logo=assets/images/okr-logo.svg -okr.clientconfig.title=Puzzle OKR \ No newline at end of file +okr.clientcustomization.favicon=assets/favicon.png +okr.clientcustomization.logo=assets/images/okr-logo.svg +okr.clientcustomization.title=Puzzle OKR diff --git a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java index 0be1e46dbe..c39e250d3a 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java @@ -1,6 +1,6 @@ package ch.puzzle.okr.controller; -import ch.puzzle.okr.service.clientconfig.ClientConfigService; +import ch.puzzle.okr.service.clientcustomization.ClientConfigService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; diff --git a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java index a11a62c71e..b545c3df38 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java @@ -1,7 +1,7 @@ package ch.puzzle.okr.service; import ch.puzzle.okr.dto.ClientConfigDto; -import ch.puzzle.okr.service.clientconfig.ClientConfigService; +import ch.puzzle.okr.service.clientcustomization.ClientConfigService; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/frontend/src/app/shared/services/chustomization.service.spec.ts b/frontend/src/app/shared/services/chustomization.service.spec.ts deleted file mode 100644 index d209fe0ecd..0000000000 --- a/frontend/src/app/shared/services/chustomization.service.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { CustomizationService } from "./customization.service"; -import { DOCUMENT } from "@angular/common"; -import { ConfigService } from "../../config.service"; -import { Observable, of } from "rxjs"; - -describe('CustomizationService', () => { - let service: CustomizationService; - - const body = { - title: "title", - favicon: "favicon", - logo: "logo", - customStyles: {cssVar1: "foo"} - }; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: DOCUMENT, useValue: undefined}, - { provide: ConfigService, useValue: { - config$: of(body) - }} - ] - }); - service = TestBed.inject(CustomizationService); - }); - - it('should be created', () => { - const currentConfig = service.getCurrentConfig(); - expect(currentConfig?.title).toBe(body.title) - expect(currentConfig?.logo).toBe(body.logo) - expect(currentConfig?.favicon).toBe(body.favicon) - expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']) - }); -}); diff --git a/frontend/src/app/shared/services/customization.service.spec.ts b/frontend/src/app/shared/services/customization.service.spec.ts new file mode 100644 index 0000000000..2a5701e1a4 --- /dev/null +++ b/frontend/src/app/shared/services/customization.service.spec.ts @@ -0,0 +1,41 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { CustomizationService } from './customization.service'; +import { DOCUMENT } from '@angular/common'; +import { ConfigService } from '../../config.service'; +import { Observable, of } from 'rxjs'; + +describe('CustomizationService', () => { + let service: CustomizationService; + + const body = { + title: 'title', + favicon: 'favicon', + logo: 'logo', + customStyles: { cssVar1: 'foo' }, + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: DOCUMENT, useValue: undefined }, + { + provide: ConfigService, + useValue: { + config$: of(body), + }, + }, + ], + }); + service = TestBed.inject(CustomizationService); + }); + + it('should be created', () => { + const currentConfig = service.getCurrentConfig(); + expect(currentConfig?.title).toBe(body.title); + expect(currentConfig?.logo).toBe(body.logo); + expect(currentConfig?.favicon).toBe(body.favicon); + expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']); + }); +}); From bcf145457f66a8e860a9be92138712420b9578ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 17:12:49 +0100 Subject: [PATCH 08/19] Correct package name --- backend/src/main/java/ch/puzzle/okr/OkrApplication.java | 2 +- .../java/ch/puzzle/okr/controller/ClientConfigController.java | 2 +- .../ClientConfigService.java | 2 +- .../ClientCustomizationProperties.java | 2 +- .../java/ch/puzzle/okr/controller/ClientConfigControllerIT.java | 2 +- .../test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename backend/src/main/java/ch/puzzle/okr/service/{clientcustomization => clientconfig}/ClientConfigService.java (95%) rename backend/src/main/java/ch/puzzle/okr/service/{clientcustomization => clientconfig}/ClientCustomizationProperties.java (95%) diff --git a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java index f9273892a7..c718c0f533 100644 --- a/backend/src/main/java/ch/puzzle/okr/OkrApplication.java +++ b/backend/src/main/java/ch/puzzle/okr/OkrApplication.java @@ -1,6 +1,6 @@ package ch.puzzle.okr; -import ch.puzzle.okr.service.clientcustomization.ClientCustomizationProperties; +import ch.puzzle.okr.service.clientconfig.ClientCustomizationProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; diff --git a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java index 43b9f9f50f..218da02f8a 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/ClientConfigController.java @@ -1,7 +1,7 @@ package ch.puzzle.okr.controller; import ch.puzzle.okr.dto.ClientConfigDto; -import ch.puzzle.okr.service.clientcustomization.ClientConfigService; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java similarity index 95% rename from backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java rename to backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java index f7e48dd497..ff1bffc1d7 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -1,4 +1,4 @@ -package ch.puzzle.okr.service.clientcustomization; +package ch.puzzle.okr.service.clientconfig; import ch.puzzle.okr.dto.ClientConfigDto; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientCustomizationProperties.java similarity index 95% rename from backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java rename to backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientCustomizationProperties.java index 094ff5918f..a992b4d8ab 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientcustomization/ClientCustomizationProperties.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientCustomizationProperties.java @@ -1,4 +1,4 @@ -package ch.puzzle.okr.service.clientcustomization; +package ch.puzzle.okr.service.clientconfig; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java index c39e250d3a..0be1e46dbe 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/ClientConfigControllerIT.java @@ -1,6 +1,6 @@ package ch.puzzle.okr.controller; -import ch.puzzle.okr.service.clientcustomization.ClientConfigService; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; diff --git a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java index b545c3df38..a11a62c71e 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java @@ -1,7 +1,7 @@ package ch.puzzle.okr.service; import ch.puzzle.okr.dto.ClientConfigDto; -import ch.puzzle.okr.service.clientcustomization.ClientConfigService; +import ch.puzzle.okr.service.clientconfig.ClientConfigService; import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; From d6a014aeb8400d6f649d7bd1bfa491e492cf8375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Thu, 21 Mar 2024 17:15:35 +0100 Subject: [PATCH 09/19] Try fix test --- frontend/src/app/shared/services/customization.service.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/shared/services/customization.service.spec.ts b/frontend/src/app/shared/services/customization.service.spec.ts index 2a5701e1a4..90010819e0 100644 --- a/frontend/src/app/shared/services/customization.service.spec.ts +++ b/frontend/src/app/shared/services/customization.service.spec.ts @@ -19,7 +19,6 @@ describe('CustomizationService', () => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ - { provide: DOCUMENT, useValue: undefined }, { provide: ConfigService, useValue: { From 322dfbce0f76d26674bff2c41890a5d729a332b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 25 Mar 2024 09:44:18 +0100 Subject: [PATCH 10/19] Fix frontend tests --- .../services/customization.service.spec.ts | 127 +++++++++++++++--- 1 file changed, 108 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/shared/services/customization.service.spec.ts b/frontend/src/app/shared/services/customization.service.spec.ts index 90010819e0..65640424df 100644 --- a/frontend/src/app/shared/services/customization.service.spec.ts +++ b/frontend/src/app/shared/services/customization.service.spec.ts @@ -1,40 +1,129 @@ -import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { CustomizationService } from './customization.service'; -import { DOCUMENT } from '@angular/common'; import { ConfigService } from '../../config.service'; -import { Observable, of } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; +import { ClientConfig } from '../types/model/ClientConfig'; -describe('CustomizationService', () => { - let service: CustomizationService; +class CallRecorder { + private calls: { [key: string]: any[] } = {}; + + public add(key: string, value: any): void { + if (!this.calls[key]) { + this.calls[key] = []; + } + this.calls[key].push(value); + } + + public getCallByIdx(key: string, index = 0): any[] { + return this.calls[key][index]; + } + + public getCallCount(key: string): number { + return this.calls[key]?.length ?? 0; + } - const body = { + public clear(): void { + this.calls = {}; + } +} + +describe('CustomizationService', () => { + const body: ClientConfig = { + activeProfile: 'test', + issuer: 'some-issuer.com', + clientId: 'my-client-id', title: 'title', favicon: 'favicon', logo: 'logo', customStyles: { cssVar1: 'foo' }, }; + let service: CustomizationService; + let configServiceMock: ConfigService; + let documentMock: Document; + let callRecorder = new CallRecorder(); + let configSubject: BehaviorSubject; + beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { - provide: ConfigService, - useValue: { - config$: of(body), + configSubject = new BehaviorSubject(body); + configServiceMock = { config$: configSubject.asObservable() } as ConfigService; + callRecorder.clear(); + + documentMock = { + getElementById: (id: string) => { + return { + setAttribute: function () { + callRecorder.add(`${id}-setAttribute`, arguments); }, - }, - ], - }); - service = TestBed.inject(CustomizationService); + } as unknown as HTMLElement; + }, + querySelector: (selector: string) => { + return { + set innerHTML(value: string) { + callRecorder.add(`${selector}-innerHTML`, arguments); + }, + get style() { + return { + setProperty: function () { + callRecorder.add(`${selector}.style-setProperty`, arguments); + }, + removeProperty: function () { + callRecorder.add(`${selector}.style-removeProperty`, arguments); + }, + }; + }, + }; + }, + } as unknown as Document; + service = new CustomizationService(configServiceMock, documentMock); }); - it('should be created', () => { + it('should call correct apis when config is ready', () => { const currentConfig = service.getCurrentConfig(); expect(currentConfig?.title).toBe(body.title); expect(currentConfig?.logo).toBe(body.logo); expect(currentConfig?.favicon).toBe(body.favicon); expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']); + + expect(callRecorder.getCallCount('title-innerHTML')).toBe(1); + expect(callRecorder.getCallCount('favicon-setAttribute')).toBe(1); + expect(callRecorder.getCallCount('html.style-setProperty')).toBe(1); + expect(callRecorder.getCallCount('html.style-removeProperty')).toBe(0); + + expect(callRecorder.getCallByIdx('title-innerHTML', 0)[0]).toBe('title'); + expect(callRecorder.getCallByIdx('favicon-setAttribute', 0)[0]).toBe('href'); + expect(callRecorder.getCallByIdx('favicon-setAttribute', 0)[1]).toBe('favicon'); + expect(callRecorder.getCallByIdx('html.style-setProperty', 0)[0]).toBe('--cssVar1'); + expect(callRecorder.getCallByIdx('html.style-setProperty', 0)[1]).toBe('foo'); + }); + + it('should update if config changed afterwards', () => { + const bodySecond = { + activeProfile: 'test-second', + issuer: 'some-issuer.com-second', + clientId: 'my-client-id-second', + title: 'title-second', + favicon: 'favicon-second', + logo: 'logo-second', + customStyles: { cssVarNew: 'bar' }, + }; + configSubject.next(bodySecond); + + const currentConfig = service.getCurrentConfig(); + expect(currentConfig?.title).toBe(bodySecond.title); + expect(currentConfig?.logo).toBe(bodySecond.logo); + expect(currentConfig?.favicon).toBe(bodySecond.favicon); + expect(currentConfig?.customStyles['cssVarNew']).toBe(bodySecond.customStyles['cssVarNew']); + expect(currentConfig?.customStyles['cssVar1']).toBe(undefined); + + expect(callRecorder.getCallCount('title-innerHTML')).toBe(2); + expect(callRecorder.getCallCount('favicon-setAttribute')).toBe(2); + expect(callRecorder.getCallCount('html.style-setProperty')).toBe(2); + expect(callRecorder.getCallCount('html.style-removeProperty')).toBe(1); + + expect(callRecorder.getCallByIdx('title-innerHTML', 1)[0]).toBe('title-second'); + expect(callRecorder.getCallByIdx('favicon-setAttribute', 1)[0]).toBe('href'); + expect(callRecorder.getCallByIdx('favicon-setAttribute', 1)[1]).toBe('favicon-second'); + expect(callRecorder.getCallByIdx('html.style-setProperty', 1)[0]).toBe('--cssVarNew'); + expect(callRecorder.getCallByIdx('html.style-setProperty', 1)[1]).toBe('bar'); }); }); From bd3cf12ad90dc8b3b67ade1b63ec81f2f9ba4472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 25 Mar 2024 09:45:32 +0100 Subject: [PATCH 11/19] Steamline selectors --- .../services/customization.service.spec.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/shared/services/customization.service.spec.ts b/frontend/src/app/shared/services/customization.service.spec.ts index 65640424df..9419bb8de8 100644 --- a/frontend/src/app/shared/services/customization.service.spec.ts +++ b/frontend/src/app/shared/services/customization.service.spec.ts @@ -59,15 +59,15 @@ describe('CustomizationService', () => { querySelector: (selector: string) => { return { set innerHTML(value: string) { - callRecorder.add(`${selector}-innerHTML`, arguments); + callRecorder.add(`${selector}.innerHTML`, arguments); }, get style() { return { setProperty: function () { - callRecorder.add(`${selector}.style-setProperty`, arguments); + callRecorder.add(`${selector}.style.setProperty`, arguments); }, removeProperty: function () { - callRecorder.add(`${selector}.style-removeProperty`, arguments); + callRecorder.add(`${selector}.style.removeProperty`, arguments); }, }; }, @@ -84,16 +84,16 @@ describe('CustomizationService', () => { expect(currentConfig?.favicon).toBe(body.favicon); expect(currentConfig?.customStyles['cssVar1']).toBe(body.customStyles['cssVar1']); - expect(callRecorder.getCallCount('title-innerHTML')).toBe(1); + expect(callRecorder.getCallCount('title.innerHTML')).toBe(1); expect(callRecorder.getCallCount('favicon-setAttribute')).toBe(1); - expect(callRecorder.getCallCount('html.style-setProperty')).toBe(1); - expect(callRecorder.getCallCount('html.style-removeProperty')).toBe(0); + expect(callRecorder.getCallCount('html.style.setProperty')).toBe(1); + expect(callRecorder.getCallCount('html.style.removeProperty')).toBe(0); - expect(callRecorder.getCallByIdx('title-innerHTML', 0)[0]).toBe('title'); + expect(callRecorder.getCallByIdx('title.innerHTML', 0)[0]).toBe('title'); expect(callRecorder.getCallByIdx('favicon-setAttribute', 0)[0]).toBe('href'); expect(callRecorder.getCallByIdx('favicon-setAttribute', 0)[1]).toBe('favicon'); - expect(callRecorder.getCallByIdx('html.style-setProperty', 0)[0]).toBe('--cssVar1'); - expect(callRecorder.getCallByIdx('html.style-setProperty', 0)[1]).toBe('foo'); + expect(callRecorder.getCallByIdx('html.style.setProperty', 0)[0]).toBe('--cssVar1'); + expect(callRecorder.getCallByIdx('html.style.setProperty', 0)[1]).toBe('foo'); }); it('should update if config changed afterwards', () => { @@ -115,15 +115,15 @@ describe('CustomizationService', () => { expect(currentConfig?.customStyles['cssVarNew']).toBe(bodySecond.customStyles['cssVarNew']); expect(currentConfig?.customStyles['cssVar1']).toBe(undefined); - expect(callRecorder.getCallCount('title-innerHTML')).toBe(2); + expect(callRecorder.getCallCount('title.innerHTML')).toBe(2); expect(callRecorder.getCallCount('favicon-setAttribute')).toBe(2); - expect(callRecorder.getCallCount('html.style-setProperty')).toBe(2); - expect(callRecorder.getCallCount('html.style-removeProperty')).toBe(1); + expect(callRecorder.getCallCount('html.style.setProperty')).toBe(2); + expect(callRecorder.getCallCount('html.style.removeProperty')).toBe(1); - expect(callRecorder.getCallByIdx('title-innerHTML', 1)[0]).toBe('title-second'); + expect(callRecorder.getCallByIdx('title.innerHTML', 1)[0]).toBe('title-second'); expect(callRecorder.getCallByIdx('favicon-setAttribute', 1)[0]).toBe('href'); expect(callRecorder.getCallByIdx('favicon-setAttribute', 1)[1]).toBe('favicon-second'); - expect(callRecorder.getCallByIdx('html.style-setProperty', 1)[0]).toBe('--cssVarNew'); - expect(callRecorder.getCallByIdx('html.style-setProperty', 1)[1]).toBe('bar'); + expect(callRecorder.getCallByIdx('html.style.setProperty', 1)[0]).toBe('--cssVarNew'); + expect(callRecorder.getCallByIdx('html.style.setProperty', 1)[1]).toBe('bar'); }); }); From 9c077f8967de69d99d2f9176b2cbfd6489d81eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 25 Mar 2024 09:58:12 +0100 Subject: [PATCH 12/19] Correct styling. Add tests for special css variable properties in integration test --- .../okr/service/clientconfig/ClientConfigService.java | 1 + .../ch/puzzle/okr/service/ClientConfigServiceIT.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java index ff1bffc1d7..f06c7ca325 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -15,6 +15,7 @@ public class ClientConfigService { @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private String clientId; + private final ClientCustomizationProperties clientCustomizationProperties; public ClientConfigService(ClientCustomizationProperties clientCustomizationProperties) { diff --git a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java index a11a62c71e..a3f82fe631 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java @@ -5,10 +5,15 @@ import ch.puzzle.okr.test.SpringIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringIntegrationTest +@SpringBootTest(properties = { + "okr.clientcustomization.customstyles.okr-topbar-background-color=#affe00", + "okr.clientcustomization.customstyles.okr-other-css-style=rgba(50,60,70,0.5)", +}) class ClientConfigServiceIT { @Autowired @@ -20,6 +25,10 @@ void saveKeyResultShouldSaveNewKeyResult() { assertEquals("prod", clientConfig.activeProfile()); assertEquals("http://localhost:8544/realms/pitc", clientConfig.issuer()); + assertEquals("assets/favicon.png", clientConfig.favicon()); + assertEquals("assets/images/okr-logo.svg", clientConfig.logo()); + assertEquals("#affe00", clientConfig.customStyles().get("okr-topbar-background-color")); + assertEquals("rgba(50,60,70,0.5)", clientConfig.customStyles().get("okr-other-css-style")); } } From 85195907dbc51b4fd6dc79ec2d743eeda358eb37 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 25 Mar 2024 08:58:44 +0000 Subject: [PATCH 13/19] [FM] Automated formating backend --- .../java/ch/puzzle/okr/service/ClientConfigServiceIT.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java index a3f82fe631..0f2dfa35ed 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/ClientConfigServiceIT.java @@ -10,10 +10,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @SpringIntegrationTest -@SpringBootTest(properties = { - "okr.clientcustomization.customstyles.okr-topbar-background-color=#affe00", - "okr.clientcustomization.customstyles.okr-other-css-style=rgba(50,60,70,0.5)", -}) +@SpringBootTest(properties = { "okr.clientcustomization.customstyles.okr-topbar-background-color=#affe00", + "okr.clientcustomization.customstyles.okr-other-css-style=rgba(50,60,70,0.5)", }) class ClientConfigServiceIT { @Autowired From cf633df7f331957365eacf6d52ba9759b0882fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 10:38:49 +0200 Subject: [PATCH 14/19] Add propper subscription handling --- .../application-top-bar.component.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index fa723334ff..0438a78d27 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; -import { BehaviorSubject, map, ReplaySubject } from 'rxjs'; +import { BehaviorSubject, ReplaySubject, Subscription } from 'rxjs'; import { ConfigService } from '../config.service'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TeamManagementComponent } from '../shared/dialog/team-management/team-management.component'; @@ -15,7 +15,7 @@ import { isMobileDevice } from '../shared/common'; styleUrls: ['./application-top-bar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ApplicationTopBarComponent implements OnInit { +export class ApplicationTopBarComponent implements OnInit, OnDestroy { username: ReplaySubject = new ReplaySubject(); menuIsOpen = false; @@ -23,6 +23,7 @@ export class ApplicationTopBarComponent implements OnInit { hasAdminAccess!: ReplaySubject; logoSrc$ = new BehaviorSubject('assets/images/okr-logo.svg'); private dialogRef!: MatDialogRef | undefined; + private subscription?: Subscription; constructor( private oauthService: OAuthService, @@ -33,21 +34,23 @@ export class ApplicationTopBarComponent implements OnInit { ) {} ngOnInit(): void { - this.configService.config$ - .pipe( - map((config) => { - if (config.logo) { - this.logoSrc$.next(config.logo); - } - }), - ) - .subscribe(); + this.subscription = this.configService.config$.subscribe({ + next: (config) => { + if (config.logo) { + this.logoSrc$.next(config.logo); + } + }, + }); if (this.oauthService.hasValidIdToken()) { this.username.next(this.oauthService.getIdentityClaims()['name']); } } + ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + logOut() { const currentUrlTree = this.router.createUrlTree([], { queryParams: {} }); this.router.navigateByUrl(currentUrlTree).then(() => { From 792d7c19a6cf4d2d1fe770eac63e5bec85b2ed88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 10:39:03 +0200 Subject: [PATCH 15/19] remove empty file --- frontend/src/app/shared/services/chustomization.service.spec.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frontend/src/app/shared/services/chustomization.service.spec.ts diff --git a/frontend/src/app/shared/services/chustomization.service.spec.ts b/frontend/src/app/shared/services/chustomization.service.spec.ts deleted file mode 100644 index e69de29bb2..0000000000 From a3e0db495446fe3549e682dd96c603d129d59f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 11:04:56 +0200 Subject: [PATCH 16/19] Re-trigger Integration Tests --- .../okr/service/persistence/QuarterPersistenceServiceIT.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java index 8acb8b2bf2..b641dd1dd4 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java @@ -68,6 +68,7 @@ void shouldReturnCurrentQuarterFutureQuarterAnd4PastQuarters() { @Test void shouldReturnCurrentQuarter() { Quarter quarter = quarterPersistenceService.getCurrentQuarter(); + assertTrue(LocalDate.now().isAfter(quarter.getStartDate())); assertTrue(LocalDate.now().isBefore(quarter.getEndDate())); assertNotNull(quarter.getId()); From bca66971336931a2912f0437164f1bc860edfd9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 11:17:44 +0200 Subject: [PATCH 17/19] Add empty svg as default --- .../app/application-top-bar/application-top-bar.component.ts | 2 +- frontend/src/assets/images/empty.svg | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 frontend/src/assets/images/empty.svg diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 0438a78d27..873cf185a0 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -21,7 +21,7 @@ export class ApplicationTopBarComponent implements OnInit, OnDestroy { @Input() hasAdminAccess!: ReplaySubject; - logoSrc$ = new BehaviorSubject('assets/images/okr-logo.svg'); + logoSrc$ = new BehaviorSubject('assets/images/empty.svg'); private dialogRef!: MatDialogRef | undefined; private subscription?: Subscription; diff --git a/frontend/src/assets/images/empty.svg b/frontend/src/assets/images/empty.svg new file mode 100644 index 0000000000..2a63845fcf --- /dev/null +++ b/frontend/src/assets/images/empty.svg @@ -0,0 +1,3 @@ + + + From 9509f74e8e495aa1a8b7920f0e8a68936ab74737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 11:17:48 +0200 Subject: [PATCH 18/19] Add empty svg as default --- frontend/src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/index.html b/frontend/src/index.html index a368b64e9b..7d125adb4c 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -5,7 +5,7 @@ OKR Tool - + From 9416b4c3f87f2baea76d9844eac80b1355ebeb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20K=C3=A4ser?= Date: Mon, 8 Apr 2024 11:36:50 +0200 Subject: [PATCH 19/19] Fix It Test data --- .../main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql index 8939474bdf..34aec66957 100644 --- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql +++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql @@ -40,6 +40,7 @@ values (1, 'GJ 22/23-Q4', '2023-04-01', '2023-06-30'), (6, 'GJ 21/22-Q4', '2022-04-01', '2022-06-30'), (7, 'GJ 23/24-Q2', '2023-10-01', '2023-12-31'), (8, 'GJ 23/24-Q3', '2024-01-01', '2024-03-31'), + (9, 'GJ 23/24-Q4', '2024-04-01', '2024-06-30'), (199, 'Backlog', null, null); insert into team (id, version, name)