Skip to content

Conversation

@bjorngoa
Copy link
Contributor

@bjorngoa bjorngoa commented Dec 4, 2025

Azure DevOps links

User story

This pull request introduces a new Environment Toggle feature to the Account section, allowing users to select and display active environment-specific features. The implementation includes new UI components, updates to the Account avatar and button to reflect environment and impersonation status, and removes the old ImpersonateAvatar in favor of a more flexible status-based avatar system. Additionally, dependencies are updated and minor styling improvements are made.

  • Added a new EnvironmentToggle component, enabling users to select active environment features from a combobox, with selections persisted in local storage and reflected throughout the Account UI.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an environment toggle feature that allows users to switch between production and current environment for specific features (SAM API calls). The implementation adds localStorage-based feature toggles with visual indicators in the TopBar account section.

Key Changes:

  • Added two custom hooks for managing environment toggle state and reading active toggles from localStorage
  • Integrated environment avatar and chips into the account UI to visually indicate when non-production features are active
  • Created a settings panel in the Account dropdown for toggling features with apply/cancel functionality

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/organisms/TopBar/Account/environmentToggles/hooks/useToggleActiveEnvironment.ts New hook to read active environment features from localStorage on component mount
src/organisms/TopBar/Account/environmentToggles/hooks/useEnvironmentToggle.ts New hook to manage feature toggle state with save/reset functionality and page reload on apply
src/organisms/TopBar/Account/EnvironmentAvatar.tsx New component displaying a warning-styled avatar when environment features are active
src/organisms/TopBar/Account/AccountButton.tsx Updated to show environment feature chips and prioritize environment avatar over impersonation avatar
src/organisms/TopBar/Account/AccountAvatar.tsx Updated to display environment avatar with label when features are active
src/organisms/TopBar/Account/Account.tsx Added environment toggle switches section with apply/cancel buttons, only shown in non-production
src/organisms/TopBar/Account/Account.styles.ts Added SwitchWrapper styling and updated ButtonWrapper to support multiple buttons in a row
src/atoms/enums/Environment.ts Added PointToProdFeaturesLocalStorageKey enum defining four toggleable features
Comments suppressed due to low confidence (1)

src/organisms/TopBar/Account/AccountButton.tsx:67

  • The avatar display priority might not align with expectations. Currently, environment features take precedence over impersonation (isActiveFeatureOnCurrentEnvironment is checked first). This means when both an environment toggle is active AND a user is being impersonated, only the environment avatar shows. Consider if this is the intended behavior, or if impersonation should take priority since it's a more critical security indicator.
          {isActiveFeatureOnCurrentEnvironment ? (
            <EnvironmentAvatar size={36} />
          ) : activeImpersonationUser ? (
            <ImpersonateAvatar size={36} />
          ) : (
            <ProfileAvatar size={36} name={account?.name} url={photo} />
          )}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 35 to 37
activeFeaturesOnCurrentEnvironment != null
? activeFeaturesOnCurrentEnvironment.length > 0
: false,
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null check != null is redundant since the state is initialized to [] (not null) and never set to null anywhere in the code. This condition will always be true. Consider simplifying to just check the length: activeFeaturesOnCurrentEnvironment.length > 0.

