1
0
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)

[![Gitar](https://raw.githubusercontent.com/gitarcode/.github/main/assets/gitar-banner.svg)](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:
gitar-bot[bot] 2024-12-11 10:11:23 +02:00 committed by GitHub
parent eb7e2a655d
commit 8c189cabd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1 additions and 921 deletions

View File

@ -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

View File

@ -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();
});
});

View File

@ -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 })}
/>
</>
);
};

View File

@ -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>
);
};

View File

@ -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' },
]);
});
});

View File

@ -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>
);
};

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -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,
};
};

View File

@ -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;

View File

@ -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: {

View File

@ -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,

View File

@ -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,

View File

@ -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,