Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Updating Stream Summary on form values change ([#12451](https://github.com/linode/manager/pull/12451))
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form';

import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { LandingHeader } from 'src/components/LandingHeader';
import { getDestinationTypeOption } from 'src/features/DataStream/dataStreamUtils';
import { DestinationLinodeObjectStorageDetailsForm } from 'src/features/DataStream/Shared/DestinationLinodeObjectStorageDetailsForm';
import {
destinationType,
Expand Down Expand Up @@ -65,9 +66,7 @@ export const DestinationCreate = () => {
field.onChange(value);
}}
options={destinationTypeOptions}
value={destinationTypeOptions.find(
({ value }) => value === field.value
)}
value={getDestinationTypeOption(field.value)}
/>
)}
rules={{ required: true }}
Expand Down
7 changes: 6 additions & 1 deletion packages/manager/src/features/DataStream/Shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ export const destinationType = {
export type DestinationType =
(typeof destinationType)[keyof typeof destinationType];

export const destinationTypeOptions = [
export interface DestinationTypeOption {
label: string;
value: string;
}

export const destinationTypeOptions: DestinationTypeOption[] = [
{
value: destinationType.CustomHttps,
label: 'Custom HTTPS',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Typography } from '@linode/ui';
import { styled } from '@mui/material/styles';

export const StyledHeader = styled(Typography, {
label: 'StyledHeader',
})(({ theme }) => ({
font: theme.font.bold,
fontSize: theme.tokens.font.FontSize.M,
lineHeight: theme.tokens.font.LineHeight.Xs,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { describe, expect } from 'vitest';

import { destinationType } from 'src/features/DataStream/Shared/types';
import { StreamCreateCheckoutBar } from 'src/features/DataStream/Streams/StreamCreate/CheckoutBar/StreamCreateCheckoutBar';
import { StreamCreateGeneralInfo } from 'src/features/DataStream/Streams/StreamCreate/StreamCreateGeneralInfo';
import { streamType } from 'src/features/DataStream/Streams/StreamCreate/types';
import {
renderWithTheme,
renderWithThemeAndHookFormContext,
} from 'src/utilities/testHelpers';

describe('StreamCreateCheckoutBar', () => {
const getDeliveryPriceContext = () => screen.getByText(/\/unit/i).textContent;

const renderComponent = () => {
renderWithThemeAndHookFormContext({
component: <StreamCreateCheckoutBar />,
useFormOptions: {
defaultValues: {
destination_type: destinationType.LinodeObjectStorage,
},
},
});
};

it('should render checkout bar with disabled checkout button', async () => {
renderComponent();
const submitButton = screen.getByText('Create Stream');

expect(submitButton).toBeDisabled();
});

it('should render Delivery summary with destination type and price', () => {
renderComponent();
const deliveryTitle = screen.getByText('Delivery');
const deliveryType = screen.getByText('Linode Object Storage');

expect(deliveryTitle).toBeInTheDocument();
expect(deliveryType).toBeInTheDocument();
});

const TestFormComponent = () => {
const methods = useForm({
defaultValues: {
type: streamType.AuditLogs,
destination_type: destinationType.LinodeObjectStorage,
label: '',
},
});

return (
<FormProvider {...methods}>
<form>
<StreamCreateGeneralInfo />
<StreamCreateCheckoutBar />
</form>
</FormProvider>
);
};

it('should not update Delivery summary price on label change', async () => {
renderWithTheme(<TestFormComponent />);
const initialPrice = getDeliveryPriceContext();

// change form label value
const nameInput = screen.getByPlaceholderText('Stream name...');
await userEvent.type(nameInput, 'Test');

expect(getDeliveryPriceContext()).toEqual(initialPrice);
});

it('should update Delivery summary price on form value change', async () => {
renderWithTheme(<TestFormComponent />);
const initialPrice = getDeliveryPriceContext();
const streamTypesAutocomplete = screen.getByRole('combobox');

// change form type value
await userEvent.click(streamTypesAutocomplete);
const errorLogs = await screen.findByText('Error Logs');
await userEvent.click(errorLogs);

expect(getDeliveryPriceContext()).not.toEqual(initialPrice);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Box, Divider, Typography } from '@linode/ui';
import * as React from 'react';
import { useFormContext, useWatch } from 'react-hook-form';

import { CheckoutBar } from 'src/components/CheckoutBar/CheckoutBar';
import { displayPrice } from 'src/components/DisplayPrice';
import { getDestinationTypeOption } from 'src/features/DataStream/dataStreamUtils';
import { StyledHeader } from 'src/features/DataStream/Streams/StreamCreate/CheckoutBar/StreamCreateCheckoutBar.styles';
import { eventType } from 'src/features/DataStream/Streams/StreamCreate/types';

import type { CreateStreamForm } from 'src/features/DataStream/Streams/StreamCreate/types';

export const StreamCreateCheckoutBar = () => {
const { control } = useFormContext<CreateStreamForm>();
const destinationType = useWatch({ control, name: 'destination_type' });
const formValues = useWatch({
control,
name: [
eventType.Authentication,
eventType.Authorization,
eventType.Configuration,
'status',
'type',
],
});
const price = getPrice(formValues);
const onDeploy = () => {};

return (
<CheckoutBar
calculatedPrice={price}
disabled={true}
heading="Stream Summary"
onDeploy={onDeploy}
priceSelectionText="Select Data Set and define a Destination to view pricing and create a stream."
submitText="Create Stream"
>
<>
<Divider dark spacingBottom={16} spacingTop={16} />
<Box>
<StyledHeader mb={1}>Delivery</StyledHeader>
<Typography mb={1}>
{getDestinationTypeOption(destinationType)?.label ?? ''}
</Typography>
<Typography>{displayPrice(price)}/unit</Typography>
</Box>
<Divider dark spacingBottom={0} spacingTop={16} />
</>
</CheckoutBar>
);
};

// TODO: remove after proper price calculation is implemented
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const getPrice = (data): number =>
// eslint-disable-next-line sonarjs/pseudo-random
Math.random() * 100;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DocumentTitleSegment } from 'src/components/DocumentTitle';
import { LandingHeader } from 'src/components/LandingHeader';
import { destinationType } from 'src/features/DataStream/Shared/types';

import { StreamCreateCheckoutBar } from './StreamCreateCheckoutBar';
import { StreamCreateCheckoutBar } from './CheckoutBar/StreamCreateCheckoutBar';
import { StreamCreateDataSet } from './StreamCreateDataSet';
import { StreamCreateDelivery } from './StreamCreateDelivery';
import { StreamCreateGeneralInfo } from './StreamCreateGeneralInfo';
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { Controller, useFormContext, useWatch } from 'react-hook-form';

import { DocsLink } from 'src/components/DocsLink/DocsLink';
import { getDestinationTypeOption } from 'src/features/DataStream/dataStreamUtils';
import { DestinationLinodeObjectStorageDetailsForm } from 'src/features/DataStream/Shared/DestinationLinodeObjectStorageDetailsForm';
import {
destinationType,
Expand Down Expand Up @@ -69,9 +70,7 @@ export const StreamCreateDelivery = () => {
field.onChange(value);
}}
options={destinationTypeOptions}
value={destinationTypeOptions.find(
({ value }) => value === field.value
)}
value={getDestinationTypeOption(field.value)}
/>
)}
rules={{ required: true }}
Expand Down
23 changes: 23 additions & 0 deletions packages/manager/src/features/DataStream/dataStreamUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from 'vitest';

import { getDestinationTypeOption } from 'src/features/DataStream/dataStreamUtils';
import {
destinationType,
destinationTypeOptions,
} from 'src/features/DataStream/Shared/types';

describe('dataStream utils functions', () => {
describe('getDestinationTypeOption ', () => {
it('should return option object matching provided value', () => {
const result = getDestinationTypeOption(
destinationType.LinodeObjectStorage
);
expect(result).toEqual(destinationTypeOptions[1]);
});

it('should return undefined when no option is a match', () => {
const result = getDestinationTypeOption('random value');
expect(result).toEqual(undefined);
});
});
});
8 changes: 8 additions & 0 deletions packages/manager/src/features/DataStream/dataStreamUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { destinationTypeOptions } from 'src/features/DataStream/Shared/types';

import type { DestinationTypeOption } from 'src/features/DataStream/Shared/types';

export const getDestinationTypeOption = (
destinationTypeValue: string
): DestinationTypeOption | undefined =>
destinationTypeOptions.find(({ value }) => value === destinationTypeValue);