Suggested change
activeFeaturesOnCurrentEnvironment != null
? activeFeaturesOnCurrentEnvironment.length > 0
: false,
activeFeaturesOnCurrentEnvironment.length > 0,

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Dec 4, 2025

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 99.17% (🎯 100%) 16442 / 16578
🔵 Statements 99.17% (🎯 100%) 16442 / 16578
🔵 Functions 99.07% (🎯 100%) 1178 / 1189
🔵 Branches 99.31% (🎯 100%) 4031 / 4059
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/atoms/utils/environmentToggle.ts 89.18% 66.66% 66.66% 89.18%
src/organisms/TopBar/Account/Account.styles.ts 100% 100% 100% 100%
src/organisms/TopBar/Account/Account.tsx 94.93% 90.9% 100% 94.93%
src/organisms/TopBar/Account/AccountAvatar.tsx 66.66% 66.66% 60% 66.66%
src/organisms/TopBar/Account/AccountButton.tsx 78.57% 58.82% 50% 78.57%
src/organisms/TopBar/Account/EnvironmentToggle.tsx 18.18% 100% 50% 18.18%
src/organisms/TopBar/Account/StatusAvatar.tsx 100% 100% 60% 100%
src/organisms/TopBar/Account/StatusChips.tsx 100% 100% 100% 100%
src/organisms/TopBar/Account/ImpersonateMenu/DeleteUser/DeleteUser.tsx 100% 100% 100% 100%
Generated in workflow #2003 for commit 70b87c9 by the Vitest Coverage Report Action

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings December 9, 2025 07:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{activeFeatures[0].split('-key')[0]}
</ImpersonationRoleChip>
)}
{activeFeatures && activeFeatures.length > 1 && (
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant null check: The check activeFeatures && is unnecessary since activeFeatures is never null (it's initialized to an empty array).

Simplify to: {activeFeatures.length > 1 && (

Suggested change
{activeFeatures && activeFeatures.length > 1 && (
{activeFeatures.length > 1 && (

Copilot uses AI. Check for mistakes.
Comment on lines 178 to 186
<EnvironmentToggleWrapper>
{enableEnvironmentToggle &&
ACTIVE_ENVIRONMENT !== EnvironmentType.PRODUCTION && (
<EnvironmentToggle
setEnvironmentToggle={setEnvironmentToggle}
environmentToggle={environmentToggle}
/>
)}
</EnvironmentToggleWrapper>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this wrapper be inside the EnvironmentToggle component?
How does it look now if you dont have environment toggle enabled?

@@ -0,0 +1,3 @@
export function getActiveFeatureDisplayName(activeFeature: string) {
Copy link
Contributor

@aslakihle aslakihle Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could take the enum that exists in SAM as a type.
And the util itself could do a replace of "-" to a space " " and maybe capitalize the first letter, and they should look better as names.
And not do the split on "-key", gven that the comments on the SAM PR about changing the names (to "feature-toggle" for example) is done

Copilot AI review requested due to automatic review settings December 12, 2025 11:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 13 to 41
const Wrapper = styled.div<StatusVariantProps>`
width: fit-content;
border: 2px solid
${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.success__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
border-radius: 50%;
> div:first-child {
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__text.rgba;
case 'environment':
return colors.interactive.success__text.rgba;
case 'impersonate':
return colors.interactive.warning__text.rgba;
default:
return colors.interactive.warning__text.rgba;
}
}};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant code duplication in the switch statement logic for variant-based styling. The same switch statement pattern appears in StatusAvatar.tsx (lines 17-26 and 30-40), AccountButton.tsx (lines 27-38), and AccountAvatar.tsx (lines 28-51). Consider extracting this logic into a shared utility function or constants object to improve maintainability.

Suggested change
const Wrapper = styled.div<StatusVariantProps>`
width: fit-content;
border: 2px solid
${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.success__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
border-radius: 50%;
> div:first-child {
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__text.rgba;
case 'environment':
return colors.interactive.success__text.rgba;
case 'impersonate':
return colors.interactive.warning__text.rgba;
default:
return colors.interactive.warning__text.rgba;
}
}};
// Utility functions to map variant to color
function getBorderColorForVariant(variant?: StatusVariant) {
switch (variant) {
case 'combined':
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}
function getBackgroundColorForVariant(variant?: StatusVariant) {
switch (variant) {
case 'combined':
case 'impersonate':
return colors.interactive.warning__text.rgba;
case 'environment':
return colors.interactive.success__text.rgba;
default:
return colors.interactive.warning__text.rgba;
}
}
const Wrapper = styled.div<StatusVariantProps>`
width: fit-content;
border: 2px solid
${({ $variant }) => getBorderColorForVariant($variant)};
border-radius: 50%;
> div:first-child {
background: ${({ $variant }) => getBackgroundColorForVariant($variant)};

Copilot uses AI. Check for mistakes.
Comment on lines 3 to 8
export function getActiveFeatureDisplayName(
activeFeature: EnvironmentToggleFeatures
) {
return (
activeFeature.charAt(0).toUpperCase() +
activeFeature.slice(1).replaceAll('-', ' ')
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name "getActiveFeatureDisplayName" is misleading. The parameter is named "activeFeature", but the function simply formats any string by capitalizing the first letter and replacing hyphens with spaces. It doesn't specifically get or determine which feature is active. Consider renaming to something like "formatFeatureName" or "toDisplayName" to better reflect what it does.

Suggested change
export function getActiveFeatureDisplayName(
activeFeature: EnvironmentToggleFeatures
) {
return (
activeFeature.charAt(0).toUpperCase() +
activeFeature.slice(1).replaceAll('-', ' ')
export function formatFeatureName(
feature: EnvironmentToggleFeatures
) {
return (
feature.charAt(0).toUpperCase() +
feature.slice(1).replaceAll('-', ' ')

Copilot uses AI. Check for mistakes.
getActiveFeatureDisplayName(item.value as EnvironmentToggleFeatures)
);

const impersonationRoles = activeImpersonationUser?.roles?.sort() ?? [];
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sort() method mutates the original array. This could cause unintended side effects if the roles array is used elsewhere. Consider using toSorted() or creating a copy with slice() before sorting to avoid mutation.

Suggested change
const impersonationRoles = activeImpersonationUser?.roles?.sort() ?? [];
const impersonationRoles = activeImpersonationUser?.roles?.toSorted() ?? [];

Copilot uses AI. Check for mistakes.
return (
<StatusAvatar
size={36}
name={environmentToggle.map((x) => x.value).join(', ')}
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript may not properly narrow the type of environmentToggle in this block. While isActiveFeatureOnCurrentEnvironment checks that environmentToggle is not null/undefined, TypeScript might still treat it as potentially undefined when calling map() on line 71. Consider using a non-null assertion (environmentToggle!.map) or restructuring the condition to help TypeScript narrow the type.

Suggested change
name={environmentToggle.map((x) => x.value).join(', ')}
name={environmentToggle!.map((x) => x.value).join(', ')}

Copilot uses AI. Check for mistakes.
Comment on lines 26 to 38
export const StatusChip = styled(Chip)<StatusVariantProps>`
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant code duplication in the switch statement logic for variant-based styling. The same switch statement pattern appears in StatusAvatar.tsx (lines 17-26 and 30-40), AccountButton.tsx (lines 27-38), and AccountAvatar.tsx (lines 28-51). Consider extracting this logic into a shared utility function or constants object to improve maintainability.

Suggested change
export const StatusChip = styled(Chip)<StatusVariantProps>`
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
// Extracted variant-to-color mapping
const STATUS_VARIANT_COLORS: Record<string, string> = {
combined: colors.interactive.warning__resting.rgba,
environment: colors.interactive.success__resting.rgba,
impersonate: colors.interactive.warning__resting.rgba,
};
export const StatusChip = styled(Chip)<StatusVariantProps>`
background: ${({ $variant }) =>
STATUS_VARIANT_COLORS[$variant ?? 'combined'] ||
colors.interactive.warning__resting.rgba};

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings December 12, 2025 12:46
aslakihle
aslakihle previously approved these changes Dec 12, 2025
Copy link
Contributor

@aslakihle aslakihle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 16 to 26
${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.success__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement has identical return values for 'combined', 'impersonate', and 'default' cases. This duplication can be simplified by combining these cases together to improve maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines 30 to 41
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__text.rgba;
case 'environment':
return colors.interactive.success__text.rgba;
case 'impersonate':
return colors.interactive.warning__text.rgba;
default:
return colors.interactive.warning__text.rgba;
}
}};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement has identical return values for 'combined', 'impersonate', and 'default' cases. This duplication can be simplified by combining these cases together to improve maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +31
setTimeout(() => {
queryClient.invalidateQueries();
setIsLoading(false);
}, 2000);
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded 2000ms timeout value should be extracted as a named constant with a descriptive name (e.g., QUERY_INVALIDATION_DELAY) to improve code maintainability and make the intent clearer.

Copilot uses AI. Check for mistakes.
setEnvironmentToggle(value);
setIsLoading(true);
setTimeout(() => {
queryClient.invalidateQueries();
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling invalidateQueries without any filter will invalidate all queries in the application, which may cause unnecessary network requests and performance issues. Consider targeting specific query keys that are affected by the environment toggle change.

Suggested change
queryClient.invalidateQueries();
queryClient.invalidateQueries(['environment']);

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 38
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement has identical return values for 'combined', 'impersonate', and 'default' cases. This duplication can be simplified by combining these cases together to improve maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 51
background: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.warning__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
outline-color: ${({ $variant }) => {
switch ($variant) {
case 'combined':
return colors.interactive.success__resting.rgba;
case 'environment':
return colors.interactive.success__resting.rgba;
case 'impersonate':
return colors.interactive.warning__resting.rgba;
default:
return colors.interactive.warning__resting.rgba;
}
}};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement has identical return values for 'combined', 'impersonate', and 'default' cases. This duplication can be simplified by combining these cases together to improve maintainability.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings December 12, 2025 13:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@aslakihle aslakihle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

@aslakihle aslakihle merged commit f5e7b8f into main Dec 15, 2025
9 of 11 checks passed
@aslakihle aslakihle deleted the feat/env-toggle branch December 15, 2025 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants