1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01: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 { useUiFlag } from 'hooks/useUiFlag';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import { OrderEnvironments } from './OrderEnvironments/OrderEnvironments';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(4),
}));
@ -38,9 +37,6 @@ export const EnvironmentTable = () => {
const { setToastApiError } = useToast();
const { environments, mutateEnvironments } = useEnvironments();
const isFeatureEnabled = useUiFlag('EEA');
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
'purchaseAdditionalEnvironments',
);
const moveListItem: MoveListItem = useCallback(
async (dragIndex: number, dropIndex: number, save = false) => {
@ -116,7 +112,7 @@ export const EnvironmentTable = () => {
<PageHeader title={`Environments (${count})`} actions={headerActions} />
);
if (!isFeatureEnabled && !isPurchaseAdditionalEnvironmentsEnabled) {
if (!isFeatureEnabled) {
return (
<PageContent header={header}>
<PremiumFeature feature='environments' />
@ -126,7 +122,6 @@ export const EnvironmentTable = () => {
return (
<PageContent header={header}>
<OrderEnvironments />
<StyledAlert severity='info'>
This is the order of environments that you have today in each
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;
enableLegacyVariants?: boolean;
flagCreator?: boolean;
purchaseAdditionalEnvironments?: boolean;
unleashAI?: boolean;
releasePlans?: 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');
});
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 () => {
const emailService = new EmailService({
server: {

View File

@ -70,11 +70,6 @@ export type ChangeRequestScheduleConflictData =
environment: string;
};
export type OrderEnvironmentData = {
name: string;
type: string;
};
export class EmailService {
private logger: Logger;
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(
userEmail: string,
userName: string,

View File

@ -47,7 +47,6 @@ export type IFlagKey =
| 'extendedMetrics'
| 'removeUnsafeInlineStyleSrc'
| 'projectRoleAssignment'
| 'purchaseAdditionalEnvironments'
| 'originMiddlewareRequestLogging'
| 'unleashAI'
| 'webhookDomainLogging'
@ -239,10 +238,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_PROJECT_ROLE_ASSIGNMENT,
false,
),
purchaseAdditionalEnvironments: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PURCHASE_ADDITIONAL_ENVIRONMENTS,
false,
),
originMiddlewareRequestLogging: parseEnvVarBoolean(
process.env.UNLEASH_ORIGIN_MIDDLEWARE_REQUEST_LOGGING,
false,

View File

@ -48,7 +48,6 @@ process.nextTick(async () => {
manyStrategiesPagination: true,
enableLegacyVariants: false,
extendedMetrics: true,
purchaseAdditionalEnvironments: true,
originMiddlewareRequestLogging: true,
unleashAI: true,
webhookDomainLogging: true,