mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
[Gitar] Cleaning up stale flag: purchaseAdditionalEnvironments with value false (#8955)
[](https://gitar.ai) This automated PR permanently removes the `purchaseAdditionalEnvironments` feature flag. --- This automated PR was generated by [Gitar](https://gitar.ai). View [docs](https://gitar.ai/docs). --------- Co-authored-by: Gitar <noreply@gitar.ai> Co-authored-by: sjaanus <sellinjaanus@gmail.com>
This commit is contained in:
parent
eb7e2a655d
commit
8c189cabd2
@ -28,7 +28,6 @@ import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
|||||||
import type { IEnvironment } from 'interfaces/environments';
|
import type { IEnvironment } from 'interfaces/environments';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||||
import { OrderEnvironments } from './OrderEnvironments/OrderEnvironments';
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
marginBottom: theme.spacing(4),
|
marginBottom: theme.spacing(4),
|
||||||
}));
|
}));
|
||||||
@ -38,9 +37,6 @@ export const EnvironmentTable = () => {
|
|||||||
const { setToastApiError } = useToast();
|
const { setToastApiError } = useToast();
|
||||||
const { environments, mutateEnvironments } = useEnvironments();
|
const { environments, mutateEnvironments } = useEnvironments();
|
||||||
const isFeatureEnabled = useUiFlag('EEA');
|
const isFeatureEnabled = useUiFlag('EEA');
|
||||||
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
|
|
||||||
'purchaseAdditionalEnvironments',
|
|
||||||
);
|
|
||||||
|
|
||||||
const moveListItem: MoveListItem = useCallback(
|
const moveListItem: MoveListItem = useCallback(
|
||||||
async (dragIndex: number, dropIndex: number, save = false) => {
|
async (dragIndex: number, dropIndex: number, save = false) => {
|
||||||
@ -116,7 +112,7 @@ export const EnvironmentTable = () => {
|
|||||||
<PageHeader title={`Environments (${count})`} actions={headerActions} />
|
<PageHeader title={`Environments (${count})`} actions={headerActions} />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isFeatureEnabled && !isPurchaseAdditionalEnvironmentsEnabled) {
|
if (!isFeatureEnabled) {
|
||||||
return (
|
return (
|
||||||
<PageContent header={header}>
|
<PageContent header={header}>
|
||||||
<PremiumFeature feature='environments' />
|
<PremiumFeature feature='environments' />
|
||||||
@ -126,7 +122,6 @@ export const EnvironmentTable = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent header={header}>
|
<PageContent header={header}>
|
||||||
<OrderEnvironments />
|
|
||||||
<StyledAlert severity='info'>
|
<StyledAlert severity='info'>
|
||||||
This is the order of environments that you have today in each
|
This is the order of environments that you have today in each
|
||||||
feature flag. Rearranging them here will change also the order
|
feature flag. Rearranging them here will change also the order
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
|
||||||
import { render } from 'utils/testRenderer';
|
|
||||||
import { OrderEnvironments } from './OrderEnvironments';
|
|
||||||
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
|
||||||
|
|
||||||
const server = testServerSetup();
|
|
||||||
|
|
||||||
const setupServerRoutes = (changeRequestsEnabled = true) => {
|
|
||||||
testServerRoute(server, '/api/admin/ui-config', {
|
|
||||||
environment: 'Pro',
|
|
||||||
flags: {
|
|
||||||
purchaseAdditionalEnvironments: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('OrderEnvironmentsDialog Component', () => {
|
|
||||||
test('should show error if environment name is empty', async () => {
|
|
||||||
setupServerRoutes();
|
|
||||||
render(<OrderEnvironments />);
|
|
||||||
|
|
||||||
await waitFor(async () => {
|
|
||||||
const openDialogButton = await screen.queryByRole('button', {
|
|
||||||
name: /view pricing/i,
|
|
||||||
});
|
|
||||||
expect(openDialogButton).toBeInTheDocument();
|
|
||||||
fireEvent.click(openDialogButton!);
|
|
||||||
});
|
|
||||||
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByText(/environment name is required/i),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,84 +0,0 @@
|
|||||||
import { useState, type FC } from 'react';
|
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
|
||||||
import { PurchasableFeature } from './PurchasableFeature/PurchasableFeature';
|
|
||||||
import { OrderEnvironmentsDialog } from './OrderEnvironmentsDialog/OrderEnvironmentsDialog';
|
|
||||||
import { OrderEnvironmentsConfirmation } from './OrderEnvironmentsConfirmation/OrderEnvironmentsConfirmation';
|
|
||||||
import { useFormErrors } from 'hooks/useFormErrors';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { useOrderEnvironmentApi } from 'hooks/api/actions/useOrderEnvironmentsApi/useOrderEnvironmentsApi';
|
|
||||||
import type { OrderEnvironmentsSchema } from 'openapi';
|
|
||||||
|
|
||||||
type OrderEnvironmentsProps = {};
|
|
||||||
|
|
||||||
export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
|
|
||||||
const [purchaseDialogOpen, setPurchaseDialogOpen] = useState(false);
|
|
||||||
const [confirmationState, setConfirmationState] = useState<{
|
|
||||||
isOpen: boolean;
|
|
||||||
environmentsCount?: number;
|
|
||||||
}>({ isOpen: false });
|
|
||||||
const { isPro } = useUiConfig();
|
|
||||||
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
|
|
||||||
'purchaseAdditionalEnvironments',
|
|
||||||
);
|
|
||||||
const errors = useFormErrors();
|
|
||||||
const { orderEnvironments } = useOrderEnvironmentApi();
|
|
||||||
const { setToastApiError } = useToast();
|
|
||||||
|
|
||||||
if (!isPro() || !isPurchaseAdditionalEnvironmentsEnabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async (
|
|
||||||
environments: OrderEnvironmentsSchema['environments'],
|
|
||||||
) => {
|
|
||||||
let hasErrors = false;
|
|
||||||
environments.forEach((environment, index) => {
|
|
||||||
const field = `environment-${index}`;
|
|
||||||
const environmentName = environment.name.trim();
|
|
||||||
if (environmentName === '') {
|
|
||||||
errors.setFormError(field, 'Environment name is required');
|
|
||||||
hasErrors = true;
|
|
||||||
} else {
|
|
||||||
errors.removeFormError(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasErrors) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await orderEnvironments({ environments });
|
|
||||||
setPurchaseDialogOpen(false);
|
|
||||||
setConfirmationState({
|
|
||||||
isOpen: true,
|
|
||||||
environmentsCount: environments.length,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
setToastApiError(formatUnknownError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PurchasableFeature
|
|
||||||
title='Order additional environments'
|
|
||||||
description='With our Pro plan, you now have the flexibility to expand your workspace by adding up to three additional environments.'
|
|
||||||
onClick={() => setPurchaseDialogOpen(true)}
|
|
||||||
/>
|
|
||||||
<OrderEnvironmentsDialog
|
|
||||||
open={purchaseDialogOpen}
|
|
||||||
onClose={() => setPurchaseDialogOpen(false)}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
<OrderEnvironmentsConfirmation
|
|
||||||
open={confirmationState.isOpen}
|
|
||||||
orderedEnvironments={confirmationState.environmentsCount || 0}
|
|
||||||
onClose={() => setConfirmationState({ isOpen: false })}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { Typography } from '@mui/material';
|
|
||||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
|
||||||
|
|
||||||
type OrderEnvironmentsConfirmationProps = {
|
|
||||||
open: boolean;
|
|
||||||
orderedEnvironments: number;
|
|
||||||
onClose: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OrderEnvironmentsConfirmation: FC<
|
|
||||||
OrderEnvironmentsConfirmationProps
|
|
||||||
> = ({ open, orderedEnvironments, onClose }) => {
|
|
||||||
return (
|
|
||||||
<Dialogue
|
|
||||||
open={open}
|
|
||||||
title='Order confirmed'
|
|
||||||
onClick={onClose}
|
|
||||||
primaryButtonText='Close'
|
|
||||||
>
|
|
||||||
<Typography>
|
|
||||||
You have ordered <strong>{orderedEnvironments}</strong>{' '}
|
|
||||||
additional{' '}
|
|
||||||
{orderedEnvironments === 1 ? 'environment' : 'environments'}. It
|
|
||||||
may take up to 24 hours before you will get access.
|
|
||||||
</Typography>
|
|
||||||
</Dialogue>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,212 +0,0 @@
|
|||||||
import { vi } from 'vitest';
|
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
|
||||||
import { OrderEnvironmentsDialog } from './OrderEnvironmentsDialog';
|
|
||||||
|
|
||||||
describe('OrderEnvironmentsDialog Component', () => {
|
|
||||||
const renderComponent = (props = {}) =>
|
|
||||||
render(
|
|
||||||
<OrderEnvironmentsDialog
|
|
||||||
open={true}
|
|
||||||
onClose={() => {}}
|
|
||||||
onSubmit={() => {}}
|
|
||||||
{...props}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
test('should disable "Order" button until the checkbox is checked', () => {
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const orderButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments leads to extra costs/i,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(orderButton).toBeDisabled();
|
|
||||||
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
expect(orderButton).toBeEnabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render correct number of environment name inputs based on selected environments', () => {
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
let environmentInputs =
|
|
||||||
screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
expect(environmentInputs).toHaveLength(1);
|
|
||||||
|
|
||||||
const selectButton = screen.getByRole('combobox', {
|
|
||||||
name: /select the number of additional environments/i,
|
|
||||||
});
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
|
|
||||||
const option2 = screen.getByRole('option', { name: '2 environments' });
|
|
||||||
fireEvent.click(option2);
|
|
||||||
|
|
||||||
environmentInputs = screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
expect(environmentInputs).toHaveLength(2);
|
|
||||||
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
const option3 = screen.getByRole('option', { name: '3 environments' });
|
|
||||||
fireEvent.click(option3);
|
|
||||||
|
|
||||||
environmentInputs = screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
expect(environmentInputs).toHaveLength(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should enable "Order" button only when checkbox is checked', () => {
|
|
||||||
renderComponent();
|
|
||||||
|
|
||||||
const orderButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments leads to extra costs/i,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(orderButton).toBeDisabled();
|
|
||||||
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
expect(orderButton).toBeEnabled();
|
|
||||||
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
expect(orderButton).toBeDisabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should output environment names', () => {
|
|
||||||
const onSubmitMock = vi.fn();
|
|
||||||
renderComponent({ onSubmit: onSubmitMock });
|
|
||||||
|
|
||||||
const selectButton = screen.getByRole('combobox', {
|
|
||||||
name: /select the number of additional environments/i,
|
|
||||||
});
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
|
|
||||||
const option2 = screen.getByRole('option', { name: '2 environments' });
|
|
||||||
fireEvent.click(option2);
|
|
||||||
|
|
||||||
const environmentInputs =
|
|
||||||
screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
|
|
||||||
fireEvent.change(environmentInputs[0], { target: { value: 'Dev' } });
|
|
||||||
fireEvent.change(environmentInputs[1], {
|
|
||||||
target: { value: 'Staging' },
|
|
||||||
});
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments leads to extra costs/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledWith([
|
|
||||||
{ name: 'Dev', type: 'development' },
|
|
||||||
{ name: 'Staging', type: 'development' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should call onClose when "Cancel" button is clicked', () => {
|
|
||||||
const onCloseMock = vi.fn();
|
|
||||||
renderComponent({ onClose: onCloseMock });
|
|
||||||
|
|
||||||
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
|
||||||
fireEvent.click(cancelButton);
|
|
||||||
|
|
||||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should adjust environment name inputs when decreasing environments', () => {
|
|
||||||
const onSubmitMock = vi.fn();
|
|
||||||
renderComponent({ onSubmit: onSubmitMock });
|
|
||||||
|
|
||||||
const selectButton = screen.getByRole('combobox', {
|
|
||||||
name: /select the number of additional environments/i,
|
|
||||||
});
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
|
|
||||||
const option3 = screen.getByRole('option', { name: '3 environments' });
|
|
||||||
fireEvent.click(option3);
|
|
||||||
|
|
||||||
let environmentInputs =
|
|
||||||
screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
expect(environmentInputs).toHaveLength(3);
|
|
||||||
|
|
||||||
fireEvent.change(environmentInputs[0], { target: { value: 'Dev' } });
|
|
||||||
fireEvent.change(environmentInputs[1], {
|
|
||||||
target: { value: 'Staging' },
|
|
||||||
});
|
|
||||||
fireEvent.change(environmentInputs[2], { target: { value: 'Prod' } });
|
|
||||||
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
const option2 = screen.getByRole('option', { name: '2 environments' });
|
|
||||||
fireEvent.click(option2);
|
|
||||||
|
|
||||||
environmentInputs = screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
expect(environmentInputs).toHaveLength(2);
|
|
||||||
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments leads to extra costs/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledWith([
|
|
||||||
{ name: 'Dev', type: 'development' },
|
|
||||||
{ name: 'Staging', type: 'development' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should allow for changing environment types', () => {
|
|
||||||
const onSubmitMock = vi.fn();
|
|
||||||
renderComponent({ onSubmit: onSubmitMock });
|
|
||||||
|
|
||||||
const selectButton = screen.getByRole('combobox', {
|
|
||||||
name: /select the number of additional environments/i,
|
|
||||||
});
|
|
||||||
fireEvent.mouseDown(selectButton);
|
|
||||||
const option3 = screen.getByRole('option', { name: '2 environments' });
|
|
||||||
fireEvent.click(option3);
|
|
||||||
|
|
||||||
const checkbox = screen.getByRole('checkbox', {
|
|
||||||
name: /i understand adding environments leads to extra costs/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(checkbox);
|
|
||||||
|
|
||||||
const environmentInputs =
|
|
||||||
screen.getAllByLabelText(/environment \d+ name/i);
|
|
||||||
fireEvent.change(environmentInputs[0], { target: { value: 'Test' } });
|
|
||||||
fireEvent.change(environmentInputs[1], {
|
|
||||||
target: { value: 'Staging' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const typeSelects = screen.getAllByRole('combobox', {
|
|
||||||
name: /type of environment/i,
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.mouseDown(typeSelects[0]);
|
|
||||||
const optionTesting = screen.getByRole('option', {
|
|
||||||
name: /testing/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(optionTesting);
|
|
||||||
|
|
||||||
fireEvent.mouseDown(typeSelects[1]);
|
|
||||||
const optionProduction = screen.getByRole('option', {
|
|
||||||
name: /pre\-production/i,
|
|
||||||
});
|
|
||||||
fireEvent.click(optionProduction);
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole('button', { name: /order/i });
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(onSubmitMock).toHaveBeenCalledWith([
|
|
||||||
{ name: 'Test', type: 'testing' },
|
|
||||||
{ name: 'Staging', type: 'pre-production' },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,253 +0,0 @@
|
|||||||
import { useState, type FC } from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Dialog,
|
|
||||||
styled,
|
|
||||||
Typography,
|
|
||||||
TextField,
|
|
||||||
} from '@mui/material';
|
|
||||||
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
|
||||||
import { OrderEnvironmentsDialogPricing } from './OrderEnvironmentsDialogPricing/OrderEnvironmentsDialogPricing';
|
|
||||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
|
||||||
import type { IFormErrors } from 'hooks/useFormErrors';
|
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|
||||||
import type { OrderEnvironmentsSchemaEnvironmentsItem } from 'openapi';
|
|
||||||
|
|
||||||
type OrderEnvironmentsDialogProps = {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onSubmit: (environments: OrderEnvironmentsSchemaEnvironmentsItem[]) => void;
|
|
||||||
errors?: IFormErrors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledDialog = styled(Dialog)(({ theme }) => ({
|
|
||||||
maxWidth: '940px',
|
|
||||||
margin: 'auto',
|
|
||||||
'& .MuiDialog-paper': {
|
|
||||||
borderRadius: theme.shape.borderRadiusExtraLarge,
|
|
||||||
maxWidth: theme.spacing(170),
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
padding: 0,
|
|
||||||
'& .MuiPaper-root > section': {
|
|
||||||
overflowX: 'hidden',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledTitle = styled('div')(({ theme }) => ({
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledFooter = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledGeneralSelect = styled(GeneralSelect)(({ theme }) => ({
|
|
||||||
margin: theme.spacing(1, 0),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledTypeSelect = styled(GeneralSelect)(({ theme }) => ({
|
|
||||||
minWidth: '166px',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledEnvironmentInputs = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledFields = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
paddingTop: theme.spacing(3),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledEnvironmentNameInputs = styled('fieldset')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
border: 'none',
|
|
||||||
padding: 0,
|
|
||||||
margin: 0,
|
|
||||||
gap: theme.spacing(1.5),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledCheckbox = styled(Checkbox)(({ theme }) => ({
|
|
||||||
marginBottom: theme.spacing(0.4),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const PRICE = 10;
|
|
||||||
const OPTIONS = [1, 2, 3];
|
|
||||||
const ENVIRONMENT_TYPES = [
|
|
||||||
'development',
|
|
||||||
'testing',
|
|
||||||
'pre-production',
|
|
||||||
'production',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
onSubmit,
|
|
||||||
errors,
|
|
||||||
}) => {
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
|
||||||
const [selectedOption, setSelectedOption] = useState(OPTIONS[0]);
|
|
||||||
const [costCheckboxChecked, setCostCheckboxChecked] = useState(false);
|
|
||||||
const [environments, setEnvironments] = useState<
|
|
||||||
{ name: string; type: string }[]
|
|
||||||
>([{ name: '', type: ENVIRONMENT_TYPES[0] }]);
|
|
||||||
|
|
||||||
const trackEnvironmentSelect = () => {
|
|
||||||
trackEvent('order-environments', {
|
|
||||||
props: {
|
|
||||||
eventType: 'selected environment count',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTypeChange = (index: number, type: string) => {
|
|
||||||
setEnvironments(
|
|
||||||
environments.map((env, i) =>
|
|
||||||
i === index ? { ...env, type } : { ...env },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onNameChange = (index: number, name: string) => {
|
|
||||||
setEnvironments(
|
|
||||||
environments.map((env, i) =>
|
|
||||||
i === index ? { ...env, name } : { ...env },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledDialog open={open} title=''>
|
|
||||||
<FormTemplate
|
|
||||||
compact
|
|
||||||
description={
|
|
||||||
<OrderEnvironmentsDialogPricing
|
|
||||||
pricingOptions={OPTIONS.map((option) => ({
|
|
||||||
environments: option,
|
|
||||||
price: option * PRICE,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
footer={
|
|
||||||
<StyledFooter>
|
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
|
||||||
<Button
|
|
||||||
variant='contained'
|
|
||||||
disabled={!costCheckboxChecked}
|
|
||||||
onClick={() => onSubmit(environments)}
|
|
||||||
>
|
|
||||||
Order
|
|
||||||
</Button>
|
|
||||||
</StyledFooter>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledTitle>
|
|
||||||
<Typography variant='h3' component='div'>
|
|
||||||
Order additional environments
|
|
||||||
</Typography>
|
|
||||||
</StyledTitle>
|
|
||||||
<Typography variant='body2' color='text.secondary'>
|
|
||||||
With our PRO plan, you have the flexibility to expand your
|
|
||||||
workspace by adding environments at ${PRICE} per user per
|
|
||||||
month.
|
|
||||||
</Typography>
|
|
||||||
<StyledFields>
|
|
||||||
<Box>
|
|
||||||
<Typography component='label' id='numberOfEnvironments'>
|
|
||||||
Select the number of additional environments
|
|
||||||
</Typography>
|
|
||||||
<StyledGeneralSelect
|
|
||||||
labelId='numberOfEnvironments'
|
|
||||||
value={`${selectedOption}`}
|
|
||||||
options={OPTIONS.map((option) => ({
|
|
||||||
key: `${option}`,
|
|
||||||
label: `${option} environment${option > 1 ? 's' : ''}`,
|
|
||||||
}))}
|
|
||||||
onChange={(option) => {
|
|
||||||
const value = Number.parseInt(option, 10);
|
|
||||||
setSelectedOption(value);
|
|
||||||
setEnvironments((envs) =>
|
|
||||||
[
|
|
||||||
...envs,
|
|
||||||
...Array(value).fill({
|
|
||||||
name: '',
|
|
||||||
type: ENVIRONMENT_TYPES[0],
|
|
||||||
}),
|
|
||||||
].slice(0, value),
|
|
||||||
);
|
|
||||||
trackEnvironmentSelect();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<StyledEnvironmentNameInputs>
|
|
||||||
<Typography>
|
|
||||||
How would you like the environment
|
|
||||||
{selectedOption > 1 ? 's' : ''} to be named?
|
|
||||||
</Typography>
|
|
||||||
{[...Array(selectedOption)].map((_, i) => {
|
|
||||||
const error = errors?.getFormError(
|
|
||||||
`environment-${i}`,
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<StyledEnvironmentInputs key={i}>
|
|
||||||
<StyledTypeSelect
|
|
||||||
label='Type of environment'
|
|
||||||
labelId={`environmentType${i}`}
|
|
||||||
value={
|
|
||||||
environments[i]?.type ||
|
|
||||||
ENVIRONMENT_TYPES[0]
|
|
||||||
}
|
|
||||||
options={ENVIRONMENT_TYPES.map(
|
|
||||||
(type) => ({
|
|
||||||
key: type,
|
|
||||||
label: type,
|
|
||||||
}),
|
|
||||||
)}
|
|
||||||
onChange={(type) => {
|
|
||||||
onTypeChange(i, type);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
size='small'
|
|
||||||
label={`Environment ${i + 1} Name`}
|
|
||||||
value={environments[i]?.name || ''}
|
|
||||||
onChange={(e) => {
|
|
||||||
onNameChange(i, e.target.value);
|
|
||||||
}}
|
|
||||||
error={!!error}
|
|
||||||
helperText={error}
|
|
||||||
/>
|
|
||||||
</StyledEnvironmentInputs>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</StyledEnvironmentNameInputs>
|
|
||||||
<Box>
|
|
||||||
<StyledCheckbox
|
|
||||||
edge='start'
|
|
||||||
id='costsCheckbox'
|
|
||||||
checked={costCheckboxChecked}
|
|
||||||
onChange={() =>
|
|
||||||
setCostCheckboxChecked((state) => !state)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Typography component='label' htmlFor='costsCheckbox'>
|
|
||||||
I understand adding environments leads to extra
|
|
||||||
costs
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</StyledFields>
|
|
||||||
</FormTemplate>
|
|
||||||
</StyledDialog>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,71 +0,0 @@
|
|||||||
import type { FC } from 'react';
|
|
||||||
import { Box, Card, styled, Typography } from '@mui/material';
|
|
||||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
|
||||||
import { BILLING_PRO_DEFAULT_INCLUDED_SEATS } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
|
|
||||||
|
|
||||||
type OrderEnvironmentsDialogPricingProps = {
|
|
||||||
pricingOptions: Array<{ environments: number; price: number }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(1),
|
|
||||||
justifyContent: 'center',
|
|
||||||
height: '100%',
|
|
||||||
[theme.breakpoints.up('lg')]: {
|
|
||||||
marginTop: theme.spacing(7.5),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledCard = styled(Card)(({ theme }) => ({
|
|
||||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
|
||||||
boxShadow: 'none',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledCardContent = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
flexDirection: 'row',
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledExtraText = styled('div')(({ theme }) => ({
|
|
||||||
paddingTop: theme.spacing(2),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const OrderEnvironmentsDialogPricing: FC<
|
|
||||||
OrderEnvironmentsDialogPricingProps
|
|
||||||
> = ({ pricingOptions }) => (
|
|
||||||
<StyledContainer>
|
|
||||||
<Typography variant='h3' component='div' color='white' gutterBottom>
|
|
||||||
Pricing
|
|
||||||
</Typography>
|
|
||||||
{pricingOptions.map((option) => (
|
|
||||||
<StyledCard key={option.environments}>
|
|
||||||
<StyledCardContent>
|
|
||||||
<EnvironmentIcon enabled />
|
|
||||||
<Box>
|
|
||||||
<Box>
|
|
||||||
<Typography variant='body2' fontWeight='bold'>
|
|
||||||
{option.environments} additional environment
|
|
||||||
{option.environments > 1 ? 's' : ''}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Typography variant='body2'>
|
|
||||||
${option.price} per user per month
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</StyledCardContent>
|
|
||||||
</StyledCard>
|
|
||||||
))}
|
|
||||||
<StyledExtraText>
|
|
||||||
<Typography variant='body2' color='white'>
|
|
||||||
With Pro, there is a minimum of{' '}
|
|
||||||
{BILLING_PRO_DEFAULT_INCLUDED_SEATS} users, meaning an
|
|
||||||
additional environment will cost at least $50 per month.
|
|
||||||
</Typography>
|
|
||||||
</StyledExtraText>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
@ -1,83 +0,0 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
|
||||||
import { Box, Button, styled, Typography } from '@mui/material';
|
|
||||||
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
|
||||||
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
|
|
||||||
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
|
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
|
||||||
|
|
||||||
type PurchasableFeatureProps = {
|
|
||||||
title: ReactNode;
|
|
||||||
description: ReactNode;
|
|
||||||
onClick: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Icon = () => (
|
|
||||||
<ThemeMode darkmode={<ProPlanIconLight />} lightmode={<ProPlanIcon />} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
|
||||||
padding: theme.spacing(2, 3),
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
background: theme.palette.background.elevation2,
|
|
||||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: theme.spacing(3),
|
|
||||||
[theme.breakpoints.down('md')]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledIconContainer = styled(Box)(() => ({
|
|
||||||
width: '36px',
|
|
||||||
flexShrink: 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledMessage = styled(Box)(() => ({
|
|
||||||
flexGrow: 1,
|
|
||||||
display: 'flex',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledButtonContainer = styled(Box)(() => ({
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const PurchasableFeature: FC<PurchasableFeatureProps> = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
onClick,
|
|
||||||
}) => {
|
|
||||||
const { trackEvent } = usePlausibleTracker();
|
|
||||||
|
|
||||||
const onViewPricingClick = () => {
|
|
||||||
onClick();
|
|
||||||
trackEvent('order-environments', {
|
|
||||||
props: {
|
|
||||||
eventType: 'view pricing clicked',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledContainer>
|
|
||||||
<StyledMessage>
|
|
||||||
<StyledIconContainer>
|
|
||||||
<Icon />
|
|
||||||
</StyledIconContainer>
|
|
||||||
<Box>
|
|
||||||
<Typography variant='h3'>{title}</Typography>
|
|
||||||
<Typography>{description}</Typography>
|
|
||||||
</Box>
|
|
||||||
</StyledMessage>
|
|
||||||
<StyledButtonContainer>
|
|
||||||
<Button variant='contained' onClick={onViewPricingClick}>
|
|
||||||
View pricing
|
|
||||||
</Button>
|
|
||||||
</StyledButtonContainer>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
import useAPI from '../useApi/useApi';
|
|
||||||
import type { OrderEnvironmentsSchema } from 'openapi';
|
|
||||||
|
|
||||||
export const useOrderEnvironmentApi = () => {
|
|
||||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
|
||||||
propagateErrors: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const orderEnvironments = async (payload: OrderEnvironmentsSchema) => {
|
|
||||||
const req = createRequest('api/admin/order-environments', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
await makeRequest(req.caller, req.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
orderEnvironments,
|
|
||||||
errors,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
};
|
|
@ -85,7 +85,6 @@ export type UiFlags = {
|
|||||||
manyStrategiesPagination?: boolean;
|
manyStrategiesPagination?: boolean;
|
||||||
enableLegacyVariants?: boolean;
|
enableLegacyVariants?: boolean;
|
||||||
flagCreator?: boolean;
|
flagCreator?: boolean;
|
||||||
purchaseAdditionalEnvironments?: boolean;
|
|
||||||
unleashAI?: boolean;
|
unleashAI?: boolean;
|
||||||
releasePlans?: boolean;
|
releasePlans?: boolean;
|
||||||
'enterprise-payg'?: boolean;
|
'enterprise-payg'?: boolean;
|
||||||
|
@ -103,47 +103,6 @@ test('should strip special characters from email subject', async () => {
|
|||||||
expect(emailService.stripSpecialCharacters('tom-jones')).toBe('tom-jones');
|
expect(emailService.stripSpecialCharacters('tom-jones')).toBe('tom-jones');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can send order environments email', async () => {
|
|
||||||
process.env.ORDER_ENVIRONMENTS_BCC = 'bcc@bcc.com';
|
|
||||||
const emailService = new EmailService({
|
|
||||||
email: {
|
|
||||||
host: 'test',
|
|
||||||
port: 587,
|
|
||||||
secure: false,
|
|
||||||
smtpuser: '',
|
|
||||||
smtppass: '',
|
|
||||||
sender: 'noreply@getunleash.ai',
|
|
||||||
},
|
|
||||||
getLogger: noLoggerProvider,
|
|
||||||
} as unknown as IUnleashConfig);
|
|
||||||
|
|
||||||
const customerId = 'customer133';
|
|
||||||
const environments = [
|
|
||||||
{ name: 'test', type: 'development' },
|
|
||||||
{ name: 'live', type: 'production' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const content = await emailService.sendOrderEnvironmentEmail(
|
|
||||||
'user@user.com',
|
|
||||||
customerId,
|
|
||||||
environments,
|
|
||||||
);
|
|
||||||
expect(content.from).toBe('noreply@getunleash.ai');
|
|
||||||
expect(content.subject).toBe('Unleash - ordered environments successfully');
|
|
||||||
expect(
|
|
||||||
content.html.includes(
|
|
||||||
`<li>Name: ${environments[0].name}, Type: ${environments[0].type}</li>`,
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
expect(
|
|
||||||
content.html.includes(
|
|
||||||
`<li>Name: ${environments[1].name}, Type: ${environments[1].type}</li>`,
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
expect(content.html.includes(customerId)).toBe(true);
|
|
||||||
expect(content.bcc).toBe('bcc@bcc.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can send productivity report email', async () => {
|
test('Can send productivity report email', async () => {
|
||||||
const emailService = new EmailService({
|
const emailService = new EmailService({
|
||||||
server: {
|
server: {
|
||||||
|
@ -70,11 +70,6 @@ export type ChangeRequestScheduleConflictData =
|
|||||||
environment: string;
|
environment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OrderEnvironmentData = {
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
private config: IUnleashConfig;
|
private config: IUnleashConfig;
|
||||||
@ -462,71 +457,6 @@ export class EmailService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendOrderEnvironmentEmail(
|
|
||||||
userEmail: string,
|
|
||||||
customerId: string,
|
|
||||||
environments: OrderEnvironmentData[],
|
|
||||||
): Promise<IEmailEnvelope> {
|
|
||||||
if (this.configured()) {
|
|
||||||
const context = {
|
|
||||||
userEmail,
|
|
||||||
customerId,
|
|
||||||
environments: environments.map((data) => ({
|
|
||||||
name: this.stripSpecialCharacters(data.name),
|
|
||||||
type: this.stripSpecialCharacters(data.type),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
const bodyHtml = await this.compileTemplate(
|
|
||||||
'order-environments',
|
|
||||||
TemplateFormat.HTML,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
const bodyText = await this.compileTemplate(
|
|
||||||
'order-environments',
|
|
||||||
TemplateFormat.PLAIN,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
const email = {
|
|
||||||
from: this.sender,
|
|
||||||
to: userEmail,
|
|
||||||
bcc:
|
|
||||||
process.env.ORDER_ENVIRONMENTS_BCC ||
|
|
||||||
'pro-sales@getunleash.io',
|
|
||||||
subject: ORDER_ENVIRONMENTS_SUBJECT,
|
|
||||||
html: bodyHtml,
|
|
||||||
text: bodyText,
|
|
||||||
};
|
|
||||||
process.nextTick(() => {
|
|
||||||
this.mailer!.sendMail(email).then(
|
|
||||||
() =>
|
|
||||||
this.logger.info(
|
|
||||||
'Successfully sent order environments email',
|
|
||||||
),
|
|
||||||
(e) =>
|
|
||||||
this.logger.warn(
|
|
||||||
'Failed to send order environments email',
|
|
||||||
e,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return Promise.resolve(email);
|
|
||||||
}
|
|
||||||
return new Promise((res) => {
|
|
||||||
this.logger.warn(
|
|
||||||
'No mailer is configured. Please read the docs on how to configure an email service',
|
|
||||||
);
|
|
||||||
res({
|
|
||||||
from: this.sender,
|
|
||||||
to: userEmail,
|
|
||||||
bcc: '',
|
|
||||||
subject: ORDER_ENVIRONMENTS_SUBJECT,
|
|
||||||
html: '',
|
|
||||||
text: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendProductivityReportEmail(
|
async sendProductivityReportEmail(
|
||||||
userEmail: string,
|
userEmail: string,
|
||||||
userName: string,
|
userName: string,
|
||||||
|
@ -47,7 +47,6 @@ export type IFlagKey =
|
|||||||
| 'extendedMetrics'
|
| 'extendedMetrics'
|
||||||
| 'removeUnsafeInlineStyleSrc'
|
| 'removeUnsafeInlineStyleSrc'
|
||||||
| 'projectRoleAssignment'
|
| 'projectRoleAssignment'
|
||||||
| 'purchaseAdditionalEnvironments'
|
|
||||||
| 'originMiddlewareRequestLogging'
|
| 'originMiddlewareRequestLogging'
|
||||||
| 'unleashAI'
|
| 'unleashAI'
|
||||||
| 'webhookDomainLogging'
|
| 'webhookDomainLogging'
|
||||||
@ -239,10 +238,6 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_PROJECT_ROLE_ASSIGNMENT,
|
process.env.UNLEASH_EXPERIMENTAL_PROJECT_ROLE_ASSIGNMENT,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
purchaseAdditionalEnvironments: parseEnvVarBoolean(
|
|
||||||
process.env.UNLEASH_EXPERIMENTAL_PURCHASE_ADDITIONAL_ENVIRONMENTS,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
originMiddlewareRequestLogging: parseEnvVarBoolean(
|
originMiddlewareRequestLogging: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_ORIGIN_MIDDLEWARE_REQUEST_LOGGING,
|
process.env.UNLEASH_ORIGIN_MIDDLEWARE_REQUEST_LOGGING,
|
||||||
false,
|
false,
|
||||||
|
@ -48,7 +48,6 @@ process.nextTick(async () => {
|
|||||||
manyStrategiesPagination: true,
|
manyStrategiesPagination: true,
|
||||||
enableLegacyVariants: false,
|
enableLegacyVariants: false,
|
||||||
extendedMetrics: true,
|
extendedMetrics: true,
|
||||||
purchaseAdditionalEnvironments: true,
|
|
||||||
originMiddlewareRequestLogging: true,
|
originMiddlewareRequestLogging: true,
|
||||||
unleashAI: true,
|
unleashAI: true,
|
||||||
webhookDomainLogging: true,
|
webhookDomainLogging: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user