diff --git a/api/src/api_graphql/mutations/user_setting_mutation.py b/api/src/api_graphql/mutations/user_setting_mutation.py index 1fb012d..bca1a9b 100644 --- a/api/src/api_graphql/mutations/user_setting_mutation.py +++ b/api/src/api_graphql/mutations/user_setting_mutation.py @@ -1,7 +1,9 @@ from api.route import Route from api_graphql.abc.mutation_abc import MutationABC +from api_graphql.field.mutation_field_builder import MutationFieldBuilder from api_graphql.input.user_setting_input import UserSettingInput from core.logger import APILogger +from core.string import first_to_lower from data.schemas.public.user_setting import UserSetting from data.schemas.public.user_setting_dao import userSettingsDao from data.schemas.system.setting_dao import settingsDao @@ -13,13 +15,20 @@ logger = APILogger(__name__) class UserSettingMutation(MutationABC): def __init__(self): MutationABC.__init__(self, "UserSetting") - self.mutation( - "change", - self.resolve_change, - UserSettingInput, - require_any_permission=[Permissions.settings_update], + self.field( + MutationFieldBuilder("change") + .with_resolver(self.resolve_change) + .with_change_broadcast( + f"{first_to_lower(self.name.replace("Mutation", ""))}Change" + ) + .with_input(UserSettingInput, "input") + .with_require_any([], [self._x]) ) + @staticmethod + async def _x(ctx): + return ctx.data.user_id == (await Route.get_user()).id + @staticmethod async def resolve_change(obj: UserSettingInput, *_): logger.debug(f"create new setting: {input}") diff --git a/api/src/core/configuration/feature_flags.py b/api/src/core/configuration/feature_flags.py index ce259a5..8eaa333 100644 --- a/api/src/core/configuration/feature_flags.py +++ b/api/src/core/configuration/feature_flags.py @@ -1,3 +1,5 @@ +from typing import Union + from core.configuration.feature_flags_enum import FeatureFlagsEnum from core.environment import Environment from data.schemas.system.feature_flag_dao import featureFlagDao @@ -6,19 +8,31 @@ from data.schemas.system.feature_flag_dao import featureFlagDao class FeatureFlags: _flags = { FeatureFlagsEnum.version_endpoint.value: True, # 15.01.2025 + FeatureFlagsEnum.technical_demo_banner.value: False, # 18.04.2025 FeatureFlagsEnum.per_user_setup.value: Environment.get( "PER_USER_SETUP", bool, False ), # 18.04.2025 } + _overwrite_flags = [ + FeatureFlagsEnum.per_user_setup.value, + ] + + @staticmethod + def overwrite_flag(key: str): + return key in FeatureFlags._overwrite_flags + @staticmethod def get_default(key: FeatureFlagsEnum) -> bool: return FeatureFlags._flags[key.value] @staticmethod - async def has_feature(key: FeatureFlagsEnum) -> bool: - value = await featureFlagDao.find_by_key(key.value) - if value is None: - return FeatureFlags.get_default(key) + async def has_feature(key: Union[str, FeatureFlagsEnum]) -> bool: + key_value = key.value if isinstance(key, FeatureFlagsEnum) else key - return value.value + value = await featureFlagDao.find_by_key(key_value) + return ( + value.value + if value + else FeatureFlags.get_default(FeatureFlagsEnum(key_value)) + ) diff --git a/api/src/core/configuration/feature_flags_enum.py b/api/src/core/configuration/feature_flags_enum.py index d4475ca..c5af428 100644 --- a/api/src/core/configuration/feature_flags_enum.py +++ b/api/src/core/configuration/feature_flags_enum.py @@ -3,4 +3,5 @@ from enum import Enum class FeatureFlagsEnum(Enum): version_endpoint = "VersionEndpoint" + technical_demo_banner = "TechnicalDemoBanner" per_user_setup = "PerUserSetup" diff --git a/api/src/data/seeder/feature_flags_seeder.py b/api/src/data/seeder/feature_flags_seeder.py index 185312c..373e556 100644 --- a/api/src/data/seeder/feature_flags_seeder.py +++ b/api/src/data/seeder/feature_flags_seeder.py @@ -21,6 +21,7 @@ class FeatureFlagsSeeder(DataSeederABC): x.value: FeatureFlags.get_default(x) for x in FeatureFlagsEnum } + # Create new feature flags to_create = [ FeatureFlag(0, x, possible_feature_flags[x]) for x in possible_feature_flags.keys() @@ -31,6 +32,19 @@ class FeatureFlagsSeeder(DataSeederABC): to_create_dicts = {x.key: x.value for x in to_create} logger.debug(f"Created feature flags: {to_create_dicts}") + # Update existing feature flags if they can be overwritten and have a different value + to_update = [ + FeatureFlag(x.id, x.key, possible_feature_flags[x.key]) + for x in feature_flags + if FeatureFlags.overwrite_flag(x.key) + and x.value != possible_feature_flags[x.key] + ] + if len(to_update) > 0: + await featureFlagDao.update_many(to_update) + to_update_dicts = {x.key: x.value for x in to_update} + logger.debug(f"Updated feature flags: {to_update_dicts}") + + # Delete feature flags that are no longer defined to_delete = [ x for x in feature_flags if x.key not in possible_feature_flags.keys() ] diff --git a/web/src/app/service/sidebar.service.ts b/web/src/app/service/sidebar.service.ts index b6f3a2d..49bff52 100644 --- a/web/src/app/service/sidebar.service.ts +++ b/web/src/app/service/sidebar.service.ts @@ -3,6 +3,7 @@ import { BehaviorSubject } from 'rxjs'; import { MenuElement } from 'src/app/model/view/menu-element'; import { AuthService } from 'src/app/service/auth.service'; import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum'; +import { FeatureFlagService } from 'src/app/service/feature-flag.service'; @Injectable({ providedIn: 'root', @@ -11,7 +12,10 @@ export class SidebarService { visible$ = new BehaviorSubject(true); elements$ = new BehaviorSubject([]); - constructor(private auth: AuthService) { + constructor( + private auth: AuthService, + private featureFlags: FeatureFlagService + ) { this.auth.user$.subscribe(async () => { await this.setElements(); }); @@ -40,16 +44,19 @@ export class SidebarService { label: 'common.groups', icon: 'pi pi-tags', routerLink: ['/admin/groups'], - visible: await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups]), + visible: + (await this.auth.hasAnyPermissionLazy([PermissionsEnum.groups])) || + (await this.featureFlags.get('PerUserSetup')), }, { label: 'common.urls', icon: 'pi pi-tag', routerLink: ['/admin/urls'], - visible: await this.auth.hasAnyPermissionLazy([ - PermissionsEnum.shortUrls, - PermissionsEnum.shortUrlsByAssignment, - ]), + visible: + (await this.auth.hasAnyPermissionLazy([ + PermissionsEnum.shortUrls, + PermissionsEnum.shortUrlsByAssignment, + ])) || (await this.featureFlags.get('PerUserSetup')), }, await this.sectionAdmin(), ];