♻️(frontend) replace env NEXT_PUBLIC_FEATURE_TEAM

NEXT_PUBLIC_FEATURE_TEAM is a buid-time env
variable, it is not easy to overload it per
environment.
We will use the config endpoint to get the
feature flag at runtime.
To do so, we are using the ConfigStore.
This commit is contained in:
Anthony LC
2024-08-20 10:31:34 +02:00
committed by Anthony LC
parent 75647fe289
commit 32889b9e8c
12 changed files with 111 additions and 19 deletions

View File

@@ -8,6 +8,10 @@ and this project adheres to
## [Unreleased]
### Added
🔧Runtime config for the frontend #345
## [1.0.1] - 2024-08-19
### Fixed

View File

@@ -1,2 +1 @@
NEXT_PUBLIC_API_ORIGIN=http://localhost:8071
NEXT_PUBLIC_FEATURE_TEAM=true

View File

@@ -1,2 +1 @@
NEXT_PUBLIC_API_ORIGIN=http://test.jest
NEXT_PUBLIC_FEATURE_TEAM=true

View File

@@ -1,23 +1,41 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';
import { useConfigStore } from '@/core';
import { AppWrapper } from '@/tests/utils';
import Page from '../pages';
const mockedPush = jest.fn();
const mockedUseRouter = jest.fn().mockReturnValue({
push: mockedPush,
});
jest.mock('next/navigation', () => ({
...jest.requireActual('next/navigation'),
useRouter: () => ({}),
useRouter: () => mockedUseRouter(),
}));
describe('Page', () => {
it('checks Page rendering', () => {
afterEach(() => jest.clearAllMocks());
it('checks Page rendering with team feature', () => {
useConfigStore.setState({
config: { FEATURES: { TEAMS: true }, LANGUAGES: [] },
});
render(<Page />, { wrapper: AppWrapper });
expect(
screen.getByRole('button', {
name: /Create a new team/i,
}),
).toBeInTheDocument();
expect(mockedPush).toHaveBeenCalledWith('/teams/');
});
it('checks Page rendering without team feature', () => {
useConfigStore.setState({
config: { FEATURES: { TEAMS: false }, LANGUAGES: [] },
});
render(<Page />, { wrapper: AppWrapper });
expect(mockedPush).toHaveBeenCalledWith('/mail-domains/');
});
});

View File

@@ -5,13 +5,17 @@ import { Footer } from '@/features/footer/Footer';
import { HEADER_HEIGHT, Header } from '@/features/header';
import { Menu } from '@/features/menu';
import { useConfigStore } from './config';
export function MainLayout({ children }: PropsWithChildren) {
const { config } = useConfigStore();
return (
<Box>
<Box $height="100vh">
<Header />
<Box $css="flex: 1;" $direction="row">
{process.env.NEXT_PUBLIC_FEATURE_TEAM === 'true' && <Menu />}
{config?.FEATURES.TEAMS && <Menu />}
<Box
as="main"
$height={`calc(100vh - ${HEADER_HEIGHT})`}

View File

@@ -4,6 +4,7 @@ import { render, screen } from '@testing-library/react';
import { AppWrapper } from '@/tests/utils';
import { MainLayout } from '../MainLayout';
import { useConfigStore } from '../config';
jest.mock('next/navigation', () => ({
...jest.requireActual('next/navigation'),
@@ -11,7 +12,11 @@ jest.mock('next/navigation', () => ({
}));
describe('MainLayout', () => {
it('checks menu rendering', () => {
it('checks menu rendering with team feature', () => {
useConfigStore.setState({
config: { FEATURES: { TEAMS: true }, LANGUAGES: [] },
});
render(<MainLayout />, { wrapper: AppWrapper });
expect(
@@ -28,8 +33,6 @@ describe('MainLayout', () => {
});
it('checks menu rendering without team feature', () => {
process.env.NEXT_PUBLIC_FEATURE_TEAM = 'false';
render(<MainLayout />, { wrapper: AppWrapper });
expect(

View File

@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import IconOpenClose from '@/assets/icons/icon-open-close.svg';
import { Box, BoxButton, Text } from '@/components';
import { useConfigStore } from '@/core/';
import { useCunninghamTheme } from '@/cunningham';
import { ItemList } from './ItemList';
@@ -11,6 +12,7 @@ import { PanelActions } from './PanelActions';
export const Panel = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
const { config } = useConfigStore();
const [isOpen, setIsOpen] = useState(true);
@@ -20,7 +22,7 @@ export const Panel = () => {
$minWidth: '0',
};
const styleNoTeam = process.env.NEXT_PUBLIC_FEATURE_TEAM !== 'true' && {
const styleNoTeam = !config?.FEATURES.TEAMS && {
$display: 'none',
tabIndex: -1,
};

View File

@@ -25,7 +25,7 @@ export const Menu = () => {
<MenuItem
Icon={IconGroup}
label={t('Teams')}
href="/"
href="/teams"
alias={['/teams']}
/>
<MenuItem

View File

@@ -11,7 +11,7 @@ const Page: NextPageWithLayout = () => {
useEffect(() => {
config?.FEATURES.TEAMS
? router.push('/teams/')
: router.push('mail-domains/');
: router.push('/mail-domains/');
}, [config?.FEATURES.TEAMS, router]);
return null;

View File

@@ -24,6 +24,6 @@ test.describe('404', () => {
page,
}) => {
await page.getByText('Back to home page').click();
await expect(page).toHaveURL('/');
await expect(page).toHaveURL('/teams/');
});
});

View File

@@ -0,0 +1,63 @@
import { expect, test } from '@playwright/test';
import { keyCloakSignIn } from './common';
test.describe('Config', () => {
test.beforeEach(async ({ page, browserName }) => {
await page.goto('/');
await keyCloakSignIn(page, browserName);
});
test('it checks the config api is called', async ({ page }) => {
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes('/config/') && response.status() === 200,
);
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
expect(await response.json()).toStrictEqual({
LANGUAGES: [
['en-us', 'English'],
['fr-fr', 'French'],
],
FEATURES: { TEAMS: true },
});
});
test('it checks that the config can deactivate the feature "teams"', async ({
page,
}) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
LANGUAGES: [
['en-us', 'English'],
['fr-fr', 'French'],
],
FEATURES: { TEAMS: false },
},
});
} else {
await route.continue();
}
});
await expect(page.locator('menu')).toBeHidden();
await expect(
page.getByRole('button', {
name: 'Create a new team',
}),
).toBeHidden();
await expect(
page.getByRole('button', {
name: 'Add your mail domain',
}),
).toBeVisible();
});
});

View File

@@ -12,7 +12,7 @@ test.describe('Menu', () => {
{
name: 'Teams',
isDefault: true,
expectedUrl: '',
expectedUrl: '/teams/',
expectedText: 'Create a new team',
},
{