From 4617bfbfa9ff43deb8e9893eb54350f9a9485c45 Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Tue, 19 Nov 2024 20:09:05 +0530 Subject: [PATCH 01/92] upcoming: [DI: 21998] - Added Severity, Region, Engine Option component to the Create Alert form and made necessary changes --- packages/api-v4/src/cloudpulse/alerts.ts | 11 ++- packages/api-v4/src/cloudpulse/types.ts | 4 +- .../src/factories/cloudpulse/alerts.ts | 28 ++++++ .../CreateAlertDefinition.test.tsx | 6 +- .../CreateAlert/CreateAlertDefinition.tsx | 30 ++++-- .../GeneralInformation/EngineOption.test.tsx | 32 +++++++ .../GeneralInformation/EngineOption.tsx | 49 ++++++++++ .../GeneralInformation/RegionSelect.test.tsx | 33 +++++++ .../GeneralInformation/RegionSelect.tsx | 46 +++++++++ .../ServiceTypeSelect.test.tsx | 94 +++++++++++++++++++ .../GeneralInformation/ServiceTypeSelect.tsx | 73 ++++++++++++++ .../Alerts/CreateAlert/utilities.ts | 12 +++ .../features/CloudPulse/Alerts/constants.ts | 11 +++ packages/manager/src/mocks/serverHandlers.ts | 34 +++---- .../manager/src/queries/cloudpulse/alerts.ts | 4 +- packages/validation/src/cloudpulse.schema.ts | 48 +++++----- 16 files changed, 455 insertions(+), 60 deletions(-) create mode 100644 packages/manager/src/factories/cloudpulse/alerts.ts create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.test.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.test.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts diff --git a/packages/api-v4/src/cloudpulse/alerts.ts b/packages/api-v4/src/cloudpulse/alerts.ts index 84847f4a027..2083a6a1c8c 100644 --- a/packages/api-v4/src/cloudpulse/alerts.ts +++ b/packages/api-v4/src/cloudpulse/alerts.ts @@ -3,9 +3,16 @@ import Request, { setURL, setMethod, setData } from '../request'; import { Alert, CreateAlertDefinitionPayload } from './types'; import { BETA_API_ROOT as API_ROOT } from 'src/constants'; -export const createAlertDefinition = (data: CreateAlertDefinitionPayload) => +export const createAlertDefinition = ( + data: CreateAlertDefinitionPayload, + service_type: string +) => Request( - setURL(`${API_ROOT}/monitor/alert-definitions`), + setURL( + `${API_ROOT}/monitor/services/${encodeURIComponent( + service_type + )}/alert-definitions` + ), setMethod('POST'), setData(data, createAlertDefinitionSchema) ); diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index 3ef376f7c60..a446db69a11 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -158,8 +158,8 @@ export interface CreateAlertDefinitionPayload { export interface CreateAlertDefinitionForm extends CreateAlertDefinitionPayload { region: string; - service_type: string; - engine_type: string; + service_type: string | null; + engine_type: string | null; } export interface MetricCriteria { metric: string; diff --git a/packages/manager/src/factories/cloudpulse/alerts.ts b/packages/manager/src/factories/cloudpulse/alerts.ts new file mode 100644 index 00000000000..5082891c5a0 --- /dev/null +++ b/packages/manager/src/factories/cloudpulse/alerts.ts @@ -0,0 +1,28 @@ +import Factory from 'src/factories/factoryProxy'; +import { pickRandom } from 'src/utilities/random'; + +import type { Alert } from '@linode/api-v4'; + +export const alertFactory = Factory.Sync.makeFactory({ + channels: [], + created: new Date().toISOString(), + created_by: Factory.each(() => pickRandom(['user1', 'user2', 'user3'])), + description: '', + id: Factory.each(() => Math.floor(Math.random() * 1000000)), + label: Factory.each((id) => `Alert-${id}`), + resource_ids: ['1', '2', '3'], + rule_criteria: { + rules: [], + }, + service_type: Factory.each(() => pickRandom(['linode', 'dbaas'])), + severity: Factory.each(() => pickRandom([0, 1, 2, 3])), + status: Factory.each(() => pickRandom(['enabled', 'disabled'])), + triggerCondition: { + evaluation_period_seconds: 0, + polling_interval_seconds: 0, + trigger_occurrences: 0, + }, + type: Factory.each(() => pickRandom(['default', 'custom'])), + updated: new Date().toISOString(), + updated_by: Factory.each(() => pickRandom(['user1', 'user2', 'user3'])), +}); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx index 4a3bb4a0098..25e6afad080 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx @@ -30,7 +30,9 @@ describe('AlertDefinition Create', () => { await userEvent.click(submitButton!); - expect(getByText('Name is required')).toBeVisible(); - expect(getByText('Severity is required')).toBeVisible(); + expect(getByText('Name is required.')).toBeVisible(); + expect(getByText('Severity is required.')).toBeVisible(); + expect(getByText('Service type is required.')).toBeVisible(); + expect(getByText('Region is required.')).toBeVisible(); }); }); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index f8dd667b077..e9f933959a6 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx @@ -13,10 +13,13 @@ import { Typography } from 'src/components/Typography'; import { useCreateAlertDefinition } from 'src/queries/cloudpulse/alerts'; import { CloudPulseAlertSeveritySelect } from './GeneralInformation/AlertSeveritySelect'; +import { EngineOption } from './GeneralInformation/EngineOption'; +import { CloudPulseRegionSelect } from './GeneralInformation/RegionSelect'; +import { CloudPulseServiceSelect } from './GeneralInformation/ServiceTypeSelect'; +import { getCreateAlertPayload } from './utilities'; import type { CreateAlertDefinitionForm, - CreateAlertDefinitionPayload, MetricCriteria, TriggerCondition, } from '@linode/api-v4/lib/cloudpulse/types'; @@ -37,12 +40,12 @@ const criteriaInitialValues: MetricCriteria[] = [ ]; const initialValues: CreateAlertDefinitionForm = { channel_ids: [], - engine_type: '', + engine_type: null, label: '', region: '', resource_ids: [], rule_criteria: { rules: criteriaInitialValues }, - service_type: '', + service_type: null, severity: null, triggerCondition: triggerConditionInitialValues, }; @@ -64,19 +67,29 @@ export const CreateAlertDefinition = () => { const alertCreateExit = () => history.push('/monitor/cloudpulse/alerts/definitions'); - const formMethods = useForm({ + const formMethods = useForm({ defaultValues: initialValues, mode: 'onBlur', resolver: yupResolver(createAlertDefinitionSchema), }); - const { control, formState, handleSubmit, setError } = formMethods; + const { + control, + formState, + getValues, + handleSubmit, + setError, + watch, + } = formMethods; const { enqueueSnackbar } = useSnackbar(); - const { mutateAsync: createAlert } = useCreateAlertDefinition(); + const { mutateAsync: createAlert } = useCreateAlertDefinition( + getValues('service_type')! + ); + const serviceWatcher = watch('service_type'); const onSubmit = handleSubmit(async (values) => { try { - await createAlert(values); + await createAlert(getCreateAlertPayload(values)); enqueueSnackbar('Alert successfully created', { variant: 'success', }); @@ -132,6 +145,9 @@ export const CreateAlertDefinition = () => { control={control} name="description" /> + + {serviceWatcher === 'dbaas' && } + { + it('should render the component when resource type is dbaas', () => { + const { getByLabelText, getByTestId } = renderWithThemeAndHookFormContext({ + component: , + }); + expect(getByLabelText('Engine Option')).toBeInTheDocument(); + expect(getByTestId('engine-option')).toBeInTheDocument(); + }); + it('should render the options happy path', () => { + renderWithThemeAndHookFormContext({ + component: , + }); + fireEvent.click(screen.getByRole('button', { name: 'Open' })); + expect(screen.getByRole('option', { name: 'MySQL' })); + expect(screen.getByRole('option', { name: 'PostgreSQL' })); + }); + it('should be able to select an option', () => { + renderWithThemeAndHookFormContext({ + component: , + }); + fireEvent.click(screen.getByRole('button', { name: 'Open' })); + fireEvent.click(screen.getByRole('option', { name: 'MySQL' })); + expect(screen.getByRole('combobox')).toHaveAttribute('value', 'MySQL'); + }); +}); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx new file mode 100644 index 00000000000..aaf0fdc6153 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; + +import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; + +import { engineTypeOptions } from '../../constants'; + +import type { CreateAlertDefinitionForm } from '@linode/api-v4'; +import type { FieldPathByValue } from 'react-hook-form'; + +interface EngineOptionProps { + /** + * name used for the component to set in the form + */ + name: FieldPathByValue; +} +export const EngineOption = (props: EngineOptionProps) => { + const { name } = props; + const { control } = useFormContext(); + return ( + ( + { + if (reason === 'selectOption') { + field.onChange(selected.value); + } + if (reason === 'clear') { + field.onChange(null); + } + }} + value={ + field.value !== null + ? engineTypeOptions.find((option) => option.value === field.value) + : null + } + data-testid="engine-option" + errorText={fieldState.error?.message} + label="Engine Option" + onBlur={field.onBlur} + options={engineTypeOptions} + placeholder="Select an Engine" + /> + )} + control={control} + name={name} + /> + ); +}; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.test.tsx new file mode 100644 index 00000000000..805b134507b --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.test.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; + +import * as regions from 'src/queries/regions/regions'; +import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; + +import { CloudPulseRegionSelect } from './RegionSelect'; + +import type { Region } from '@linode/api-v4'; + +describe('RegionSelect', () => { + vi.spyOn(regions, 'useRegionsQuery').mockReturnValue({ + data: Array(), + } as ReturnType); + + it('should render a RegionSelect component', () => { + const { getByTestId } = renderWithThemeAndHookFormContext({ + component: , + }); + expect(getByTestId('region-select')).toBeInTheDocument(); + }); + it('should render a Region Select component with proper error message on api call failure', () => { + vi.spyOn(regions, 'useRegionsQuery').mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + } as ReturnType); + const { getByText } = renderWithThemeAndHookFormContext({ + component: , + }); + + expect(getByText('Failed to fetch Region.')); + }); +}); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx new file mode 100644 index 00000000000..316ae2457b4 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; + +import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; +import { useRegionsQuery } from 'src/queries/regions/regions'; + +export interface CloudViewRegionSelectProps { + /** + * name used for the component to set in the form + */ + name: string; +} + +export const CloudPulseRegionSelect = React.memo( + (props: CloudViewRegionSelectProps) => { + const { name } = props; + const { data: regions, isError, isLoading } = useRegionsQuery(); + const { control } = useFormContext(); + return ( + ( + { + field.onChange(value?.id); + }} + currentCapability={undefined} + disableClearable={false} + fullWidth + label="Region" + loading={isLoading} + placeholder="Select a Region" + regions={regions ?? []} + textFieldProps={{ onBlur: field.onBlur }} + value={field.value} + /> + )} + control={control} + name={name} + /> + ); + } +); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx new file mode 100644 index 00000000000..f8a79d17748 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx @@ -0,0 +1,94 @@ +import { fireEvent, screen } from '@testing-library/react'; +import * as React from 'react'; + +import { serviceTypesFactory } from 'src/factories'; +import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; + +import { CloudPulseServiceSelect } from './ServiceTypeSelect'; + +const queryMocks = vi.hoisted(() => ({ + useCloudPulseServiceTypes: vi.fn().mockReturnValue({}), +})); + +vi.mock('src/queries/cloudpulse/services', async () => { + const actual = await vi.importActual('src/queries/cloudpulse/services'); + return { + ...actual, + useCloudPulseServiceTypes: queryMocks.useCloudPulseServiceTypes, + }; +}); + +const mockResponse = { + data: [ + serviceTypesFactory.build({ + label: 'Linode', + service_type: 'linode', + }), + serviceTypesFactory.build({ + label: 'Databases', + service_type: 'dbaas', + }), + ], +}; + +describe('ServiceTypeSelect component tests', () => { + it('should render the Autocomplete component', () => { + const { getAllByText, getByTestId } = renderWithThemeAndHookFormContext({ + component: , + }); + expect(getByTestId('servicetype-select')).toBeInTheDocument(); + getAllByText('Service'); + }); + + it('should render service types happy path', () => { + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: mockResponse, + isError: false, + isLoading: false, + status: 'success', + }); + renderWithThemeAndHookFormContext({ + component: , + }); + fireEvent.click(screen.getByRole('button', { name: 'Open' })); + expect( + screen.getByRole('option', { + name: 'Linode', + }) + ).toBeInTheDocument(); + expect( + screen.getByRole('option', { + name: 'Databases', + }) + ).toBeInTheDocument(); + }); + + it('should be able to select a service type', () => { + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: mockResponse, + isError: false, + isLoading: false, + status: 'success', + }); + renderWithThemeAndHookFormContext({ + component: , + }); + fireEvent.click(screen.getByRole('button', { name: 'Open' })); + fireEvent.click(screen.getByRole('option', { name: 'Linode' })); + expect(screen.getByRole('combobox')).toHaveAttribute('value', 'Linode'); + }); + it('should render error messages when there is an API call failure', () => { + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + status: 'error', + }); + renderWithThemeAndHookFormContext({ + component: , + }); + expect( + screen.getByText('Failed to fetch the service types.') + ).toBeInTheDocument(); + }); +}); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx new file mode 100644 index 00000000000..079e913ec35 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; + +import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; +import { useCloudPulseServiceTypes } from 'src/queries/cloudpulse/services'; + +import type { Item } from '../../constants'; +import type { CreateAlertDefinitionForm } from '@linode/api-v4'; +import type { FieldPathByValue } from 'react-hook-form'; + +interface CloudPulseServiceSelectProps { + /** + * name used for the component in the form + */ + name: FieldPathByValue; +} + +export const CloudPulseServiceSelect = ( + props: CloudPulseServiceSelectProps +) => { + const { name } = props; + const { + data: serviceOptions, + error: serviceTypesError, + isLoading: serviceTypesLoading, + } = useCloudPulseServiceTypes(true); + const { control } = useFormContext(); + + const getServicesList = React.useMemo((): Item[] => { + return serviceOptions && serviceOptions.data.length > 0 + ? serviceOptions.data.map((service) => ({ + label: service.label, + value: service.service_type, + })) + : []; + }, [serviceOptions]); + + return ( + ( + { + if (selected) { + field.onChange(selected.value); + } + if (reason === 'clear') { + field.onChange(null); + } + }} + value={ + field.value !== null + ? getServicesList.find((option) => option.value === field.value) + : null + } + data-testid="servicetype-select" + fullWidth + label="Service" + loading={serviceTypesLoading && !serviceTypesError} + onBlur={field.onBlur} + options={getServicesList} + placeholder="Select a Service" + sx={{ marginTop: '5px' }} + /> + )} + control={control} + name={name} + /> + ); +}; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts new file mode 100644 index 00000000000..b5c83b1bd4e --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts @@ -0,0 +1,12 @@ +import { omitProps } from '@linode/ui'; + +import type { + CreateAlertDefinitionForm, + CreateAlertDefinitionPayload, +} from '@linode/api-v4'; + +export const getCreateAlertPayload = ( + formValues: CreateAlertDefinitionForm +): CreateAlertDefinitionPayload => { + return omitProps(formValues, ['service_type', 'region', 'engine_type']); +}; diff --git a/packages/manager/src/features/CloudPulse/Alerts/constants.ts b/packages/manager/src/features/CloudPulse/Alerts/constants.ts index 070f84dfbe5..08f6223e263 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/constants.ts +++ b/packages/manager/src/features/CloudPulse/Alerts/constants.ts @@ -10,3 +10,14 @@ export const alertSeverityOptions: Item[] = [ { label: 'Medium', value: 1 }, { label: 'Severe', value: 0 }, ]; + +export const engineTypeOptions: Item[] = [ + { + label: 'MySQL', + value: 'mysql', + }, + { + label: 'PostgreSQL', + value: 'postgresql', + }, +]; diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index 85950a1b9e4..bbdd33ecd11 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -110,6 +110,7 @@ import { pickRandom } from 'src/utilities/random'; import type { AccountMaintenance, + CreateAlertDefinitionPayload, CreateObjectStorageKeyPayload, Dashboard, FirewallStatus, @@ -122,6 +123,7 @@ import type { User, VolumeStatus, } from '@linode/api-v4'; +import { alertFactory } from 'src/factories/cloudpulse/alerts'; export const makeResourcePage = ( e: T[], @@ -2333,28 +2335,16 @@ export const handlers = [ return HttpResponse.json(response); }), - http.post('*/monitor/alert-definitions', async ({ request }) => { - const reqBody = await request.json(); - const response = { - data: [ - { - created: '2021-10-16T04:00:00', - created_by: 'user1', - id: '35892357', - notifications: [ - { - notification_id: '42804', - template_name: 'notification', - }, - ], - reqBody, - updated: '2021-10-16T04:00:00', - updated_by: 'user2', - }, - ], - }; - return HttpResponse.json(response); - }), + http.post( + '*/monitor/services/:service_type/alert-definitions', + async ({ request }) => { + const reqBody = await request.json(); + const response = alertFactory.build({ + ...(reqBody as CreateAlertDefinitionPayload), + }); + return HttpResponse.json(response); + } + ), http.get('*/monitor/services', () => { const response: ServiceTypesList = { data: [ diff --git a/packages/manager/src/queries/cloudpulse/alerts.ts b/packages/manager/src/queries/cloudpulse/alerts.ts index 69605058dea..05e90196051 100644 --- a/packages/manager/src/queries/cloudpulse/alerts.ts +++ b/packages/manager/src/queries/cloudpulse/alerts.ts @@ -9,10 +9,10 @@ import type { } from '@linode/api-v4/lib/cloudpulse'; import type { APIError } from '@linode/api-v4/lib/types'; -export const useCreateAlertDefinition = () => { +export const useCreateAlertDefinition = (service_type: string) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (data) => createAlertDefinition(data), + mutationFn: (data) => createAlertDefinition(data, service_type), onSuccess() { queryClient.invalidateQueries(queryFactory.alerts); }, diff --git a/packages/validation/src/cloudpulse.schema.ts b/packages/validation/src/cloudpulse.schema.ts index ecd7ded1d5e..f437ec69ea4 100644 --- a/packages/validation/src/cloudpulse.schema.ts +++ b/packages/validation/src/cloudpulse.schema.ts @@ -2,44 +2,46 @@ import { array, number, object, string } from 'yup'; const engineOptionValidation = string().when('service_type', { is: 'dbaas', - then: string().required(), - otherwise: string().notRequired(), + then: string().required('Engine type is required.').nullable(), + otherwise: string().notRequired().nullable(), }); const dimensionFilters = object({ - dimension_label: string().required('Label is required for the filter'), - operator: string().required('Operator is required'), - value: string().required('Value is required'), + dimension_label: string().required('Label is required for the filter.'), + operator: string().required('Operator is required.'), + value: string().required('Value is required.'), }); const metricCriteria = object({ - metric: string().required('Metric Data Field is required'), - aggregation_type: string().required('Aggregation type is required'), - operator: string().required('Criteria Operator is required'), - value: number() - .required('Threshold value is required') - .min(0, 'Threshold value cannot be negative'), + metric: string().required('Metric Data Field is required.'), + aggregation_type: string().required('Aggregation type is required.'), + operator: string().required('Criteria Operator is required.'), + threshold: number() + .required('Threshold value is require.') + .min(0, 'Threshold value cannot be negative.'), dimension_filters: array().of(dimensionFilters).notRequired(), }); const triggerCondition = object({ - criteria_condition: string().required('Criteria condition is required'), - polling_interval_seconds: string().required('Polling Interval is required'), - evaluation_period_seconds: string().required('Evaluation Period is required'), + criteria_condition: string().required('Criteria condition is required.'), + polling_interval_seconds: string().required('Polling Interval is required.'), + evaluation_period_seconds: string().required( + 'Evaluation Period is required.' + ), trigger_occurrences: number() - .required('Trigger Occurrences is required') - .positive('Number of occurrences must be greater than zero'), + .required('Trigger Occurrences is required.') + .positive('Number of occurrences must be greater than zero.'), }); export const createAlertDefinitionSchema = object().shape({ - label: string().required('Name is required'), + label: string().required('Name is required.'), description: string().optional(), - region: string().required('Region is required'), - engineOption: engineOptionValidation, - service_type: string().required('Service type is required'), - resource_ids: array().of(string()).min(1, 'At least one resource is needed'), - severity: string().required('Severity is required').nullable(), + region: string().required('Region is required.'), + engine_type: engineOptionValidation, + service_type: string().required('Service type is required.').nullable(), + resource_ids: array().of(string()).min(1, 'At least one resource is needed.'), + severity: string().required('Severity is required.').nullable(), criteria: array() .of(metricCriteria) - .min(1, 'At least one metric criteria is needed'), + .min(1, 'At least one metric criteria is needed.'), triggerCondition, }); From c2b296eebea52876f3de362812b95f47907e2824 Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Tue, 19 Nov 2024 21:29:25 +0530 Subject: [PATCH 02/92] upcoming: [DI:21998] - Fixed the ServiceTypeSelect test file and remove Memo from RegionSelect component --- .../GeneralInformation/RegionSelect.tsx | 64 +++++++++---------- .../ServiceTypeSelect.test.tsx | 21 ++---- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx index 316ae2457b4..ac314d57a12 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx @@ -11,36 +11,34 @@ export interface CloudViewRegionSelectProps { name: string; } -export const CloudPulseRegionSelect = React.memo( - (props: CloudViewRegionSelectProps) => { - const { name } = props; - const { data: regions, isError, isLoading } = useRegionsQuery(); - const { control } = useFormContext(); - return ( - ( - { - field.onChange(value?.id); - }} - currentCapability={undefined} - disableClearable={false} - fullWidth - label="Region" - loading={isLoading} - placeholder="Select a Region" - regions={regions ?? []} - textFieldProps={{ onBlur: field.onBlur }} - value={field.value} - /> - )} - control={control} - name={name} - /> - ); - } -); +export const CloudPulseRegionSelect = (props: CloudViewRegionSelectProps) => { + const { name } = props; + const { data: regions, isError, isLoading } = useRegionsQuery(); + const { control } = useFormContext(); + return ( + ( + { + field.onChange(value?.id); + }} + currentCapability={undefined} + disableClearable={false} + fullWidth + label="Region" + loading={isLoading} + placeholder="Select a Region" + regions={regions ?? []} + textFieldProps={{ onBlur: field.onBlur }} + value={field.value} + /> + )} + control={control} + name={name} + /> + ); +}; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx index f8a79d17748..4d59c89c6fc 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx @@ -31,6 +31,12 @@ const mockResponse = { ], }; +queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: mockResponse, + isError: true, + isLoading: false, + status: 'success', +}); describe('ServiceTypeSelect component tests', () => { it('should render the Autocomplete component', () => { const { getAllByText, getByTestId } = renderWithThemeAndHookFormContext({ @@ -41,12 +47,6 @@ describe('ServiceTypeSelect component tests', () => { }); it('should render service types happy path', () => { - queryMocks.useCloudPulseServiceTypes.mockReturnValue({ - data: mockResponse, - isError: false, - isLoading: false, - status: 'success', - }); renderWithThemeAndHookFormContext({ component: , }); @@ -64,12 +64,6 @@ describe('ServiceTypeSelect component tests', () => { }); it('should be able to select a service type', () => { - queryMocks.useCloudPulseServiceTypes.mockReturnValue({ - data: mockResponse, - isError: false, - isLoading: false, - status: 'success', - }); renderWithThemeAndHookFormContext({ component: , }); @@ -80,9 +74,8 @@ describe('ServiceTypeSelect component tests', () => { it('should render error messages when there is an API call failure', () => { queryMocks.useCloudPulseServiceTypes.mockReturnValue({ data: undefined, - isError: true, + error: 'an error happened', isLoading: false, - status: 'error', }); renderWithThemeAndHookFormContext({ component: , From a3e9178b8005b5b65f5e993e456c5d941ea8195a Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Tue, 19 Nov 2024 21:46:44 +0530 Subject: [PATCH 03/92] upcoming: [DI:21998] - Added changesets --- packages/api-v4/.changeset/pr-11286-changed-1732032917339.md | 5 +++++ packages/manager/.changeset/pr-11286-added-1732032870588.md | 5 +++++ .../validation/.changeset/pr-11286-added-1732032964808.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 packages/api-v4/.changeset/pr-11286-changed-1732032917339.md create mode 100644 packages/manager/.changeset/pr-11286-added-1732032870588.md create mode 100644 packages/validation/.changeset/pr-11286-added-1732032964808.md diff --git a/packages/api-v4/.changeset/pr-11286-changed-1732032917339.md b/packages/api-v4/.changeset/pr-11286-changed-1732032917339.md new file mode 100644 index 00000000000..b09a7a861af --- /dev/null +++ b/packages/api-v4/.changeset/pr-11286-changed-1732032917339.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Changed +--- + +Added service_type as parameter for the Create Alert POST request ([#11286](https://github.com/linode/manager/pull/11286)) diff --git a/packages/manager/.changeset/pr-11286-added-1732032870588.md b/packages/manager/.changeset/pr-11286-added-1732032870588.md new file mode 100644 index 00000000000..b84bc1138d3 --- /dev/null +++ b/packages/manager/.changeset/pr-11286-added-1732032870588.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Add Service, Engine Option, Region components to the Create Alert form ([#11286](https://github.com/linode/manager/pull/11286)) diff --git a/packages/validation/.changeset/pr-11286-added-1732032964808.md b/packages/validation/.changeset/pr-11286-added-1732032964808.md new file mode 100644 index 00000000000..40db1d927b5 --- /dev/null +++ b/packages/validation/.changeset/pr-11286-added-1732032964808.md @@ -0,0 +1,5 @@ +--- +"@linode/validation": Added +--- + +Added '.' for the error messages ([#11286](https://github.com/linode/manager/pull/11286)) From 90b860a6d44ac3efa2865e44bbd40026e770a399 Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Tue, 19 Nov 2024 21:51:19 +0530 Subject: [PATCH 04/92] upcoming: [DI:21998] - Fixed a typo --- packages/validation/src/cloudpulse.schema.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/validation/src/cloudpulse.schema.ts b/packages/validation/src/cloudpulse.schema.ts index f437ec69ea4..6e2ad970fa0 100644 --- a/packages/validation/src/cloudpulse.schema.ts +++ b/packages/validation/src/cloudpulse.schema.ts @@ -16,7 +16,7 @@ const metricCriteria = object({ aggregation_type: string().required('Aggregation type is required.'), operator: string().required('Criteria Operator is required.'), threshold: number() - .required('Threshold value is require.') + .required('Threshold value is required.') .min(0, 'Threshold value cannot be negative.'), dimension_filters: array().of(dimensionFilters).notRequired(), }); @@ -37,7 +37,7 @@ export const createAlertDefinitionSchema = object().shape({ description: string().optional(), region: string().required('Region is required.'), engine_type: engineOptionValidation, - service_type: string().required('Service type is required.').nullable(), + service_type: string().required('Service is required.').nullable(), resource_ids: array().of(string()).min(1, 'At least one resource is needed.'), severity: string().required('Severity is required.').nullable(), criteria: array() From c2fbd5a09d8ccc358beef11884b08c61e493d273 Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Tue, 19 Nov 2024 22:12:17 +0530 Subject: [PATCH 05/92] upcoming: [DI:21998] - Modified the Create Alert test file to reflect the changes in error messages --- .../Alerts/CreateAlert/CreateAlertDefinition.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx index 25e6afad080..118751e9e7b 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx @@ -32,7 +32,7 @@ describe('AlertDefinition Create', () => { expect(getByText('Name is required.')).toBeVisible(); expect(getByText('Severity is required.')).toBeVisible(); - expect(getByText('Service type is required.')).toBeVisible(); + expect(getByText('Service is required.')).toBeVisible(); expect(getByText('Region is required.')).toBeVisible(); }); }); From 04e528b457783f6ac7ec9d3c24fb64f9222e358a Mon Sep 17 00:00:00 2001 From: Santosh Preetham S Date: Wed, 20 Nov 2024 04:23:29 +0530 Subject: [PATCH 06/92] upcoming: [DI-21998] - Addressed the review comments --- packages/api-v4/src/cloudpulse/types.ts | 4 +-- .../pr-11286-added-1732032870588.md | 2 +- .../src/factories/cloudpulse/alerts.ts | 27 ++++++++++++------- .../CreateAlertDefinition.test.tsx | 8 +++--- .../AlertSeveritySelect.test.tsx | 21 ++++++++------- .../AlertSeveritySelect.tsx | 2 +- .../GeneralInformation/EngineOption.test.tsx | 17 +++++++----- .../GeneralInformation/RegionSelect.tsx | 1 - .../ServiceTypeSelect.test.tsx | 17 +++++++----- packages/manager/src/mocks/serverHandlers.ts | 2 +- .../pr-11286-added-1732032964808.md | 2 +- 11 files changed, 60 insertions(+), 43 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index a446db69a11..bef6bae044f 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -7,8 +7,8 @@ type DimensionFilterOperatorType = | 'startswith' | 'endswith' | null; -type AlertDefinitionType = 'default' | 'custom'; -type AlertStatusType = 'enabled' | 'disabled'; +export type AlertDefinitionType = 'default' | 'custom'; +export type AlertStatusType = 'enabled' | 'disabled'; export interface Dashboard { id: number; label: string; diff --git a/packages/manager/.changeset/pr-11286-added-1732032870588.md b/packages/manager/.changeset/pr-11286-added-1732032870588.md index b84bc1138d3..d8a2d1f91b7 100644 --- a/packages/manager/.changeset/pr-11286-added-1732032870588.md +++ b/packages/manager/.changeset/pr-11286-added-1732032870588.md @@ -2,4 +2,4 @@ "@linode/manager": Added --- -Add Service, Engine Option, Region components to the Create Alert form ([#11286](https://github.com/linode/manager/pull/11286)) +Service, Engine Option, Region components to the Create Alert form ([#11286](https://github.com/linode/manager/pull/11286)) diff --git a/packages/manager/src/factories/cloudpulse/alerts.ts b/packages/manager/src/factories/cloudpulse/alerts.ts index 5082891c5a0..6459a8d1800 100644 --- a/packages/manager/src/factories/cloudpulse/alerts.ts +++ b/packages/manager/src/factories/cloudpulse/alerts.ts @@ -1,28 +1,37 @@ import Factory from 'src/factories/factoryProxy'; -import { pickRandom } from 'src/utilities/random'; -import type { Alert } from '@linode/api-v4'; +import type { + Alert, + AlertDefinitionType, + AlertSeverityType, + AlertStatusType, +} from '@linode/api-v4'; +const types: AlertDefinitionType[] = ['custom', 'default']; +const status: AlertStatusType[] = ['enabled', 'disabled']; +const severity: AlertSeverityType[] = [0, 1, 2, 3]; +const users = ['user1', 'user2', 'user3']; +const serviceTypes = ['linode', 'dbaas']; export const alertFactory = Factory.Sync.makeFactory({ channels: [], created: new Date().toISOString(), - created_by: Factory.each(() => pickRandom(['user1', 'user2', 'user3'])), + created_by: Factory.each((i) => users[i % users.length]), description: '', id: Factory.each(() => Math.floor(Math.random() * 1000000)), label: Factory.each((id) => `Alert-${id}`), - resource_ids: ['1', '2', '3'], + resource_ids: ['0', '1', '2', '3'], rule_criteria: { rules: [], }, - service_type: Factory.each(() => pickRandom(['linode', 'dbaas'])), - severity: Factory.each(() => pickRandom([0, 1, 2, 3])), - status: Factory.each(() => pickRandom(['enabled', 'disabled'])), + service_type: Factory.each((i) => serviceTypes[i % serviceTypes.length]), + severity: Factory.each((i) => severity[i % severity.length]), + status: Factory.each((i) => status[i % status.length]), triggerCondition: { evaluation_period_seconds: 0, polling_interval_seconds: 0, trigger_occurrences: 0, }, - type: Factory.each(() => pickRandom(['default', 'custom'])), + type: Factory.each((i) => types[i % types.length]), updated: new Date().toISOString(), - updated_by: Factory.each(() => pickRandom(['user1', 'user2', 'user3'])), + updated_by: Factory.each((i) => users[(i + 3) % users.length]), }); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx index 118751e9e7b..35d4e4ad328 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen, within } from '@testing-library/react'; +import { screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; @@ -6,18 +6,18 @@ import { renderWithTheme } from 'src/utilities/testHelpers'; import { CreateAlertDefinition } from './CreateAlertDefinition'; describe('AlertDefinition Create', () => { - it('should render input components', () => { + it('should render input components', async () => { const { getByLabelText } = renderWithTheme(); expect(getByLabelText('Name')).toBeVisible(); expect(getByLabelText('Description (optional)')).toBeVisible(); expect(getByLabelText('Severity')).toBeVisible(); }); - it('should be able to enter a value in the textbox', () => { + it('should be able to enter a value in the textbox', async () => { const { getByLabelText } = renderWithTheme(); const input = getByLabelText('Name'); - fireEvent.change(input, { target: { value: 'text' } }); + await userEvent.type(input, 'text'); const specificInput = within(screen.getByTestId('alert-name')).getByTestId( 'textfield-input' ); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.test.tsx index 6c4971df8fb..6ee98f68bf2 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; @@ -13,20 +14,22 @@ describe('EngineOption component tests', () => { expect(getByLabelText('Severity')).toBeInTheDocument(); expect(getByTestId('severity')).toBeInTheDocument(); }); - it('should render the options happy path', () => { + it('should render the options happy path', async () => { renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); - expect(screen.getByRole('option', { name: 'Info' })); + userEvent.click(screen.getByRole('button', { name: 'Open' })); + expect(await screen.findByRole('option', { name: 'Info' })); expect(screen.getByRole('option', { name: 'Low' })); }); - it('should be able to select an option', () => { + it('should be able to select an option', async () => { renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); - fireEvent.click(screen.getByRole('option', { name: 'Medium' })); + userEvent.click(screen.getByRole('button', { name: 'Open' })); + await userEvent.click( + await screen.findByRole('option', { name: 'Medium' }) + ); expect(screen.getByRole('combobox')).toHaveAttribute('value', 'Medium'); }); it('should render the tooltip text', () => { @@ -35,12 +38,12 @@ describe('EngineOption component tests', () => { }); const severityContainer = container.getByTestId('severity'); - fireEvent.click(severityContainer); + userEvent.click(severityContainer); expect( screen.getByRole('button', { name: - 'Define a severity level associated with the alert to help you prioritize and manage alerts in the Recent activity tab', + 'Define a severity level associated with the alert to help you prioritize and manage alerts in the Recent activity tab.', }) ).toBeVisible(); }); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx index 76f1afca123..8e1f0299340 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx @@ -41,7 +41,7 @@ export const CloudPulseAlertSeveritySelect = ( }} textFieldProps={{ labelTooltipText: - 'Define a severity level associated with the alert to help you prioritize and manage alerts in the Recent activity tab', + 'Define a severity level associated with the alert to help you prioritize and manage alerts in the Recent activity tab.', }} value={ field.value !== null diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.test.tsx index 157e8c24a4b..d13978262fb 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; @@ -13,20 +14,22 @@ describe('EngineOption component tests', () => { expect(getByLabelText('Engine Option')).toBeInTheDocument(); expect(getByTestId('engine-option')).toBeInTheDocument(); }); - it('should render the options happy path', () => { + it('should render the options happy path', async () => { + const user = userEvent.setup(); renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); - expect(screen.getByRole('option', { name: 'MySQL' })); + user.click(screen.getByRole('button', { name: 'Open' })); + expect(await screen.findByRole('option', { name: 'MySQL' })); expect(screen.getByRole('option', { name: 'PostgreSQL' })); }); - it('should be able to select an option', () => { + it('should be able to select an option', async () => { + const user = userEvent.setup(); renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); - fireEvent.click(screen.getByRole('option', { name: 'MySQL' })); + user.click(screen.getByRole('button', { name: 'Open' })); + await user.click(await screen.findByRole('option', { name: 'MySQL' })); expect(screen.getByRole('combobox')).toHaveAttribute('value', 'MySQL'); }); }); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx index ac314d57a12..8fdf80f3fc8 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx @@ -27,7 +27,6 @@ export const CloudPulseRegionSelect = (props: CloudViewRegionSelectProps) => { field.onChange(value?.id); }} currentCapability={undefined} - disableClearable={false} fullWidth label="Region" loading={isLoading} diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx index 4d59c89c6fc..5e14b886375 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.test.tsx @@ -1,4 +1,5 @@ -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { serviceTypesFactory } from 'src/factories'; @@ -46,13 +47,13 @@ describe('ServiceTypeSelect component tests', () => { getAllByText('Service'); }); - it('should render service types happy path', () => { + it('should render service types happy path', async () => { renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); + userEvent.click(screen.getByRole('button', { name: 'Open' })); expect( - screen.getByRole('option', { + await screen.findByRole('option', { name: 'Linode', }) ).toBeInTheDocument(); @@ -63,12 +64,14 @@ describe('ServiceTypeSelect component tests', () => { ).toBeInTheDocument(); }); - it('should be able to select a service type', () => { + it('should be able to select a service type', async () => { renderWithThemeAndHookFormContext({ component: , }); - fireEvent.click(screen.getByRole('button', { name: 'Open' })); - fireEvent.click(screen.getByRole('option', { name: 'Linode' })); + userEvent.click(screen.getByRole('button', { name: 'Open' })); + await userEvent.click( + await screen.findByRole('option', { name: 'Linode' }) + ); expect(screen.getByRole('combobox')).toHaveAttribute('value', 'Linode'); }); it('should render error messages when there is an API call failure', () => { diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index bbdd33ecd11..1f796368707 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -106,6 +106,7 @@ import { getStorage } from 'src/utilities/storage'; const getRandomWholeNumber = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1) + min); +import { alertFactory } from 'src/factories/cloudpulse/alerts'; import { pickRandom } from 'src/utilities/random'; import type { @@ -123,7 +124,6 @@ import type { User, VolumeStatus, } from '@linode/api-v4'; -import { alertFactory } from 'src/factories/cloudpulse/alerts'; export const makeResourcePage = ( e: T[], diff --git a/packages/validation/.changeset/pr-11286-added-1732032964808.md b/packages/validation/.changeset/pr-11286-added-1732032964808.md index 40db1d927b5..7f87d4de4fe 100644 --- a/packages/validation/.changeset/pr-11286-added-1732032964808.md +++ b/packages/validation/.changeset/pr-11286-added-1732032964808.md @@ -2,4 +2,4 @@ "@linode/validation": Added --- -Added '.' for the error messages ([#11286](https://github.com/linode/manager/pull/11286)) +Punctuation for the error messages ([#11286](https://github.com/linode/manager/pull/11286)) From 84de90446232ca62fcc47eefc67da5a4ce082327 Mon Sep 17 00:00:00 2001 From: smans-akamai Date: Wed, 20 Nov 2024 06:13:27 -0500 Subject: [PATCH 07/92] change: [UIE-8268] dbaas summary add tooltip for ipv6 to read-only host for new db clusters (#11291) --- .../.changeset/pr-11291-changed-1732048644474.md | 5 +++++ .../DatabaseSummaryConnectionDetails.tsx | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 packages/manager/.changeset/pr-11291-changed-1732048644474.md diff --git a/packages/manager/.changeset/pr-11291-changed-1732048644474.md b/packages/manager/.changeset/pr-11291-changed-1732048644474.md new file mode 100644 index 00000000000..5130132f896 --- /dev/null +++ b/packages/manager/.changeset/pr-11291-changed-1732048644474.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Tooltip for IPV6 should also be added to read-only host in dbaas summary ([#11291](https://github.com/linode/manager/pull/11291)) diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx index 2d9611e280a..e3943379c98 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx @@ -116,11 +116,11 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { const readOnlyHost = () => { const defaultValue = isLegacy ? '-' : 'N/A'; const value = readOnlyHostValue ? readOnlyHostValue : defaultValue; - const displayCopyTooltip = value !== '-' && value !== 'N/A'; + const hasHost = value !== '-' && value !== 'N/A'; return ( <> {value} - {value && displayCopyTooltip && ( + {value && hasHost && ( )} {isLegacy && ( @@ -130,6 +130,14 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { text={privateHostCopy} /> )} + {!isLegacy && hasHost && ( + + )} ); }; From 25332362ac958cc44ffcd9c9cf2c81a775ed5e76 Mon Sep 17 00:00:00 2001 From: santoshp210-akamai <159890961+santoshp210-akamai@users.noreply.github.com> Date: Wed, 20 Nov 2024 21:18:54 +0530 Subject: [PATCH 08/92] upcoming: [DI-21998] - Changed the name of the utility getCreateAlertPayload to filterFormValues and replaced the type of service_typeto a union type of relevant services instead of string --- packages/api-v4/src/cloudpulse/types.ts | 5 +++-- .../CreateAlert/CreateAlertDefinition.tsx | 6 +++--- .../GeneralInformation/ServiceTypeSelect.tsx | 20 ++++++++++++++----- .../Alerts/CreateAlert/utilities.ts | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index bef6bae044f..cc742db3de4 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -1,6 +1,7 @@ export type AlertSeverityType = 0 | 1 | 2 | 3 | null; type MetricAggregationType = 'avg' | 'sum' | 'min' | 'max' | 'count' | null; type MetricOperatorType = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | null; +export type AlertServiceType = 'linode' | 'dbaas' | null; type DimensionFilterOperatorType = | 'eq' | 'neq' @@ -158,7 +159,7 @@ export interface CreateAlertDefinitionPayload { export interface CreateAlertDefinitionForm extends CreateAlertDefinitionPayload { region: string; - service_type: string | null; + service_type: AlertServiceType | null; engine_type: string | null; } export interface MetricCriteria { @@ -187,7 +188,7 @@ export interface Alert { status: AlertStatusType; type: AlertDefinitionType; severity: AlertSeverityType; - service_type: string; + service_type: AlertServiceType; resource_ids: string[]; rule_criteria: { rules: MetricCriteria[]; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index e9f933959a6..56a5185e28d 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx @@ -16,7 +16,7 @@ import { CloudPulseAlertSeveritySelect } from './GeneralInformation/AlertSeverit import { EngineOption } from './GeneralInformation/EngineOption'; import { CloudPulseRegionSelect } from './GeneralInformation/RegionSelect'; import { CloudPulseServiceSelect } from './GeneralInformation/ServiceTypeSelect'; -import { getCreateAlertPayload } from './utilities'; +import { filterFormValues } from './utilities'; import type { CreateAlertDefinitionForm, @@ -89,7 +89,7 @@ export const CreateAlertDefinition = () => { const serviceWatcher = watch('service_type'); const onSubmit = handleSubmit(async (values) => { try { - await createAlert(getCreateAlertPayload(values)); + await createAlert(filterFormValues(values)); enqueueSnackbar('Alert successfully created', { variant: 'success', }); @@ -146,7 +146,7 @@ export const CreateAlertDefinition = () => { name="description" /> - {serviceWatcher === 'dbaas' && } + {serviceWatcher === 'dbaas' && } ; + name: FieldPathByValue; } export const CloudPulseServiceSelect = ( @@ -26,11 +29,14 @@ export const CloudPulseServiceSelect = ( } = useCloudPulseServiceTypes(true); const { control } = useFormContext(); - const getServicesList = React.useMemo((): Item[] => { + const getServicesList = React.useMemo((): Item< + string, + AlertServiceType + >[] => { return serviceOptions && serviceOptions.data.length > 0 ? serviceOptions.data.map((service) => ({ label: service.label, - value: service.service_type, + value: service.service_type as AlertServiceType, })) : []; }, [serviceOptions]); @@ -43,7 +49,11 @@ export const CloudPulseServiceSelect = ( fieldState.error?.message ?? (serviceTypesError ? 'Failed to fetch the service types.' : '') } - onChange={(_, selected: { label: string; value: string }, reason) => { + onChange={( + _, + selected: { label: string; value: AlertServiceType }, + reason + ) => { if (selected) { field.onChange(selected.value); } diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts index b5c83b1bd4e..60f47e02d0d 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts @@ -5,7 +5,7 @@ import type { CreateAlertDefinitionPayload, } from '@linode/api-v4'; -export const getCreateAlertPayload = ( +export const filterFormValues = ( formValues: CreateAlertDefinitionForm ): CreateAlertDefinitionPayload => { return omitProps(formValues, ['service_type', 'region', 'engine_type']); From 6c15fbd1c7532b226eb737493eef58c7a8b967cb Mon Sep 17 00:00:00 2001 From: Nikhil Agrawal <165884194+nikhagra-akamai@users.noreply.github.com> Date: Wed, 20 Nov 2024 22:14:21 +0530 Subject: [PATCH 09/92] upcoming: [DI-21200] - Migration from ChartJS to Recharts in CloudPulse (#11204) * upcoming: [DI-20870] - Migrated chartjs to recharts for ACLP graphs * upcoming: [DI-20870] - Removed unused imports * upcoming: [DI-20870] - Removed CloudPulseWidgetColorPalette file * upcoming: [DI-20870] - Optimized code * upcoming: [DI-21200] - Added connectNulls & color properties * upcoming: [DI-21200] - Added sort data based on timestamp * upcoming: [DI-21200] - Updated x-axis tick format * upcoming: [DI-21200] - Updated PR comments * upcoming: [DI-21200] - Hide legends if not data present * upcoming: [DI-21200] - Updated legends height * upcoming: [DI-21200] - Updated legend row color type * upcoming: [DI-21200] - Code refactor * upcoming: [DI-21200] - Added changeset * upcoming: [DI-21200] - Moved DataSet type from types to CloudPulseLineGraph * upcoming: [DI-21200] - updated height of chart & legend rows * upcoming: [DI-21200] - Updated no data display positioning * upcoming: [DI-21841] - Removed connect nulls * [DI-21597]-Automating and testing the migration of Recharts * [DI-21597]:Removed commented-out code and explanatory comments related to the line chart in DBaaS test cases * [DI-21597]-Initial commit: Automating and testing the migration process for Recharts * [DI-21597]-Initial commit: Automating and testing the migration process for Recharts * upcoming: [DI-21842] - Added capability to toggle dots in AreaChart * upcoming: [DI-21842] - Added logic to generate x-axis ticks * upcoming: [DI-21842] - Add optional prop for tickCount * upcoming: [DI-21200] - Code refactoring * upcoming: [DI-21200] - Code optimization * upcoming: [DI-21200] - Updated type safety * upcoming: [DI-21200] - Updated x-axis tick count for different screen size * upcoming: [DI-21200] - updated test cases * upcoming: [DI-21200] - Fixed eslint error * upcoming: [DI-21200] - Added test cases * upcoming: [DI-21200] - Updated changeset * upcoming: [DI-21200] - Renamed metrics display styles file * upcoming: [DI-21200] - Updated dataset type in place of any * upcoming: [DI-21200] - updated test cases --------- Co-authored-by: agorthi --- ...r-11204-upcoming-features-1732020050249.md | 5 + packages/api-v4/src/cloudpulse/types.ts | 2 +- ...r-11204-upcoming-features-1730713318901.md | 5 + .../dbaas-widgets-verification.spec.ts | 191 +++++++++--------- .../linode-widget-verification.spec.ts | 186 +++++++++-------- .../src/components/AreaChart/AreaChart.tsx | 55 ++++- .../src/components/AreaChart/utils.test.ts | 23 +++ .../manager/src/components/AreaChart/utils.ts | 40 +++- ...lay.styles.ts => MetricsDisplay.styles.ts} | 4 +- .../components/LineGraph/MetricsDisplay.tsx | 18 +- packages/manager/src/factories/dashboards.ts | 3 +- .../Utils/CloudPulseWidgetColorPalette.ts | 111 ---------- .../CloudPulse/Utils/CloudPulseWidgetUtils.ts | 176 ++++++++-------- .../CloudPulse/Utils/unitConversion.ts | 5 +- .../src/features/CloudPulse/Utils/utils.ts | 2 +- .../CloudPulse/Widget/CloudPulseWidget.tsx | 60 +++--- .../Widget/components/CloudPulseLineGraph.tsx | 47 ++--- 17 files changed, 455 insertions(+), 478 deletions(-) create mode 100644 packages/api-v4/.changeset/pr-11204-upcoming-features-1732020050249.md create mode 100644 packages/manager/.changeset/pr-11204-upcoming-features-1730713318901.md rename packages/manager/src/components/LineGraph/{MetricDisplay.styles.ts => MetricsDisplay.styles.ts} (92%) delete mode 100644 packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetColorPalette.ts diff --git a/packages/api-v4/.changeset/pr-11204-upcoming-features-1732020050249.md b/packages/api-v4/.changeset/pr-11204-upcoming-features-1732020050249.md new file mode 100644 index 00000000000..4444ce3f7ef --- /dev/null +++ b/packages/api-v4/.changeset/pr-11204-upcoming-features-1732020050249.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +Modify `chart_type` property type in `types.ts` ([#11204](https://github.com/linode/manager/pull/11204)) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index 3ef376f7c60..b763ff44721 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -39,7 +39,7 @@ export interface Widgets { namespace_id: number; color: string; size: number; - chart_type: string; + chart_type: 'line' | 'area'; y_label: string; filters: Filters[]; serviceType: string; diff --git a/packages/manager/.changeset/pr-11204-upcoming-features-1730713318901.md b/packages/manager/.changeset/pr-11204-upcoming-features-1730713318901.md new file mode 100644 index 00000000000..2b1252d3b29 --- /dev/null +++ b/packages/manager/.changeset/pr-11204-upcoming-features-1730713318901.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Replace `LineGraph` with `AreaChart` and add `DataSet` type in `CloudPulseLineGraph` component, add `connectNulls`, `dotRadius`, `showDot`, `xAxisTickCount` property and `ChartVariant` interface in `AreaChart.ts` ([#11204](https://github.com/linode/manager/pull/11204)) diff --git a/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts index d1f1181337d..e57b259d25e 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts @@ -29,11 +29,12 @@ import { mockGetUserPreferences } from 'support/intercepts/profile'; import { mockGetRegions } from 'support/intercepts/regions'; import { extendRegion } from 'support/util/regions'; import { CloudPulseMetricsResponse, Database } from '@linode/api-v4'; -import { transformData } from 'src/features/CloudPulse/Utils/unitConversion'; -import { getMetrics } from 'src/utilities/statMetrics'; import { Interception } from 'cypress/types/net-stubbing'; import { generateRandomMetricsData } from 'support/util/cloudpulse'; import { mockGetDatabases } from 'support/intercepts/databases'; +import { generateGraphData } from 'src/features/CloudPulse/Utils/CloudPulseWidgetUtils'; +import type { Flags } from 'src/featureFlags'; +import { formatToolTip } from 'src/features/CloudPulse/Utils/unitConversion'; /** * This test ensures that widget titles are displayed correctly on the dashboard. @@ -48,6 +49,8 @@ import { mockGetDatabases } from 'support/intercepts/databases'; const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; const timeDurationToSelect = 'Last 24 Hours'; +const flags : Partial= {aclp: { enabled: true, beta: true}} + const { metrics, id, @@ -101,28 +104,49 @@ const metricsAPIResponsePayload = cloudPulseMetricsResponseFactory.build({ }); /** - * Verifies the presence and values of specific properties within the aclpPreference object - * of the request payload. This function checks that the expected properties exist - * and have the expected values, allowing for validation of user preferences in the application. + * Generates graph data from a given CloudPulse metrics response and + * extracts average, last, and maximum metric values from the first + * legend row. The values are rounded to two decimal places for + * better readability. + * + * @param responsePayload - The metrics response object containing + * the necessary data for graph generation. + * @param label - The label for the graph, used for display purposes. + * + * @returns An object containing rounded values for max average, last, * - * @param requestPayload - The payload received from the request, containing the aclpPreference object. - * @param expectedValues - An object containing the expected values for properties to validate against the requestPayload. - * Expected properties may include: - * - dashboardId: The ID of the dashboard. - * - timeDuration: The selected time duration for metrics. - * - engine: The database engine used. - * - region: The selected region for the dashboard. - * - resources: An array of resource identifiers. - * - role: The role associated with the dashboard user. */ + const getWidgetLegendRowValuesFromResponse = ( - responsePayload: CloudPulseMetricsResponse + responsePayload: CloudPulseMetricsResponse, + label: string, + unit: string ) => { - const data = transformData(responsePayload.data.result[0].values, 'Bytes'); - const { average, last, max } = getMetrics(data); - const roundedAverage = Math.round(average * 100) / 100; - const roundedLast = Math.round(last * 100) / 100; - const roundedMax = Math.round(max * 100) / 100; + // Generate graph data using the provided parameters + const graphData = generateGraphData({ + flags, + label: label, + metricsList: responsePayload, + resources: [ + { + id: '1', + label: clusterName, + region: 'us-ord', + }, + ], + serviceType: serviceType, + status: 'success', + unit: unit, + }); + + // Destructure metrics data from the first legend row + const { average, last, max } = graphData.legendRowsData[0].data; + + // Round the metrics values to two decimal places + const roundedAverage = formatToolTip(average, unit); + const roundedLast = formatToolTip(last, unit); + const roundedMax = formatToolTip(max, unit); + // Return the rounded values in an object return { average: roundedAverage, last: roundedLast, max: roundedMax }; }; @@ -142,9 +166,7 @@ const databaseMock: Database = databaseFactory.build({ describe('Integration Tests for DBaaS Dashboard ', () => { beforeEach(() => { - mockAppendFeatureFlags({ - aclp: { beta: true, enabled: true }, - }); + mockAppendFeatureFlags(flags); mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse mockGetLinodes([mockLinode]); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); @@ -214,7 +236,7 @@ describe('Integration Tests for DBaaS Dashboard ', () => { cy.get(widgetSelector) .should('be.visible') .find('h2') - .should('have.text', `${testData.title} (${testData.unit.trim()})`); + .should('have.text', `${testData.title} (${testData.unit})`); cy.get(widgetSelector) .should('be.visible') .within(() => { @@ -249,34 +271,29 @@ describe('Integration Tests for DBaaS Dashboard ', () => { ); }); - //validate the widget linegrah is present - cy.findByTestId('linegraph-wrapper').within(() => { + //validate the widget areachart is present + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); @@ -308,34 +325,29 @@ describe('Integration Tests for DBaaS Dashboard ', () => { ); }); - //validate the widget linegrah is present - cy.findByTestId('linegraph-wrapper').within(() => { + //validate the widget areachart is present + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); @@ -384,33 +396,29 @@ describe('Integration Tests for DBaaS Dashboard ', () => { .should('be.enabled') .click(); cy.get('@widget').should('be.visible'); - cy.findByTestId('linegraph-wrapper').within(() => { + + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); // click zoom out and validate the same @@ -421,33 +429,28 @@ describe('Integration Tests for DBaaS Dashboard ', () => { .scrollIntoView() .click({ force: true }); cy.get('@widget').should('be.visible'); - cy.findByTestId('linegraph-wrapper').within(() => { + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts index b0620e4148e..731e53bf993 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts @@ -28,10 +28,11 @@ import { mockGetUserPreferences } from 'support/intercepts/profile'; import { mockGetRegions } from 'support/intercepts/regions'; import { extendRegion } from 'support/util/regions'; import { CloudPulseMetricsResponse } from '@linode/api-v4'; -import { transformData } from 'src/features/CloudPulse/Utils/unitConversion'; -import { getMetrics } from 'src/utilities/statMetrics'; import { generateRandomMetricsData } from 'support/util/cloudpulse'; import { Interception } from 'cypress/types/net-stubbing'; +import { generateGraphData } from 'src/features/CloudPulse/Utils/CloudPulseWidgetUtils'; +import { Flags } from 'src/featureFlags'; +import { formatToolTip } from 'src/features/CloudPulse/Utils/unitConversion'; /** * This test ensures that widget titles are displayed correctly on the dashboard. @@ -45,7 +46,7 @@ import { Interception } from 'cypress/types/net-stubbing'; */ const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; const timeDurationToSelect = 'Last 24 Hours'; - +const flags : Partial = {aclp: {enabled: true, beta: true}} const { metrics, id, @@ -97,33 +98,55 @@ const metricsAPIResponsePayload = cloudPulseMetricsResponseFactory.build({ }); /** - * `verifyWidgetValues` processes and verifies the metric values of a widget from the provided response payload. + * Generates graph data from a given CloudPulse metrics response and + * extracts average, last, and maximum metric values from the first + * legend row. The values are rounded to two decimal places for + * better readability. + * + * @param responsePayload - The metrics response object containing + * the necessary data for graph generation. + * @param label - The label for the graph, used for display purposes. * - * This method performs the following steps: - * 1. Transforms the raw data from the response payload into a more manageable format using `transformData`. - * 2. Extracts key metrics (average, last, and max) from the transformed data using `getMetrics`. - * 3. Rounds these metrics to two decimal places for accuracy. - * 4. Returns an object containing the rounded average, last, and max values for further verification or comparison. + * @returns An object containing rounded values for average, last, * - * @param {CloudPulseMetricsResponse} responsePayload - The response payload containing metric data for a widget. - * @returns {Object} An object with the rounded average, last, and max metric values. */ + const getWidgetLegendRowValuesFromResponse = ( - responsePayload: CloudPulseMetricsResponse + responsePayload: CloudPulseMetricsResponse, + label: string, + unit: string ) => { - const data = transformData(responsePayload.data.result[0].values, 'Bytes'); - const { average, last, max } = getMetrics(data); - const roundedAverage = Math.round(average * 100) / 100; - const roundedLast = Math.round(last * 100) / 100; - const roundedMax = Math.round(max * 100) / 100; + // Generate graph data using the provided parameters + const graphData = generateGraphData({ + flags, + label: label, + metricsList: responsePayload, + resources: [ + { + id: '1', + label: resource, + region: 'us-ord', + }, + ], + serviceType: serviceType, + status: 'success', + unit: unit, + }); + + // Destructure metrics data from the first legend row + const { average, last, max } = graphData.legendRowsData[0].data; + + // Round the metrics values to two decimal places + const roundedAverage = formatToolTip(average, unit); + const roundedLast = formatToolTip(last, unit); + const roundedMax = formatToolTip(max, unit); + // Return the rounded values in an object return { average: roundedAverage, last: roundedLast, max: roundedMax }; }; describe('Integration Tests for Linode Dashboard ', () => { beforeEach(() => { - mockAppendFeatureFlags({ - aclp: { beta: true, enabled: true }, - }); + mockAppendFeatureFlags(flags); mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse mockGetLinodes([mockLinode]); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); @@ -214,34 +237,32 @@ describe('Integration Tests for Linode Dashboard ', () => { ); }); - //validate the widget linegrah is present - cy.findByTestId('linegraph-wrapper').within(() => { + //validate the widget areachart is present + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + + cy.log('expectedWidgetValues ', expectedWidgetValues.max); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); @@ -273,34 +294,32 @@ describe('Integration Tests for Linode Dashboard ', () => { ); }); - //validate the widget linegrah is present - cy.findByTestId('linegraph-wrapper').within(() => { + //validate the widget areachart is present + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); - cy.get(`[data-qa-graph-column-title="Max"]`) + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) .should('be.visible') .should( 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` + `${testData.title} (${testData.unit.trim()})` ); + cy.get(`[data-qa-graph-column-title="Max"]`) + .should('be.visible') + .should('have.text', `${expectedWidgetValues.max}`); + cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); @@ -349,33 +368,28 @@ describe('Integration Tests for Linode Dashboard ', () => { .should('be.enabled') .click(); cy.get('@widget').should('be.visible'); - cy.findByTestId('linegraph-wrapper').within(() => { + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload - ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' + metricsAPIResponsePayload, + testData.title, + testData.unit ); + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) + .should('be.visible') + .should('have.text', `${testData.title} (${testData.unit})`); + cy.get(`[data-qa-graph-column-title="Max"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.max}`); cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); // click zoom out and validate the same @@ -386,33 +400,33 @@ describe('Integration Tests for Linode Dashboard ', () => { .scrollIntoView() .click({ force: true }); cy.get('@widget').should('be.visible'); - cy.findByTestId('linegraph-wrapper').within(() => { + + cy.findByTestId('areachart-wrapper').within(() => { const expectedWidgetValues = getWidgetLegendRowValuesFromResponse( - metricsAPIResponsePayload + metricsAPIResponsePayload, + testData.title, + testData.unit ); - cy.findByText(`${testData.title} (${testData.unit})`).should( - 'be.visible' - ); - cy.get(`[data-qa-graph-column-title="Max"]`) + + const graphRowTitle = `[data-qa-graph-row-title="${testData.title} (${testData.unit})"]`; + cy.get(graphRowTitle) .should('be.visible') .should( 'have.text', - `${expectedWidgetValues.max} ${testData.unit}` + `${testData.title} (${testData.unit.trim()})` ); + cy.get(`[data-qa-graph-column-title="Max"]`) + .should('be.visible') + .should('have.text', `${expectedWidgetValues.max}`); + cy.get(`[data-qa-graph-column-title="Avg"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.average} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.average}`); cy.get(`[data-qa-graph-column-title="Last"]`) .should('be.visible') - .should( - 'have.text', - `${expectedWidgetValues.last} ${testData.unit}` - ); + .should('have.text', `${expectedWidgetValues.last}`); }); }); }); diff --git a/packages/manager/src/components/AreaChart/AreaChart.tsx b/packages/manager/src/components/AreaChart/AreaChart.tsx index ff43f4793ef..db9768a9413 100644 --- a/packages/manager/src/components/AreaChart/AreaChart.tsx +++ b/packages/manager/src/components/AreaChart/AreaChart.tsx @@ -19,6 +19,7 @@ import MetricsDisplay from 'src/components/LineGraph/MetricsDisplay'; import { StyledBottomLegend } from 'src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/TablesPanel'; import { + generate12HourTicks, humanizeLargeData, tooltipLabelFormatter, tooltipValueFormatter, @@ -27,7 +28,14 @@ import { import type { TooltipProps } from 'recharts'; import type { MetricsDisplayRow } from 'src/components/LineGraph/MetricsDisplay'; -interface AreaProps { +export interface DataSet { + [label: string]: number; + timestamp: number; +} + +export type ChartVariant = 'area' | 'line'; + +export interface AreaProps { /** * color for the area */ @@ -52,7 +60,7 @@ interface XAxisProps { tickGap: number; } -interface AreaChartProps { +export interface AreaChartProps { /** * list of areas to be displayed */ @@ -63,11 +71,21 @@ interface AreaChartProps { */ ariaLabel: string; + /** + * connect nulls value between two data points + */ + connectNulls?: boolean; + /** * data to be displayed on the graph */ data: any; + /** + * radius of the dots to be displayed + */ + dotRadius?: number; + /** * */ @@ -93,6 +111,11 @@ interface AreaChartProps { */ margin?: { bottom: number; left: number; right: number; top: number }; + /** + * control the visibility of dots for each data points + */ + showDot?: boolean; + /** * true to display legends rows else false to hide * @default false @@ -113,7 +136,7 @@ interface AreaChartProps { * make chart appear as a line or area chart * @default area */ - variant?: 'area' | 'line'; + variant?: ChartVariant; /** * The width of chart container. @@ -124,24 +147,35 @@ interface AreaChartProps { * x-axis properties */ xAxis: XAxisProps; + + /** + * number of x-axis ticks should be shown + * 0 or undefined : ticks will be generated by recharts + * non-zero value : this many ticks will be generated based on the starting & ending timestamp in the data + */ + xAxisTickCount?: number; } export const AreaChart = (props: AreaChartProps) => { const { areas, ariaLabel, + connectNulls, data, + dotRadius = 3, fillOpacity, height = '100%', legendHeight, legendRows, - margin = { bottom: 0, left: -20, right: 0, top: 0 }, + margin = { bottom: 0, left: -20, right: 30, top: 0 }, + showDot, showLegend, timezone, unit, variant, width = '100%', xAxis, + xAxisTickCount, } = props; const theme = useTheme(); @@ -220,7 +254,7 @@ export const AreaChart = (props: AreaChartProps) => { }; return ( - <> + <_AreaChart aria-label={ariaLabel} data={data} margin={margin}> { vertical={false} /> { )} {areas.map(({ color, dataKey }) => ( { timezone={timezone} unit={unit} /> - + ); }; diff --git a/packages/manager/src/components/AreaChart/utils.test.ts b/packages/manager/src/components/AreaChart/utils.test.ts index 1b98dd93e9d..c2b1bc3d99b 100644 --- a/packages/manager/src/components/AreaChart/utils.test.ts +++ b/packages/manager/src/components/AreaChart/utils.test.ts @@ -1,12 +1,14 @@ import { determinePower } from 'src/utilities/unitConversions'; import { + generate12HourTicks, getAccessibleTimestamp, humanizeLargeData, tooltipLabelFormatter, tooltipValueFormatter, } from './utils'; +import type { DataSet } from './AreaChart'; import type { StorageSymbol } from 'src/utilities/unitConversions'; const timestamp = 1704204000000; @@ -72,3 +74,24 @@ describe('determinePower', () => { ).toBe(1); }); }); + +describe('generate x-axis ticks', () => { + const data: DataSet[] = [ + { label: 0.3744841110560275, timestamp: 1721854379 }, + { label: 0.4980357104166823, timestamp: 1721857979 }, + { label: 0.3290476561287732, timestamp: 1721861579 }, + { label: 0.4214879396496189, timestamp: 1721865179 }, + { label: 0.2269247326830727, timestamp: 1721868779 }, + { label: 0.3393055885526987, timestamp: 1721872379 }, + { label: 0.5237102833940027, timestamp: 1721875979 }, + ]; + it('should return empty x-axis tick list', () => { + const ticks = generate12HourTicks(data, 'GMT', 0); + expect(ticks.length).toBe(0); + }); + + it('should return 7 x-axis tick', () => { + const ticks = generate12HourTicks(data, 'GMT', 7); + expect(ticks.length).toBe(7); + }); +}); diff --git a/packages/manager/src/components/AreaChart/utils.ts b/packages/manager/src/components/AreaChart/utils.ts index 6026169e58a..2586b048801 100644 --- a/packages/manager/src/components/AreaChart/utils.ts +++ b/packages/manager/src/components/AreaChart/utils.ts @@ -2,7 +2,8 @@ import { DateTime } from 'luxon'; import { roundTo } from 'src/utilities/roundTo'; -import { LinodeNetworkTimeData } from './types'; +import type { DataSet } from './AreaChart'; +import type { LinodeNetworkTimeData } from './types'; export const getAccessibleTimestamp = (timestamp: number, timezone: string) => DateTime.fromMillis(timestamp, { zone: timezone }).toLocaleString( @@ -36,6 +37,43 @@ export const humanizeLargeData = (value: number) => { return `${value}`; }; +/** + * + * @param data dataset for which ticks should be generated + * @param timezone timezone applicable to timestamp in the dataset + * @param tickCount number of ticks to be generated + * @returns list of tickCount number of x-axis ticks + */ +export const generate12HourTicks = ( + data: DataSet[], + timezone: string, + tickCount: number +) => { + if (data.length === 0) { + return []; + } + + // Get start and end time from data + const startTime = data[0].timestamp; + const endTime = data[data.length - 1].timestamp; + + // Calculate duration in hours + const duration = DateTime.fromMillis(endTime, { zone: timezone }).diff( + DateTime.fromMillis(startTime, { zone: timezone }), + 'hours' + ).hours; + + // Generate fixed number of ticks across the 12-hour period + // Use 7 ticks (every 2 hours) to prevent overcrowding + const interval = duration / (tickCount - 1); + + return Array.from({ length: tickCount }, (_, i) => { + return DateTime.fromMillis(startTime, { zone: timezone }) + .plus({ hours: i * interval }) + .toMillis(); + }); +}; + export const timeData: LinodeNetworkTimeData[] = [ { 'Public Outbound Traffic': 5.434939999999999, diff --git a/packages/manager/src/components/LineGraph/MetricDisplay.styles.ts b/packages/manager/src/components/LineGraph/MetricsDisplay.styles.ts similarity index 92% rename from packages/manager/src/components/LineGraph/MetricDisplay.styles.ts rename to packages/manager/src/components/LineGraph/MetricsDisplay.styles.ts index a867521b183..411bba84f51 100644 --- a/packages/manager/src/components/LineGraph/MetricDisplay.styles.ts +++ b/packages/manager/src/components/LineGraph/MetricsDisplay.styles.ts @@ -40,7 +40,9 @@ export const StyledButton = styled(Button, { '&:before': { backgroundColor: hidden ? theme.color.disabledText - : theme.graphs[legendColor], + : theme.graphs[legendColor] + ? theme.graphs[legendColor] + : legendColor, flexShrink: 0, }, }), diff --git a/packages/manager/src/components/LineGraph/MetricsDisplay.tsx b/packages/manager/src/components/LineGraph/MetricsDisplay.tsx index c22a3c375e8..1f767e972fa 100644 --- a/packages/manager/src/components/LineGraph/MetricsDisplay.tsx +++ b/packages/manager/src/components/LineGraph/MetricsDisplay.tsx @@ -10,7 +10,7 @@ import { StyledButton, StyledTable, StyledTableCell, -} from './MetricDisplay.styles'; +} from './MetricsDisplay.styles'; import type { Metrics } from 'src/utilities/statMetrics'; @@ -19,15 +19,6 @@ const ROW_HEADERS = ['Max', 'Avg', 'Last'] as const; type MetricKey = 'average' | 'last' | 'max'; const METRIC_KEYS: MetricKey[] = ['max', 'average', 'last']; -export type LegendColor = - | 'blue' - | 'darkGreen' - | 'green' - | 'lightGreen' - | 'purple' - | 'red' - | 'yellow'; - interface Props { /** * Array of rows to hide. Each row should contain the legend title. @@ -47,7 +38,7 @@ export interface MetricsDisplayRow { data: Metrics; format: (n: number) => string; handleLegendClick?: () => void; - legendColor: LegendColor; + legendColor: string; legendTitle: string; } @@ -95,12 +86,15 @@ const MetricRow = ({ legendColor={legendColor} onClick={handleLegendClick} > - {legendTitle} + + {legendTitle} + {METRIC_KEYS.map((key, idx) => ( ({ diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetColorPalette.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetColorPalette.ts deleted file mode 100644 index 3b9325521d0..00000000000 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetColorPalette.ts +++ /dev/null @@ -1,111 +0,0 @@ -export const RED = [ - '#ee2c2c80', - '#ff633d80', - '#F27E7E80', - '#EA7C7280', - '#E2796580', - '#D9775980', - '#D1744D80', - '#C9724080', - '#C16F3480', - '3B86D2880', - '#B06A1B80', - '#A8680F80', -]; - -export const GREEN = [ - '#10a21d80', - '#31ce3e80', - '#d9b0d980', - '#ffdc7d80', - '#7EF29D80', - '#72E39E80', - '#65D3A080', - '#59C4A180', - '#4DB5A280', - '#40A5A480', - '#3496A580', - '#2887A680', - '#1B77A880', - '#0F68A980', -]; - -export const BLUE = [ - '#3683dc80', - '#0F91A880', - '#1B9CAC80', - '#28A7AF80', - '#34B1B380', - '#40BCB680', - '#4DC7BA80', - '#59D2BD80', - '#65DCC180', - '#72E7C480', - '#7EF2C880', -]; - -export const YELLOW = [ - '#ffb34d80', - '#F2EE7E80', - '#E6E67280', - '#DBDE6580', - '#CFD75980', - '#C4CF4D80', - '#B8C74080', - '#ADBF3480', - '#A1B82880', - '#96B01B80', - '#8AA80F80', -]; - -export const PINK = [ - '#F27EE180', - '#EA72D180', - '#E265C280', - '#D959B280', - '#D14DA280', - '#C9409380', - '#C1348380', - '#B8287380', - '#B01B6480', - '#A80F5480', -]; - -export const DEFAULT = [ - // thick colors from each... - '#4067E580', - '#FE993380', - '#12A59480', - '#AB4ABA80', - '#D63C4280', - '#05A2C280', - '#E043A780', - '#00B05080', - '#7259D680', - '#99D52A80', - '#71717880', - '#FFD70080', - '#40E0D080', - '#8DA4EF80', - '#C25D0580', - '#067A6F80', - '#CF91D880', - '#EB909180', - '#0C779280', - '#E38EC380', - '#97CF9C80', - '#AA99EC80', - '#94BA2C80', - '#4B4B5180', - '#FFE76680', - '#33B2A680', -]; - -export const COLOR_MAP = new Map([ - ['blue', BLUE], - ['default', DEFAULT], - ['green', GREEN], - ['pink', PINK], - ['red', RED], - ['yellow', YELLOW], -]); diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts index 00afdd83ce7..f4066808b3d 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts @@ -1,8 +1,9 @@ -import { isToday } from 'src/utilities/isToday'; +import { Alias } from '@linode/design-language-system'; + import { getMetrics } from 'src/utilities/statMetrics'; -import { COLOR_MAP, DEFAULT } from './CloudPulseWidgetColorPalette'; import { + convertValueToUnit, formatToolTip, generateUnitByBaseUnit, transformData, @@ -13,7 +14,6 @@ import { } from './utils'; import type { CloudPulseResources } from '../shared/CloudPulseResourcesSelect'; -import type { LegendRow } from '../Widget/CloudPulseWidget'; import type { CloudPulseMetricsList, CloudPulseMetricsRequest, @@ -22,7 +22,9 @@ import type { Widgets, } from '@linode/api-v4'; import type { Theme } from '@mui/material'; -import type { DataSet } from 'src/components/LineGraph/LineGraph'; +import type { DataSet } from 'src/components/AreaChart/AreaChart'; +import type { AreaProps } from 'src/components/AreaChart/AreaChart'; +import type { MetricsDisplayRow } from 'src/components/LineGraph/MetricsDisplay'; import type { CloudPulseResourceTypeMapFlag, FlagSet } from 'src/featureFlags'; interface LabelNameOptionsProps { @@ -57,7 +59,7 @@ interface LabelNameOptionsProps { unit: string; } -interface graphDataOptionsProps { +interface GraphDataOptionsProps { /** * flags associated with metricsList */ @@ -84,24 +86,14 @@ interface graphDataOptionsProps { serviceType: string; /** - * status returned from react query ( loading | error | success) + * status returned from react query ( pending | error | success) */ - status: string | undefined; + status: 'error' | 'pending' | 'success'; /** * unit of the data */ unit: string; - - /** - * widget chart type - */ - widgetChartType: string; - - /** - * preferred color for the widget's graph - */ - widgetColor: string; } interface MetricRequestProps { @@ -143,11 +135,33 @@ interface DimensionNameProperties { resources: CloudPulseResources[]; } +interface GraphData { + /** + * array of area props to be shown on graph + */ + areas: AreaProps[]; + + /** + * plots to be shown of each dimension + */ + dimensions: DataSet[]; + + /** + * legends rows available for each dimension + */ + legendRowsData: MetricsDisplayRow[]; + + /** + * maximum possible rolled up unit for the data + */ + unit: string; +} + /** * * @returns parameters which will be necessary to populate graph & legends */ -export const generateGraphData = (props: graphDataOptionsProps) => { +export const generateGraphData = (props: GraphDataOptionsProps): GraphData => { const { flags, label, @@ -156,28 +170,22 @@ export const generateGraphData = (props: graphDataOptionsProps) => { serviceType, status, unit, - widgetChartType, - widgetColor, } = props; - - const dimensions: DataSet[] = []; - const legendRowsData: LegendRow[] = []; - - // If the color is not found in the map, fallback to default color theme - const colors = COLOR_MAP.get(widgetColor) ?? DEFAULT; - let today = false; - + const legendRowsData: MetricsDisplayRow[] = []; + const dimension: { [timestamp: number]: { [label: string]: number } } = {}; + const areas: AreaProps[] = []; + const colors = Object.values(Alias.Chart.Categorical); if (status === 'success') { metricsList?.data?.result?.forEach( (graphData: CloudPulseMetricsList, index) => { if (!graphData) { return; } + const transformedData = { metric: graphData.metric, values: transformData(graphData.values, unit), }; - const color = colors[index]; const { end, start } = convertTimeDurationToStartAndEndTimeRange({ unit: 'min', value: 30, @@ -194,33 +202,62 @@ export const generateGraphData = (props: graphDataOptionsProps) => { serviceType, unit, }; - - const dimension = { - backgroundColor: color, - borderColor: color, - data: seriesDataFormatter(transformedData.values, start, end), - fill: widgetChartType === 'area', - label: getLabelName(labelOptions), - }; + const labelName = getLabelName(labelOptions); + const data = seriesDataFormatter(transformedData.values, start, end); + const color = colors[index % 22].Primary; + areas.push({ + color, + dataKey: labelName, + }); + + // map each label & its data point to its timestamp + data.forEach((dataPoint) => { + const timestamp = dataPoint[0]; + const value = dataPoint[1]; + if (value !== null) { + dimension[timestamp] = { + ...dimension[timestamp], + [labelName]: value, + }; + } + }); // construct a legend row with the dimension - const legendRow = { - data: getMetrics(dimension.data as number[][]), + const legendRow: MetricsDisplayRow = { + data: getMetrics(data as number[][]), format: (value: number) => formatToolTip(value, unit), legendColor: color, - legendTitle: dimension.label, + legendTitle: labelName, }; legendRowsData.push(legendRow); - dimensions.push(dimension); - today ||= isToday(start, end); } ); } + const maxUnit = generateMaxUnit(legendRowsData, unit); + const dimensions = Object.entries(dimension) + .map( + ([timestamp, resource]): DataSet => { + const rolledUpData = Object.entries(resource).reduce( + (oldValue, newValue) => { + return { + ...oldValue, + [newValue[0]]: convertValueToUnit(newValue[1], maxUnit), + }; + }, + {} + ); + + return { timestamp: Number(timestamp), ...rolledUpData }; + } + ) + .sort( + (dimension1, dimension2) => dimension1.timestamp - dimension2.timestamp + ); return { + areas, dimensions, legendRowsData, - today, - unit: generateMaxUnit(legendRowsData, unit), + unit: maxUnit, }; }; @@ -230,7 +267,7 @@ export const generateGraphData = (props: graphDataOptionsProps) => { * @param unit base unit of the values * @returns maximum possible rolled up unit based on the unit */ -const generateMaxUnit = (legendRowsData: LegendRow[], unit: string) => { +const generateMaxUnit = (legendRowsData: MetricsDisplayRow[], unit: string) => { const maxValue = Math.max( 0, ...legendRowsData?.map((row) => row?.data.max ?? 0) @@ -319,20 +356,6 @@ export const mapResourceIdToName = ( return resourcesObj?.label ?? id ?? ''; }; -/** - * - * @param data data set to be checked for empty - * @returns true if data is not empty or contains all the null values otherwise false - */ -export const isDataEmpty = (data: DataSet[]): boolean => { - return data.every( - (thisSeries) => - thisSeries.data.length === 0 || - // If we've padded the data, every y value will be null - thisSeries.data.every((thisPoint) => thisPoint[1] === null) - ); -}; - /** * * @param theme mui theme @@ -347,38 +370,3 @@ export const getAutocompleteWidgetStyles = (theme: Theme) => ({ width: '90px', }, }); - -/** - * This method handles the existing issue in chart JS, and it will deleted when the recharts migration is completed - * @param arraysToBeFilled The list of dimension data to be filled - * @returns The list of dimension data filled with null values for missing timestamps - */ -// TODO: CloudPulse - delete when recharts migration completed -export const fillMissingTimeStampsAcrossDimensions = ( - ...arraysToBeFilled: [number, number | null][][] -): [number, number | null][][] => { - if (arraysToBeFilled.length === 0) return []; - - // Step 1: Collect all unique keys from all arrays - const allTimestamps = new Set(); - - // Collect timestamps from each array, array[0], contains the number timestamp - arraysToBeFilled.forEach((array) => { - array.forEach(([timeStamp]) => allTimestamps.add(timeStamp)); - }); - - // Step 2: Sort the timestamps to maintain chronological order - const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b); - - // Step 3: Synchronize the arrays to have null values for all missing timestamps - return arraysToBeFilled.map((array) => { - // Step 3.1: Convert the array into a map for fast lookup - const map = new Map(array.map(([key, value]) => [key, value])); - - // Step 3.2: Build the synchronized array by checking if a key exists - return sortedTimestamps.map((key) => { - // If the current array has the key, use its value; otherwise, set it to null, so that the gap is properly visible - return [key, map.get(key) ?? null] as [number, number | null]; - }); - }); -}; diff --git a/packages/manager/src/features/CloudPulse/Utils/unitConversion.ts b/packages/manager/src/features/CloudPulse/Utils/unitConversion.ts index 422607f383a..7dfccb8d7bb 100644 --- a/packages/manager/src/features/CloudPulse/Utils/unitConversion.ts +++ b/packages/manager/src/features/CloudPulse/Utils/unitConversion.ts @@ -244,5 +244,8 @@ export const transformData = ( ): [number, number][] => { const unit: string = generateCurrentUnit(baseUnit); - return data.map((d) => [d[0], Number(d[1]) * (multiplier[unit] ?? 1)]); + return data.map((d) => [ + d[0], + d[1] !== null ? Number(d[1]) * (multiplier[unit] ?? 1) : d[1], + ]); }; diff --git a/packages/manager/src/features/CloudPulse/Utils/utils.ts b/packages/manager/src/features/CloudPulse/Utils/utils.ts index 7c168f4e487..6801fd4b034 100644 --- a/packages/manager/src/features/CloudPulse/Utils/utils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/utils.ts @@ -108,7 +108,7 @@ export const seriesDataFormatter = ( const formattedArray: StatWithDummyPoint[] = data.map(([x, y]) => ({ x: Number(x), - y: y ? Number(y) : null, + y: y !== null ? Number(y) : null, })); return convertData(formattedArray, startTime, endTime); diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index 61c561eda0c..8ee8a56eb32 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -10,15 +10,10 @@ import { useProfile } from 'src/queries/profile/profile'; import { generateGraphData, getCloudPulseMetricRequest, - fillMissingTimeStampsAcrossDimensions, } from '../Utils/CloudPulseWidgetUtils'; import { AGGREGATE_FUNCTION, SIZE, TIME_GRANULARITY } from '../Utils/constants'; import { constructAdditionalRequestFilters } from '../Utils/FilterBuilder'; -import { - convertValueToUnit, - formatToolTip, - generateCurrentUnit, -} from '../Utils/unitConversion'; +import { generateCurrentUnit } from '../Utils/unitConversion'; import { useAclpPreference } from '../Utils/UserPreference'; import { convertStringToCamelCasesWithSpaces } from '../Utils/utils'; import { CloudPulseAggregateFunction } from './components/CloudPulseAggregateFunction'; @@ -34,7 +29,12 @@ import type { TimeDuration, TimeGranularity, } from '@linode/api-v4'; -import type { DataSet } from 'src/components/LineGraph/LineGraph'; +import type { DataSet } from 'src/components/AreaChart/AreaChart'; +import type { + AreaProps, + ChartVariant, +} from 'src/components/AreaChart/AreaChart'; +import type { MetricsDisplayRow } from 'src/components/LineGraph/MetricsDisplay'; import type { Metrics } from 'src/utilities/statMetrics'; export interface CloudPulseWidgetProperties { @@ -238,11 +238,12 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { url: flags.aclpReadEndpoint!, } ); - let data: DataSet[] = []; - let legendRows: LegendRow[] = []; - let today: boolean = false; + let legendRows: MetricsDisplayRow[] = []; + let currentUnit = unit; + let areas: AreaProps[] = []; + const variant: ChartVariant = widget.chart_type; if (!isLoading && metricsList) { const generatedData = generateGraphData({ flags, @@ -252,29 +253,19 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { serviceType, status, unit, - widgetChartType: widget.chart_type, - widgetColor: widget.color, }); data = generatedData.dimensions; - - // add missing timestamps across all the dimensions - const filledArrays = fillMissingTimeStampsAcrossDimensions( - ...data.map((data) => data.data) - ); - - //update the chart data with updated arrays - filledArrays.forEach((arr, index) => { - data[index].data = arr; - }); - legendRows = generatedData.legendRowsData; - today = generatedData.today; scaledWidgetUnit.current = generatedData.unit; // here state doesn't matter, as this is always the latest re-render + currentUnit = generatedData.unit; + areas = generatedData.areas; } const metricsApiCallError = error?.[0]?.reason; + const tickFormat = + duration.unit === 'min' || duration.unit === 'hr' ? 'hh:mm a' : 'LLL dd'; return ( @@ -335,22 +326,19 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { ? metricsApiCallError ?? 'Error while rendering graph' : undefined } - formatData={(data: number | null) => - data === null - ? data - : convertValueToUnit(data, scaledWidgetUnit.current) - } - legendRows={ - legendRows && legendRows.length > 0 ? legendRows : undefined - } + areas={areas} ariaLabel={ariaLabel ? ariaLabel : ''} data={data} - formatTooltip={(value: number) => formatToolTip(value, unit)} - gridSize={widget.size} + dotRadius={1.5} + height={424} + legendRows={legendRows} loading={isLoading || metricsApiCallError === jweTokenExpiryError} // keep loading until we fetch the refresh token - showToday={today} + showDot + showLegend={data.length !== 0} timezone={timezone} - title={widget.label} + unit={currentUnit} + variant={variant} + xAxis={{ tickFormat, tickGap: 60 }} /> diff --git a/packages/manager/src/features/CloudPulse/Widget/components/CloudPulseLineGraph.tsx b/packages/manager/src/features/CloudPulse/Widget/components/CloudPulseLineGraph.tsx index c0c24841b5d..a54e2c19747 100644 --- a/packages/manager/src/features/CloudPulse/Widget/components/CloudPulseLineGraph.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/components/CloudPulseLineGraph.tsx @@ -1,30 +1,25 @@ import { CircleProgress } from '@linode/ui'; -import { Box, Typography, useTheme } from '@mui/material'; +import { Box, Typography, useMediaQuery, useTheme } from '@mui/material'; import * as React from 'react'; +import { AreaChart } from 'src/components/AreaChart/AreaChart'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; -import { LineGraph } from 'src/components/LineGraph/LineGraph'; -import { isDataEmpty } from '../../Utils/CloudPulseWidgetUtils'; +import type { AreaChartProps } from 'src/components/AreaChart/AreaChart'; -import type { LegendRow } from '../CloudPulseWidget'; -import type { LineGraphProps } from 'src/components/LineGraph/LineGraph'; - -export interface CloudPulseLineGraph extends LineGraphProps { - ariaLabel?: string; +export interface CloudPulseLineGraph extends AreaChartProps { error?: string; - gridSize: number; - legendRows?: LegendRow[]; loading?: boolean; - subtitle?: string; - title: string; } export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => { - const { ariaLabel, data, error, legendRows, loading, ...rest } = props; + const { error, loading, ...rest } = props; const theme = useTheme(); + // to reduce the x-axis tick count for small screen + const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); + if (loading) { return ; } @@ -34,7 +29,6 @@ export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => { } const noDataMessage = 'No data to display'; - return ( {error ? ( @@ -42,29 +36,18 @@ export const CloudPulseLineGraph = React.memo((props: CloudPulseLineGraph) => { ) : ( - )} - {isDataEmpty(data) && ( + {rest.data.length === 0 && ( From b91a44618800164902e8655114f99d897865d80f Mon Sep 17 00:00:00 2001 From: santoshp210-akamai <159890961+santoshp210-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:13:14 +0530 Subject: [PATCH 10/92] upcoming: [DI-21998] - Fixed the type in alerts.ts and removed Random --- packages/manager/src/factories/cloudpulse/alerts.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/factories/cloudpulse/alerts.ts b/packages/manager/src/factories/cloudpulse/alerts.ts index 6459a8d1800..2860dc3e59e 100644 --- a/packages/manager/src/factories/cloudpulse/alerts.ts +++ b/packages/manager/src/factories/cloudpulse/alerts.ts @@ -3,6 +3,7 @@ import Factory from 'src/factories/factoryProxy'; import type { Alert, AlertDefinitionType, + AlertServiceType, AlertSeverityType, AlertStatusType, } from '@linode/api-v4'; @@ -11,13 +12,13 @@ const types: AlertDefinitionType[] = ['custom', 'default']; const status: AlertStatusType[] = ['enabled', 'disabled']; const severity: AlertSeverityType[] = [0, 1, 2, 3]; const users = ['user1', 'user2', 'user3']; -const serviceTypes = ['linode', 'dbaas']; +const serviceTypes: AlertServiceType[] = ['linode', 'dbaas']; export const alertFactory = Factory.Sync.makeFactory({ channels: [], created: new Date().toISOString(), created_by: Factory.each((i) => users[i % users.length]), description: '', - id: Factory.each(() => Math.floor(Math.random() * 1000000)), + id: Factory.each((i) => i), label: Factory.each((id) => `Alert-${id}`), resource_ids: ['0', '1', '2', '3'], rule_criteria: { From 39f2a1892a4ff181ceee92b3d73443f77d9c585e Mon Sep 17 00:00:00 2001 From: santoshp210-akamai <159890961+santoshp210-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 02:33:28 +0530 Subject: [PATCH 11/92] upcoming: [DI-21998] - Extended the createAlertDefinitionSchema and separated the interface from api-v4/types.ts for the non payload fields --- packages/api-v4/src/cloudpulse/types.ts | 6 ------ .../Alerts/CreateAlert/CreateAlertDefinition.tsx | 6 +++--- .../GeneralInformation/AlertSeveritySelect.tsx | 6 ++---- .../GeneralInformation/EngineOption.tsx | 2 +- .../GeneralInformation/RegionSelect.tsx | 7 +++++-- .../GeneralInformation/ServiceTypeSelect.tsx | 6 ++---- .../CloudPulse/Alerts/CreateAlert/schemas.ts | 15 +++++++++++++++ .../CloudPulse/Alerts/CreateAlert/types.ts | 11 +++++++++++ .../CloudPulse/Alerts/CreateAlert/utilities.ts | 6 ++---- packages/validation/src/cloudpulse.schema.ts | 10 +--------- 10 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/schemas.ts create mode 100644 packages/manager/src/features/CloudPulse/Alerts/CreateAlert/types.ts diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index cc742db3de4..0bffe363a3f 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -156,12 +156,6 @@ export interface CreateAlertDefinitionPayload { triggerCondition: TriggerCondition; channel_ids: number[]; } -export interface CreateAlertDefinitionForm - extends CreateAlertDefinitionPayload { - region: string; - service_type: AlertServiceType | null; - engine_type: string | null; -} export interface MetricCriteria { metric: string; aggregation_type: MetricAggregationType; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index 56a5185e28d..c8b7ed57632 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx @@ -1,6 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Paper } from '@linode/ui'; -import { createAlertDefinitionSchema } from '@linode/validation'; import { useSnackbar } from 'notistack'; import * as React from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -16,10 +15,11 @@ import { CloudPulseAlertSeveritySelect } from './GeneralInformation/AlertSeverit import { EngineOption } from './GeneralInformation/EngineOption'; import { CloudPulseRegionSelect } from './GeneralInformation/RegionSelect'; import { CloudPulseServiceSelect } from './GeneralInformation/ServiceTypeSelect'; +import { CreateAlertDefinitionFormSchema } from './schemas'; import { filterFormValues } from './utilities'; +import type { CreateAlertDefinitionForm } from './types'; import type { - CreateAlertDefinitionForm, MetricCriteria, TriggerCondition, } from '@linode/api-v4/lib/cloudpulse/types'; @@ -70,7 +70,7 @@ export const CreateAlertDefinition = () => { const formMethods = useForm({ defaultValues: initialValues, mode: 'onBlur', - resolver: yupResolver(createAlertDefinitionSchema), + resolver: yupResolver(CreateAlertDefinitionFormSchema), }); const { diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx index 8e1f0299340..c10ecf6d0b0 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/AlertSeveritySelect.tsx @@ -5,10 +5,8 @@ import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { alertSeverityOptions } from '../../constants'; -import type { - AlertSeverityType, - CreateAlertDefinitionForm, -} from '@linode/api-v4'; +import type { CreateAlertDefinitionForm } from '../types'; +import type { AlertSeverityType } from '@linode/api-v4'; import type { FieldPathByValue } from 'react-hook-form'; export interface CloudPulseAlertSeveritySelectProps { /** diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx index aaf0fdc6153..2c6b0406739 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/EngineOption.tsx @@ -5,7 +5,7 @@ import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { engineTypeOptions } from '../../constants'; -import type { CreateAlertDefinitionForm } from '@linode/api-v4'; +import type { CreateAlertDefinitionForm } from '../types'; import type { FieldPathByValue } from 'react-hook-form'; interface EngineOptionProps { diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx index 8fdf80f3fc8..fba152700a6 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/RegionSelect.tsx @@ -4,17 +4,20 @@ import { Controller, useFormContext } from 'react-hook-form'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { useRegionsQuery } from 'src/queries/regions/regions'; +import type { CreateAlertDefinitionForm } from '../types'; +import type { FieldPathByValue } from 'react-hook-form'; + export interface CloudViewRegionSelectProps { /** * name used for the component to set in the form */ - name: string; + name: FieldPathByValue; } export const CloudPulseRegionSelect = (props: CloudViewRegionSelectProps) => { const { name } = props; const { data: regions, isError, isLoading } = useRegionsQuery(); - const { control } = useFormContext(); + const { control } = useFormContext(); return ( ( diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx index db0691047d6..1364c5b87fb 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx @@ -5,10 +5,8 @@ import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { useCloudPulseServiceTypes } from 'src/queries/cloudpulse/services'; import type { Item } from '../../constants'; -import type { - AlertServiceType, - CreateAlertDefinitionForm, -} from '@linode/api-v4'; +import type { CreateAlertDefinitionForm } from '../types'; +import type { AlertServiceType } from '@linode/api-v4'; import type { FieldPathByValue } from 'react-hook-form'; interface CloudPulseServiceSelectProps { diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/schemas.ts b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/schemas.ts new file mode 100644 index 00000000000..2e8fee3e200 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/schemas.ts @@ -0,0 +1,15 @@ +import { createAlertDefinitionSchema } from '@linode/validation'; +import { object, string } from 'yup'; + +const engineOptionValidation = string().when('service_type', { + is: 'dbaas', + otherwise: string().notRequired().nullable(), + then: string().required('Engine type is required.').nullable(), +}); +export const CreateAlertDefinitionFormSchema = createAlertDefinitionSchema.concat( + object({ + engine_type: engineOptionValidation, + region: string().required('Region is required.'), + service_type: string().required('Service is required.').nullable(), + }) +); diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/types.ts b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/types.ts new file mode 100644 index 00000000000..c8ddfa27aa9 --- /dev/null +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/types.ts @@ -0,0 +1,11 @@ +import type { + AlertServiceType, + CreateAlertDefinitionPayload, +} from '@linode/api-v4'; + +export interface CreateAlertDefinitionForm + extends CreateAlertDefinitionPayload { + engine_type: null | string; + region: string; + service_type: AlertServiceType | null; +} diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts index 60f47e02d0d..62164f68dd2 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/utilities.ts @@ -1,9 +1,7 @@ import { omitProps } from '@linode/ui'; -import type { - CreateAlertDefinitionForm, - CreateAlertDefinitionPayload, -} from '@linode/api-v4'; +import type { CreateAlertDefinitionForm } from './types'; +import type { CreateAlertDefinitionPayload } from '@linode/api-v4'; export const filterFormValues = ( formValues: CreateAlertDefinitionForm diff --git a/packages/validation/src/cloudpulse.schema.ts b/packages/validation/src/cloudpulse.schema.ts index 6e2ad970fa0..e8b7e63c414 100644 --- a/packages/validation/src/cloudpulse.schema.ts +++ b/packages/validation/src/cloudpulse.schema.ts @@ -1,10 +1,5 @@ import { array, number, object, string } from 'yup'; -const engineOptionValidation = string().when('service_type', { - is: 'dbaas', - then: string().required('Engine type is required.').nullable(), - otherwise: string().notRequired().nullable(), -}); const dimensionFilters = object({ dimension_label: string().required('Label is required for the filter.'), operator: string().required('Operator is required.'), @@ -32,12 +27,9 @@ const triggerCondition = object({ .positive('Number of occurrences must be greater than zero.'), }); -export const createAlertDefinitionSchema = object().shape({ +export const createAlertDefinitionSchema = object({ label: string().required('Name is required.'), description: string().optional(), - region: string().required('Region is required.'), - engine_type: engineOptionValidation, - service_type: string().required('Service is required.').nullable(), resource_ids: array().of(string()).min(1, 'At least one resource is needed.'), severity: string().required('Severity is required.').nullable(), criteria: array() From 260c0b3643db756c0f6445deb30858e22f77e635 Mon Sep 17 00:00:00 2001 From: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:40:00 -0800 Subject: [PATCH 12/92] feat: [M3-8915] - Improve UI for billing contact info when sensitive data is masked (#11276) * Update display of masked billing contact info * Clean up MaskableText; use default export * Add test coverage for masked billing contact info section * Use consistent icon and allow umasking in panel * Address UX feedback: move the visibility icon and allow toggle * Add label to VisibilityTooltip * Skip test for now * Address feedback: use CDS 2.0 icons * Better implementation using icons directly, no tooltip * Clean up, clean up * Update test coverage * Added changeset: Improve billing contact info display when Mask Sensitive Data setting is enabled * Fix failing tests caused by icon replacement * Address feedback: switch order of buttons; stray Box styling clean up --- .../pr-11276-changed-1732122131530.md | 5 + .../e2e/core/billing/billing-contact.spec.ts | 25 +++ .../src/assets/icons/visibilityHide.svg | 4 + .../src/assets/icons/visibilityShow.svg | 4 + .../CopyTooltip/CopyTooltip.test.tsx | 2 +- .../MaskableText/MaskableText.test.tsx | 4 +- .../src/features/Billing/BillingDetail.tsx | 4 +- .../ContactInformation.styles.ts | 37 ++++ .../ContactInformation.test.tsx | 2 +- .../ContactInfoPanel/ContactInformation.tsx | 188 +++++++++--------- .../BillingPanels/ContactInfoPanel/index.ts | 1 - .../Linodes/LinodesLanding/IPAddress.test.tsx | 4 +- .../VisibilityTooltip/VisibilityTooltip.tsx | 20 +- 13 files changed, 189 insertions(+), 111 deletions(-) create mode 100644 packages/manager/.changeset/pr-11276-changed-1732122131530.md create mode 100644 packages/manager/src/assets/icons/visibilityHide.svg create mode 100644 packages/manager/src/assets/icons/visibilityShow.svg create mode 100644 packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.styles.ts delete mode 100644 packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/index.ts diff --git a/packages/manager/.changeset/pr-11276-changed-1732122131530.md b/packages/manager/.changeset/pr-11276-changed-1732122131530.md new file mode 100644 index 00000000000..f84950bdaab --- /dev/null +++ b/packages/manager/.changeset/pr-11276-changed-1732122131530.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Improve billing contact info display when Mask Sensitive Data setting is enabled ([#11276](https://github.com/linode/manager/pull/11276)) diff --git a/packages/manager/cypress/e2e/core/billing/billing-contact.spec.ts b/packages/manager/cypress/e2e/core/billing/billing-contact.spec.ts index 346023aa089..5c3feba46dd 100644 --- a/packages/manager/cypress/e2e/core/billing/billing-contact.spec.ts +++ b/packages/manager/cypress/e2e/core/billing/billing-contact.spec.ts @@ -4,6 +4,7 @@ import type { Account } from '@linode/api-v4'; import { ui } from 'support/ui'; import { TAX_ID_HELPER_TEXT } from 'src/features/Billing/constants'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetUserPreferences } from 'support/intercepts/profile'; /* eslint-disable sonarjs/no-duplicate-string */ const accountData = accountFactory.build({ @@ -72,7 +73,31 @@ describe('Billing Contact', () => { }, }); }); + it('Mask Contact Info', () => { + mockGetUserPreferences({ maskSensitiveData: true }).as( + 'getUserPreferences' + ); + mockGetAccount(accountData).as('getAccount'); + cy.visitWithLogin('/account/billing'); + + cy.contains('This data is sensitive and hidden for privacy.'); + + // Confirm edit button and contact info is hidden when setting is enabled. + cy.findByText('Edit').should('not.exist'); + cy.get('[data-qa-contact-name]').should('not.exist'); + + cy.findByRole('button', { name: 'Show' }).should('be.visible').click(); + + // Confirm edit button and contact info is visible when setting is disabled. + cy.findByText('Edit').should('be.visible'); + cy.get('[data-qa-contact-name]').should('be.visible'); + + cy.findByRole('button', { name: 'Hide' }).should('be.visible').click(); + }); it('Edit Contact Info', () => { + mockGetUserPreferences({ maskSensitiveData: false }).as( + 'getUserPreferences' + ); // mock the user's account data and confirm that it is displayed correctly upon page load mockGetAccount(accountData).as('getAccount'); cy.visitWithLogin('/account/billing'); diff --git a/packages/manager/src/assets/icons/visibilityHide.svg b/packages/manager/src/assets/icons/visibilityHide.svg new file mode 100644 index 00000000000..4fd7e5572df --- /dev/null +++ b/packages/manager/src/assets/icons/visibilityHide.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/manager/src/assets/icons/visibilityShow.svg b/packages/manager/src/assets/icons/visibilityShow.svg new file mode 100644 index 00000000000..858614cc421 --- /dev/null +++ b/packages/manager/src/assets/icons/visibilityShow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/manager/src/components/CopyTooltip/CopyTooltip.test.tsx b/packages/manager/src/components/CopyTooltip/CopyTooltip.test.tsx index d90b014d13c..0ab2a7f1c3a 100644 --- a/packages/manager/src/components/CopyTooltip/CopyTooltip.test.tsx +++ b/packages/manager/src/components/CopyTooltip/CopyTooltip.test.tsx @@ -69,7 +69,7 @@ describe('CopyTooltip', () => { ); const copyIconButton = getByLabelText(`Copy ${mockText} to clipboard`); - const visibilityToggle = getByTestId('VisibilityIcon'); + const visibilityToggle = getByTestId('VisibilityTooltip'); // Text should be masked expect(copyIconButton).toBeInTheDocument(); diff --git a/packages/manager/src/components/MaskableText/MaskableText.test.tsx b/packages/manager/src/components/MaskableText/MaskableText.test.tsx index 8e4061db9a9..8c910d491bf 100644 --- a/packages/manager/src/components/MaskableText/MaskableText.test.tsx +++ b/packages/manager/src/components/MaskableText/MaskableText.test.tsx @@ -85,7 +85,7 @@ describe('MaskableText', () => { expect(queryByText(maskedText)).not.toBeInTheDocument(); }); - it('should render a toggleable VisibilityIcon tooltip if isToggleable is provided', async () => { + it('should render a toggleable VisibilityTooltip if isToggleable is provided', async () => { queryMocks.usePreferences.mockReturnValue({ data: preferences, }); @@ -94,7 +94,7 @@ describe('MaskableText', () => { ); - const visibilityToggle = getByTestId('VisibilityIcon'); + const visibilityToggle = getByTestId('VisibilityTooltip'); // Original text should be masked expect(getByText(maskedText)).toBeVisible(); diff --git a/packages/manager/src/features/Billing/BillingDetail.tsx b/packages/manager/src/features/Billing/BillingDetail.tsx index b5221e168c9..9b07e1cd117 100644 --- a/packages/manager/src/features/Billing/BillingDetail.tsx +++ b/packages/manager/src/features/Billing/BillingDetail.tsx @@ -15,7 +15,7 @@ import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import { BillingActivityPanel } from './BillingPanels/BillingActivityPanel/BillingActivityPanel'; import BillingSummary from './BillingPanels/BillingSummary'; -import ContactInfo from './BillingPanels/ContactInfoPanel'; +import { ContactInformation } from './BillingPanels/ContactInfoPanel/ContactInformation'; import PaymentInformation from './BillingPanels/PaymentInfoPanel'; export const BillingDetail = () => { @@ -67,7 +67,7 @@ export const BillingDetail = () => { paymentMethods={paymentMethods} promotions={account?.active_promotions} /> - ({ + '& .dif': { + '& .MuiChip-root': { + position: 'absolute', + right: -10, + top: '-4px', + }, + position: 'relative', + width: 'auto', + }, + marginBottom: theme.spacing(1), +})); + +export const StyledVisibilityShowIcon = styled(VisibilityShowIcon)( + ({ theme }) => ({ + '& path': { + stroke: theme.palette.primary.main, + }, + marginRight: theme.spacing(0.5), + }) +); + +// eslint-disable-next-line sonarjs/no-identical-functions +export const StyledVisibilityHideIcon = styled(VisibilityHideIcon)( + ({ theme }) => ({ + '& path': { + stroke: theme.palette.primary.main, + }, + marginRight: theme.spacing(0.5), + }) +); diff --git a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx index f536dda3c10..987aec67b5a 100644 --- a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx @@ -4,7 +4,7 @@ import { profileFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { renderWithTheme } from 'src/utilities/testHelpers'; -import ContactInformation from './ContactInformation'; +import { ContactInformation } from './ContactInformation'; const EDIT_BUTTON_ID = 'edit-contact-info'; diff --git a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.tsx b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.tsx index e35c2a8387f..90b57c660d8 100644 --- a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.tsx @@ -1,23 +1,29 @@ import { Box, TooltipIcon } from '@linode/ui'; -import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import { allCountries } from 'country-region-data'; +import { useState } from 'react'; import * as React from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; -import { MaskableText } from 'src/components/MaskableText/MaskableText'; +import { Link } from 'src/components/Link'; import { Typography } from 'src/components/Typography'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { EDIT_BILLING_CONTACT } from 'src/features/Billing/constants'; import { StyledAutorenewIcon } from 'src/features/TopMenu/NotificationMenu/NotificationMenu'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { useNotificationsQuery } from 'src/queries/account/notifications'; +import { usePreferences } from 'src/queries/profile/preferences'; import { BillingActionButton, BillingBox, BillingPaper, } from '../../BillingDetail'; +import { + StyledTypography, + StyledVisibilityHideIcon, + StyledVisibilityShowIcon, +} from './ContactInformation.styles'; import BillingContactDrawer from './EditBillingContactDrawer'; import type { Profile } from '@linode/api-v4'; @@ -38,20 +44,7 @@ interface Props { zip: string; } -const StyledTypography = styled(Typography)(({ theme }) => ({ - '& .dif': { - '& .MuiChip-root': { - position: 'absolute', - right: -10, - top: '-4px', - }, - position: 'relative', - width: 'auto', - }, - marginBottom: theme.spacing(1), -})); - -const ContactInformation = (props: Props) => { +export const ContactInformation = React.memo((props: Props) => { const { address1, address2, @@ -80,6 +73,8 @@ const ContactInformation = (props: Props) => { const { data: notifications } = useNotificationsQuery(); + const { data: preferences } = usePreferences(); + const [focusEmail, setFocusEmail] = React.useState(false); const isChildUser = Boolean(profile?.user_type === 'child'); @@ -123,6 +118,10 @@ const ContactInformation = (props: Props) => { } }, [editContactDrawerOpen, history.location.state]); + const [isContactInfoMasked, setIsContactInfoMasked] = useState( + preferences?.maskSensitiveData + ); + /** * Finding the country from the countryData JSON * `country-region-data` mapping: @@ -157,105 +156,108 @@ const ContactInformation = (props: Props) => { Billing Contact - { - history.push('/account/billing/edit'); - handleEditDrawerOpen(); - }} - tooltipText={getRestrictedResourceText({ - includeContactInfo: false, - isChildUser, - resourceType: 'Account', - })} - data-testid="edit-contact-info" - disableFocusRipple - disableRipple - disableTouchRipple - disabled={isReadOnly} - > - {EDIT_BILLING_CONTACT} - + + {!isContactInfoMasked && ( + { + history.push('/account/billing/edit'); + handleEditDrawerOpen(); + }} + tooltipText={getRestrictedResourceText({ + includeContactInfo: false, + isChildUser, + resourceType: 'Account', + })} + data-testid="edit-contact-info" + disableFocusRipple + disableRipple + disableTouchRipple + disabled={isReadOnly} + > + {EDIT_BILLING_CONTACT} + + )} + {preferences?.maskSensitiveData && ( + setIsContactInfoMasked(!isContactInfoMasked)} + sx={{ marginLeft: !isContactInfoMasked ? 2 : 0 }} + > + {isContactInfoMasked ? ( + + ) : ( + + )} + {isContactInfoMasked ? 'Show' : 'Hide'} + + )} + - - - {(firstName || - lastName || - company || - address1 || - address2 || - city || - state || - zip || - country) && ( - - {(firstName || lastName) && ( - + {preferences?.maskSensitiveData && isContactInfoMasked ? ( + + This data is sensitive and hidden for privacy. To unmask all + sensitive data by default, go to{' '} + profile settings. + + ) : ( + + {(firstName || + lastName || + company || + address1 || + address2 || + city || + state || + zip || + country) && ( + + {(firstName || lastName) && ( {firstName} {lastName} - - )} - {company && ( - - <> - {' '} - - {company} - - - - )} - {(address1 || address2 || city || state || zip || country) && ( - + )} + {company && ( + + {company} + + )} + {(address1 || address2 || city || state || zip || country) && ( <> {address1} {address2} - - )} - + )} {city} {city && state && ','} {state} {zip} - - {countryName} - - - )} - - + + )} + {email} - - {phone && ( - + {phone && ( {phone} - - )} - {taxId && ( - + )} + {taxId && ( { /> )} - - )} + )} + - + )} { @@ -288,6 +290,4 @@ const ContactInformation = (props: Props) => { /> ); -}; - -export default React.memo(ContactInformation); +}); diff --git a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/index.ts b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/index.ts deleted file mode 100644 index d770e30c1c3..00000000000 --- a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ContactInformation'; diff --git a/packages/manager/src/features/Linodes/LinodesLanding/IPAddress.test.tsx b/packages/manager/src/features/Linodes/LinodesLanding/IPAddress.test.tsx index 3431e9ba257..eb41b8cbc7f 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/IPAddress.test.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/IPAddress.test.tsx @@ -130,7 +130,7 @@ describe('IPAddress masked', () => { /> ); - const visibilityToggles = getAllByTestId('VisibilityIcon'); + const visibilityToggles = getAllByTestId('VisibilityTooltip'); // First IP address should be masked expect(getAllByText('•••••••••••••••')[0]).toBeVisible(); @@ -166,7 +166,7 @@ describe('IPAddress masked', () => { /> ); - const visibilityToggles = getAllByTestId('VisibilityIcon'); + const visibilityToggles = getAllByTestId('VisibilityTooltip'); // First IP address should be masked but visible expect(getAllByText('•••••••••••••••')[0]).toBeVisible(); diff --git a/packages/ui/src/components/VisibilityTooltip/VisibilityTooltip.tsx b/packages/ui/src/components/VisibilityTooltip/VisibilityTooltip.tsx index a6d43e97d94..4ab51ae43d5 100644 --- a/packages/ui/src/components/VisibilityTooltip/VisibilityTooltip.tsx +++ b/packages/ui/src/components/VisibilityTooltip/VisibilityTooltip.tsx @@ -1,5 +1,6 @@ -import VisibilityIcon from '@mui/icons-material/Visibility'; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import VisibilityShowIcon from 'src/assets/icons/visibilityShow.svg'; +import VisibilityHideIcon from 'src/assets/icons/visibilityHide.svg'; + import { styled } from '@mui/material/styles'; import React from 'react'; @@ -37,12 +38,13 @@ export const VisibilityTooltip = (props: Props) => { title={!isVisible ? 'Show' : 'Hide'} disableInteractive placement={placement ?? 'top'} + data-testid="VisibilityTooltip" > {!isVisible ? ( - + ) : ( - + )} @@ -53,13 +55,15 @@ const StyledToggleButton = styled(IconButton, { label: 'StyledToggleButton', })(({ theme }) => ({ '& svg': { - color: theme.palette.grey[500], - fontSize: '0.875rem', + '& path': { + stroke: theme.palette.grey[500], + }, }, '& svg:hover': { - color: theme.palette.primary.main, + '& path': { + stroke: theme.palette.primary.main, + }, }, - fontSize: '0.875rem', marginLeft: theme.spacing(), minHeight: 'auto', minWidth: 'auto', From 18b2bf91d8e6816e6a8af80585fb603dcd602836 Mon Sep 17 00:00:00 2001 From: carrillo-erik <119514965+carrillo-erik@users.noreply.github.com> Date: Thu, 21 Nov 2024 02:23:59 -0800 Subject: [PATCH 13/92] fix: [M3-8787] - Fix Alignment for Backup Label in Add-ons Panel (#11160) * fix: [M3-8787] - Fix Alignment for Backup Label in Add-ons Panel * Add changeset * Implement changes after speaking with UX * Final touches to make UI look uniform --- .../manager/.changeset/pr-11160-fixed-1729811033133.md | 5 +++++ .../src/features/Linodes/LinodeCreate/Addons/Backups.tsx | 9 ++++++--- .../features/Linodes/LinodeCreate/Addons/PrivateIP.tsx | 9 ++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 packages/manager/.changeset/pr-11160-fixed-1729811033133.md diff --git a/packages/manager/.changeset/pr-11160-fixed-1729811033133.md b/packages/manager/.changeset/pr-11160-fixed-1729811033133.md new file mode 100644 index 00000000000..81ae4456fa6 --- /dev/null +++ b/packages/manager/.changeset/pr-11160-fixed-1729811033133.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Alignment for Backup Label in Add-ons Panel ([#11160](https://github.com/linode/manager/pull/11160)) diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx index 7cd4de48dcd..83fff33d5f9 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx @@ -69,9 +69,11 @@ export const Backups = () => { label={ - Backups + + Backups + {backupsMonthlyPrice && ( - + per month )} @@ -87,7 +89,7 @@ export const Backups = () => { variant="warning" /> )} - + {isAccountBackupsEnabled ? ( You have enabled automatic backups for your account. This Linode @@ -108,6 +110,7 @@ export const Backups = () => { control={} data-testid="backups" onChange={field.onChange} + sx={{ alignItems: 'start' }} /> ); }; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx index 742784a531e..6366c01a554 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx @@ -34,9 +34,11 @@ export const PrivateIP = () => { return ( - Private IP - + + + Private IP + + Use Private IP for a backend node to a NodeBalancer. Use VPC instead for private communication between your Linodes. @@ -46,6 +48,7 @@ export const PrivateIP = () => { control={} disabled={isDistributedRegionSelected || isLinodeCreateRestricted} onChange={field.onChange} + sx={{ alignItems: 'start' }} /> ); }; From 654d24c4ad97d5ff95629edec9c47886de778815 Mon Sep 17 00:00:00 2001 From: Purvesh Makode Date: Thu, 21 Nov 2024 16:02:30 +0530 Subject: [PATCH 14/92] feat: [M3-8741] - Add linter rules for common pr feedback points (#11258) * Add linter rules for common pr feedback points * Add linter rules in ui package * Add linter rules in api-v4 * Update eslint rules in validation pkg * refactor rules and add perfectionist rules to ui package * Added changeset: Linter rules for common pr feedback points * Added changeset: Linter rules for common pr feedback points * Added changeset: Linter rules for common pr feedback points * Remove existing broken camelCase rule --- .../pr-11258-added-1731996671316.md | 5 + packages/api-v4/.eslintrc.json | 28 ++--- .../pr-11258-added-1731996833931.md | 5 + packages/manager/.eslintrc.cjs | 5 +- .../pr-11258-added-1731997009185.md | 5 + packages/ui/.eslintrc.json | 100 ++++++++++++------ .../pr-11258-added-1731997101694.md | 5 + packages/validation/.eslintrc.json | 23 +--- 8 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 packages/api-v4/.changeset/pr-11258-added-1731996671316.md create mode 100644 packages/manager/.changeset/pr-11258-added-1731996833931.md create mode 100644 packages/ui/.changeset/pr-11258-added-1731997009185.md create mode 100644 packages/validation/.changeset/pr-11258-added-1731997101694.md diff --git a/packages/api-v4/.changeset/pr-11258-added-1731996671316.md b/packages/api-v4/.changeset/pr-11258-added-1731996671316.md new file mode 100644 index 00000000000..bb3b5c30b18 --- /dev/null +++ b/packages/api-v4/.changeset/pr-11258-added-1731996671316.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Added +--- + +Linter rules for common pr feedback points ([#11258](https://github.com/linode/manager/pull/11258)) diff --git a/packages/api-v4/.eslintrc.json b/packages/api-v4/.eslintrc.json index e8664f86a6f..aa613f8f952 100644 --- a/packages/api-v4/.eslintrc.json +++ b/packages/api-v4/.eslintrc.json @@ -1,20 +1,11 @@ { - "ignorePatterns": [ - "node_modules", - "lib", - "index.js", - "!.eslintrc.js" - ], + "ignorePatterns": ["node_modules", "lib", "index.js", "!.eslintrc.js"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 2020, "warnOnUnsupportedTypeScriptVersion": true }, - "plugins": [ - "@typescript-eslint", - "sonarjs", - "prettier" - ], + "plugins": ["@typescript-eslint", "sonarjs", "prettier"], "extends": [ "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", @@ -38,21 +29,17 @@ "array-callback-return": "error", "no-invalid-this": "off", "no-new-wrappers": "error", - "no-restricted-imports": [ - "error", - "rxjs" - ], + "no-restricted-imports": ["error", "rxjs"], "no-console": "error", "no-undef-init": "off", "radix": "error", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-namespace": "warn", - "@typescript-eslint/camelcase": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-empty-interface": "warn", "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/interface-name-prefix": "off", "sonarjs/cognitive-complexity": "warn", @@ -62,6 +49,7 @@ "sonarjs/no-redundant-jump": "warn", "sonarjs/no-small-switch": "warn", "no-multiple-empty-lines": "error", + "camelcase": ["warn", { "properties": "always" }], "curly": "warn", "sort-keys": "off", "comma-dangle": "off", @@ -74,9 +62,7 @@ }, "overrides": [ { - "files": [ - "*ts" - ], + "files": ["*ts"], "rules": { "@typescript-eslint/ban-types": [ "warn", @@ -97,4 +83,4 @@ } } ] -} \ No newline at end of file +} diff --git a/packages/manager/.changeset/pr-11258-added-1731996833931.md b/packages/manager/.changeset/pr-11258-added-1731996833931.md new file mode 100644 index 00000000000..ddc63206c46 --- /dev/null +++ b/packages/manager/.changeset/pr-11258-added-1731996833931.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Linter rules for common pr feedback points ([#11258](https://github.com/linode/manager/pull/11258)) diff --git a/packages/manager/.eslintrc.cjs b/packages/manager/.eslintrc.cjs index bc0ad624e4a..ae58ca2a7f9 100644 --- a/packages/manager/.eslintrc.cjs +++ b/packages/manager/.eslintrc.cjs @@ -160,13 +160,12 @@ module.exports = { rules: { '@linode/cloud-manager/deprecate-formik': 'warn', '@linode/cloud-manager/no-custom-fontWeight': 'error', - '@typescript-eslint/camelcase': 'off', '@typescript-eslint/consistent-type-imports': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/no-empty-interface': 'warn', - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/no-namespace': 'warn', // this would disallow usage of ! postfix operator on non null types @@ -175,6 +174,7 @@ module.exports = { '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-use-before-define': 'off', 'array-callback-return': 'error', + camelcase: ['warn', { properties: 'always' }], 'comma-dangle': 'off', // Prettier and TS both handle and check for this one // radix: Codacy considers it as an error, i put it here to fix it before push curly: 'warn', @@ -274,6 +274,7 @@ module.exports = { 'react/no-unescaped-entities': 'warn', // requires the definition of proptypes for react components 'react/prop-types': 'off', + 'react/self-closing-comp': 'warn', 'react-hooks/exhaustive-deps': 'warn', 'react-hooks/rules-of-hooks': 'error', 'react-refresh/only-export-components': 'warn', diff --git a/packages/ui/.changeset/pr-11258-added-1731997009185.md b/packages/ui/.changeset/pr-11258-added-1731997009185.md new file mode 100644 index 00000000000..d720a6e1421 --- /dev/null +++ b/packages/ui/.changeset/pr-11258-added-1731997009185.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Added +--- + +Linter rules for common pr feedback points ([#11258](https://github.com/linode/manager/pull/11258)) diff --git a/packages/ui/.eslintrc.json b/packages/ui/.eslintrc.json index c3e7d4466ad..6363fc14afc 100644 --- a/packages/ui/.eslintrc.json +++ b/packages/ui/.eslintrc.json @@ -1,10 +1,5 @@ { - "ignorePatterns": [ - "node_modules", - "lib", - "index.js", - "!.eslintrc.js" - ], + "ignorePatterns": ["node_modules", "lib", "index.js", "!.eslintrc.js"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 2020, @@ -12,6 +7,7 @@ }, "plugins": [ "@typescript-eslint", + "react", "sonarjs", "prettier", "@linode/eslint-plugin-cloud-manager" @@ -20,9 +16,24 @@ "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:sonarjs/recommended", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:perfectionist/recommended-natural" ], "rules": { + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-namespace": "warn", + "@typescript-eslint/consistent-type-imports": "warn", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/interface-name-prefix": "off", + "array-callback-return": "error", + "camelcase": ["warn", { "properties": "always" }], + "comma-dangle": "off", + "curly": "warn", "no-unused-vars": [ "warn", { @@ -36,40 +47,67 @@ "no-throw-literal": "warn", "no-loop-func": "error", "no-await-in-loop": "error", - "array-callback-return": "error", "no-invalid-this": "off", "no-new-wrappers": "error", - "no-restricted-imports": [ - "error", - "rxjs" - ], + "no-restricted-imports": ["error", "rxjs"], "no-console": "error", "no-undef-init": "off", + "no-multiple-empty-lines": "error", + "no-trailing-spaces": "warn", + "no-mixed-requires": "warn", + "object-shorthand": "warn", + // Perfectionist + "perfectionist/sort-array-includes": "warn", + "perfectionist/sort-classes": "warn", + "perfectionist/sort-enums": "warn", + "perfectionist/sort-exports": "warn", + "perfectionist/sort-imports": [ + "warn", + { + "custom-groups": { + "type": { + "react": ["react", "react-*"], + "src": ["src*"] + }, + "value": { + "src": ["src/**/*"] + } + }, + "groups": [ + ["builtin", "libraries", "external"], + ["src", "internal"], + ["parent", "sibling", "index"], + "object", + "unknown", + ["type", "internal-type", "parent-type", "sibling-type", "index-type"] + ], + "newlines-between": "always" + } + ], + "perfectionist/sort-interfaces": "warn", + "perfectionist/sort-jsx-props": "warn", + "perfectionist/sort-map-elements": "warn", + "perfectionist/sort-named-exports": "warn", + "perfectionist/sort-named-imports": "warn", + "perfectionist/sort-object-types": "warn", + "perfectionist/sort-objects": "warn", + "perfectionist/sort-union-types": "warn", + // prettier + "prettier/prettier": "warn", + // radix "radix": "error", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-namespace": "warn", - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-empty-interface": "warn", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/interface-name-prefix": "off", + // react and jsx specific rules + "react/self-closing-comp": "warn", + "react/jsx-no-useless-fragment": "warn", + "react/no-unescaped-entities": "warn", + // sonar "sonarjs/cognitive-complexity": "off", "sonarjs/no-duplicate-string": "warn", "sonarjs/prefer-immediate-return": "warn", "sonarjs/no-identical-functions": "warn", "sonarjs/no-redundant-jump": "warn", "sonarjs/no-small-switch": "warn", - "no-multiple-empty-lines": "error", - "curly": "warn", "sort-keys": "off", - "comma-dangle": "off", - "no-trailing-spaces": "warn", - "no-mixed-requires": "warn", - "spaced-comment": "warn", - "object-shorthand": "warn", - "prettier/prettier": "warn" + "spaced-comment": "warn" } -} \ No newline at end of file +} diff --git a/packages/validation/.changeset/pr-11258-added-1731997101694.md b/packages/validation/.changeset/pr-11258-added-1731997101694.md new file mode 100644 index 00000000000..b4b2248a37d --- /dev/null +++ b/packages/validation/.changeset/pr-11258-added-1731997101694.md @@ -0,0 +1,5 @@ +--- +"@linode/validation": Added +--- + +Linter rules for common pr feedback points ([#11258](https://github.com/linode/manager/pull/11258)) diff --git a/packages/validation/.eslintrc.json b/packages/validation/.eslintrc.json index 3a698171ea9..d82f136b975 100644 --- a/packages/validation/.eslintrc.json +++ b/packages/validation/.eslintrc.json @@ -1,20 +1,11 @@ { - "ignorePatterns": [ - "node_modules", - "lib", - "index.js", - "!.eslintrc.js" - ], + "ignorePatterns": ["node_modules", "lib", "index.js", "!.eslintrc.js"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 2020, "warnOnUnsupportedTypeScriptVersion": true }, - "plugins": [ - "@typescript-eslint", - "sonarjs", - "prettier" - ], + "plugins": ["@typescript-eslint", "sonarjs", "prettier"], "extends": [ "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", @@ -38,21 +29,17 @@ "array-callback-return": "error", "no-invalid-this": "off", "no-new-wrappers": "error", - "no-restricted-imports": [ - "error", - "rxjs" - ], + "no-restricted-imports": ["error", "rxjs"], "no-console": "error", "no-undef-init": "off", "radix": "error", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-namespace": "warn", - "@typescript-eslint/camelcase": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-empty-interface": "warn", "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/interface-name-prefix": "off", "sonarjs/cognitive-complexity": "warn", @@ -71,4 +58,4 @@ "object-shorthand": "warn", "prettier/prettier": "warn" } -} \ No newline at end of file +} From eb4b0e7802002dbcfa07969b96469d2348f0e499 Mon Sep 17 00:00:00 2001 From: hasyed-akamai Date: Thu, 21 Nov 2024 16:28:54 +0530 Subject: [PATCH 15/92] test: [M3-8542] - Add unit tests for `CopyableTextField` component (#11268) * test: [M3-8542] - Add unit tests for CopyableTextField component * Added changeset: unit test cases for `CopyableTextField` component * Add `renderWithTheme` directly in each test case for consistency --- .../pr-11268-added-1731665724499.md | 5 ++ .../CopyableTextField.test.tsx | 87 +++++++++++++++++++ .../CopyableTextField/CopyableTextField.tsx | 2 +- 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/manager/.changeset/pr-11268-added-1731665724499.md create mode 100644 packages/manager/src/components/CopyableTextField/CopyableTextField.test.tsx diff --git a/packages/manager/.changeset/pr-11268-added-1731665724499.md b/packages/manager/.changeset/pr-11268-added-1731665724499.md new file mode 100644 index 00000000000..ce947fc48c3 --- /dev/null +++ b/packages/manager/.changeset/pr-11268-added-1731665724499.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +unit test cases for `CopyableTextField` component ([#11268](https://github.com/linode/manager/pull/11268)) diff --git a/packages/manager/src/components/CopyableTextField/CopyableTextField.test.tsx b/packages/manager/src/components/CopyableTextField/CopyableTextField.test.tsx new file mode 100644 index 00000000000..0c849c41cbb --- /dev/null +++ b/packages/manager/src/components/CopyableTextField/CopyableTextField.test.tsx @@ -0,0 +1,87 @@ +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { downloadFile } from 'src/utilities/downloadFile'; +import { renderWithTheme } from 'src/utilities/testHelpers'; + +import { CopyableTextField } from './CopyableTextField'; + +import type { CopyableTextFieldProps } from './CopyableTextField'; + +vi.mock('src/utilities/downloadFile', () => ({ + downloadFile: vi.fn(), +})); + +const mockLabel = 'label'; +const mockValue = 'Text to Copy'; +const defaultProps: CopyableTextFieldProps = { + label: mockLabel, + value: mockValue, +}; + +describe('CopyableTextField', () => { + it('should render label and CopyableText', () => { + const { getByRole } = renderWithTheme( + + ); + + const textbox = getByRole('textbox', { name: mockLabel }); + + expect(textbox).toBeVisible(); + expect(textbox).toHaveAttribute( + 'value', + expect.stringContaining(mockValue) + ); + }); + + it('should render the copy icon button and tooltip and allow user to copy the Text', async () => { + const { findByRole, getByRole } = renderWithTheme( + + ); + + const copyIcon = getByRole('button', { + name: `Copy ${mockValue} to clipboard`, + }); + + await userEvent.hover(copyIcon); + const copyTooltip = await findByRole('tooltip'); + expect(copyTooltip).toBeInTheDocument(); + expect(copyTooltip).toHaveTextContent('Copy'); + + await userEvent.click(copyIcon); + expect(copyIcon.getAttribute('data-qa-tooltip')).toBe('Copied!'); + }); + + it('should render the download icon button and tooltip and allow user to download the Text File', async () => { + const { findByRole, getByRole } = renderWithTheme( + + ); + + const downloadIcon = getByRole('button', { name: `Download ${mockValue}` }); + + await userEvent.hover(downloadIcon); + const copyTooltip = await findByRole('tooltip'); + expect(copyTooltip).toBeInTheDocument(); + expect(copyTooltip).toHaveTextContent('Download'); + + await userEvent.click(downloadIcon); + expect(downloadFile).toHaveBeenCalledTimes(1); + expect(downloadFile).toHaveBeenCalledWith(`${mockLabel}.txt`, mockValue); + }); + + it('should not render any icon when hideIcons is true', () => { + const { queryByRole } = renderWithTheme( + + ); + + const copyIcon = queryByRole('button', { + name: `Copy ${mockValue} to clipboard`, + }); + const downloadIcon = queryByRole('button', { + name: `Download ${mockValue}`, + }); + + expect(copyIcon).not.toBeInTheDocument(); + expect(downloadIcon).not.toBeInTheDocument(); + }); +}); diff --git a/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx b/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx index 7d3f057975f..e09302c54aa 100644 --- a/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx +++ b/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx @@ -10,7 +10,7 @@ import { DownloadTooltip } from '../DownloadTooltip'; import type { CopyTooltipProps } from 'src/components/CopyTooltip/CopyTooltip'; import type { TextFieldProps } from 'src/components/TextField'; -interface CopyableTextFieldProps extends TextFieldProps { +export interface CopyableTextFieldProps extends TextFieldProps { /** * Optional props that are passed to the underlying CopyTooltip component */ From f7a2793e6feb08e3f2d25929d49d3afd0f29bacf Mon Sep 17 00:00:00 2001 From: hasyed-akamai Date: Thu, 21 Nov 2024 16:39:54 +0530 Subject: [PATCH 16/92] refactor: [M3-8644] - Move `Checkbox` to `linode/ui` package (#11279) * refactor: [M3-8644] - Move `Checkbox` to `linode/ui` package * Added changeset: Move `Checkbox` from `linode/manager` to `linode/ui` package * Update Changeset description Co-authored-by: Purvesh Makode * Added changeset: Move `Checkbox` from `manager` to `ui` package * Change import for TooltipIcon --------- Co-authored-by: Purvesh Makode --- .../.changeset/pr-11279-removed-1732009349912.md | 5 +++++ .../src/components/AccessPanel/UserSSHKeyPanel.tsx | 3 +-- .../manager/src/components/Encryption/Encryption.tsx | 3 +-- .../src/components/FormControlLabel.stories.tsx | 3 +-- packages/manager/src/components/FormGroup.stories.tsx | 5 +++-- .../SelectableTableRow/SelectableTableRow.tsx | 2 +- .../Account/Agreements/EUAgreementCheckbox.tsx | 3 +-- packages/manager/src/features/Betas/BetaSignup.tsx | 3 +-- .../DatabaseSettingsSuspendClusterDialog.tsx | 3 +-- .../EntityTransfersCreate/TransferTable.styles.ts | 2 +- .../EntityTransfersLanding/ConfirmTransferDialog.tsx | 3 +-- .../features/Images/ImagesCreate/CreateImageTab.tsx | 11 +++++++++-- .../src/features/Images/ImagesCreate/ImageUpload.tsx | 3 +-- .../KubernetesClusterDetail/UpgradeClusterDialog.tsx | 3 +-- .../src/features/Linodes/CloneLanding/Configs.tsx | 6 +++--- .../src/features/Linodes/CloneLanding/Disks.tsx | 6 +++--- .../features/Linodes/LinodeCreate/Addons/Backups.tsx | 3 +-- .../Linodes/LinodeCreate/Addons/PrivateIP.tsx | 3 +-- .../Linodes/LinodeCreate/FirewallAuthorization.tsx | 2 +- .../src/features/Linodes/LinodeCreate/VPC/VPC.tsx | 11 +++++++++-- .../LinodeBackup/RestoreToLinodeDrawer.tsx | 3 +-- .../LinodesDetail/LinodeRebuild/RebuildFromImage.tsx | 3 +-- .../LinodesDetail/LinodeResize/LinodeResize.tsx | 2 +- .../Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx | 3 +-- .../features/Linodes/MigrateLinode/CautionNotice.tsx | 3 +-- .../Profile/OAuthClients/CreateOAuthClientDrawer.tsx | 3 +-- .../Profile/OAuthClients/EditOAuthClientDrawer.tsx | 3 +-- .../VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx | 10 ++++++++-- .../src/features/Volumes/AttachVolumeDrawer.tsx | 3 +-- .../src/features/Volumes/CloneVolumeDrawer.tsx | 3 +-- .../manager/src/features/Volumes/EditVolumeDrawer.tsx | 3 +-- .../ui/.changeset/pr-11279-added-1731998133269.md | 5 +++++ .../{manager => ui}/src/assets/icons/checkbox.svg | 0 .../src/assets/icons/checkboxChecked.svg | 0 packages/ui/src/assets/icons/index.ts | 2 ++ .../src/components/Checkbox}/Checkbox.stories.tsx | 0 .../src/components/Checkbox}/Checkbox.tsx | 9 +++++---- packages/ui/src/components/Checkbox/index.ts | 1 + packages/ui/src/components/index.ts | 1 + 39 files changed, 78 insertions(+), 62 deletions(-) create mode 100644 packages/manager/.changeset/pr-11279-removed-1732009349912.md create mode 100644 packages/ui/.changeset/pr-11279-added-1731998133269.md rename packages/{manager => ui}/src/assets/icons/checkbox.svg (100%) rename packages/{manager => ui}/src/assets/icons/checkboxChecked.svg (100%) rename packages/{manager/src/components => ui/src/components/Checkbox}/Checkbox.stories.tsx (100%) rename packages/{manager/src/components => ui/src/components/Checkbox}/Checkbox.tsx (92%) create mode 100644 packages/ui/src/components/Checkbox/index.ts diff --git a/packages/manager/.changeset/pr-11279-removed-1732009349912.md b/packages/manager/.changeset/pr-11279-removed-1732009349912.md new file mode 100644 index 00000000000..88c3020d898 --- /dev/null +++ b/packages/manager/.changeset/pr-11279-removed-1732009349912.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Removed +--- + +Move `Checkbox` from `manager` to `ui` package ([#11279](https://github.com/linode/manager/pull/11279)) diff --git a/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.tsx b/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.tsx index d059e88beea..32d08d014cd 100644 --- a/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.tsx +++ b/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.tsx @@ -1,9 +1,8 @@ -import { Button } from '@linode/ui'; +import { Button, Checkbox } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import * as React from 'react'; import { makeStyles } from 'tss-react/mui'; -import { Checkbox } from 'src/components/Checkbox'; import { Table } from 'src/components/Table'; import { TableBody } from 'src/components/TableBody'; import { TableCell } from 'src/components/TableCell'; diff --git a/packages/manager/src/components/Encryption/Encryption.tsx b/packages/manager/src/components/Encryption/Encryption.tsx index fb55ce2c2ff..c8919e78868 100644 --- a/packages/manager/src/components/Encryption/Encryption.tsx +++ b/packages/manager/src/components/Encryption/Encryption.tsx @@ -1,8 +1,7 @@ -import { Box, Notice } from '@linode/ui'; +import { Box, Checkbox, Notice } from '@linode/ui'; import { List, ListItem } from '@mui/material'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { Typography } from 'src/components/Typography'; export interface EncryptionProps { diff --git a/packages/manager/src/components/FormControlLabel.stories.tsx b/packages/manager/src/components/FormControlLabel.stories.tsx index 101de8d7b5a..f306160b57c 100644 --- a/packages/manager/src/components/FormControlLabel.stories.tsx +++ b/packages/manager/src/components/FormControlLabel.stories.tsx @@ -1,7 +1,6 @@ -import { Radio } from '@linode/ui'; +import { Checkbox, Radio } from '@linode/ui'; import React from 'react'; -import { Checkbox } from './Checkbox'; import { FormControlLabel } from './FormControlLabel'; import { Toggle } from './Toggle/Toggle'; diff --git a/packages/manager/src/components/FormGroup.stories.tsx b/packages/manager/src/components/FormGroup.stories.tsx index c8fa0dfe645..cbedd10ec28 100644 --- a/packages/manager/src/components/FormGroup.stories.tsx +++ b/packages/manager/src/components/FormGroup.stories.tsx @@ -1,10 +1,11 @@ -import { Meta, StoryObj } from '@storybook/react'; +import { Checkbox } from '@linode/ui'; import React from 'react'; -import { Checkbox } from './Checkbox'; import { FormControlLabel } from './FormControlLabel'; import { FormGroup } from './FormGroup'; +import type { Meta, StoryObj } from '@storybook/react'; + const meta: Meta = { component: FormGroup, title: 'Components/Form/FormGroup', diff --git a/packages/manager/src/components/SelectableTableRow/SelectableTableRow.tsx b/packages/manager/src/components/SelectableTableRow/SelectableTableRow.tsx index caa8d3c345b..c76965c7422 100644 --- a/packages/manager/src/components/SelectableTableRow/SelectableTableRow.tsx +++ b/packages/manager/src/components/SelectableTableRow/SelectableTableRow.tsx @@ -1,7 +1,7 @@ +import { Checkbox } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { TableCell } from 'src/components/TableCell'; import { TableRow } from 'src/components/TableRow'; diff --git a/packages/manager/src/features/Account/Agreements/EUAgreementCheckbox.tsx b/packages/manager/src/features/Account/Agreements/EUAgreementCheckbox.tsx index 17eced44109..5762cb9651e 100644 --- a/packages/manager/src/features/Account/Agreements/EUAgreementCheckbox.tsx +++ b/packages/manager/src/features/Account/Agreements/EUAgreementCheckbox.tsx @@ -1,8 +1,7 @@ -import { Box } from '@linode/ui'; +import { Box, Checkbox } from '@linode/ui'; import { useTheme } from '@mui/material'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { Link } from 'src/components/Link'; import { Typography } from 'src/components/Typography'; diff --git a/packages/manager/src/features/Betas/BetaSignup.tsx b/packages/manager/src/features/Betas/BetaSignup.tsx index 1c38b97a867..b0d7088edce 100644 --- a/packages/manager/src/features/Betas/BetaSignup.tsx +++ b/packages/manager/src/features/Betas/BetaSignup.tsx @@ -1,4 +1,4 @@ -import { CircleProgress, Paper, Stack } from '@linode/ui'; +import { Checkbox, CircleProgress, Paper, Stack } from '@linode/ui'; import { createLazyRoute, useNavigate, @@ -8,7 +8,6 @@ import { useSnackbar } from 'notistack'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { HighlightedMarkdown } from 'src/components/HighlightedMarkdown/HighlightedMarkdown'; import { LandingHeader } from 'src/components/LandingHeader/LandingHeader'; import { NotFound } from 'src/components/NotFound'; diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsSuspendClusterDialog.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsSuspendClusterDialog.tsx index a9c8aa0b7d2..8614e5578d7 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsSuspendClusterDialog.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsSuspendClusterDialog.tsx @@ -1,10 +1,9 @@ -import { Notice } from '@linode/ui'; +import { Checkbox, Notice } from '@linode/ui'; import { useSnackbar } from 'notistack'; import * as React from 'react'; import { useHistory } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { Typography } from 'src/components/Typography'; import { useSuspendDatabaseMutation } from 'src/queries/databases/databases'; diff --git a/packages/manager/src/features/EntityTransfers/EntityTransfersCreate/TransferTable.styles.ts b/packages/manager/src/features/EntityTransfers/EntityTransfersCreate/TransferTable.styles.ts index ab6ca313a15..6c5dcdaa1bd 100644 --- a/packages/manager/src/features/EntityTransfers/EntityTransfersCreate/TransferTable.styles.ts +++ b/packages/manager/src/features/EntityTransfers/EntityTransfersCreate/TransferTable.styles.ts @@ -1,6 +1,6 @@ +import { Checkbox } from '@linode/ui'; import { styled } from '@mui/material/styles'; -import { Checkbox } from 'src/components/Checkbox'; import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField'; import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; import { Table } from 'src/components/Table'; diff --git a/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/ConfirmTransferDialog.tsx b/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/ConfirmTransferDialog.tsx index fd69f869c2a..9e69baf3102 100644 --- a/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/ConfirmTransferDialog.tsx +++ b/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/ConfirmTransferDialog.tsx @@ -1,10 +1,9 @@ import { acceptEntityTransfer } from '@linode/api-v4/lib/entity-transfers'; -import { CircleProgress, Notice } from '@linode/ui'; +import { Checkbox, CircleProgress, Notice } from '@linode/ui'; import { useQueryClient } from '@tanstack/react-query'; import { useSnackbar } from 'notistack'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; import { diff --git a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx index 2e774e1dea3..0f485b543f6 100644 --- a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx +++ b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx @@ -1,5 +1,13 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Button, Notice, Paper, Stack, TooltipIcon } from '@linode/ui'; +import { + Box, + Button, + Checkbox, + Notice, + Paper, + Stack, + TooltipIcon, +} from '@linode/ui'; import { createImageSchema } from '@linode/validation'; import { useSnackbar } from 'notistack'; import * as React from 'react'; @@ -7,7 +15,6 @@ import { Controller, useForm } from 'react-hook-form'; import { useHistory, useLocation } from 'react-router-dom'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { Checkbox } from 'src/components/Checkbox'; import { DISK_ENCRYPTION_IMAGES_CAVEAT_COPY } from 'src/components/Encryption/constants'; import { useIsDiskEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; import { Link } from 'src/components/Link'; diff --git a/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx b/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx index d4ad752700f..937897314c5 100644 --- a/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx +++ b/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Button, Notice, Paper, Stack } from '@linode/ui'; +import { Box, Button, Checkbox, Notice, Paper, Stack } from '@linode/ui'; import { useSnackbar } from 'notistack'; import React, { useState } from 'react'; import { flushSync } from 'react-dom'; @@ -8,7 +8,6 @@ import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { Link } from 'src/components/Link'; import { Prompt } from 'src/components/Prompt/Prompt'; diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/UpgradeClusterDialog.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/UpgradeClusterDialog.tsx index 87f00290cad..238624a8b18 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/UpgradeClusterDialog.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/UpgradeClusterDialog.tsx @@ -1,10 +1,9 @@ -import { CircleProgress, Notice } from '@linode/ui'; +import { Checkbox, CircleProgress, Notice } from '@linode/ui'; import { useSnackbar } from 'notistack'; import * as React from 'react'; import { makeStyles } from 'tss-react/mui'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { Typography } from 'src/components/Typography'; import { diff --git a/packages/manager/src/features/Linodes/CloneLanding/Configs.tsx b/packages/manager/src/features/Linodes/CloneLanding/Configs.tsx index cbfc351b636..1248cc00d94 100644 --- a/packages/manager/src/features/Linodes/CloneLanding/Configs.tsx +++ b/packages/manager/src/features/Linodes/CloneLanding/Configs.tsx @@ -1,7 +1,6 @@ -import { Config } from '@linode/api-v4/lib/linodes'; +import { Checkbox } from '@linode/ui'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import Paginate from 'src/components/Paginate'; import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; import { Table } from 'src/components/Table'; @@ -10,7 +9,8 @@ import { TableCell } from 'src/components/TableCell'; import { TableRow } from 'src/components/TableRow'; import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty'; -import { ConfigSelection } from './utilities'; +import type { ConfigSelection } from './utilities'; +import type { Config } from '@linode/api-v4/lib/linodes'; export interface ConfigsProps { configSelection: ConfigSelection; diff --git a/packages/manager/src/features/Linodes/CloneLanding/Disks.tsx b/packages/manager/src/features/Linodes/CloneLanding/Disks.tsx index 9a118c043cb..07b96272aa5 100644 --- a/packages/manager/src/features/Linodes/CloneLanding/Disks.tsx +++ b/packages/manager/src/features/Linodes/CloneLanding/Disks.tsx @@ -1,9 +1,8 @@ -import { Disk } from '@linode/api-v4/lib/linodes'; +import { Checkbox } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import { intersection, pathOr } from 'ramda'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import Paginate from 'src/components/Paginate'; import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; import { Table } from 'src/components/Table'; @@ -13,7 +12,8 @@ import { TableHead } from 'src/components/TableHead'; import { TableRow } from 'src/components/TableRow'; import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty'; -import { DiskSelection } from './utilities'; +import type { DiskSelection } from './utilities'; +import type { Disk } from '@linode/api-v4/lib/linodes'; export interface DisksProps { diskSelection: DiskSelection; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx index 83fff33d5f9..ba7ee89eb91 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx @@ -1,8 +1,7 @@ -import { Notice, Stack } from '@linode/ui'; +import { Checkbox, Notice, Stack } from '@linode/ui'; import React, { useMemo } from 'react'; import { useController, useFormContext, useWatch } from 'react-hook-form'; -import { Checkbox } from 'src/components/Checkbox'; import { Currency } from 'src/components/Currency'; import { DISK_ENCRYPTION_BACKUPS_CAVEAT_COPY } from 'src/components/Encryption/constants'; import { FormControlLabel } from 'src/components/FormControlLabel'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx index 6366c01a554..164100b7885 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx @@ -1,8 +1,7 @@ -import { Stack } from '@linode/ui'; +import { Checkbox, Stack } from '@linode/ui'; import React, { useMemo } from 'react'; import { useController, useWatch } from 'react-hook-form'; -import { Checkbox } from 'src/components/Checkbox'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Typography } from 'src/components/Typography'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/FirewallAuthorization.tsx b/packages/manager/src/features/Linodes/LinodeCreate/FirewallAuthorization.tsx index 418bbed7792..e0f673b34be 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/FirewallAuthorization.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/FirewallAuthorization.tsx @@ -1,8 +1,8 @@ +import { Checkbox } from '@linode/ui'; import React from 'react'; import { useController, useFormContext } from 'react-hook-form'; import { AkamaiBanner } from 'src/components/AkamaiBanner/AkamaiBanner'; -import { Checkbox } from 'src/components/Checkbox'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { useFlags } from 'src/hooks/useFlags'; import { isNotNullOrUndefined } from 'src/utilities/nullOrUndefined'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx index fc43808739b..5d754c64ea0 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx @@ -1,9 +1,16 @@ -import { Box, Divider, Notice, Paper, Stack, TooltipIcon } from '@linode/ui'; +import { + Box, + Checkbox, + Divider, + Notice, + Paper, + Stack, + TooltipIcon, +} from '@linode/ui'; import React, { useState } from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { Checkbox } from 'src/components/Checkbox'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Link } from 'src/components/Link'; import { LinkButton } from 'src/components/LinkButton'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/RestoreToLinodeDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/RestoreToLinodeDrawer.tsx index 027af464c2b..ee913dcc1af 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/RestoreToLinodeDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/RestoreToLinodeDrawer.tsx @@ -1,11 +1,10 @@ -import { FormControl, FormHelperText, Notice } from '@linode/ui'; +import { Checkbox, FormControl, FormHelperText, Notice } from '@linode/ui'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { useEventsPollingActions } from 'src/queries/events/events'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/RebuildFromImage.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/RebuildFromImage.tsx index 87bd9d7c402..76c162ac447 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/RebuildFromImage.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/RebuildFromImage.tsx @@ -1,5 +1,5 @@ import { rebuildLinode } from '@linode/api-v4'; -import { Box, Divider } from '@linode/ui'; +import { Box, Checkbox, Divider } from '@linode/ui'; import { RebuildLinodeSchema } from '@linode/validation/lib/linodes.schema'; import Grid from '@mui/material/Unstable_Grid2'; import { Formik } from 'formik'; @@ -9,7 +9,6 @@ import * as React from 'react'; import { useLocation } from 'react-router-dom'; import { AccessPanel } from 'src/components/AccessPanel/AccessPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { ImageSelect } from 'src/components/ImageSelect/ImageSelect'; import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm'; import { Typography } from 'src/components/Typography'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.tsx index 2b9eb452472..2e0295c3cd1 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.tsx @@ -1,6 +1,7 @@ import { Box, Button, + Checkbox, CircleProgress, Divider, Notice, @@ -11,7 +12,6 @@ import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { Dialog } from 'src/components/Dialog/Dialog'; import { ErrorMessage } from 'src/components/ErrorMessage'; import { Link } from 'src/components/Link'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx index f417d3cd7f7..a0b3993cba7 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx @@ -1,10 +1,9 @@ -import { Box, Paper, Stack, TooltipIcon } from '@linode/ui'; +import { Box, Checkbox, Paper, Stack, TooltipIcon } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { Checkbox } from 'src/components/Checkbox'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; diff --git a/packages/manager/src/features/Linodes/MigrateLinode/CautionNotice.tsx b/packages/manager/src/features/Linodes/MigrateLinode/CautionNotice.tsx index 440ca5272b3..6b1f0501a3b 100644 --- a/packages/manager/src/features/Linodes/MigrateLinode/CautionNotice.tsx +++ b/packages/manager/src/features/Linodes/MigrateLinode/CautionNotice.tsx @@ -1,9 +1,8 @@ -import { Notice } from '@linode/ui'; +import { Checkbox, Notice } from '@linode/ui'; import { styled, useTheme } from '@mui/material/styles'; import { DateTime } from 'luxon'; import * as React from 'react'; -import { Checkbox } from 'src/components/Checkbox'; import { Link } from 'src/components/Link'; import { Typography } from 'src/components/Typography'; import { API_MAX_PAGE_SIZE } from 'src/constants'; diff --git a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx index 47c80675997..452ed429c3b 100644 --- a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx +++ b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx @@ -1,9 +1,8 @@ -import { FormControl, Notice } from '@linode/ui'; +import { Checkbox, FormControl, Notice } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { TextField } from 'src/components/TextField'; diff --git a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx index aa0d637bdfc..ee51e7b3c94 100644 --- a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx +++ b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx @@ -1,9 +1,8 @@ -import { FormControl, Notice } from '@linode/ui'; +import { Checkbox, FormControl, Notice } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { TextField } from 'src/components/TextField'; diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx index d4d5593de2d..2ec401a49fd 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx @@ -1,11 +1,17 @@ import { appendConfigInterface } from '@linode/api-v4'; -import { Box, Button, FormHelperText, Notice, TooltipIcon } from '@linode/ui'; +import { + Box, + Button, + Checkbox, + FormHelperText, + Notice, + TooltipIcon, +} from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import { useFormik } from 'formik'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { Checkbox } from 'src/components/Checkbox'; import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; diff --git a/packages/manager/src/features/Volumes/AttachVolumeDrawer.tsx b/packages/manager/src/features/Volumes/AttachVolumeDrawer.tsx index 6a2d0ffa4b5..37e29f2e1aa 100644 --- a/packages/manager/src/features/Volumes/AttachVolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/AttachVolumeDrawer.tsx @@ -1,4 +1,4 @@ -import { Box, FormHelperText, Notice } from '@linode/ui'; +import { Box, Checkbox, FormHelperText, Notice } from '@linode/ui'; import { styled } from '@mui/material/styles'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { number, object } from 'yup'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { BLOCK_STORAGE_ENCRYPTION_SETTING_IMMUTABLE_COPY } from 'src/components/Encryption/constants'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; diff --git a/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx b/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx index 5a8a21b59d9..96bf7ebd401 100644 --- a/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx @@ -1,10 +1,9 @@ -import { Box, Notice } from '@linode/ui'; +import { Box, Checkbox, Notice } from '@linode/ui'; import { CloneVolumeSchema } from '@linode/validation/lib/volumes.schema'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { BLOCK_STORAGE_CLONING_INHERITANCE_CAVEAT } from 'src/components/Encryption/constants'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; diff --git a/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx b/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx index 84ba1c888dc..4e700f58bfb 100644 --- a/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx @@ -1,10 +1,9 @@ -import { Box, Notice } from '@linode/ui'; +import { Box, Checkbox, Notice } from '@linode/ui'; import { UpdateVolumeSchema } from '@linode/validation'; import { useFormik } from 'formik'; import React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { Checkbox } from 'src/components/Checkbox'; import { Drawer } from 'src/components/Drawer'; import { BLOCK_STORAGE_ENCRYPTION_SETTING_IMMUTABLE_COPY } from 'src/components/Encryption/constants'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; diff --git a/packages/ui/.changeset/pr-11279-added-1731998133269.md b/packages/ui/.changeset/pr-11279-added-1731998133269.md new file mode 100644 index 00000000000..650e2d9fdf0 --- /dev/null +++ b/packages/ui/.changeset/pr-11279-added-1731998133269.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Added +--- + +Move `Checkbox` from `manager` to `ui` package ([#11279](https://github.com/linode/manager/pull/11279)) diff --git a/packages/manager/src/assets/icons/checkbox.svg b/packages/ui/src/assets/icons/checkbox.svg similarity index 100% rename from packages/manager/src/assets/icons/checkbox.svg rename to packages/ui/src/assets/icons/checkbox.svg diff --git a/packages/manager/src/assets/icons/checkboxChecked.svg b/packages/ui/src/assets/icons/checkboxChecked.svg similarity index 100% rename from packages/manager/src/assets/icons/checkboxChecked.svg rename to packages/ui/src/assets/icons/checkboxChecked.svg diff --git a/packages/ui/src/assets/icons/index.ts b/packages/ui/src/assets/icons/index.ts index 68292d89459..c6e3040446d 100644 --- a/packages/ui/src/assets/icons/index.ts +++ b/packages/ui/src/assets/icons/index.ts @@ -1,5 +1,7 @@ export { default as AlertIcon } from './alert.svg'; export { default as CheckIcon } from './check.svg'; +export { default as CheckboxIcon } from './checkbox.svg'; +export { default as CheckboxCheckedIcon } from './checkboxChecked.svg'; export { default as RadioIcon } from './radio.svg'; export { default as RadioIconRadioed } from './radioRadioed.svg'; export { default as WarningIcon } from './warning.svg'; diff --git a/packages/manager/src/components/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx similarity index 100% rename from packages/manager/src/components/Checkbox.stories.tsx rename to packages/ui/src/components/Checkbox/Checkbox.stories.tsx diff --git a/packages/manager/src/components/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx similarity index 92% rename from packages/manager/src/components/Checkbox.tsx rename to packages/ui/src/components/Checkbox/Checkbox.tsx index ca3ee94a534..f469ea54d6a 100644 --- a/packages/manager/src/components/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -1,11 +1,12 @@ -import { TooltipIcon } from '@linode/ui'; +import { TooltipIcon } from '../TooltipIcon'; import _Checkbox from '@mui/material/Checkbox'; import { styled } from '@mui/material/styles'; import * as React from 'react'; -import CheckboxIcon from 'src/assets/icons/checkbox.svg'; -import CheckboxCheckedIcon from 'src/assets/icons/checkboxChecked.svg'; -import { FormControlLabel } from 'src/components/FormControlLabel'; +import { CheckboxIcon, CheckboxCheckedIcon } from '../../assets/icons'; + +// @TODO: Import from 'ui' package once FormControlLabel is migrated. +import { FormControlLabel } from '@mui/material'; import type { CheckboxProps } from '@mui/material/Checkbox'; import type { SxProps, Theme } from '@mui/material/styles'; diff --git a/packages/ui/src/components/Checkbox/index.ts b/packages/ui/src/components/Checkbox/index.ts new file mode 100644 index 00000000000..f5c939faf48 --- /dev/null +++ b/packages/ui/src/components/Checkbox/index.ts @@ -0,0 +1 @@ +export * from './Checkbox'; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index dcdedbe55d7..c13e48f4ba1 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -2,6 +2,7 @@ export * from './BetaChip'; export * from './Box'; export * from './Button'; export * from './Chip'; +export * from './Checkbox'; export * from './CircleProgress'; export * from './ClickAwayListener'; export * from './Divider'; From 26988f379e8671e94022dc52941441ae6e03a239 Mon Sep 17 00:00:00 2001 From: Purvesh Makode Date: Thu, 21 Nov 2024 19:03:18 +0530 Subject: [PATCH 17/92] refactor: [M3-8910] - Move `H1Header` to `at linode/ui` package (#11283) * Move H1Header to ui package * Added changeset: Move `H1Header` from `manager` to `ui` package * Added changeset: Move `H1Header` from `manager` to `ui` package * update or add todo modularization comments * Update modularization todo comment in Checkbox * Reordering imports --- .../manager/.changeset/pr-11283-removed-1732021380915.md | 5 +++++ .../manager/src/components/Breadcrumb/FinalCrumb.styles.tsx | 2 +- .../manager/src/components/EditableText/EditableText.tsx | 4 +--- packages/manager/src/components/Placeholder/Placeholder.tsx | 3 +-- packages/manager/src/components/StackScript/StackScript.tsx | 3 +-- .../manager/src/features/CancelLanding/CancelLanding.tsx | 3 +-- packages/manager/src/features/Events/EventsLanding.styles.ts | 2 +- packages/manager/src/features/Help/Panels/SearchPanel.tsx | 4 +--- .../Help/SupportSearchLanding/SupportSearchLanding.test.tsx | 2 +- .../Help/SupportSearchLanding/SupportSearchLanding.tsx | 3 +-- packages/manager/src/features/Search/SearchLanding.styles.ts | 3 +-- packages/ui/.changeset/pr-11283-added-1732021489065.md | 5 +++++ packages/ui/src/components/Checkbox/Checkbox.tsx | 2 +- .../{manager => ui}/src/components/H1Header/H1Header.tsx | 4 ++-- packages/ui/src/components/H1Header/index.ts | 1 + packages/ui/src/components/Notice/Notice.tsx | 1 + packages/ui/src/components/Radio/Radio.stories.tsx | 2 +- packages/ui/src/components/Tooltip/Tooltip.stories.tsx | 2 ++ packages/ui/src/components/index.ts | 1 + 19 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 packages/manager/.changeset/pr-11283-removed-1732021380915.md create mode 100644 packages/ui/.changeset/pr-11283-added-1732021489065.md rename packages/{manager => ui}/src/components/H1Header/H1Header.tsx (90%) create mode 100644 packages/ui/src/components/H1Header/index.ts diff --git a/packages/manager/.changeset/pr-11283-removed-1732021380915.md b/packages/manager/.changeset/pr-11283-removed-1732021380915.md new file mode 100644 index 00000000000..e5ad86d403c --- /dev/null +++ b/packages/manager/.changeset/pr-11283-removed-1732021380915.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Removed +--- + +Move `H1Header` from `manager` to `ui` package ([#11283](https://github.com/linode/manager/pull/11283)) diff --git a/packages/manager/src/components/Breadcrumb/FinalCrumb.styles.tsx b/packages/manager/src/components/Breadcrumb/FinalCrumb.styles.tsx index a68c4a18c33..c3f3b964420 100644 --- a/packages/manager/src/components/Breadcrumb/FinalCrumb.styles.tsx +++ b/packages/manager/src/components/Breadcrumb/FinalCrumb.styles.tsx @@ -1,7 +1,7 @@ +import { H1Header } from '@linode/ui'; import { styled } from '@mui/material'; import { EditableText } from 'src/components/EditableText/EditableText'; -import { H1Header } from 'src/components/H1Header/H1Header'; export const StyledDiv = styled('div', { label: 'StyledDiv' })({ display: 'flex', diff --git a/packages/manager/src/components/EditableText/EditableText.tsx b/packages/manager/src/components/EditableText/EditableText.tsx index ed085300a92..326f1e84363 100644 --- a/packages/manager/src/components/EditableText/EditableText.tsx +++ b/packages/manager/src/components/EditableText/EditableText.tsx @@ -1,4 +1,4 @@ -import { Button, ClickAwayListener } from '@linode/ui'; +import { Button, ClickAwayListener, H1Header } from '@linode/ui'; import Check from '@mui/icons-material/Check'; import Close from '@mui/icons-material/Close'; import Edit from '@mui/icons-material/Edit'; @@ -6,8 +6,6 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; -import { H1Header } from 'src/components/H1Header/H1Header'; - import { TextField } from '../TextField'; import type { TextFieldProps } from '../TextField'; diff --git a/packages/manager/src/components/Placeholder/Placeholder.tsx b/packages/manager/src/components/Placeholder/Placeholder.tsx index b0c728e268b..842df956b49 100644 --- a/packages/manager/src/components/Placeholder/Placeholder.tsx +++ b/packages/manager/src/components/Placeholder/Placeholder.tsx @@ -1,9 +1,8 @@ -import { Button, fadeIn } from '@linode/ui'; +import { Button, H1Header, fadeIn } from '@linode/ui'; import { styled, useTheme } from '@mui/material/styles'; import * as React from 'react'; import LinodeIcon from 'src/assets/addnewmenu/linode.svg'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { Typography } from 'src/components/Typography'; import { TransferDisplay } from '../TransferDisplay/TransferDisplay'; diff --git a/packages/manager/src/components/StackScript/StackScript.tsx b/packages/manager/src/components/StackScript/StackScript.tsx index b21f00b1eb6..2b832e8b1db 100644 --- a/packages/manager/src/components/StackScript/StackScript.tsx +++ b/packages/manager/src/components/StackScript/StackScript.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Chip, Divider, TooltipIcon } from '@linode/ui'; +import { Box, Button, Chip, Divider, H1Header, TooltipIcon } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import * as React from 'react'; import { Link, useHistory } from 'react-router-dom'; @@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui'; import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip'; import { DateTimeDisplay } from 'src/components/DateTimeDisplay'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { ScriptCode } from 'src/components/ScriptCode/ScriptCode'; import { Typography } from 'src/components/Typography'; import { useAccountManagement } from 'src/hooks/useAccountManagement'; diff --git a/packages/manager/src/features/CancelLanding/CancelLanding.tsx b/packages/manager/src/features/CancelLanding/CancelLanding.tsx index 5aabf333903..ddc10fa42d1 100644 --- a/packages/manager/src/features/CancelLanding/CancelLanding.tsx +++ b/packages/manager/src/features/CancelLanding/CancelLanding.tsx @@ -1,11 +1,10 @@ -import { Button } from '@linode/ui'; +import { Button, H1Header } from '@linode/ui'; import { path } from 'ramda'; import * as React from 'react'; import { Redirect, useLocation } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; import AkamaiLogo from 'src/assets/logo/akamai-logo.svg'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { Typography } from 'src/components/Typography'; import type { Theme } from '@mui/material/styles'; diff --git a/packages/manager/src/features/Events/EventsLanding.styles.ts b/packages/manager/src/features/Events/EventsLanding.styles.ts index cbad399bfbc..cc5e4ab283d 100644 --- a/packages/manager/src/features/Events/EventsLanding.styles.ts +++ b/packages/manager/src/features/Events/EventsLanding.styles.ts @@ -1,6 +1,6 @@ +import { H1Header } from '@linode/ui'; import { styled } from '@mui/material/styles'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { TableCell } from 'src/components/TableCell'; import { Typography } from 'src/components/Typography'; diff --git a/packages/manager/src/features/Help/Panels/SearchPanel.tsx b/packages/manager/src/features/Help/Panels/SearchPanel.tsx index b8f00d29b18..bf903ecbb41 100644 --- a/packages/manager/src/features/Help/Panels/SearchPanel.tsx +++ b/packages/manager/src/features/Help/Panels/SearchPanel.tsx @@ -1,9 +1,7 @@ -import { Paper } from '@linode/ui'; +import { H1Header, Paper } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; -import { H1Header } from 'src/components/H1Header/H1Header'; - import AlgoliaSearchBar from './AlgoliaSearchBar'; export const SearchPanel = () => { diff --git a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.test.tsx b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.test.tsx index 517fd9a61ef..ef4ff4437c3 100644 --- a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.test.tsx +++ b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.test.tsx @@ -1,9 +1,9 @@ +import { H1Header } from '@linode/ui'; import { screen } from '@testing-library/react'; import { assocPath } from 'ramda'; import * as React from 'react'; import { reactRouterProps } from 'src/__data__/reactRouterProps'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { renderWithTheme } from 'src/utilities/testHelpers'; import SupportSearchLanding from './SupportSearchLanding'; diff --git a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx index bec36434a12..260059ab9fe 100644 --- a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx +++ b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx @@ -1,4 +1,4 @@ -import { Box, InputAdornment, Notice } from '@linode/ui'; +import { Box, H1Header, InputAdornment, Notice } from '@linode/ui'; import Search from '@mui/icons-material/Search'; import Grid from '@mui/material/Unstable_Grid2'; import { createLazyRoute } from '@tanstack/react-router'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { useHistory } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; -import { H1Header } from 'src/components/H1Header/H1Header'; import { TextField } from 'src/components/TextField'; import { COMMUNITY_SEARCH_URL, DOCS_SEARCH_URL } from 'src/constants'; import { getQueryParamFromQueryString } from 'src/utilities/queryParams'; diff --git a/packages/manager/src/features/Search/SearchLanding.styles.ts b/packages/manager/src/features/Search/SearchLanding.styles.ts index 6603686dd99..0401f84f8ee 100644 --- a/packages/manager/src/features/Search/SearchLanding.styles.ts +++ b/packages/manager/src/features/Search/SearchLanding.styles.ts @@ -1,10 +1,9 @@ import { keyframes } from '@emotion/react'; -import { Stack } from '@linode/ui'; +import { H1Header, Stack } from '@linode/ui'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import Error from 'src/assets/icons/error.svg'; -import { H1Header } from 'src/components/H1Header/H1Header'; export const StyledStack = styled(Stack, { label: 'StyledStack', diff --git a/packages/ui/.changeset/pr-11283-added-1732021489065.md b/packages/ui/.changeset/pr-11283-added-1732021489065.md new file mode 100644 index 00000000000..44e26684853 --- /dev/null +++ b/packages/ui/.changeset/pr-11283-added-1732021489065.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Added +--- + +Move `H1Header` from `manager` to `ui` package ([#11283](https://github.com/linode/manager/pull/11283)) diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx index f469ea54d6a..396c1952411 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { CheckboxIcon, CheckboxCheckedIcon } from '../../assets/icons'; -// @TODO: Import from 'ui' package once FormControlLabel is migrated. +// @todo: modularization - Import from 'ui' package once FormControlLabel is migrated. import { FormControlLabel } from '@mui/material'; import type { CheckboxProps } from '@mui/material/Checkbox'; diff --git a/packages/manager/src/components/H1Header/H1Header.tsx b/packages/ui/src/components/H1Header/H1Header.tsx similarity index 90% rename from packages/manager/src/components/H1Header/H1Header.tsx rename to packages/ui/src/components/H1Header/H1Header.tsx index c434cdd53e6..59acd691c2e 100644 --- a/packages/manager/src/components/H1Header/H1Header.tsx +++ b/packages/ui/src/components/H1Header/H1Header.tsx @@ -1,7 +1,7 @@ +// @todo: modularization - Import from 'ui' package once Typography is migrated. +import { Typography } from '@mui/material'; import * as React from 'react'; -import { Typography } from 'src/components/Typography'; - import type { SxProps, Theme } from '@mui/material/styles'; interface H1HeaderProps { diff --git a/packages/ui/src/components/H1Header/index.ts b/packages/ui/src/components/H1Header/index.ts new file mode 100644 index 00000000000..6f677ae77b3 --- /dev/null +++ b/packages/ui/src/components/H1Header/index.ts @@ -0,0 +1 @@ +export * from './H1Header'; diff --git a/packages/ui/src/components/Notice/Notice.tsx b/packages/ui/src/components/Notice/Notice.tsx index a75c8f428b7..99fa1a74b7f 100644 --- a/packages/ui/src/components/Notice/Notice.tsx +++ b/packages/ui/src/components/Notice/Notice.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { WarningIcon, AlertIcon as Error, CheckIcon } from '../../assets/icons'; +// @todo: modularization - Import from 'ui' package once Typography is migrated. import { Typography } from '@mui/material'; import { useStyles } from './Notice.styles'; diff --git a/packages/ui/src/components/Radio/Radio.stories.tsx b/packages/ui/src/components/Radio/Radio.stories.tsx index 29c850aa6b9..38b848c453d 100644 --- a/packages/ui/src/components/Radio/Radio.stories.tsx +++ b/packages/ui/src/components/Radio/Radio.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -// @TODO: Import from 'ui' package once FormControlLabel is migrated. +// @todo: modularization - Import from 'ui' package once FormControlLabel is migrated. import { FormControlLabel } from '@mui/material'; import { Box } from '../Box'; diff --git a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx index 77cdde63f93..d8f467dcd28 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx +++ b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx @@ -2,6 +2,8 @@ import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import { Tooltip } from './Tooltip'; + +// @todo: modularization - Import from 'ui' package once Typography is migrated. import { Typography } from '@mui/material'; const meta: Meta = { diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index c13e48f4ba1..431a80dccfd 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -8,6 +8,7 @@ export * from './ClickAwayListener'; export * from './Divider'; export * from './FormControl'; export * from './FormHelperText'; +export * from './H1Header'; export * from './IconButton'; export * from './Input'; export * from './InputAdornment'; From d8812c4eefb9f2e3f5b3a3f4677a00ff4a255ddd Mon Sep 17 00:00:00 2001 From: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:42:08 -0500 Subject: [PATCH 18/92] fix: [M3-8923] - Fix Account Maintenance Account X-Filter (#11277) * create const and use it * Added changeset: Incorrect Account Maintenance X-Filter --------- Co-authored-by: Banks Nussman --- .../manager/.changeset/pr-11277-fixed-1732027147462.md | 5 +++++ .../components/MaintenanceBanner/MaintenanceBanner.tsx | 3 ++- .../src/features/Account/Maintenance/MaintenanceTable.tsx | 3 ++- .../manager/src/features/Account/Maintenance/utilities.ts | 3 +++ .../LinodesDetail/LinodesDetailHeader/Notifications.tsx | 8 +++++--- .../Linodes/LinodesLanding/LinodesLandingCSVDownload.tsx | 3 ++- packages/manager/src/features/Linodes/index.tsx | 3 ++- 7 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 packages/manager/.changeset/pr-11277-fixed-1732027147462.md create mode 100644 packages/manager/src/features/Account/Maintenance/utilities.ts diff --git a/packages/manager/.changeset/pr-11277-fixed-1732027147462.md b/packages/manager/.changeset/pr-11277-fixed-1732027147462.md new file mode 100644 index 00000000000..89841d5da39 --- /dev/null +++ b/packages/manager/.changeset/pr-11277-fixed-1732027147462.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Incorrect Account Maintenance X-Filter ([#11277](https://github.com/linode/manager/pull/11277)) diff --git a/packages/manager/src/components/MaintenanceBanner/MaintenanceBanner.tsx b/packages/manager/src/components/MaintenanceBanner/MaintenanceBanner.tsx index 2c77c1d38f9..96a79f173f3 100644 --- a/packages/manager/src/components/MaintenanceBanner/MaintenanceBanner.tsx +++ b/packages/manager/src/components/MaintenanceBanner/MaintenanceBanner.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { Typography } from 'src/components/Typography'; +import { PENDING_MAINTENANCE_FILTER } from 'src/features/Account/Maintenance/utilities'; import { useAllAccountMaintenanceQuery } from 'src/queries/account/maintenance'; import { useProfile } from 'src/queries/profile/profile'; import { formatDate } from 'src/utilities/formatDate'; @@ -22,7 +23,7 @@ export const MaintenanceBanner = React.memo((props: Props) => { const { data: accountMaintenanceData } = useAllAccountMaintenanceQuery( {}, - { status: { '+or': ['pending, started'] } } + PENDING_MAINTENANCE_FILTER ); const { diff --git a/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx b/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx index 2452a8b6570..05e5949d160 100644 --- a/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx +++ b/packages/manager/src/features/Account/Maintenance/MaintenanceTable.tsx @@ -25,6 +25,7 @@ import { } from 'src/queries/account/maintenance'; import { MaintenanceTableRow } from './MaintenanceTableRow'; +import { PENDING_MAINTENANCE_FILTER } from './utilities'; import type { AccountMaintenance, Filter } from '@linode/api-v4'; @@ -70,7 +71,7 @@ export const MaintenanceTable = ({ type }: Props) => { */ const filters: Record = { completed: { status: 'completed' }, - pending: { status: { '+or': ['pending', 'started'] } }, + pending: PENDING_MAINTENANCE_FILTER, }; const filter: Filter = { diff --git a/packages/manager/src/features/Account/Maintenance/utilities.ts b/packages/manager/src/features/Account/Maintenance/utilities.ts new file mode 100644 index 00000000000..70841a9ecfa --- /dev/null +++ b/packages/manager/src/features/Account/Maintenance/utilities.ts @@ -0,0 +1,3 @@ +export const PENDING_MAINTENANCE_FILTER = Object.freeze({ + status: { '+or': ['pending', 'started'] }, +}); diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetailHeader/Notifications.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetailHeader/Notifications.tsx index 6dc81273dd7..52f0bf19bc0 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetailHeader/Notifications.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetailHeader/Notifications.tsx @@ -1,15 +1,17 @@ -import { Notification } from '@linode/api-v4/lib/account'; -import * as React from 'react'; +import React from 'react'; import { useParams } from 'react-router-dom'; import { MaintenanceBanner } from 'src/components/MaintenanceBanner/MaintenanceBanner'; import { ProductNotification } from 'src/components/ProductNotification/ProductNotification'; +import { PENDING_MAINTENANCE_FILTER } from 'src/features/Account/Maintenance/utilities'; import { useAllAccountMaintenanceQuery } from 'src/queries/account/maintenance'; import { useNotificationsQuery } from 'src/queries/account/notifications'; import { useLinodeQuery } from 'src/queries/linodes/linodes'; import { MigrationNotification } from './MigrationNotification'; +import type { Notification } from '@linode/api-v4'; + const Notifications = () => { const { linodeId } = useParams<{ linodeId: string }>(); const { data: linode } = useLinodeQuery(Number(linodeId)); @@ -24,7 +26,7 @@ const Notifications = () => { const { data: accountMaintenanceData } = useAllAccountMaintenanceQuery( {}, - { status: { '+or': ['pending, started'] } } + PENDING_MAINTENANCE_FILTER ); const maintenanceForThisLinode = accountMaintenanceData?.find( diff --git a/packages/manager/src/features/Linodes/LinodesLanding/LinodesLandingCSVDownload.tsx b/packages/manager/src/features/Linodes/LinodesLanding/LinodesLandingCSVDownload.tsx index 7b6dc0db4f4..ed9c4a4255f 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/LinodesLandingCSVDownload.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/LinodesLandingCSVDownload.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV'; +import { PENDING_MAINTENANCE_FILTER } from 'src/features/Account/Maintenance/utilities'; import { useFormattedDate } from 'src/hooks/useFormattedDate'; import { useAllAccountMaintenanceQuery } from 'src/queries/account/maintenance'; import { useAllLinodesQuery } from 'src/queries/linodes/linodes'; @@ -18,7 +19,7 @@ export const LinodesLandingCSVDownload = () => { const { data: accountMaintenance } = useAllAccountMaintenanceQuery( {}, - { status: { '+or': ['pending, started'] } } + PENDING_MAINTENANCE_FILTER ); const downloadCSV = async () => { diff --git a/packages/manager/src/features/Linodes/index.tsx b/packages/manager/src/features/Linodes/index.tsx index 2081e1f9322..4be6571712c 100644 --- a/packages/manager/src/features/Linodes/index.tsx +++ b/packages/manager/src/features/Linodes/index.tsx @@ -10,6 +10,7 @@ import { useAllLinodesQuery } from 'src/queries/linodes/linodes'; import { addMaintenanceToLinodes } from 'src/utilities/linodes'; import { storage } from 'src/utilities/storage'; +import { PENDING_MAINTENANCE_FILTER } from '../Account/Maintenance/utilities'; import { linodesInTransition } from './transitions'; import type { RegionFilter } from 'src/utilities/storage'; @@ -49,7 +50,7 @@ export const LinodesRoutes = () => { export const LinodesLandingWrapper = React.memo(() => { const { data: accountMaintenanceData } = useAllAccountMaintenanceQuery( {}, - { status: { '+or': ['pending, started'] } } + PENDING_MAINTENANCE_FILTER ); const { isGeckoLAEnabled } = useIsGeckoEnabled(); From 9c92ef8516bd3c4bf43477f3305caf03589cd09c Mon Sep 17 00:00:00 2001 From: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:09:16 -0500 Subject: [PATCH 19/92] =?UTF-8?q?refactor:=20[M3-8815]=20=E2=80=93=20Migra?= =?UTF-8?q?te=20`TextField`=20component=20to=20`ui`=20package=20(#11290)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pr-11290-removed-1732046225737.md | 5 +++++ .../src/components/ActionMenu/ActionMenu.tsx | 3 +-- .../components/Autocomplete/Autocomplete.tsx | 7 ++----- .../CopyableTextField/CopyableTextField.tsx | 5 ++--- .../DebouncedSearchTextField.tsx | 6 ++---- .../manager/src/components/Drawer.stories.tsx | 3 +-- .../EditableInput.styles.tsx | 5 ++--- .../EditableEntityLabel/EditableInput.tsx | 2 +- .../components/EditableText/EditableText.tsx | 6 ++---- .../EnhancedNumberInput.tsx | 6 ++---- .../src/components/EnhancedSelect/Select.tsx | 5 ++--- .../components/SelectControl.tsx | 6 +++--- .../MultipleIPInput/MultipleIPInput.tsx | 3 +-- .../PaginationFooter/PaginationFooter.tsx | 3 +-- .../components/PasswordInput/HideShowText.tsx | 3 ++- .../PasswordInput/PasswordInput.tsx | 2 +- .../PlacementGroupsSelect.tsx | 2 +- .../TypeToConfirm/TypeToConfirm.tsx | 4 +++- .../VerticalLinearStepper.tsx | 4 +--- .../features/Account/CloseAccountDialog.tsx | 3 +-- .../PaymentDrawer/PaymentDrawer.tsx | 2 +- .../BillingSummary/PromoDialog.tsx | 3 +-- .../UpdateContactInformationForm.tsx | 3 +-- .../AddCreditCardForm.tsx | 3 +-- .../CreateAlert/CreateAlertDefinition.tsx | 3 +-- .../DatabaseCreate/DatabaseCreate.style.ts | 3 +-- .../features/Domains/CloneDomainDrawer.tsx | 3 +-- .../Domains/CreateDomain/CreateDomain.tsx | 10 ++++++++-- .../features/Domains/DomainRecordDrawer.tsx | 3 +-- .../Domains/DomainZoneImportDrawer.tsx | 3 +-- .../src/features/Domains/EditDomainDrawer.tsx | 3 +-- .../TransferControls.styles.ts | 3 +-- .../FirewallDetail/Rules/FirewallRuleForm.tsx | 3 +-- .../FirewallLanding/CreateFirewallDrawer.tsx | 3 +-- .../SupportSearchLanding.tsx | 3 +-- .../Images/ImagesCreate/CreateImageTab.tsx | 2 +- .../Images/ImagesCreate/ImageUpload.tsx | 11 ++++++++-- .../Images/ImagesLanding/EditImageDrawer.tsx | 3 +-- .../Images/ImagesLanding/ImagesLanding.tsx | 2 +- .../CreateCluster/CreateCluster.tsx | 3 +-- .../KubeControlPaneACLDrawer.tsx | 3 +-- .../NodePoolsDisplay/AutoscalePoolDialog.tsx | 3 +-- .../Linodes/LinodeCreate/Details/Details.tsx | 3 +-- .../StackScripts/StackScriptSelectionList.tsx | 2 +- .../UserDefinedFieldInput.tsx | 10 ++++++++-- .../LinodeCreate/UserData/UserData.tsx | 3 +-- .../Linodes/LinodeCreate/VLAN/VLAN.tsx | 3 +-- .../features/Linodes/LinodeCreate/VPC/VPC.tsx | 2 +- .../Linodes/LinodeCreate/VPC/VPCRanges.tsx | 3 +-- .../LinodeBackup/CaptureSnapshot.tsx | 3 +-- .../LinodeConfigs/LinodeConfigDialog.tsx | 2 +- .../LinodeNetworking/EditIPRDNSDrawer.tsx | 3 +-- .../LinodeNetworking/EditRangeRDNSDrawer.tsx | 3 +-- .../LinodeNetworking/IPSharing.tsx | 3 +-- .../UserDataAccordion/UserDataAccordion.tsx | 3 +-- .../LinodeSettings/AlertSection.tsx | 3 +-- .../LinodeSettings/InterfaceSelect.tsx | 3 +-- .../LinodeSettingsLabelPanel.tsx | 3 +-- .../LinodesDetail/LinodeSettings/VPCPanel.tsx | 10 ++++++++-- .../LinodeStorage/CreateDiskDrawer.tsx | 3 +-- .../LinodeStorage/RenameDiskDrawer.tsx | 3 +-- .../LinodeStorage/ResizeDiskDrawer.tsx | 3 +-- .../ServiceTargets/LinodeOrIPSelect.tsx | 2 +- .../DetailTabs/Processes/ProcessesLanding.tsx | 18 +++++++++-------- .../Managed/Contacts/ContactsDrawer.tsx | 3 +-- .../Credentials/AddCredentialDrawer.tsx | 3 +-- .../Credentials/UpdateCredentialDrawer.tsx | 3 +-- .../src/features/Managed/MonitorDrawer.tsx | 3 +-- .../Managed/SSHAccess/EditSSHAccessDrawer.tsx | 3 +-- .../NodeBalancers/NodeBalancerActiveCheck.tsx | 3 +-- .../NodeBalancers/NodeBalancerConfigNode.tsx | 3 +-- .../NodeBalancers/NodeBalancerConfigPanel.tsx | 3 +-- .../NodeBalancers/NodeBalancerCreate.tsx | 3 +-- .../NodeBalancerSettings.tsx | 3 +-- .../AccessKeyLanding/AccessKeyDrawer.tsx | 3 +-- .../AccessKeyLanding/OMC_AccessKeyDrawer.tsx | 3 +-- .../ObjectStorage/BucketDetail/BucketSSL.tsx | 3 +-- .../BucketDetail/CreateFolderDrawer.tsx | 2 +- .../BucketLanding/CreateBucketDrawer.tsx | 3 +-- .../BucketLanding/OMC_CreateBucketDrawer.tsx | 3 +-- .../PlacementGroupsCreateDrawer.tsx | 3 +-- .../PlacementGroupsEditDrawer.tsx | 3 +-- .../APITokens/CreateAPITokenDrawer.tsx | 9 +++++++-- .../Profile/APITokens/EditAPITokenDrawer.tsx | 3 +-- .../PhoneVerification.styles.ts | 3 +-- .../PhoneVerification/PhoneVerification.tsx | 3 +-- .../SecurityQuestions/Answer.tsx | 4 ++-- .../TwoFactor/ConfirmToken.tsx | 3 +-- .../Profile/DisplaySettings/EmailForm.tsx | 3 +-- .../Profile/DisplaySettings/UsernameForm.tsx | 3 +-- .../Profile/LishSettings/LishSettings.tsx | 3 +-- .../OAuthClients/CreateOAuthClientDrawer.tsx | 3 +-- .../OAuthClients/EditOAuthClientDrawer.tsx | 3 +-- .../Profile/SSHKeys/CreateSSHKeyDrawer.tsx | 3 +-- .../Profile/SSHKeys/EditSSHKeyDrawer.tsx | 3 +-- .../StackScriptForm/StackScriptForm.styles.ts | 3 +-- .../StackScriptForm/StackScriptForm.tsx | 3 +-- .../FieldTypes/UserDefinedText.tsx | 3 +-- .../features/Support/AttachFileListItem.tsx | 3 +-- .../TabbedReply/TicketReply.tsx | 3 +-- .../SupportTicketAccountLimitFields.tsx | 2 +- .../SupportTickets/SupportTicketDialog.tsx | 3 +-- .../SupportTicketProductSelectionFields.tsx | 3 +-- .../SupportTicketSMTPFields.tsx | 2 +- .../src/features/Users/CreateUserDrawer.tsx | 3 +-- .../Users/UserProfile/UserEmailPanel.tsx | 3 +-- .../Users/UserProfile/UsernamePanel.tsx | 3 +-- .../FormComponents/VPCTopSectionContent.tsx | 2 +- .../features/VPCs/VPCCreate/SubnetNode.tsx | 3 +-- .../VPCDetail/SubnetAssignLinodesDrawer.tsx | 2 +- .../VPCs/VPCDetail/SubnetCreateDrawer.tsx | 3 +-- .../VPCs/VPCDetail/SubnetEditDrawer.tsx | 3 +-- .../VPCs/VPCLanding/VPCEditDrawer.tsx | 3 +-- .../features/Volumes/CloneVolumeDrawer.tsx | 3 +-- .../src/features/Volumes/EditVolumeDrawer.tsx | 3 +-- .../src/features/Volumes/VolumeCreate.tsx | 11 ++++++++-- .../VolumeDrawer/LinodeVolumeCreateForm.tsx | 3 +-- .../Volumes/VolumeDrawer/SizeField.tsx | 2 +- .../src/features/Volumes/VolumesLanding.tsx | 8 ++++++-- .../pr-11290-added-1732046096599.md | 5 +++++ packages/ui/src/assets/icons/index.ts | 3 ++- .../src/assets/icons/plusSign.svg | 0 .../src/components/Button/StyledTagButton.ts | 4 ++-- .../TextField}/TextField.stories.tsx | 2 +- .../components/TextField}/TextField.test.tsx | 6 +++--- .../src/components/TextField}/TextField.tsx | 20 +++++++++---------- packages/ui/src/components/TextField/index.ts | 1 + packages/ui/src/components/index.ts | 1 + .../src/utilities/convertToKebabCase.ts} | 0 packages/ui/src/utilities/index.ts | 1 + 130 files changed, 218 insertions(+), 256 deletions(-) create mode 100644 packages/manager/.changeset/pr-11290-removed-1732046225737.md create mode 100644 packages/ui/.changeset/pr-11290-added-1732046096599.md rename packages/{manager => ui}/src/assets/icons/plusSign.svg (100%) rename packages/{manager/src/components => ui/src/components/TextField}/TextField.stories.tsx (98%) rename packages/{manager/src/components => ui/src/components/TextField}/TextField.test.tsx (94%) rename packages/{manager/src/components => ui/src/components/TextField}/TextField.tsx (96%) create mode 100644 packages/ui/src/components/TextField/index.ts rename packages/{manager/src/utilities/convertToKebobCase.ts => ui/src/utilities/convertToKebabCase.ts} (100%) diff --git a/packages/manager/.changeset/pr-11290-removed-1732046225737.md b/packages/manager/.changeset/pr-11290-removed-1732046225737.md new file mode 100644 index 00000000000..1c4bb413e13 --- /dev/null +++ b/packages/manager/.changeset/pr-11290-removed-1732046225737.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Removed +--- + +`TextField` component and `convertToKebabCase` utility function (migrated to `ui` package) ([#11290](https://github.com/linode/manager/pull/11290)) diff --git a/packages/manager/src/components/ActionMenu/ActionMenu.tsx b/packages/manager/src/components/ActionMenu/ActionMenu.tsx index 7114800f3e4..3465a79c883 100644 --- a/packages/manager/src/components/ActionMenu/ActionMenu.tsx +++ b/packages/manager/src/components/ActionMenu/ActionMenu.tsx @@ -1,11 +1,10 @@ -import { TooltipIcon } from '@linode/ui'; +import { TooltipIcon, convertToKebabCase } from '@linode/ui'; import { IconButton, ListItemText } from '@mui/material'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import * as React from 'react'; import KebabIcon from 'src/assets/icons/kebab.svg'; -import { convertToKebabCase } from 'src/utilities/convertToKebobCase'; export interface Action { disabled?: boolean; diff --git a/packages/manager/src/components/Autocomplete/Autocomplete.tsx b/packages/manager/src/components/Autocomplete/Autocomplete.tsx index be2b95935b1..f2ec3a4a14e 100644 --- a/packages/manager/src/components/Autocomplete/Autocomplete.tsx +++ b/packages/manager/src/components/Autocomplete/Autocomplete.tsx @@ -1,20 +1,17 @@ -import { CircleProgress } from '@linode/ui'; -import { Box, InputAdornment } from '@linode/ui'; +import { Box, CircleProgress, InputAdornment, TextField } from '@linode/ui'; import CloseIcon from '@mui/icons-material/Close'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import MuiAutocomplete from '@mui/material/Autocomplete'; import React from 'react'; -import { TextField } from 'src/components/TextField'; - import { CustomPopper, SelectedIcon, StyledListItem, } from './Autocomplete.styles'; +import type { TextFieldProps } from '@linode/ui'; import type { AutocompleteProps } from '@mui/material/Autocomplete'; -import type { TextFieldProps } from 'src/components/TextField'; export interface EnhancedAutocompleteProps< T extends { label: string }, diff --git a/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx b/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx index e09302c54aa..3d794afbad7 100644 --- a/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx +++ b/packages/manager/src/components/CopyableTextField/CopyableTextField.tsx @@ -1,14 +1,13 @@ -import { Box } from '@linode/ui'; +import { Box, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip'; -import { TextField } from 'src/components/TextField'; import { DownloadTooltip } from '../DownloadTooltip'; +import type { TextFieldProps } from '@linode/ui'; import type { CopyTooltipProps } from 'src/components/CopyTooltip/CopyTooltip'; -import type { TextFieldProps } from 'src/components/TextField'; export interface CopyableTextFieldProps extends TextFieldProps { /** diff --git a/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx b/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx index 887bc8762ec..8e9ed20fb6f 100644 --- a/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx +++ b/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx @@ -1,4 +1,4 @@ -import { CircleProgress } from '@linode/ui'; +import { CircleProgress, TextField } from '@linode/ui'; import { IconButton, InputAdornment } from '@linode/ui'; import Clear from '@mui/icons-material/Clear'; import Search from '@mui/icons-material/Search'; @@ -6,9 +6,7 @@ import { styled } from '@mui/material/styles'; import * as React from 'react'; import { debounce } from 'throttle-debounce'; -import { TextField } from 'src/components/TextField'; - -import type { TextFieldProps } from 'src/components/TextField'; +import type { TextFieldProps } from '@linode/ui'; export interface DebouncedSearchProps extends TextFieldProps { className?: string; diff --git a/packages/manager/src/components/Drawer.stories.tsx b/packages/manager/src/components/Drawer.stories.tsx index 43a96102085..056ebd1d38a 100644 --- a/packages/manager/src/components/Drawer.stories.tsx +++ b/packages/manager/src/components/Drawer.stories.tsx @@ -1,10 +1,9 @@ -import { Button } from '@linode/ui'; +import { Button, TextField } from '@linode/ui'; import { action } from '@storybook/addon-actions'; import React from 'react'; import { ActionsPanel } from './ActionsPanel/ActionsPanel'; import { Drawer } from './Drawer'; -import { TextField } from './TextField'; import { Typography } from './Typography'; import type { Meta, StoryObj } from '@storybook/react'; diff --git a/packages/manager/src/components/EditableEntityLabel/EditableInput.styles.tsx b/packages/manager/src/components/EditableEntityLabel/EditableInput.styles.tsx index a52c164703a..c8758718cfe 100644 --- a/packages/manager/src/components/EditableEntityLabel/EditableInput.styles.tsx +++ b/packages/manager/src/components/EditableEntityLabel/EditableInput.styles.tsx @@ -1,12 +1,11 @@ -import { Box, Button, fadeIn } from '@linode/ui'; +import { Box, Button, TextField, fadeIn } from '@linode/ui'; import Edit from '@mui/icons-material/Edit'; import { styled } from '@mui/material/styles'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import type { EditableTextVariant } from './EditableInput'; -import type { TextFieldProps } from 'src/components/TextField'; +import type { TextFieldProps } from '@linode/ui'; export const StyledTypography = styled(Typography, { label: 'EditableInput__StyledTypography', diff --git a/packages/manager/src/components/EditableEntityLabel/EditableInput.tsx b/packages/manager/src/components/EditableEntityLabel/EditableInput.tsx index 4cc412ad0a9..dd195ef8adb 100644 --- a/packages/manager/src/components/EditableEntityLabel/EditableInput.tsx +++ b/packages/manager/src/components/EditableEntityLabel/EditableInput.tsx @@ -12,7 +12,7 @@ import { StyledTypography, } from './EditableInput.styles'; -import type { TextFieldProps } from 'src/components/TextField'; +import type { TextFieldProps } from '@linode/ui'; export type EditableTextVariant = 'h1' | 'h2' | 'table-cell'; diff --git a/packages/manager/src/components/EditableText/EditableText.tsx b/packages/manager/src/components/EditableText/EditableText.tsx index 326f1e84363..6332fde70db 100644 --- a/packages/manager/src/components/EditableText/EditableText.tsx +++ b/packages/manager/src/components/EditableText/EditableText.tsx @@ -1,4 +1,4 @@ -import { Button, ClickAwayListener, H1Header } from '@linode/ui'; +import { Button, ClickAwayListener, H1Header, TextField } from '@linode/ui'; import Check from '@mui/icons-material/Check'; import Close from '@mui/icons-material/Close'; import Edit from '@mui/icons-material/Edit'; @@ -6,9 +6,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; -import { TextField } from '../TextField'; - -import type { TextFieldProps } from '../TextField'; +import type { TextFieldProps } from '@linode/ui'; import type { Theme } from '@mui/material/styles'; const useStyles = makeStyles()( diff --git a/packages/manager/src/components/EnhancedNumberInput/EnhancedNumberInput.tsx b/packages/manager/src/components/EnhancedNumberInput/EnhancedNumberInput.tsx index 8bf1d22d705..026e5ec82e1 100644 --- a/packages/manager/src/components/EnhancedNumberInput/EnhancedNumberInput.tsx +++ b/packages/manager/src/components/EnhancedNumberInput/EnhancedNumberInput.tsx @@ -1,10 +1,8 @@ -import { Box, Button } from '@linode/ui'; +import { Box, Button, PlusSignIcon, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; import Minus from 'src/assets/icons/LKEminusSign.svg'; -import Plus from 'src/assets/icons/LKEplusSign.svg'; -import { TextField } from 'src/components/TextField'; const sxTextFieldBase = { '&::-webkit-inner-spin-button': { @@ -155,6 +153,6 @@ const MinusIcon = styled(Minus)({ width: 12, }); -const PlusIcon = styled(Plus)({ +const PlusIcon = styled(PlusSignIcon)({ width: 14, }); diff --git a/packages/manager/src/components/EnhancedSelect/Select.tsx b/packages/manager/src/components/EnhancedSelect/Select.tsx index 77f4ec721e5..acaccafa7ec 100644 --- a/packages/manager/src/components/EnhancedSelect/Select.tsx +++ b/packages/manager/src/components/EnhancedSelect/Select.tsx @@ -1,10 +1,9 @@ +import { convertToKebabCase } from '@linode/ui'; import { useTheme } from '@mui/material'; import * as React from 'react'; import ReactSelect from 'react-select'; import CreatableSelect from 'react-select/creatable'; -import { convertToKebabCase } from 'src/utilities/convertToKebobCase'; - import { DropdownIndicator } from './components/DropdownIndicator'; import Input from './components/Input'; import { LoadingIndicator } from './components/LoadingIndicator'; @@ -17,6 +16,7 @@ import Control from './components/SelectControl'; import { SelectPlaceholder as Placeholder } from './components/SelectPlaceholder'; import { reactSelectStyles, useStyles } from './Select.styles'; +import type { TextFieldProps } from '@linode/ui'; import type { Theme } from '@mui/material'; import type { ActionMeta, @@ -24,7 +24,6 @@ import type { ValueType, } from 'react-select'; import type { CreatableProps as CreatableSelectProps } from 'react-select/creatable'; -import type { TextFieldProps } from 'src/components/TextField'; export interface Item { data?: any; diff --git a/packages/manager/src/components/EnhancedSelect/components/SelectControl.tsx b/packages/manager/src/components/EnhancedSelect/components/SelectControl.tsx index 590b194b65d..0e42fba78b2 100644 --- a/packages/manager/src/components/EnhancedSelect/components/SelectControl.tsx +++ b/packages/manager/src/components/EnhancedSelect/components/SelectControl.tsx @@ -1,14 +1,13 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; -import { ControlProps } from 'react-select'; -import { TextField } from 'src/components/TextField'; +import type { ControlProps } from 'react-select'; type Props = ControlProps; const SelectControl: React.FC = (props) => { return ( = (props) => { : props.selectProps.placeholder } fullWidth + placeholder={props.selectProps.placeholder} {...props.selectProps.textFieldProps} /> ); diff --git a/packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx b/packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx index 3420d5f84c2..325382742ce 100644 --- a/packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx +++ b/packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx @@ -1,4 +1,4 @@ -import { Button, InputLabel, Notice, TooltipIcon } from '@linode/ui'; +import { Button, InputLabel, Notice, TextField, TooltipIcon } from '@linode/ui'; import Close from '@mui/icons-material/Close'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; @@ -6,7 +6,6 @@ import { makeStyles } from 'tss-react/mui'; import { LinkButton } from 'src/components/LinkButton'; import { StyledLinkButtonBox } from 'src/components/SelectFirewallPanel/SelectFirewallPanel'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import type { InputBaseProps } from '@mui/material/InputBase'; diff --git a/packages/manager/src/components/PaginationFooter/PaginationFooter.tsx b/packages/manager/src/components/PaginationFooter/PaginationFooter.tsx index 4440e4d22f5..920d7c1173a 100644 --- a/packages/manager/src/components/PaginationFooter/PaginationFooter.tsx +++ b/packages/manager/src/components/PaginationFooter/PaginationFooter.tsx @@ -1,11 +1,10 @@ -import { Box } from '@linode/ui'; +import { Box, TextField } from '@linode/ui'; import { styled, useTheme } from '@mui/material/styles'; import * as React from 'react'; import { MenuItem } from 'src/components/MenuItem'; import { PaginationControls } from '../PaginationControls/PaginationControls'; -import { TextField } from '../TextField'; export const MIN_PAGE_SIZE = 25; diff --git a/packages/manager/src/components/PasswordInput/HideShowText.tsx b/packages/manager/src/components/PasswordInput/HideShowText.tsx index 1a617d8d0a7..b758106568a 100644 --- a/packages/manager/src/components/PasswordInput/HideShowText.tsx +++ b/packages/manager/src/components/PasswordInput/HideShowText.tsx @@ -1,8 +1,9 @@ +import { TextField } from '@linode/ui'; import Visibility from '@mui/icons-material/Visibility'; import VisibilityOff from '@mui/icons-material/VisibilityOff'; import * as React from 'react'; -import { TextField, TextFieldProps } from '../TextField'; +import type { TextFieldProps } from '@linode/ui'; export const HideShowText = (props: TextFieldProps) => { const [hidden, setHidden] = React.useState(true); diff --git a/packages/manager/src/components/PasswordInput/PasswordInput.tsx b/packages/manager/src/components/PasswordInput/PasswordInput.tsx index 9c7f05f5677..64688e34699 100644 --- a/packages/manager/src/components/PasswordInput/PasswordInput.tsx +++ b/packages/manager/src/components/PasswordInput/PasswordInput.tsx @@ -5,7 +5,7 @@ import zxcvbn from 'zxcvbn'; import { StrengthIndicator } from '../PasswordInput/StrengthIndicator'; import { HideShowText } from './HideShowText'; -import type { TextFieldProps } from 'src/components/TextField'; +import type { TextFieldProps } from '@linode/ui'; interface Props extends TextFieldProps { disabledReason?: JSX.Element | string; diff --git a/packages/manager/src/components/PlacementGroupsSelect/PlacementGroupsSelect.tsx b/packages/manager/src/components/PlacementGroupsSelect/PlacementGroupsSelect.tsx index 9d9b90f96b1..be623f2297a 100644 --- a/packages/manager/src/components/PlacementGroupsSelect/PlacementGroupsSelect.tsx +++ b/packages/manager/src/components/PlacementGroupsSelect/PlacementGroupsSelect.tsx @@ -8,8 +8,8 @@ import { useAllPlacementGroupsQuery } from 'src/queries/placementGroups'; import { PlacementGroupSelectOption } from './PlacementGroupSelectOption'; import type { APIError, PlacementGroup, Region } from '@linode/api-v4'; +import type { TextFieldProps } from '@linode/ui'; import type { SxProps, Theme } from '@mui/material/styles'; -import type { TextFieldProps } from 'src/components/TextField'; export interface PlacementGroupsSelectProps { /** diff --git a/packages/manager/src/components/TypeToConfirm/TypeToConfirm.tsx b/packages/manager/src/components/TypeToConfirm/TypeToConfirm.tsx index 84f70c6380c..a832cb1630a 100644 --- a/packages/manager/src/components/TypeToConfirm/TypeToConfirm.tsx +++ b/packages/manager/src/components/TypeToConfirm/TypeToConfirm.tsx @@ -1,9 +1,11 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; import { Link } from 'src/components/Link'; -import { TextField, TextFieldProps } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; +import type { TextFieldProps } from '@linode/ui'; + export interface TypeToConfirmProps extends Omit { confirmationText?: JSX.Element | string; hideInstructions?: boolean; diff --git a/packages/manager/src/components/VerticalLinearStepper/VerticalLinearStepper.tsx b/packages/manager/src/components/VerticalLinearStepper/VerticalLinearStepper.tsx index 84133c5c4a5..8e2e46e0e62 100644 --- a/packages/manager/src/components/VerticalLinearStepper/VerticalLinearStepper.tsx +++ b/packages/manager/src/components/VerticalLinearStepper/VerticalLinearStepper.tsx @@ -1,4 +1,4 @@ -import { Button } from '@linode/ui'; +import { Button, convertToKebabCase } from '@linode/ui'; import { Step, StepConnector, @@ -11,8 +11,6 @@ import Box from '@mui/material/Box'; import useMediaQuery from '@mui/material/useMediaQuery'; import React, { useState } from 'react'; -import { convertToKebabCase } from 'src/utilities/convertToKebobCase'; - import { CustomStepIcon, StyledCircleIcon, diff --git a/packages/manager/src/features/Account/CloseAccountDialog.tsx b/packages/manager/src/features/Account/CloseAccountDialog.tsx index 78226d57fd4..a3e873e652e 100644 --- a/packages/manager/src/features/Account/CloseAccountDialog.tsx +++ b/packages/manager/src/features/Account/CloseAccountDialog.tsx @@ -1,11 +1,10 @@ import { cancelAccount } from '@linode/api-v4/lib/account'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; import { useHistory } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; -import { TextField } from 'src/components/TextField'; import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog'; import { Typography } from 'src/components/Typography'; import { useProfile } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PaymentDrawer/PaymentDrawer.tsx b/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PaymentDrawer/PaymentDrawer.tsx index 2b1a6eb1709..dca232af14b 100644 --- a/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PaymentDrawer/PaymentDrawer.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PaymentDrawer/PaymentDrawer.tsx @@ -5,6 +5,7 @@ import { InputAdornment, Notice, Stack, + TextField, TooltipIcon, } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; @@ -18,7 +19,6 @@ import { Drawer } from 'src/components/Drawer'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; import { LinearProgress } from 'src/components/LinearProgress'; import { SupportLink } from 'src/components/SupportLink'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; diff --git a/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PromoDialog.tsx b/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PromoDialog.tsx index 519cb89c2b5..9111db7cb70 100644 --- a/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PromoDialog.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/BillingSummary/PromoDialog.tsx @@ -1,5 +1,5 @@ import { addPromotion } from '@linode/api-v4/lib'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useQueryClient } from '@tanstack/react-query'; import { useSnackbar } from 'notistack'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { makeStyles } from 'tss-react/mui'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { accountQueries } from 'src/queries/account/queries'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/UpdateContactInformationForm/UpdateContactInformationForm.tsx b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/UpdateContactInformationForm/UpdateContactInformationForm.tsx index 1d85088674b..74bde43185d 100644 --- a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/UpdateContactInformationForm/UpdateContactInformationForm.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/UpdateContactInformationForm/UpdateContactInformationForm.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import { allCountries } from 'country-region-data'; import { useFormik } from 'formik'; @@ -7,7 +7,6 @@ import { makeStyles } from 'tss-react/mui'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import EnhancedSelect from 'src/components/EnhancedSelect/Select'; -import { TextField } from 'src/components/TextField'; import { getRestrictedResourceText, useIsTaxIdEnabled, diff --git a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx index b41b64996dc..465c01d0380 100644 --- a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/AddPaymentMethodDrawer/AddCreditCardForm.tsx @@ -1,5 +1,5 @@ import { addPaymentMethod } from '@linode/api-v4/lib'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { CreditCardSchema } from '@linode/validation'; import Grid from '@mui/material/Unstable_Grid2'; import { useQueryClient } from '@tanstack/react-query'; @@ -10,7 +10,6 @@ import NumberFormat from 'react-number-format'; import { makeStyles } from 'tss-react/mui'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { TextField } from 'src/components/TextField'; import { accountQueries } from 'src/queries/account/queries'; import { parseExpiryYear } from 'src/utilities/creditCard'; import { handleAPIErrors } from 'src/utilities/formikErrorUtils'; diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index f8dd667b077..43d42c53a6e 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Paper } from '@linode/ui'; +import { Paper, TextField } from '@linode/ui'; import { createAlertDefinitionSchema } from '@linode/validation'; import { useSnackbar } from 'notistack'; import * as React from 'react'; @@ -8,7 +8,6 @@ import { useHistory } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Breadcrumb } from 'src/components/Breadcrumb/Breadcrumb'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useCreateAlertDefinition } from 'src/queries/cloudpulse/alerts'; diff --git a/packages/manager/src/features/Databases/DatabaseCreate/DatabaseCreate.style.ts b/packages/manager/src/features/Databases/DatabaseCreate/DatabaseCreate.style.ts index f98045e3104..a86596a5c02 100644 --- a/packages/manager/src/features/Databases/DatabaseCreate/DatabaseCreate.style.ts +++ b/packages/manager/src/features/Databases/DatabaseCreate/DatabaseCreate.style.ts @@ -1,8 +1,7 @@ -import { Box, Button } from '@linode/ui'; +import { Box, Button, TextField } from '@linode/ui'; import { Grid, styled } from '@mui/material'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { PlansPanel } from 'src/features/components/PlansPanel/PlansPanel'; diff --git a/packages/manager/src/features/Domains/CloneDomainDrawer.tsx b/packages/manager/src/features/Domains/CloneDomainDrawer.tsx index 8843d6eadf5..5749e5b40e9 100644 --- a/packages/manager/src/features/Domains/CloneDomainDrawer.tsx +++ b/packages/manager/src/features/Domains/CloneDomainDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice, Radio, RadioGroup } from '@linode/ui'; +import { Notice, Radio, RadioGroup, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import React from 'react'; import { useHistory } from 'react-router-dom'; @@ -6,7 +6,6 @@ import { useHistory } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { useCloneDomainMutation } from 'src/queries/domains'; import { useGrants, useProfile } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx b/packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx index 644ec9554c6..78a18a9b226 100644 --- a/packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx +++ b/packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx @@ -1,4 +1,11 @@ -import { FormHelperText, Notice, Paper, Radio, RadioGroup } from '@linode/ui'; +import { + FormHelperText, + Notice, + Paper, + Radio, + RadioGroup, + TextField, +} from '@linode/ui'; import { createDomainSchema } from '@linode/validation/lib/domains.schema'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; @@ -14,7 +21,6 @@ import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { LandingHeader } from 'src/components/LandingHeader'; import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput'; -import { TextField } from 'src/components/TextField'; import { reportException } from 'src/exceptionReporting'; import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect'; import { NodeBalancerSelect } from 'src/features/NodeBalancers/NodeBalancerSelect'; diff --git a/packages/manager/src/features/Domains/DomainRecordDrawer.tsx b/packages/manager/src/features/Domains/DomainRecordDrawer.tsx index 42b03a8359d..67b41f0ad6b 100644 --- a/packages/manager/src/features/Domains/DomainRecordDrawer.tsx +++ b/packages/manager/src/features/Domains/DomainRecordDrawer.tsx @@ -2,7 +2,7 @@ import { createDomainRecord, updateDomainRecord, } from '@linode/api-v4/lib/domains'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import produce from 'immer'; import { cond, @@ -20,7 +20,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Drawer } from 'src/components/Drawer'; import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput'; -import { TextField } from 'src/components/TextField'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; import { extendedIPToString, stringToExtendedIP } from 'src/utilities/ipUtils'; diff --git a/packages/manager/src/features/Domains/DomainZoneImportDrawer.tsx b/packages/manager/src/features/Domains/DomainZoneImportDrawer.tsx index 51803d22c3f..2946893b578 100644 --- a/packages/manager/src/features/Domains/DomainZoneImportDrawer.tsx +++ b/packages/manager/src/features/Domains/DomainZoneImportDrawer.tsx @@ -1,11 +1,10 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { useHistory } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useImportZoneMutation } from 'src/queries/domains'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/Domains/EditDomainDrawer.tsx b/packages/manager/src/features/Domains/EditDomainDrawer.tsx index 1a67a4b8d2a..739df097802 100644 --- a/packages/manager/src/features/Domains/EditDomainDrawer.tsx +++ b/packages/manager/src/features/Domains/EditDomainDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice, Radio, RadioGroup } from '@linode/ui'; +import { Notice, Radio, RadioGroup, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { useUpdateDomainMutation } from 'src/queries/domains'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/TransferControls.styles.ts b/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/TransferControls.styles.ts index f75105158ea..f2f7de037ef 100644 --- a/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/TransferControls.styles.ts +++ b/packages/manager/src/features/EntityTransfers/EntityTransfersLanding/TransferControls.styles.ts @@ -1,8 +1,7 @@ -import { Button } from '@linode/ui'; +import { Button, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; // sm = 600, md = 960, lg = 1280 diff --git a/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRuleForm.tsx b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRuleForm.tsx index 337521695cd..61653136afa 100644 --- a/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRuleForm.tsx +++ b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRuleForm.tsx @@ -1,4 +1,4 @@ -import { Notice, Radio, RadioGroup } from '@linode/ui'; +import { Notice, Radio, RadioGroup, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; @@ -6,7 +6,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { addressOptions, diff --git a/packages/manager/src/features/Firewalls/FirewallLanding/CreateFirewallDrawer.tsx b/packages/manager/src/features/Firewalls/FirewallLanding/CreateFirewallDrawer.tsx index a573130220f..8901e8e99cf 100644 --- a/packages/manager/src/features/Firewalls/FirewallLanding/CreateFirewallDrawer.tsx +++ b/packages/manager/src/features/Firewalls/FirewallLanding/CreateFirewallDrawer.tsx @@ -1,5 +1,5 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -import { Box, Notice, Radio, RadioGroup } from '@linode/ui'; +import { Box, Notice, Radio, RadioGroup, TextField } from '@linode/ui'; import { CreateFirewallSchema } from '@linode/validation/lib/firewalls.schema'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -11,7 +11,6 @@ import { Drawer } from 'src/components/Drawer'; import { ErrorMessage } from 'src/components/ErrorMessage'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { FIREWALL_LIMITS_CONSIDERATIONS_LINK } from 'src/constants'; import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect'; diff --git a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx index 260059ab9fe..fddcebec8d9 100644 --- a/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx +++ b/packages/manager/src/features/Help/SupportSearchLanding/SupportSearchLanding.tsx @@ -1,4 +1,4 @@ -import { Box, H1Header, InputAdornment, Notice } from '@linode/ui'; +import { Box, H1Header, InputAdornment, Notice, TextField } from '@linode/ui'; import Search from '@mui/icons-material/Search'; import Grid from '@mui/material/Unstable_Grid2'; import { createLazyRoute } from '@tanstack/react-router'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { useHistory } from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; -import { TextField } from 'src/components/TextField'; import { COMMUNITY_SEARCH_URL, DOCS_SEARCH_URL } from 'src/constants'; import { getQueryParamFromQueryString } from 'src/utilities/queryParams'; diff --git a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx index 0f485b543f6..6ad3e56d4d5 100644 --- a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx +++ b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx @@ -6,6 +6,7 @@ import { Notice, Paper, Stack, + TextField, TooltipIcon, } from '@linode/ui'; import { createImageSchema } from '@linode/validation'; @@ -19,7 +20,6 @@ import { DISK_ENCRYPTION_IMAGES_CAVEAT_COPY } from 'src/components/Encryption/co import { useIsDiskEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; import { Link } from 'src/components/Link'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect'; diff --git a/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx b/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx index 937897314c5..b366862484e 100644 --- a/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx +++ b/packages/manager/src/features/Images/ImagesCreate/ImageUpload.tsx @@ -1,5 +1,13 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Button, Checkbox, Notice, Paper, Stack } from '@linode/ui'; +import { + Box, + Button, + Checkbox, + Notice, + Paper, + Stack, + TextField, +} from '@linode/ui'; import { useSnackbar } from 'notistack'; import React, { useState } from 'react'; import { flushSync } from 'react-dom'; @@ -13,7 +21,6 @@ import { Link } from 'src/components/Link'; import { Prompt } from 'src/components/Prompt/Prompt'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { ImageUploader } from 'src/components/Uploaders/ImageUploader/ImageUploader'; import { MAX_FILE_SIZE_IN_BYTES } from 'src/components/Uploaders/reducer'; diff --git a/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx b/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx index bd3c629d050..15abddf8b5b 100644 --- a/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { updateImageSchema } from '@linode/validation'; import * as React from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -7,7 +7,6 @@ import { Controller, useForm } from 'react-hook-form'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { useUpdateImageMutation } from 'src/queries/images'; import { useImageAndLinodeGrantCheck } from '../utils'; diff --git a/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.tsx b/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.tsx index 56ff19e48d0..f10bc930669 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.tsx @@ -5,6 +5,7 @@ import { InputAdornment, Notice, Paper, + TextField, } from '@linode/ui'; import CloseIcon from '@mui/icons-material/Close'; import { useQueryClient } from '@tanstack/react-query'; @@ -31,7 +32,6 @@ import { TableRow } from 'src/components/TableRow'; import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty'; import { TableRowError } from 'src/components/TableRowError/TableRowError'; import { TableSortCell } from 'src/components/TableSortCell'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useFlags } from 'src/hooks/useFlags'; diff --git a/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx b/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx index 2cda374ee3f..564755f65a9 100644 --- a/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx +++ b/packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx @@ -1,4 +1,4 @@ -import { Box, Notice, Paper, Stack } from '@linode/ui'; +import { Box, Notice, Paper, Stack, TextField } from '@linode/ui'; import { Divider } from '@mui/material'; import Grid from '@mui/material/Unstable_Grid2'; import { createLazyRoute } from '@tanstack/react-router'; @@ -14,7 +14,6 @@ import { ErrorState } from 'src/components/ErrorState/ErrorState'; import { LandingHeader } from 'src/components/LandingHeader'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { RegionHelperText } from 'src/components/SelectRegionPanel/RegionHelperText'; -import { TextField } from 'src/components/TextField'; import { getKubeControlPlaneACL, getKubeHighAvailability, diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx index 65b282bf332..d19782be804 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Notice, omittedProps } from '@linode/ui'; +import { Box, Notice, TextField, omittedProps } from '@linode/ui'; import { kubernetesControlPlaneACLPayloadSchema } from '@linode/validation'; import { Divider, Stack } from '@mui/material'; import { styled } from '@mui/material/styles'; @@ -11,7 +11,6 @@ import { Drawer } from 'src/components/Drawer'; import { DrawerContent } from 'src/components/DrawerContent'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { MultipleNonExtendedIPInput } from 'src/components/MultipleIPInput/MultipleNonExtendedIPInput'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { Typography } from 'src/components/Typography'; import { diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/AutoscalePoolDialog.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/AutoscalePoolDialog.tsx index b5d458499de..9a8686cea16 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/AutoscalePoolDialog.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/AutoscalePoolDialog.tsx @@ -1,4 +1,4 @@ -import { Button, Notice } from '@linode/ui'; +import { Button, Notice, TextField } from '@linode/ui'; import { AutoscaleNodePoolSchema } from '@linode/validation/lib/kubernetes.schema'; import Grid from '@mui/material/Unstable_Grid2'; import { useFormik } from 'formik'; @@ -10,7 +10,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { Typography } from 'src/components/Typography'; import { useUpdateNodePoolMutation } from 'src/queries/kubernetes'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.tsx index 514f1744675..c7c4bd3ae5b 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.tsx @@ -1,9 +1,8 @@ +import { Paper, TextField } from '@linode/ui'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { Paper } from '@linode/ui'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useIsPlacementGroupsEnabled } from 'src/features/PlacementGroups/utils'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/StackScriptSelectionList.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/StackScriptSelectionList.tsx index c2a7de5130d..220a420c09d 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/StackScriptSelectionList.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/StackScriptSelectionList.tsx @@ -6,6 +6,7 @@ import { IconButton, InputAdornment, Stack, + TextField, TooltipIcon, } from '@linode/ui'; import CloseIcon from '@mui/icons-material/Close'; @@ -25,7 +26,6 @@ import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty'; import { TableRowError } from 'src/components/TableRowError/TableRowError'; import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading'; import { TableSortCell } from 'src/components/TableSortCell'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useOrder } from 'src/hooks/useOrder'; import { diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/UserDefinedFields/UserDefinedFieldInput.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/UserDefinedFields/UserDefinedFieldInput.tsx index 32cbc5b1bcc..97d143dad6f 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/UserDefinedFields/UserDefinedFieldInput.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/StackScripts/UserDefinedFields/UserDefinedFieldInput.tsx @@ -1,4 +1,11 @@ -import { Divider, FormControl, Radio, RadioGroup, Stack } from '@linode/ui'; +import { + Divider, + FormControl, + Radio, + RadioGroup, + Stack, + TextField, +} from '@linode/ui'; import React from 'react'; import { useController, useFormContext } from 'react-hook-form'; @@ -7,7 +14,6 @@ import { FormControlLabel } from 'src/components/FormControlLabel'; import { FormLabel } from 'src/components/FormLabel'; import { Link } from 'src/components/Link'; import PasswordInput from 'src/components/PasswordInput/PasswordInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { diff --git a/packages/manager/src/features/Linodes/LinodeCreate/UserData/UserData.tsx b/packages/manager/src/features/Linodes/LinodeCreate/UserData/UserData.tsx index bd22cb4d40f..9425044f482 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/UserData/UserData.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/UserData/UserData.tsx @@ -1,10 +1,9 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import React, { useMemo } from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Accordion } from 'src/components/Accordion'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { useImageQuery } from 'src/queries/images'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VLAN/VLAN.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VLAN/VLAN.tsx index 6d2bdb0c18f..3535e2bd5ed 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VLAN/VLAN.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VLAN/VLAN.tsx @@ -1,10 +1,9 @@ -import { Stack, TooltipIcon } from '@linode/ui'; +import { Stack, TextField, TooltipIcon } from '@linode/ui'; import React from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { Accordion } from 'src/components/Accordion'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { VLANSelect } from 'src/components/VLANSelect'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx index 5d754c64ea0..7c410ea2e29 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx @@ -5,6 +5,7 @@ import { Notice, Paper, Stack, + TextField, TooltipIcon, } from '@linode/ui'; import React, { useState } from 'react'; @@ -14,7 +15,6 @@ import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Link } from 'src/components/Link'; import { LinkButton } from 'src/components/LinkButton'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { VPCSelect } from 'src/components/VPCSelect'; import { diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPCRanges.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPCRanges.tsx index cec5f7458fa..75bd7268682 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPCRanges.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPCRanges.tsx @@ -1,10 +1,9 @@ -import { Box, IconButton, Stack } from '@linode/ui'; +import { Box, IconButton, Stack, TextField } from '@linode/ui'; import CloseIcon from '@mui/icons-material/Close'; import React from 'react'; import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; import { LinkButton } from 'src/components/LinkButton'; -import { TextField } from 'src/components/TextField'; import type { CreateLinodeRequest } from '@linode/api-v4'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx index 5ca888c0856..c1f6e08f34d 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx @@ -1,10 +1,9 @@ -import { Box, Button, FormControl, Paper } from '@linode/ui'; +import { Box, Button, FormControl, Paper, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useEventsPollingActions } from 'src/queries/events/events'; import { useLinodeBackupSnapshotMutation } from 'src/queries/linodes/backups'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx index 3fe917f2110..1829196e383 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx @@ -7,6 +7,7 @@ import { FormHelperText, Notice, Radio, + TextField, TooltipIcon, } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; @@ -24,7 +25,6 @@ import { ErrorState } from 'src/components/ErrorState/ErrorState'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { FormLabel } from 'src/components/FormLabel'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { Typography } from 'src/components/Typography'; import { DeviceSelection } from 'src/features/Linodes/LinodesDetail/LinodeRescue/DeviceSelection'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx index 450bcd387ee..36ba47f6fc9 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx @@ -1,11 +1,10 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useLinodeIPMutation } from 'src/queries/linodes/networking'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx index ec935c56521..aa91a6a728a 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useLinodeQuery } from 'src/queries/linodes/linodes'; import { useLinodeIPMutation } from 'src/queries/linodes/networking'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/IPSharing.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/IPSharing.tsx index 485e77f2585..00ad1ab5771 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/IPSharing.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/IPSharing.tsx @@ -1,4 +1,4 @@ -import { Button, CircleProgress, Divider, Notice } from '@linode/ui'; +import { Button, CircleProgress, Divider, Notice, TextField } from '@linode/ui'; import { styled, useTheme } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import { remove, uniq, update } from 'ramda'; @@ -8,7 +8,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Dialog } from 'src/components/Dialog/Dialog'; import Select from 'src/components/EnhancedSelect/Select'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { API_MAX_PAGE_SIZE } from 'src/constants'; import { useFlags } from 'src/hooks/useFlags'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/UserDataAccordion/UserDataAccordion.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/UserDataAccordion/UserDataAccordion.tsx index ef0683830cc..2ea82ec34a1 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/UserDataAccordion/UserDataAccordion.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/UserDataAccordion/UserDataAccordion.tsx @@ -1,9 +1,8 @@ -import { Box, Notice } from '@linode/ui'; +import { Box, Notice, TextField } from '@linode/ui'; import * as React from 'react'; import { Accordion } from 'src/components/Accordion'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { UserDataAccordionHeading } from './UserDataAccordionHeading'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/AlertSection.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/AlertSection.tsx index 18be905a526..183a7a4ddaf 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/AlertSection.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/AlertSection.tsx @@ -1,10 +1,9 @@ -import { Box, Divider, InputAdornment, fadeIn } from '@linode/ui'; +import { Box, Divider, InputAdornment, TextField, fadeIn } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { Typography } from 'src/components/Typography'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/InterfaceSelect.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/InterfaceSelect.tsx index f9de4f1d149..0e433f224fc 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/InterfaceSelect.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/InterfaceSelect.tsx @@ -1,11 +1,10 @@ -import { Divider, Notice, Stack } from '@linode/ui'; +import { Divider, Notice, Stack, TextField } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import useMediaQuery from '@mui/material/useMediaQuery'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { VPCPanel } from 'src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel'; import { useVlansQuery } from 'src/queries/vlans'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsLabelPanel.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsLabelPanel.tsx index 1615abbfb9f..ac6b5c9f75a 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsLabelPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsLabelPanel.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { Accordion } from 'src/components/Accordion'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { TextField } from 'src/components/TextField'; import { useLinodeQuery, useLinodeUpdateMutation, diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx index a0b3993cba7..dd91a6fd16b 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx @@ -1,11 +1,17 @@ -import { Box, Checkbox, Paper, Stack, TooltipIcon } from '@linode/ui'; +import { + Box, + Checkbox, + Paper, + Stack, + TextField, + TooltipIcon, +} from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { REGION_CAVEAT_HELPER_TEXT, diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx index aaf0c092554..0b8564e3805 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/CreateDiskDrawer.tsx @@ -1,4 +1,4 @@ -import { FormHelperText, InputAdornment, Notice } from '@linode/ui'; +import { FormHelperText, InputAdornment, Notice, TextField } from '@linode/ui'; import { CreateLinodeDiskFromImageSchema, CreateLinodeDiskSchema, @@ -11,7 +11,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Drawer } from 'src/components/Drawer'; import { ModeSelect } from 'src/components/ModeSelect/ModeSelect'; -import { TextField } from 'src/components/TextField'; import { useEventsPollingActions } from 'src/queries/events/events'; import { useAllLinodeDisksQuery, diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/RenameDiskDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/RenameDiskDrawer.tsx index 30177ef637d..a4ab6518ba2 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/RenameDiskDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/RenameDiskDrawer.tsx @@ -1,11 +1,10 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { object, string } from 'yup'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useLinodeDiskUpdateMutation } from 'src/queries/linodes/disks'; import { handleAPIErrors } from 'src/utilities/formikErrorUtils'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/ResizeDiskDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/ResizeDiskDrawer.tsx index 716e42a843f..2f9602e628c 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/ResizeDiskDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeStorage/ResizeDiskDrawer.tsx @@ -1,4 +1,4 @@ -import { FormHelperText, InputAdornment, Notice } from '@linode/ui'; +import { FormHelperText, InputAdornment, Notice, TextField } from '@linode/ui'; import { ResizeLinodeDiskSchema } from '@linode/validation'; import { styled } from '@mui/material/styles'; import { useFormik } from 'formik'; @@ -9,7 +9,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Code } from 'src/components/Code/Code'; import { Drawer } from 'src/components/Drawer'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { TextTooltip } from 'src/components/TextTooltip'; import { useEventsPollingActions } from 'src/queries/events/events'; import { diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/LinodeOrIPSelect.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/LinodeOrIPSelect.tsx index bb107719cfb..8e8c5857e7d 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/LinodeOrIPSelect.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/LinodeOrIPSelect.tsx @@ -8,7 +8,7 @@ import { useInfiniteLinodesQuery } from 'src/queries/linodes/linodes'; import { useRegionsQuery } from 'src/queries/regions/regions'; import type { Filter } from '@linode/api-v4'; -import type { TextFieldProps } from 'src/components/TextField'; +import type { TextFieldProps } from '@linode/ui'; interface Props { /** diff --git a/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Processes/ProcessesLanding.tsx b/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Processes/ProcessesLanding.tsx index 3157c738200..0775c16a8c1 100644 --- a/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Processes/ProcessesLanding.tsx +++ b/packages/manager/src/features/Longview/LongviewDetail/DetailTabs/Processes/ProcessesLanding.tsx @@ -1,14 +1,9 @@ -import { APIError } from '@linode/api-v4/lib/types'; +import { TextField } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import { prop, sortBy } from 'ramda'; import * as React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; -import { TextField } from 'src/components/TextField'; -import { - LongviewProcesses, - WithStartAndEnd, -} from 'src/features/Longview/request.types'; import { statAverage, statMax } from 'src/features/Longview/shared/utilities'; import { escapeRegExp } from 'src/utilities/escapeRegExp'; import { isToday as _isToday } from 'src/utilities/isToday'; @@ -16,9 +11,16 @@ import { isToday as _isToday } from 'src/utilities/isToday'; import { StyledItemGrid } from '../CommonStyles.styles'; import { useGraphs } from '../OverviewGraphs/useGraphs'; import { ProcessesGraphs } from './ProcessesGraphs'; -import { ProcessesTable, ExtendedProcess } from './ProcessesTable'; -import { Process } from './types'; import { StyledBox, StyledTimeRangeSelect } from './ProcessesLanding.styles'; +import { ProcessesTable } from './ProcessesTable'; + +import type { ExtendedProcess } from './ProcessesTable'; +import type { Process } from './types'; +import type { APIError } from '@linode/api-v4/lib/types'; +import type { + LongviewProcesses, + WithStartAndEnd, +} from 'src/features/Longview/request.types'; interface Props { clientAPIKey?: string; diff --git a/packages/manager/src/features/Managed/Contacts/ContactsDrawer.tsx b/packages/manager/src/features/Managed/Contacts/ContactsDrawer.tsx index bba058fbeda..a6094f8d361 100644 --- a/packages/manager/src/features/Managed/Contacts/ContactsDrawer.tsx +++ b/packages/manager/src/features/Managed/Contacts/ContactsDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { createContactSchema } from '@linode/validation/lib/managed.schema'; import Grid from '@mui/material/Unstable_Grid2'; import { Formik } from 'formik'; @@ -8,7 +8,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import Select from 'src/components/EnhancedSelect/Select'; -import { TextField } from 'src/components/TextField'; import { useCreateContactMutation, useUpdateContactMutation, diff --git a/packages/manager/src/features/Managed/Credentials/AddCredentialDrawer.tsx b/packages/manager/src/features/Managed/Credentials/AddCredentialDrawer.tsx index 098fde3c6d0..b557ca95750 100644 --- a/packages/manager/src/features/Managed/Credentials/AddCredentialDrawer.tsx +++ b/packages/manager/src/features/Managed/Credentials/AddCredentialDrawer.tsx @@ -1,11 +1,10 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { Formik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; -import { TextField } from 'src/components/TextField'; import { handleFormikBlur } from 'src/utilities/formikTrimUtil'; import { creationSchema } from './credential.schema'; diff --git a/packages/manager/src/features/Managed/Credentials/UpdateCredentialDrawer.tsx b/packages/manager/src/features/Managed/Credentials/UpdateCredentialDrawer.tsx index c3114d00e66..f73f2794f26 100644 --- a/packages/manager/src/features/Managed/Credentials/UpdateCredentialDrawer.tsx +++ b/packages/manager/src/features/Managed/Credentials/UpdateCredentialDrawer.tsx @@ -1,11 +1,10 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { Formik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; -import { TextField } from 'src/components/TextField'; import { handleFormikBlur } from 'src/utilities/formikTrimUtil'; import { updateLabelSchema, updatePasswordSchema } from './credential.schema'; diff --git a/packages/manager/src/features/Managed/MonitorDrawer.tsx b/packages/manager/src/features/Managed/MonitorDrawer.tsx index 9a314541d5a..917a4afb48b 100644 --- a/packages/manager/src/features/Managed/MonitorDrawer.tsx +++ b/packages/manager/src/features/Managed/MonitorDrawer.tsx @@ -1,4 +1,4 @@ -import { InputAdornment, Notice } from '@linode/ui'; +import { InputAdornment, Notice, TextField } from '@linode/ui'; import { createServiceMonitorSchema } from '@linode/validation/lib/managed.schema'; import Grid from '@mui/material/Unstable_Grid2'; import { Formik } from 'formik'; @@ -8,7 +8,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import Select from 'src/components/EnhancedSelect/Select'; -import { TextField } from 'src/components/TextField'; import type { ManagedCredential, diff --git a/packages/manager/src/features/Managed/SSHAccess/EditSSHAccessDrawer.tsx b/packages/manager/src/features/Managed/SSHAccess/EditSSHAccessDrawer.tsx index 758f6251cb0..d0813da6a16 100644 --- a/packages/manager/src/features/Managed/SSHAccess/EditSSHAccessDrawer.tsx +++ b/packages/manager/src/features/Managed/SSHAccess/EditSSHAccessDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import { Formik } from 'formik'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { IPSelect } from 'src/components/IPSelect/IPSelect'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { useUpdateLinodeSettingsMutation } from 'src/queries/managed/managed'; import { diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerActiveCheck.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerActiveCheck.tsx index fedfe3909d5..707b5c24334 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerActiveCheck.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerActiveCheck.tsx @@ -1,9 +1,8 @@ -import { FormHelperText, InputAdornment } from '@linode/ui'; +import { FormHelperText, InputAdornment, TextField } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { setErrorMap } from './utils'; diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigNode.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigNode.tsx index 47ea747c607..34c54266b5c 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigNode.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigNode.tsx @@ -1,10 +1,9 @@ -import { Box, Button, Chip, Divider, Notice } from '@linode/ui'; +import { Box, Button, Chip, Divider, Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx index 4d77893d05f..f5a113c0fc2 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerConfigPanel.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, FormHelperText, Notice } from '@linode/ui'; +import { Button, Divider, FormHelperText, Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { ActiveCheck } from './NodeBalancerActiveCheck'; diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx index 0ced2d1e48c..db048dac255 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Notice, Paper, Stack } from '@linode/ui'; +import { Box, Button, Notice, Paper, Stack, TextField } from '@linode/ui'; import { useTheme } from '@mui/material'; import useMediaQuery from '@mui/material/useMediaQuery'; import { createLazyRoute } from '@tanstack/react-router'; @@ -27,7 +27,6 @@ import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { SelectFirewallPanel } from 'src/components/SelectFirewallPanel/SelectFirewallPanel'; import { RegionHelperText } from 'src/components/SelectRegionPanel/RegionHelperText'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { FIREWALL_GET_STARTED_LINK } from 'src/constants'; import { getRestrictedResourceText } from 'src/features/Account/utils'; diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings.tsx index 0a8c4c0e592..561d1b8395c 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSettings.tsx @@ -1,4 +1,4 @@ -import { Button, FormHelperText, InputAdornment } from '@linode/ui'; +import { Button, FormHelperText, InputAdornment, TextField } from '@linode/ui'; import { useTheme } from '@mui/material'; import { createLazyRoute } from '@tanstack/react-router'; import * as React from 'react'; @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom'; import { Accordion } from 'src/components/Accordion'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; -import { TextField } from 'src/components/TextField'; import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted'; import { useNodeBalancerQuery, diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx index d020fe88a44..f8566fb792b 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx @@ -1,4 +1,4 @@ -import { CircleProgress, Notice } from '@linode/ui'; +import { CircleProgress, Notice, TextField } from '@linode/ui'; import { createObjectStorageKeysSchema } from '@linode/validation/lib/objectStorageKeys.schema'; import { Formik } from 'formik'; import * as React from 'react'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useAccountSettings } from 'src/queries/account/settings'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx index 7c8f4deb8e8..bf170914337 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx @@ -1,4 +1,4 @@ -import { CircleProgress, Notice } from '@linode/ui'; +import { CircleProgress, Notice, TextField } from '@linode/ui'; import { createObjectStorageKeysSchema, updateObjectStorageKeysSchema, @@ -9,7 +9,6 @@ import React, { useEffect, useState } from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useAccountSettings } from 'src/queries/account/settings'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx index a0146183ed8..1198c68ee1c 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketSSL.tsx @@ -1,4 +1,4 @@ -import { Button, CircleProgress, Notice, Paper } from '@linode/ui'; +import { Button, CircleProgress, Notice, Paper, TextField } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import { useFormik } from 'formik'; @@ -9,7 +9,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { ErrorState } from 'src/components/ErrorState/ErrorState'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useBucketSSLDeleteMutation, diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/CreateFolderDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/CreateFolderDrawer.tsx index 34f8cbfaed5..eb9badab842 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/CreateFolderDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/CreateFolderDrawer.tsx @@ -1,9 +1,9 @@ +import { TextField } from '@linode/ui'; import { useFormik } from 'formik'; import React, { useEffect } from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useCreateObjectUrlMutation } from 'src/queries/object-storage/queries'; interface Props { diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx index 93e60b33c19..d6aec436ec4 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { CreateBucketSchema } from '@linode/validation'; import { styled } from '@mui/material/styles'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { Controller, useForm } from 'react-hook-form'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { EUAgreementCheckbox } from 'src/features/Account/Agreements/EUAgreementCheckbox'; import { reportAgreementSigningError, diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.tsx index dc3080c7efa..44a378f28ba 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.tsx @@ -1,5 +1,5 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { CreateBucketSchema } from '@linode/validation'; import * as React from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -8,7 +8,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Drawer } from 'src/components/Drawer'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { BucketRateLimitTable } from 'src/features/ObjectStorage/BucketLanding/BucketRateLimitTable'; import { diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx index 9da8bd6be02..b48bdc22f4f 100644 --- a/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx +++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsCreateDrawer.tsx @@ -1,4 +1,4 @@ -import { Divider, Notice, Stack } from '@linode/ui'; +import { Divider, Notice, Stack, TextField } from '@linode/ui'; import { createPlacementGroupSchema } from '@linode/validation'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -11,7 +11,6 @@ import { Drawer } from 'src/components/Drawer'; import { List } from 'src/components/List'; import { ListItem } from 'src/components/ListItem'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useFormValidateOnChange } from 'src/hooks/useFormValidateOnChange'; diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsEditDrawer.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsEditDrawer.tsx index ed9c0f34b28..728ae6759e9 100644 --- a/packages/manager/src/features/PlacementGroups/PlacementGroupsEditDrawer.tsx +++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsEditDrawer.tsx @@ -2,7 +2,7 @@ import { PLACEMENT_GROUP_POLICIES, PLACEMENT_GROUP_TYPES, } from '@linode/api-v4'; -import { CircleProgress, Divider, Notice, Stack } from '@linode/ui'; +import { CircleProgress, Divider, Notice, Stack, TextField } from '@linode/ui'; import { updatePlacementGroupSchema } from '@linode/validation'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -13,7 +13,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { DescriptionList } from 'src/components/DescriptionList/DescriptionList'; import { Drawer } from 'src/components/Drawer'; import { NotFound } from 'src/components/NotFound'; -import { TextField } from 'src/components/TextField'; import { useFormValidateOnChange } from 'src/hooks/useFormValidateOnChange'; import { useMutatePlacementGroup, diff --git a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx index 49fa0549c0d..01ceecf68ee 100644 --- a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx +++ b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.tsx @@ -1,4 +1,10 @@ -import { FormControl, FormHelperText, Notice, Radio } from '@linode/ui'; +import { + FormControl, + FormHelperText, + Notice, + Radio, + TextField, +} from '@linode/ui'; import { useFormik } from 'formik'; import { DateTime } from 'luxon'; import * as React from 'react'; @@ -10,7 +16,6 @@ import { TableBody } from 'src/components/TableBody'; import { TableCell } from 'src/components/TableCell'; import { TableHead } from 'src/components/TableHead'; import { TableRow } from 'src/components/TableRow'; -import { TextField } from 'src/components/TextField'; import { ISO_DATETIME_NO_TZ_FORMAT } from 'src/constants'; import { AccessCell } from 'src/features/ObjectStorage/AccessKeyLanding/AccessCell'; import { VPC_READ_ONLY_TOOLTIP } from 'src/features/VPCs/constants'; diff --git a/packages/manager/src/features/Profile/APITokens/EditAPITokenDrawer.tsx b/packages/manager/src/features/Profile/APITokens/EditAPITokenDrawer.tsx index 5c4e9b2bc74..fd6489e5299 100644 --- a/packages/manager/src/features/Profile/APITokens/EditAPITokenDrawer.tsx +++ b/packages/manager/src/features/Profile/APITokens/EditAPITokenDrawer.tsx @@ -1,10 +1,9 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useUpdatePersonalAccessTokenMutation } from 'src/queries/profile/tokens'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts index d017870775b..423db7ced0b 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts +++ b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.styles.ts @@ -1,8 +1,7 @@ -import { Box, FormHelperText, omittedProps } from '@linode/ui'; +import { Box, FormHelperText, TextField, omittedProps } from '@linode/ui'; import { styled } from '@mui/material/styles'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; export const StyledCodeSentMessageBox = styled(Box, { diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx index ccd3bbca505..e98c7d195db 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/PhoneVerification/PhoneVerification.tsx @@ -1,4 +1,4 @@ -import { Box, Button, InputAdornment } from '@linode/ui'; +import { Box, Button, InputAdornment, TextField } from '@linode/ui'; import { useQueryClient } from '@tanstack/react-query'; import { useFormik } from 'formik'; import { parsePhoneNumber } from 'libphonenumber-js'; @@ -7,7 +7,6 @@ import * as React from 'react'; import { LinkButton } from 'src/components/LinkButton'; import { MaskableText } from 'src/components/MaskableText/MaskableText'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { profileQueries, diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/Answer.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/Answer.tsx index d2c91448928..91203b036a3 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/Answer.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/SecurityQuestions/Answer.tsx @@ -1,7 +1,7 @@ -import { SecurityQuestion } from '@linode/api-v4/lib/profile'; +import { TextField } from '@linode/ui'; import * as React from 'react'; -import { TextField } from 'src/components/TextField'; +import type { SecurityQuestion } from '@linode/api-v4/lib/profile'; interface Props { handleChange: any; diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx index 7ba7e0478f6..fdf1cafa7b9 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ConfirmToken.tsx @@ -1,8 +1,7 @@ -import { Box, Button, Notice } from '@linode/ui'; +import { Box, Button, Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; interface Props { diff --git a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx index 718e4036814..363fed51383 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.tsx @@ -1,10 +1,9 @@ -import { Button } from '@linode/ui'; +import { Button, TextField } from '@linode/ui'; import { useSnackbar } from 'notistack'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useLocation } from 'react-router-dom'; -import { TextField } from 'src/components/TextField'; import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants'; import { useMutateProfile, useProfile } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx index c11cd626f45..dc24f0c1763 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.tsx @@ -1,9 +1,8 @@ -import { Button } from '@linode/ui'; +import { Button, TextField } from '@linode/ui'; import { useSnackbar } from 'notistack'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; -import { TextField } from 'src/components/TextField'; import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants'; import { useUpdateUserMutation } from 'src/queries/account/users'; import { useProfile } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx b/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx index 4e2502649dd..13fd0f1f037 100644 --- a/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx +++ b/packages/manager/src/features/Profile/LishSettings/LishSettings.tsx @@ -1,4 +1,4 @@ -import { Box, Button, FormControl, Notice, Paper } from '@linode/ui'; +import { Box, Button, FormControl, Notice, Paper, TextField } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import { createLazyRoute } from '@tanstack/react-router'; import { equals, lensPath, remove, set } from 'ramda'; @@ -7,7 +7,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useMutateProfile, useProfile } from 'src/queries/profile/profile'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx index 452ed429c3b..c8cc9e0e279 100644 --- a/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx +++ b/packages/manager/src/features/Profile/OAuthClients/CreateOAuthClientDrawer.tsx @@ -1,11 +1,10 @@ -import { Checkbox, FormControl, Notice } from '@linode/ui'; +import { Checkbox, FormControl, Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { useCreateOAuthClientMutation } from 'src/queries/account/oauth'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; diff --git a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx index ee51e7b3c94..e0bb31826b9 100644 --- a/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx +++ b/packages/manager/src/features/Profile/OAuthClients/EditOAuthClientDrawer.tsx @@ -1,11 +1,10 @@ -import { Checkbox, FormControl, Notice } from '@linode/ui'; +import { Checkbox, FormControl, Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { useUpdateOAuthClientMutation } from 'src/queries/account/oauth'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; diff --git a/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.tsx b/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.tsx index 3236b371132..2cdb29c1f93 100644 --- a/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.tsx +++ b/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Code } from 'src/components/Code/Code'; import { Drawer } from 'src/components/Drawer'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useCreateSSHKeyMutation } from 'src/queries/profile/profile'; import { handleFormikBlur } from 'src/utilities/formikTrimUtil'; diff --git a/packages/manager/src/features/Profile/SSHKeys/EditSSHKeyDrawer.tsx b/packages/manager/src/features/Profile/SSHKeys/EditSSHKeyDrawer.tsx index 49078d5333d..8258586566b 100644 --- a/packages/manager/src/features/Profile/SSHKeys/EditSSHKeyDrawer.tsx +++ b/packages/manager/src/features/Profile/SSHKeys/EditSSHKeyDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; @@ -6,7 +6,6 @@ import { useEffect } from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useUpdateSSHKeyMutation } from 'src/queries/profile/profile'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; diff --git a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.styles.ts b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.styles.ts index a004081854d..b61769836c7 100644 --- a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.styles.ts +++ b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.styles.ts @@ -1,9 +1,8 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; -import { TextField } from 'src/components/TextField'; export const StyledActionsPanel = styled(ActionsPanel, { label: 'StyledActionsPanel', diff --git a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx index 41bab8c59ed..54d13df8077 100644 --- a/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx +++ b/packages/manager/src/features/StackScripts/StackScriptForm/StackScriptForm.tsx @@ -1,9 +1,8 @@ -import { InputAdornment, Paper } from '@linode/ui'; +import { InputAdornment, Paper, TextField } from '@linode/ui'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; import { ImageSelect } from 'src/components/ImageSelect/ImageSelect'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; diff --git a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx index 607a946b355..e75da3550ed 100644 --- a/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx +++ b/packages/manager/src/features/StackScripts/UserDefinedFieldsPanel/FieldTypes/UserDefinedText.tsx @@ -1,10 +1,9 @@ -import { omittedProps } from '@linode/ui'; +import { TextField, omittedProps } from '@linode/ui'; import { styled } from '@mui/material/styles'; import * as React from 'react'; import { AccessPanel } from 'src/components/AccessPanel/AccessPanel'; import { RenderGuard } from 'src/components/RenderGuard'; -import { TextField } from 'src/components/TextField'; import type { UserDefinedField } from '@linode/api-v4/lib/stackscripts'; diff --git a/packages/manager/src/features/Support/AttachFileListItem.tsx b/packages/manager/src/features/Support/AttachFileListItem.tsx index 58b26d7bf3d..0dc98a2273d 100644 --- a/packages/manager/src/features/Support/AttachFileListItem.tsx +++ b/packages/manager/src/features/Support/AttachFileListItem.tsx @@ -1,4 +1,4 @@ -import { InputAdornment } from '@linode/ui'; +import { InputAdornment, TextField } from '@linode/ui'; import Close from '@mui/icons-material/Close'; import CloudUpload from '@mui/icons-material/CloudUpload'; import Grid from '@mui/material/Unstable_Grid2'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { makeStyles } from 'tss-react/mui'; import { LinearProgress } from 'src/components/LinearProgress'; -import { TextField } from 'src/components/TextField'; import type { FileAttachment } from './index'; import type { Theme } from '@mui/material/styles'; diff --git a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TicketReply.tsx b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TicketReply.tsx index 1bcd53c2e61..776c4aa6646 100644 --- a/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TicketReply.tsx +++ b/packages/manager/src/features/Support/SupportTicketDetail/TabbedReply/TicketReply.tsx @@ -1,7 +1,6 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; -import { TextField } from 'src/components/TextField'; - export interface Props { error?: string; handleChange: (value: string) => void; diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketAccountLimitFields.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketAccountLimitFields.tsx index 271ff3dfbcc..ac5d2af6b96 100644 --- a/packages/manager/src/features/Support/SupportTickets/SupportTicketAccountLimitFields.tsx +++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketAccountLimitFields.tsx @@ -1,8 +1,8 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { Link } from 'src/components/Link'; -import { TextField } from 'src/components/TextField'; import { useAccount } from 'src/queries/account/account'; import { useSpecificTypes, useTypeQuery } from 'src/queries/types'; import { extendTypesQueryResult } from 'src/utilities/extendType'; diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx index d009c0e262f..1ffda85aebe 100644 --- a/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx +++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx @@ -1,6 +1,6 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { uploadAttachment } from '@linode/api-v4/lib/support'; -import { Box, Notice } from '@linode/ui'; +import { Box, Notice, TextField } from '@linode/ui'; import { update } from 'ramda'; import * as React from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; @@ -11,7 +11,6 @@ import { Accordion } from 'src/components/Accordion'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Dialog } from 'src/components/Dialog/Dialog'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useCreateSupportTicketMutation } from 'src/queries/support'; import { sendSupportTicketExitEvent } from 'src/utilities/analytics/customEventAnalytics'; diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx index d834f55fb2d..42e28edd16e 100644 --- a/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx +++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketProductSelectionFields.tsx @@ -1,9 +1,8 @@ -import { FormHelperText } from '@linode/ui'; +import { FormHelperText, TextField } from '@linode/ui'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; -import { TextField } from 'src/components/TextField'; import { useAllDatabasesQuery } from 'src/queries/databases/databases'; import { useAllDomainsQuery } from 'src/queries/domains'; import { useAllFirewallsQuery } from 'src/queries/firewalls'; diff --git a/packages/manager/src/features/Support/SupportTickets/SupportTicketSMTPFields.tsx b/packages/manager/src/features/Support/SupportTickets/SupportTicketSMTPFields.tsx index fcb6e1e6dcc..c87ae25706e 100644 --- a/packages/manager/src/features/Support/SupportTickets/SupportTicketSMTPFields.tsx +++ b/packages/manager/src/features/Support/SupportTickets/SupportTicketSMTPFields.tsx @@ -1,7 +1,7 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { TextField } from 'src/components/TextField'; import { useAccount } from 'src/queries/account/account'; import { SMTP_FIELD_NAME_TO_LABEL_MAP } from './constants'; diff --git a/packages/manager/src/features/Users/CreateUserDrawer.tsx b/packages/manager/src/features/Users/CreateUserDrawer.tsx index b704b22da5e..4810355a83b 100644 --- a/packages/manager/src/features/Users/CreateUserDrawer.tsx +++ b/packages/manager/src/features/Users/CreateUserDrawer.tsx @@ -1,12 +1,11 @@ import { createUser } from '@linode/api-v4/lib/account'; -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import * as React from 'react'; import { withRouter } from 'react-router-dom'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; -import { TextField } from 'src/components/TextField'; import { Toggle } from 'src/components/Toggle/Toggle'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor'; diff --git a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx index 1fcec78bafb..71ba7091a5c 100644 --- a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx +++ b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.tsx @@ -1,9 +1,8 @@ -import { Button, Paper } from '@linode/ui'; +import { Button, Paper, TextField } from '@linode/ui'; import { useSnackbar } from 'notistack'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; -import { TextField } from 'src/components/TextField'; import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants'; import { useMutateProfile, useProfile } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx b/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx index 9af9ef3a82a..26ec27c2f88 100644 --- a/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx +++ b/packages/manager/src/features/Users/UserProfile/UsernamePanel.tsx @@ -1,10 +1,9 @@ -import { Button, Paper } from '@linode/ui'; +import { Button, Paper, TextField } from '@linode/ui'; import { useSnackbar } from 'notistack'; import React from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useHistory } from 'react-router-dom'; -import { TextField } from 'src/components/TextField'; import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants'; import { useUpdateUserMutation } from 'src/queries/account/users'; diff --git a/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCTopSectionContent.tsx b/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCTopSectionContent.tsx index 76cf04787a2..0befdae844f 100644 --- a/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCTopSectionContent.tsx +++ b/packages/manager/src/features/VPCs/VPCCreate/FormComponents/VPCTopSectionContent.tsx @@ -1,9 +1,9 @@ +import { TextField } from '@linode/ui'; import * as React from 'react'; import { useLocation } from 'react-router-dom'; import { Link } from 'src/components/Link'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; -import { TextField } from 'src/components/TextField'; import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; import { getQueryParamsFromQueryString } from 'src/utilities/queryParams'; diff --git a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx index 23b328a0c10..fde1a471489 100644 --- a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx +++ b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx @@ -1,10 +1,9 @@ -import { Button, FormHelperText, Stack } from '@linode/ui'; +import { Button, FormHelperText, Stack, TextField } from '@linode/ui'; import Close from '@mui/icons-material/Close'; import { styled } from '@mui/material/styles'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; -import { TextField } from 'src/components/TextField'; import { RESERVED_IP_NUMBER, calculateAvailableIPv4sRFC1918, diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx index 2ec401a49fd..db865279dcb 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx @@ -5,6 +5,7 @@ import { Checkbox, FormHelperText, Notice, + TextField, TooltipIcon, } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; @@ -17,7 +18,6 @@ import { Drawer } from 'src/components/Drawer'; import { FormControlLabel } from 'src/components/FormControlLabel'; import { Link } from 'src/components/Link'; import { RemovableSelectionsListTable } from 'src/components/RemovableSelectionsList/RemovableSelectionsListTable'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect'; import { diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx index 535c698bafc..ef9819f366d 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx @@ -1,12 +1,11 @@ import { yupResolver } from '@hookform/resolvers/yup'; -import { FormHelperText, Notice, Stack } from '@linode/ui'; +import { FormHelperText, Notice, Stack, TextField } from '@linode/ui'; import { createSubnetSchema } from '@linode/validation'; import * as React from 'react'; import { Controller, useForm } from 'react-hook-form'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useCreateSubnetMutation, useVPCQuery } from 'src/queries/vpcs/vpcs'; import { diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx index 9dfb64e400e..d4a58da251d 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetEditDrawer.tsx @@ -1,10 +1,9 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { useFormik } from 'formik'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; -import { TextField } from 'src/components/TextField'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useUpdateSubnetMutation } from 'src/queries/vpcs/vpcs'; import { getErrorMap } from 'src/utilities/errorUtils'; diff --git a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx index c9034543df6..4254e5d38a5 100644 --- a/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCLanding/VPCEditDrawer.tsx @@ -1,4 +1,4 @@ -import { Notice } from '@linode/ui'; +import { Notice, TextField } from '@linode/ui'; import { updateVPCSchema } from '@linode/validation/lib/vpcs.schema'; import { useFormik } from 'formik'; import * as React from 'react'; @@ -6,7 +6,6 @@ import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; -import { TextField } from 'src/components/TextField'; import { useGrants, useProfile } from 'src/queries/profile/profile'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useUpdateVPCMutation } from 'src/queries/vpcs/vpcs'; diff --git a/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx b/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx index 96bf7ebd401..85b9c4b6110 100644 --- a/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/CloneVolumeDrawer.tsx @@ -1,4 +1,4 @@ -import { Box, Checkbox, Notice } from '@linode/ui'; +import { Box, Checkbox, Notice, TextField } from '@linode/ui'; import { CloneVolumeSchema } from '@linode/validation/lib/volumes.schema'; import { useFormik } from 'formik'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Drawer } from 'src/components/Drawer'; import { BLOCK_STORAGE_CLONING_INHERITANCE_CAVEAT } from 'src/components/Encryption/constants'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { useEventsPollingActions } from 'src/queries/events/events'; import { useGrants } from 'src/queries/profile/profile'; diff --git a/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx b/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx index 4e700f58bfb..e617af813dc 100644 --- a/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx +++ b/packages/manager/src/features/Volumes/EditVolumeDrawer.tsx @@ -1,4 +1,4 @@ -import { Box, Checkbox, Notice } from '@linode/ui'; +import { Box, Checkbox, Notice, TextField } from '@linode/ui'; import { UpdateVolumeSchema } from '@linode/validation'; import { useFormik } from 'formik'; import React from 'react'; @@ -8,7 +8,6 @@ import { Drawer } from 'src/components/Drawer'; import { BLOCK_STORAGE_ENCRYPTION_SETTING_IMMUTABLE_COPY } from 'src/components/Encryption/constants'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { useGrants } from 'src/queries/profile/profile'; import { useUpdateVolumeMutation } from 'src/queries/volumes/volumes'; import { diff --git a/packages/manager/src/features/Volumes/VolumeCreate.tsx b/packages/manager/src/features/Volumes/VolumeCreate.tsx index 5fafdef0237..0ff208fc275 100644 --- a/packages/manager/src/features/Volumes/VolumeCreate.tsx +++ b/packages/manager/src/features/Volumes/VolumeCreate.tsx @@ -1,4 +1,12 @@ -import { Box, Button, Notice, Paper, Stack, TooltipIcon } from '@linode/ui'; +import { + Box, + Button, + Notice, + Paper, + Stack, + TextField, + TooltipIcon, +} from '@linode/ui'; import { CreateVolumeSchema } from '@linode/validation/lib/volumes.schema'; import { useTheme } from '@mui/material/styles'; import { createLazyRoute } from '@tanstack/react-router'; @@ -22,7 +30,6 @@ import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryp import { ErrorMessage } from 'src/components/ErrorMessage'; import { LandingHeader } from 'src/components/LandingHeader'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { MAX_VOLUME_SIZE } from 'src/constants'; import { EUAgreementCheckbox } from 'src/features/Account/Agreements/EUAgreementCheckbox'; diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/LinodeVolumeCreateForm.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/LinodeVolumeCreateForm.tsx index ad3a3ae8d98..d59ba9ea695 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/LinodeVolumeCreateForm.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/LinodeVolumeCreateForm.tsx @@ -1,4 +1,4 @@ -import { Box, Notice } from '@linode/ui'; +import { Box, Notice, TextField } from '@linode/ui'; import { CreateVolumeSchema } from '@linode/validation/lib/volumes.schema'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; @@ -14,7 +14,6 @@ import { import { Encryption } from 'src/components/Encryption/Encryption'; import { useIsBlockStorageEncryptionFeatureEnabled } from 'src/components/Encryption/utils'; import { TagsInput } from 'src/components/TagsInput/TagsInput'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { MAX_VOLUME_SIZE } from 'src/constants'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; diff --git a/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx b/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx index 97abcfe4927..b7594a7b851 100644 --- a/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx +++ b/packages/manager/src/features/Volumes/VolumeDrawer/SizeField.tsx @@ -3,11 +3,11 @@ import { CircleProgress, FormHelperText, InputAdornment, + TextField, } from '@linode/ui'; import * as React from 'react'; import { makeStyles } from 'tss-react/mui'; -import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { MAX_VOLUME_SIZE } from 'src/constants'; import { useVolumeTypesQuery } from 'src/queries/volumes/volumes'; diff --git a/packages/manager/src/features/Volumes/VolumesLanding.tsx b/packages/manager/src/features/Volumes/VolumesLanding.tsx index 2ea24e180ab..55cdd9941f4 100644 --- a/packages/manager/src/features/Volumes/VolumesLanding.tsx +++ b/packages/manager/src/features/Volumes/VolumesLanding.tsx @@ -1,4 +1,9 @@ -import { CircleProgress, IconButton, InputAdornment } from '@linode/ui'; +import { + CircleProgress, + IconButton, + InputAdornment, + TextField, +} from '@linode/ui'; import CloseIcon from '@mui/icons-material/Close'; import { createLazyRoute } from '@tanstack/react-router'; import * as React from 'react'; @@ -17,7 +22,6 @@ import { TableHead } from 'src/components/TableHead'; import { TableRow } from 'src/components/TableRow'; import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty'; import { TableSortCell } from 'src/components/TableSortCell'; -import { TextField } from 'src/components/TextField'; import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useOrder } from 'src/hooks/useOrder'; import { usePagination } from 'src/hooks/usePagination'; diff --git a/packages/ui/.changeset/pr-11290-added-1732046096599.md b/packages/ui/.changeset/pr-11290-added-1732046096599.md new file mode 100644 index 00000000000..2ba73245c9c --- /dev/null +++ b/packages/ui/.changeset/pr-11290-added-1732046096599.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Added +--- + +Migrate `TextField` component and `convertToKebabCase` utility function to `ui` package ([#11290](https://github.com/linode/manager/pull/11290)) diff --git a/packages/ui/src/assets/icons/index.ts b/packages/ui/src/assets/icons/index.ts index c6e3040446d..18a225e108e 100644 --- a/packages/ui/src/assets/icons/index.ts +++ b/packages/ui/src/assets/icons/index.ts @@ -2,7 +2,8 @@ export { default as AlertIcon } from './alert.svg'; export { default as CheckIcon } from './check.svg'; export { default as CheckboxIcon } from './checkbox.svg'; export { default as CheckboxCheckedIcon } from './checkboxChecked.svg'; +export { default as PlusSignIcon } from './plusSign.svg'; export { default as RadioIcon } from './radio.svg'; export { default as RadioIconRadioed } from './radioRadioed.svg'; -export { default as WarningIcon } from './warning.svg'; export { default as ReloadIcon } from './reload.svg'; +export { default as WarningIcon } from './warning.svg'; diff --git a/packages/manager/src/assets/icons/plusSign.svg b/packages/ui/src/assets/icons/plusSign.svg similarity index 100% rename from packages/manager/src/assets/icons/plusSign.svg rename to packages/ui/src/assets/icons/plusSign.svg diff --git a/packages/ui/src/components/Button/StyledTagButton.ts b/packages/ui/src/components/Button/StyledTagButton.ts index 1df2c8b3661..33954319dd2 100644 --- a/packages/ui/src/components/Button/StyledTagButton.ts +++ b/packages/ui/src/components/Button/StyledTagButton.ts @@ -1,7 +1,7 @@ import { omittedProps } from '../../utilities'; import { styled } from '@mui/material/styles'; -import Plus from 'src/assets/icons/plusSign.svg'; +import { PlusSignIcon } from '../../assets/icons'; import { Button } from './Button'; @@ -33,7 +33,7 @@ export const StyledTagButton = styled(Button, { }), })); -export const StyledPlusIcon = styled(Plus, { +export const StyledPlusIcon = styled(PlusSignIcon, { label: 'StyledPlusIcon', })(({ theme, ...props }) => ({ color: props.disabled diff --git a/packages/manager/src/components/TextField.stories.tsx b/packages/ui/src/components/TextField/TextField.stories.tsx similarity index 98% rename from packages/manager/src/components/TextField.stories.tsx rename to packages/ui/src/components/TextField/TextField.stories.tsx index a1e25a632fd..bfea2020a6b 100644 --- a/packages/manager/src/components/TextField.stories.tsx +++ b/packages/ui/src/components/TextField/TextField.stories.tsx @@ -1,4 +1,4 @@ -import { InputAdornment } from '@linode/ui'; +import { InputAdornment } from '../InputAdornment'; import React from 'react'; import { TextField } from './TextField'; diff --git a/packages/manager/src/components/TextField.test.tsx b/packages/ui/src/components/TextField/TextField.test.tsx similarity index 94% rename from packages/manager/src/components/TextField.test.tsx rename to packages/ui/src/components/TextField/TextField.test.tsx index 566e6c0ec9d..86ae0d05822 100644 --- a/packages/manager/src/components/TextField.test.tsx +++ b/packages/ui/src/components/TextField/TextField.test.tsx @@ -1,10 +1,10 @@ -import { InputAdornment } from '@linode/ui'; +import { InputAdornment } from '../InputAdornment'; import { fireEvent, getDefaultNormalizer } from '@testing-library/react'; import * as React from 'react'; -import { renderWithTheme } from 'src/utilities/testHelpers'; - import { TextField } from './TextField'; +import { expect, describe, it } from 'vitest'; +import { renderWithTheme } from '../../utilities/testHelpers'; describe('TextField', () => { const props = { diff --git a/packages/manager/src/components/TextField.tsx b/packages/ui/src/components/TextField/TextField.tsx similarity index 96% rename from packages/manager/src/components/TextField.tsx rename to packages/ui/src/components/TextField/TextField.tsx index 2f77ce6b07d..86c42977159 100644 --- a/packages/manager/src/components/TextField.tsx +++ b/packages/ui/src/components/TextField/TextField.tsx @@ -1,20 +1,18 @@ -import { - Box, - CircleProgress, - FormHelperText, - InputAdornment, - InputLabel, - TooltipIcon, -} from '@linode/ui'; +import { Box } from '../Box'; +import { CircleProgress } from '../CircleProgress'; +import { FormHelperText } from '../FormHelperText'; +import { InputAdornment } from '../InputAdornment'; +import { InputLabel } from '../InputLabel'; +import { TooltipIcon } from '../TooltipIcon'; +import { convertToKebabCase } from '../../utilities'; import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown'; import { useTheme } from '@mui/material/styles'; import { default as _TextField } from '@mui/material/TextField'; import { clamp } from 'ramda'; import * as React from 'react'; -import { convertToKebabCase } from 'src/utilities/convertToKebobCase'; - -import type { BoxProps, TooltipProps } from '@linode/ui'; +import type { BoxProps } from '../Box'; +import { TooltipProps } from '../Tooltip'; import type { StandardTextFieldProps } from '@mui/material/TextField'; interface BaseProps { diff --git a/packages/ui/src/components/TextField/index.ts b/packages/ui/src/components/TextField/index.ts new file mode 100644 index 00000000000..665fa3cb54f --- /dev/null +++ b/packages/ui/src/components/TextField/index.ts @@ -0,0 +1 @@ +export * from './TextField'; diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 431a80dccfd..5920697ab0d 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -18,6 +18,7 @@ export * from './Paper'; export * from './Radio'; export * from './RadioGroup'; export * from './Stack'; +export * from './TextField'; export * from './Tooltip'; export * from './TooltipIcon'; export * from './VisibilityTooltip'; diff --git a/packages/manager/src/utilities/convertToKebobCase.ts b/packages/ui/src/utilities/convertToKebabCase.ts similarity index 100% rename from packages/manager/src/utilities/convertToKebobCase.ts rename to packages/ui/src/utilities/convertToKebabCase.ts diff --git a/packages/ui/src/utilities/index.ts b/packages/ui/src/utilities/index.ts index 2993dc70047..88ff2a5cf49 100644 --- a/packages/ui/src/utilities/index.ts +++ b/packages/ui/src/utilities/index.ts @@ -1 +1,2 @@ +export * from './convertToKebabCase'; export * from './omittedProps'; From 7472d8be4928212da4156b99500082ceadd82368 Mon Sep 17 00:00:00 2001 From: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:04:37 -0500 Subject: [PATCH 20/92] feat: [M3-8903] - Improve Image Status Column (#11257) * inital work * update tests and refine * improve more * add changesets * disable wrapping in the status column --------- Co-authored-by: Banks Nussman --- .../pr-11257-removed-1731594995947.md | 5 ++ packages/api-v4/src/images/types.ts | 6 +- .../pr-11257-changed-1731595083756.md | 5 ++ .../core/images/machine-image-upload.spec.ts | 8 +-- .../ImageRegions/ImageRegionRow.tsx | 15 ++-- .../Images/ImagesLanding/ImageRow.test.tsx | 4 +- .../Images/ImagesLanding/ImageRow.tsx | 60 ++-------------- .../Images/ImagesLanding/ImageStatus.test.tsx | 64 +++++++++++++++++ .../Images/ImagesLanding/ImageStatus.tsx | 72 +++++++++++++++++++ 9 files changed, 167 insertions(+), 72 deletions(-) create mode 100644 packages/api-v4/.changeset/pr-11257-removed-1731594995947.md create mode 100644 packages/manager/.changeset/pr-11257-changed-1731595083756.md create mode 100644 packages/manager/src/features/Images/ImagesLanding/ImageStatus.test.tsx create mode 100644 packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx diff --git a/packages/api-v4/.changeset/pr-11257-removed-1731594995947.md b/packages/api-v4/.changeset/pr-11257-removed-1731594995947.md new file mode 100644 index 00000000000..041f5e6375e --- /dev/null +++ b/packages/api-v4/.changeset/pr-11257-removed-1731594995947.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Removed +--- + +`deleted` from the `ImageStatus` type ([#11257](https://github.com/linode/manager/pull/11257)) diff --git a/packages/api-v4/src/images/types.ts b/packages/api-v4/src/images/types.ts index cc1572d449b..4a707b361d8 100644 --- a/packages/api-v4/src/images/types.ts +++ b/packages/api-v4/src/images/types.ts @@ -1,8 +1,4 @@ -export type ImageStatus = - | 'available' - | 'creating' - | 'deleted' - | 'pending_upload'; +export type ImageStatus = 'available' | 'creating' | 'pending_upload'; export type ImageCapabilities = 'cloud-init' | 'distributed-sites'; diff --git a/packages/manager/.changeset/pr-11257-changed-1731595083756.md b/packages/manager/.changeset/pr-11257-changed-1731595083756.md new file mode 100644 index 00000000000..81f496b0900 --- /dev/null +++ b/packages/manager/.changeset/pr-11257-changed-1731595083756.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Improve the status column on the Images landing page ([#11257](https://github.com/linode/manager/pull/11257)) diff --git a/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts b/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts index 3f265228211..49c084aef1b 100644 --- a/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts +++ b/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts @@ -85,7 +85,7 @@ const assertFailed = (label: string, id: string, message: string) => { cy.get(`[data-qa-image-cell="${id}"]`).within(() => { fbtVisible(label); - fbtVisible('Failed'); + fbtVisible('Upload Failed'); fbtVisible('N/A'); }); }; @@ -99,7 +99,7 @@ const assertFailed = (label: string, id: string, message: string) => { const assertProcessing = (label: string, id: string) => { cy.get(`[data-qa-image-cell="${id}"]`).within(() => { fbtVisible(label); - fbtVisible('Processing'); + fbtVisible('Pending Upload'); fbtVisible('Pending'); }); }; @@ -172,7 +172,7 @@ describe('machine image', () => { cy.get(`[data-qa-image-cell="${mockImage.id}"]`).within(() => { cy.findByText(initialLabel).should('be.visible'); - cy.findByText('Ready').should('be.visible'); + cy.findByText('Available').should('be.visible'); ui.actionMenu .findByTitle(`Action menu for Image ${initialLabel}`) @@ -262,7 +262,7 @@ describe('machine image', () => { ui.toast.assertMessage(availableMessage); cy.get(`[data-qa-image-cell="${imageId}"]`).within(() => { fbtVisible(label); - fbtVisible('Ready'); + fbtVisible('Available'); }); }); }); diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageRegions/ImageRegionRow.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageRegions/ImageRegionRow.tsx index 0e8b07b032f..c7e499e4ae0 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImageRegions/ImageRegionRow.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImageRegions/ImageRegionRow.tsx @@ -7,7 +7,7 @@ import { StatusIcon } from 'src/components/StatusIcon/StatusIcon'; import { Typography } from 'src/components/Typography'; import { useRegionsQuery } from 'src/queries/regions/regions'; -import type { ImageRegionStatus } from '@linode/api-v4'; +import type { ImageRegionStatus, ImageStatus } from '@linode/api-v4'; import type { Status } from 'src/components/StatusIcon/StatusIcon'; type ExtendedImageRegionStatus = 'unsaved' | ImageRegionStatus; @@ -34,9 +34,7 @@ export const ImageRegionRow = (props: Props) => { {status} - + { ); }; -const IMAGE_REGION_STATUS_TO_STATUS_ICON_STATUS: Readonly< - Record +export const imageStatusIconMap: Readonly< + Record > = { available: 'active', creating: 'other', pending: 'other', 'pending deletion': 'other', - 'pending replication': 'inactive', + 'pending replication': 'other', + pending_upload: 'other', replicating: 'other', - timedout: 'inactive', + timedout: 'error', unsaved: 'inactive', }; diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageRow.test.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageRow.test.tsx index e507170eb39..0440e31b822 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImageRow.test.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImageRow.test.tsx @@ -30,7 +30,7 @@ describe('Image Table Row', () => { capabilities: ['cloud-init', 'distributed-sites'], regions: [ { region: 'us-east', status: 'available' }, - { region: 'us-southeast', status: 'pending' }, + { region: 'us-southeast', status: 'available' }, ], size: 300, total_size: 600, @@ -45,7 +45,7 @@ describe('Image Table Row', () => { // Check to see if the row rendered some data expect(getByText(image.label)).toBeVisible(); expect(getByText(image.id)).toBeVisible(); - expect(getByText('Ready')).toBeVisible(); + expect(getByText('Available')).toBeVisible(); expect(getByText('Cloud-init, Distributed')).toBeVisible(); expect(getByText('2 Regions')).toBeVisible(); expect(getByText('0.29 GB')).toBeVisible(); // Size is converted from MB to GB - 300 / 1024 = 0.292 diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageRow.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageRow.tsx index b3640a1b32a..1aa8bc50842 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImageRow.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImageRow.tsx @@ -6,15 +6,14 @@ import { Hidden } from 'src/components/Hidden'; import { LinkButton } from 'src/components/LinkButton'; import { TableCell } from 'src/components/TableCell'; import { TableRow } from 'src/components/TableRow'; -import { Typography } from 'src/components/Typography'; import { useFlags } from 'src/hooks/useFlags'; import { useProfile } from 'src/queries/profile/profile'; -import { capitalizeAllWords } from 'src/utilities/capitalize'; import { formatDate } from 'src/utilities/formatDate'; import { pluralize } from 'src/utilities/pluralize'; import { convertStorageUnit } from 'src/utilities/unitConversions'; import { ImagesActionMenu } from './ImagesActionMenu'; +import { ImageStatus } from './ImageStatus'; import type { Handlers } from './ImagesActionMenu'; import type { Event, Image, ImageCapabilities } from '@linode/api-v4'; @@ -49,29 +48,12 @@ export const ImageRow = (props: Props) => { const { data: profile } = useProfile(); const flags = useFlags(); - const isFailed = status === 'pending_upload' && event?.status === 'failed'; - const compatibilitiesList = multiRegionsEnabled ? capabilities.map((capability) => capabilityMap[capability]).join(', ') : ''; - const getStatusForImage = (status: string) => { - switch (status) { - case 'creating': - return ( - - ); - case 'available': - return 'Ready'; - case 'pending_upload': - return isFailed ? 'Failed' : 'Processing'; - default: - return capitalizeAllWords(status.replace('_', ' ')); - } - }; + const isFailedUpload = + image.status === 'pending_upload' && event?.status === 'failed'; const getSizeForImage = ( size: number, @@ -87,7 +69,7 @@ export const ImageRow = (props: Props) => { }).format(sizeInGB); return `${formattedSizeInGB} GB`; - } else if (isFailed) { + } else if (isFailedUpload) { return 'N/A'; } else { return 'Pending'; @@ -113,7 +95,9 @@ export const ImageRow = (props: Props) => { )} - {getStatusForImage(status)} + + + {multiRegionsEnabled && ( @@ -170,33 +154,3 @@ export const ImageRow = (props: Props) => { ); }; - -export const isImageUpdating = (e?: Event) => { - // Make Typescript happy, since this function can otherwise technically return undefined - if (!e) { - return false; - } - return ( - e?.action === 'disk_imagize' && ['scheduled', 'started'].includes(e.status) - ); -}; - -const progressFromEvent = (e?: Event) => { - return e?.status === 'started' && e?.percent_complete - ? e.percent_complete - : undefined; -}; - -const ProgressDisplay: React.FC<{ - progress: number | undefined; - text: string; -}> = (props) => { - const { progress, text } = props; - const displayProgress = progress ? `${progress}%` : `scheduled`; - - return ( - - {text}: {displayProgress} - - ); -}; diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageStatus.test.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.test.tsx new file mode 100644 index 00000000000..9ddc794d746 --- /dev/null +++ b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.test.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import { eventFactory, imageFactory } from 'src/factories'; +import { renderWithTheme } from 'src/utilities/testHelpers'; + +import { ImageStatus } from './ImageStatus'; + +describe('ImageStatus', () => { + it('renders the image status', () => { + const image = imageFactory.build({ status: 'available' }); + + const { getByText } = renderWithTheme( + + ); + + expect(getByText('Available')).toBeVisible(); + }); + + it('should render the first region status if any region status is not "available"', () => { + const image = imageFactory.build({ + regions: [ + { region: 'us-west', status: 'available' }, + { region: 'us-east', status: 'pending replication' }, + ], + status: 'available', + }); + + const { getByText } = renderWithTheme( + + ); + + expect(getByText('Pending Replication')).toBeVisible(); + }); + + it('should render the image status with a percent if an in progress event is happening', () => { + const image = imageFactory.build({ status: 'creating' }); + const event = eventFactory.build({ + percent_complete: 20, + status: 'started', + }); + + const { getByText } = renderWithTheme( + + ); + + expect(getByText('Creating (20%)')).toBeVisible(); + }); + + it('should render "Upload Failed" if image is pending_upload, but we have a failed image upload event', () => { + const image = imageFactory.build({ status: 'pending_upload' }); + const event = eventFactory.build({ + action: 'image_upload', + message: 'Image too large when uncompressed', + status: 'failed', + }); + + const { getByLabelText, getByText } = renderWithTheme( + + ); + + expect(getByText('Upload Failed')).toBeVisible(); + expect(getByLabelText('Image too large when uncompressed')).toBeVisible(); + }); +}); diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx new file mode 100644 index 00000000000..f16a9909552 --- /dev/null +++ b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx @@ -0,0 +1,72 @@ +import { Stack } from '@linode/ui'; +import React from 'react'; + +import { StatusIcon } from 'src/components/StatusIcon/StatusIcon'; +import { TooltipIcon } from 'src/components/TooltipIcon'; +import { capitalizeAllWords } from 'src/utilities/capitalize'; + +import { imageStatusIconMap } from './ImageRegions/ImageRegionRow'; + +import type { Event, Image } from '@linode/api-v4'; + +interface Props { + /** + * The most revent event associated with this Image + */ + event: Event | undefined; + /** + * The Image object + */ + image: Image; +} + +export const ImageStatus = (props: Props) => { + const { event, image } = props; + + if ( + event && + event.status === 'failed' && + event.action === 'image_upload' && + image.status === 'pending_upload' + ) { + // If we have a recent image upload failure, we show the user + // that the upload failed and why. + return ( + + + Upload Failed + {event.message && ( + + )} + + ); + } + + const imageRegionStatus = image.regions.find((r) => r.status !== 'available') + ?.status; + + if (imageRegionStatus) { + // If we have any non-available region statuses, expose the first one as the Image's status to the user + return ( + + + {capitalizeAllWords(imageRegionStatus)} + + ); + } + + const showEventProgress = + event && event.status === 'started' && event.percent_complete !== null; + + return ( + + + {capitalizeAllWords(image.status.replace('_', ' '))} + {showEventProgress && ` (${event.percent_complete}%)`} + + ); +}; From 40b335b57ea26f654b48401d7f6ce73dea9bcf58 Mon Sep 17 00:00:00 2001 From: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:51:39 -0500 Subject: [PATCH 21/92] fix: Broken TooltipIcon import (#11305) Co-authored-by: Banks Nussman --- .../manager/src/features/Images/ImagesLanding/ImageStatus.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx index f16a9909552..e4733b62d10 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImageStatus.tsx @@ -1,8 +1,7 @@ -import { Stack } from '@linode/ui'; +import { Stack, TooltipIcon } from '@linode/ui'; import React from 'react'; import { StatusIcon } from 'src/components/StatusIcon/StatusIcon'; -import { TooltipIcon } from 'src/components/TooltipIcon'; import { capitalizeAllWords } from 'src/utilities/capitalize'; import { imageStatusIconMap } from './ImageRegions/ImageRegionRow'; From dba4bfa0d59238ec7a4858c0bf03057cf4423501 Mon Sep 17 00:00:00 2001 From: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:47:49 -0500 Subject: [PATCH 22/92] test: [M3-8934] - Fix test failure in `linode-storage.spec.ts` (#11304) * Increase Linode create timeout const value from 3 minutes to 4 minutes * Fix test failure by using larger value when adding and resizing disks, clean up and doc improvements * Added changeset: Fix test failure in `linode-storage.spec.ts` --- .../pr-11304-tests-1732212737295.md | 5 + .../e2e/core/linodes/linode-storage.spec.ts | 105 ++++++++++++------ .../cypress/support/constants/linodes.ts | 4 +- 3 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 packages/manager/.changeset/pr-11304-tests-1732212737295.md diff --git a/packages/manager/.changeset/pr-11304-tests-1732212737295.md b/packages/manager/.changeset/pr-11304-tests-1732212737295.md new file mode 100644 index 00000000000..cf39b85111c --- /dev/null +++ b/packages/manager/.changeset/pr-11304-tests-1732212737295.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Fix test failure in `linode-storage.spec.ts` ([#11304](https://github.com/linode/manager/pull/11304)) diff --git a/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts b/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts index 4172c5d0414..88ff834b419 100644 --- a/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts @@ -1,8 +1,8 @@ /* eslint-disable sonarjs/no-duplicate-string */ +import { LINODE_CREATE_TIMEOUT } from 'support/constants/linodes'; import { Linode } from '@linode/api-v4'; import { authenticate } from 'support/api/authentication'; import { createTestLinode } from 'support/util/linodes'; -import { containsVisible, fbtClick, fbtVisible } from 'support/helpers'; import { ui } from 'support/ui'; import { cleanUp } from 'support/util/cleanup'; import { @@ -11,21 +11,30 @@ import { interceptResizeDisks, } from 'support/intercepts/linodes'; -// 3 minutes. -const LINODE_PROVISION_TIMEOUT = 180_000; - +/** + * Waits for a Linode to finish provisioning by checking the details page status indicator. + */ const waitForProvision = () => { - cy.findByText('PROVISIONING', { timeout: LINODE_PROVISION_TIMEOUT }).should( + cy.findByText('PROVISIONING', { timeout: LINODE_CREATE_TIMEOUT }).should( 'not.exist' ); - cy.findByText('BOOTING', { timeout: LINODE_PROVISION_TIMEOUT }).should( + cy.findByText('BOOTING', { timeout: LINODE_CREATE_TIMEOUT }).should( 'not.exist' ); - cy.findByText('Creating', { timeout: LINODE_PROVISION_TIMEOUT }).should( + cy.findByText('Creating', { timeout: LINODE_CREATE_TIMEOUT }).should( 'not.exist' ); }; +// Size values (in MB) to use when creating and resizing disks. +const DISK_CREATE_SIZE_MB = 512; +const DISK_RESIZE_SIZE_MB = 768; + +/** + * Deletes an in-use disk of the given name. + * + * @param diskName - Name of disk to attempt to delete. + */ const deleteInUseDisk = (diskName: string) => { waitForProvision(); @@ -55,6 +64,11 @@ const deleteInUseDisk = (diskName: string) => { ).should('be.visible'); }; +/** + * Deletes a disk of the given name. + * + * @param diskName - Name of disk to delete. + */ const deleteDisk = (diskName: string) => { waitForProvision(); @@ -77,14 +91,24 @@ const deleteDisk = (diskName: string) => { }); }; -const addDisk = (diskName: string) => { - cy.contains('PROVISIONING', { timeout: LINODE_PROVISION_TIMEOUT }).should( +/** + * Adds a new disk with the given name and optional size. + * + * If `diskSize` is not specified, the new disk will be 512MB. + * + * @param diskName - Name of new disk. + * @param diskSize - Size of new disk in megabytes. + */ +const addDisk = (diskName: string, diskSize: number = DISK_CREATE_SIZE_MB) => { + cy.contains('PROVISIONING', { timeout: LINODE_CREATE_TIMEOUT }).should( 'not.exist' ); - cy.contains('BOOTING', { timeout: LINODE_PROVISION_TIMEOUT }).should( + cy.contains('BOOTING', { timeout: LINODE_CREATE_TIMEOUT }).should( 'not.exist' ); - containsVisible('OFFLINE'); + cy.contains('OFFLINE', { timeout: LINODE_CREATE_TIMEOUT }).should( + 'be.visible' + ); ui.button.findByTitle('Add a Disk').click(); @@ -93,7 +117,7 @@ const addDisk = (diskName: string) => { .should('be.visible') .within(() => { cy.findByLabelText('Label (required)').type(diskName); - cy.findByLabelText('Size (required)').clear().type('1'); + cy.findByLabelText('Size (required)').clear().type(`${diskSize}`); ui.button.findByTitle('Create').click(); }); @@ -109,13 +133,17 @@ describe('linode storage tab', () => { cleanUp(['linodes', 'lke-clusters']); }); + /* + * - Confirms UI flow end-to-end when a user attempts to delete a Linode disk that's in use. + * - Confirms that error occurs and user is informed that they must power down their Linode. + */ it('try to delete in use disk', () => { const diskName = 'Ubuntu 24.04 LTS Disk'; cy.defer(() => createTestLinode({ booted: true })).then((linode) => { interceptDeleteDisks(linode.id).as('deleteDisk'); cy.visitWithLogin(`linodes/${linode.id}/storage`); - containsVisible('RUNNING'); - fbtVisible(diskName); + cy.contains('RUNNING', { timeout: LINODE_CREATE_TIMEOUT }); + cy.findByText(diskName).should('be.visible'); ui.button.findByTitle('Add a Disk').should('be.disabled'); @@ -129,6 +157,11 @@ describe('linode storage tab', () => { }); }); + /* + * - Confirms UI flow end-to-end when a user deletes a Linode disk. + * - Confirms that user can successfully delete a disk from a Linode. + * - Confirms that Cloud Manager UI automatically updates to reflect deleted disk. + */ it('delete disk', () => { const diskName = 'cy-test-disk'; cy.defer(() => createTestLinode({ image: null })).then((linode) => { @@ -136,7 +169,7 @@ describe('linode storage tab', () => { interceptAddDisks(linode.id).as('addDisk'); cy.visitWithLogin(`/linodes/${linode.id}/storage`); addDisk(diskName); - fbtVisible(diskName); + cy.findByText(diskName).should('be.visible'); cy.wait('@addDisk').its('response.statusCode').should('eq', 200); // Disk should show "Creating". We must wait for it to finish "Creating" before we try to delete the disk cy.findByText('Creating', { exact: false }).should('be.visible'); @@ -155,51 +188,61 @@ describe('linode storage tab', () => { }); }); + /* + * - Confirms UI flow when user adds a disk to a Linode. + * - Confirms that Cloud Manager UI automatically updates to reflect new disk. + */ it('add a disk', () => { const diskName = 'cy-test-disk'; cy.defer(() => createTestLinode({ image: null })).then((linode: Linode) => { interceptAddDisks(linode.id).as('addDisk'); cy.visitWithLogin(`/linodes/${linode.id}/storage`); addDisk(diskName); - fbtVisible(diskName); + cy.findByText(diskName).should('be.visible'); cy.wait('@addDisk').its('response.statusCode').should('eq', 200); }); }); + /* + * - Confirms UI flow when a user resizes an existing disk. + * - Confirms that Cloud Manager UI automatically updates to reflect resize. + */ it('resize disk', () => { const diskName = 'Debian 10 Disk'; - cy.defer(() => createTestLinode({ image: null })).then((linode: Linode) => { + cy.defer(() => + createTestLinode({ image: null }, { securityMethod: 'powered_off' }) + ).then((linode: Linode) => { interceptAddDisks(linode.id).as('addDisk'); interceptResizeDisks(linode.id).as('resizeDisk'); cy.visitWithLogin(`/linodes/${linode.id}/storage`); + waitForProvision(); addDisk(diskName); - fbtVisible(diskName); + + cy.findByText(diskName).should('be.visible'); cy.wait('@addDisk').its('response.statusCode').should('eq', 200); - containsVisible('Creating'); - cy.contains('PROVISIONING', { timeout: LINODE_PROVISION_TIMEOUT }).should( - 'not.exist' - ); - cy.contains('BOOTING', { timeout: LINODE_PROVISION_TIMEOUT }).should( - 'not.exist' - ); - cy.contains('Creating', { timeout: LINODE_PROVISION_TIMEOUT }).should( - 'not.exist' - ); + + cy.findByLabelText('List of Disks').within(() => { + // Confirm that "Creating" message appears then disappears. + cy.contains('Creating').should('be.visible'); + cy.contains('Creating').should('not.exist'); + }); + cy.get(`[data-qa-disk="${diskName}"]`).within(() => { - fbtClick('Resize'); + cy.findByText('Resize').should('be.visible').click(); }); ui.drawer .findByTitle(`Resize ${diskName}`) .should('be.visible') .within(() => { - cy.findByLabelText('Size (required)').clear().type('2'); + cy.findByLabelText('Size (required)') + .clear() + .type(`${DISK_RESIZE_SIZE_MB}`); ui.button.findByTitle('Resize').click(); }); cy.wait('@resizeDisk').its('response.statusCode').should('eq', 200); ui.toast.assertMessage('Disk queued for resizing.'); - // cy.findByText('Resizing', { exact: false }).should('be.visible'); ui.toast.assertMessage( `Disk ${diskName} on Linode ${linode.label} has been resized.` ); diff --git a/packages/manager/cypress/support/constants/linodes.ts b/packages/manager/cypress/support/constants/linodes.ts index f6eb377242c..c1985c9036f 100644 --- a/packages/manager/cypress/support/constants/linodes.ts +++ b/packages/manager/cypress/support/constants/linodes.ts @@ -5,6 +5,6 @@ /** * Length of time to wait for a Linode to be created. * - * Equals 3 minutes. + * Equals 4 minutes. */ -export const LINODE_CREATE_TIMEOUT = 180_000; +export const LINODE_CREATE_TIMEOUT = 240_000; From c81667da23287d9e58a4902be9981cfe2190d07f Mon Sep 17 00:00:00 2001 From: Alban Bailly <130582365+abailly-akamai@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:43:43 -0500 Subject: [PATCH 23/92] change: [M3-8916] - Implement Dialogs/Drawers loading patterns (#11273) * initial commit: drawer + dialog * post rebase fix * fix units * consolidate confirm dialogs * closeAfterTransition={false} * Cleanup * Added changeset: Implement Dialogs/Drawers loading patterns * Improved coverage * better approach for content dueing closing transition * revert test changes since not needed anymore --- .../pr-11273-changed-1731988661274.md | 5 + .../ConfirmationDialog/ConfirmationDialog.tsx | 95 ++-------- .../src/components/Dialog/Dialog.stories.tsx | 65 ++++++- .../manager/src/components/Dialog/Dialog.tsx | 83 ++++++--- .../components/DialogTitle/DialogTitle.tsx | 7 +- .../manager/src/components/Drawer.stories.tsx | 78 +++++++++ .../manager/src/components/Drawer.test.tsx | 77 ++++++++ packages/manager/src/components/Drawer.tsx | 165 ++++++++++-------- .../TypeToConfirmDialog.tsx | 12 +- .../Account/SwitchAccountDrawer.test.tsx | 8 +- .../Images/ImagesLanding/EditImageDrawer.tsx | 11 +- .../ImagesLanding/RebuildImageDrawer.tsx | 12 +- .../KubeControlPaneACLDrawer.tsx | 17 +- .../LinodeBackup/CaptureSnapshot.tsx | 8 +- .../CaptureSnapshotConfirmationDialog.tsx | 4 +- .../LinodeNetworking/EditIPRDNSDrawer.tsx | 14 +- .../LinodeNetworking/EditRangeRDNSDrawer.tsx | 14 +- .../LinodeResize/ResizeConfirmationDialog.tsx | 2 +- .../AccessKeyLanding/HostNamesDrawer.test.tsx | 6 +- .../BucketLanding/CreateBucketDrawer.tsx | 16 +- .../BucketLanding/OMC_CreateBucketDrawer.tsx | 16 +- .../PlacementGroupsLinodes.tsx | 5 +- .../PlacementGroupsUnassignModal.tsx | 5 +- .../VPCs/VPCDetail/SubnetCreateDrawer.tsx | 20 +-- packages/ui/src/foundations/themes/light.ts | 1 + 25 files changed, 474 insertions(+), 272 deletions(-) create mode 100644 packages/manager/.changeset/pr-11273-changed-1731988661274.md create mode 100644 packages/manager/src/components/Drawer.test.tsx diff --git a/packages/manager/.changeset/pr-11273-changed-1731988661274.md b/packages/manager/.changeset/pr-11273-changed-1731988661274.md new file mode 100644 index 00000000000..1987e465514 --- /dev/null +++ b/packages/manager/.changeset/pr-11273-changed-1731988661274.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Implement Dialogs/Drawers loading patterns ([#11273](https://github.com/linode/manager/pull/11273)) diff --git a/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx index af4167de76d..43937633388 100644 --- a/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx +++ b/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx @@ -1,20 +1,15 @@ -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import { styled } from '@mui/material/styles'; +import { Stack } from '@linode/ui'; import * as React from 'react'; -import { DialogTitle } from 'src/components/DialogTitle/DialogTitle'; +import { Dialog } from 'src/components/Dialog/Dialog'; -import type { DialogProps } from '@mui/material/Dialog'; +import type { DialogProps } from 'src/components/Dialog/Dialog'; export interface ConfirmationDialogProps extends DialogProps { - actions?: ((props: any) => JSX.Element) | JSX.Element; - error?: JSX.Element | string; - onClose: () => void; - onExited?: () => void; - title: string; + /** + * The actions to be displayed in the dialog. + */ + actions?: ((props: DialogProps) => JSX.Element) | JSX.Element; } /** @@ -27,75 +22,21 @@ export interface ConfirmationDialogProps extends DialogProps { * */ export const ConfirmationDialog = (props: ConfirmationDialogProps) => { - const { - actions, - children, - error, - onClose, - onExited, - title, - ...dialogProps - } = props; + const { actions, children, ...dialogProps } = props; return ( - { - if (reason !== 'backdropClick') { - onClose(); - } - }} - PaperProps={{ role: undefined }} - data-qa-dialog - data-qa-drawer - data-testid="drawer" - role="dialog" - > - - - {children} - {error && {error}} - - + + {children} + {actions && typeof actions === 'function' ? actions(dialogProps) : actions} - - + + ); }; - -const StyledDialog = styled(Dialog, { - label: 'StyledDialog', -})({ - '& .MuiDialogTitle-root': { - marginBottom: '10px', - }, -}); - -const StyledDialogActions = styled(DialogActions, { - label: 'StyledDialogActions', -})({ - '& button': { - marginBottom: 0, - }, - justifyContent: 'flex-end', -}); - -const StyledDialogContent = styled(DialogContent, { - label: 'StyledDialogContent', -})({ - display: 'flex', - flexDirection: 'column', -}); - -const StyledErrorText = styled(DialogContentText, { - label: 'StyledErrorText', -})(({ theme }) => ({ - color: theme.palette.error.dark, - marginTop: theme.spacing(2), -})); diff --git a/packages/manager/src/components/Dialog/Dialog.stories.tsx b/packages/manager/src/components/Dialog/Dialog.stories.tsx index b437e650829..ceee99b8e9e 100644 --- a/packages/manager/src/components/Dialog/Dialog.stories.tsx +++ b/packages/manager/src/components/Dialog/Dialog.stories.tsx @@ -1,6 +1,9 @@ +import { Button } from '@linode/ui'; import { action } from '@storybook/addon-actions'; +import { useArgs } from '@storybook/preview-api'; import React from 'react'; +import { Typography } from '../Typography'; import { Dialog } from './Dialog'; import type { Meta, StoryObj } from '@storybook/react'; @@ -35,6 +38,10 @@ const meta: Meta = { title: { description: 'Title that appears in the heading of the dialog.' }, }, args: { + disableAutoFocus: true, + disableEnforceFocus: true, + disablePortal: true, + disableScrollLock: true, fullHeight: false, fullWidth: false, maxWidth: 'md', @@ -42,11 +49,6 @@ const meta: Meta = { open: true, style: { position: 'unset' }, title: 'This is a Dialog', - titleBottomBorder: false, - disableAutoFocus: true, - disableEnforceFocus: true, - disablePortal: true, - disableScrollLock: true, }, component: Dialog, title: 'Components/Dialog', @@ -63,3 +65,56 @@ export const Default: Story = { ), }; + +export const Fetching: Story = { + args: { + isFetching: true, + }, + render: (args) => { + const DrawerExampleWrapper = () => { + const [{ isFetching, open }, updateArgs] = useArgs(); + + React.useEffect(() => { + if (open) { + setTimeout(() => { + updateArgs({ isFetching: false, onClose: action('onClose') }); + }, 1500); + } else { + setTimeout(() => { + updateArgs({ isFetching: true, onClose: action('onClose') }); + }, 300); + } + }, [isFetching, open, updateArgs]); + + return ( + <> + + updateArgs({ open: false })} + open={open} + > + + A most sober dialog, with a title and a description. + + + + + ); + }; + + return DrawerExampleWrapper(); + }, +}; diff --git a/packages/manager/src/components/Dialog/Dialog.tsx b/packages/manager/src/components/Dialog/Dialog.tsx index 1504ef243ce..f4df58c8269 100644 --- a/packages/manager/src/components/Dialog/Dialog.tsx +++ b/packages/manager/src/components/Dialog/Dialog.tsx @@ -1,4 +1,4 @@ -import { Box, Notice, omittedProps } from '@linode/ui'; +import { Box, CircleProgress, Notice, omittedProps } from '@linode/ui'; import _Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; import { styled, useTheme } from '@mui/material/styles'; @@ -10,12 +10,32 @@ import { convertForAria } from 'src/utilities/stringUtils'; import type { DialogProps as _DialogProps } from '@mui/material/Dialog'; export interface DialogProps extends _DialogProps { + /** + * Additional CSS to be applied to the Dialog. + */ className?: string; + /** + * Error that will be shown in the dialog. + */ error?: string; + /** + * Let the Dialog take up the entire height of the viewport. + */ fullHeight?: boolean; + /** + * Whether the drawer is fetching the entity's data. + * + * If true, the drawer will feature a loading spinner for its content. + */ + isFetching?: boolean; + /** + * Subtitle that will be shown in the dialog. + */ subtitle?: string; + /** + * Title that will be shown in the dialog. + */ title: string; - titleBottomBorder?: boolean; } /** @@ -31,7 +51,7 @@ export interface DialogProps extends _DialogProps { * - **Confirmation** * - Users must confirm a choice * - **Deletion** - * - The user must confirm the deleteion of an entity + * - The user must confirm the deletion of an entity * - Can require user to type the entity name to confirm deletion * * > Clicking off of the modal will not close it. @@ -47,26 +67,44 @@ export const Dialog = React.forwardRef( error, fullHeight, fullWidth, + isFetching, maxWidth = 'md', onClose, + open, subtitle, title, - titleBottomBorder, ...rest } = props; const titleID = convertForAria(title); + // Store the last valid children and title in refs + // This is to prevent flashes of content during the drawer's closing transition, + // and its content becomes potentially undefined + const lastChildrenRef = React.useRef(children); + const lastTitleRef = React.useRef(title); + // Update refs when the drawer is open and content is matched + if (open && children) { + lastChildrenRef.current = children; + lastTitleRef.current = title; + } + return ( { + if (onClose && reason !== 'backdropClick') { + onClose({}, 'escapeKeyDown'); + } + }} aria-labelledby={titleID} + closeAfterTransition={false} data-qa-dialog data-qa-drawer data-testid="drawer" fullHeight={fullHeight} fullWidth={fullWidth} maxWidth={(fullWidth && maxWidth) ?? undefined} - onClose={onClose} + open={open} ref={ref} role="dialog" title={title} @@ -79,11 +117,11 @@ export const Dialog = React.forwardRef( > onClose && onClose({}, 'backdropClick')} + isFetching={isFetching} + onClose={() => onClose?.({}, 'escapeKeyDown')} subtitle={subtitle} - title={title} + title={lastTitleRef.current} /> - {titleBottomBorder && } - {error && } - {children} + {isFetching ? ( + + + + ) : ( + <> + {error && } + {lastChildrenRef.current} + + )} @@ -106,19 +152,10 @@ const StyledDialog = styled(_Dialog, { '& .MuiDialog-paper': { height: props.fullHeight ? '100vh' : undefined, maxHeight: '100%', + minWidth: '500px', padding: 0, + [theme.breakpoints.down('md')]: { + minWidth: '380px', + }, }, - '& .MuiDialogActions-root': { - display: 'flex', - justifyContent: 'flex-end', - marginTop: theme.spacing(2), - }, -})); - -const StyledHr = styled('hr', { label: 'StyledHr' })(({ theme }) => ({ - backgroundColor: theme.tokens.color.Neutrals[20], - border: 'none', - height: 1, - margin: '-2em 8px 0px 8px', - width: '100%', })); diff --git a/packages/manager/src/components/DialogTitle/DialogTitle.tsx b/packages/manager/src/components/DialogTitle/DialogTitle.tsx index ecf01cd0200..62b7272aaaa 100644 --- a/packages/manager/src/components/DialogTitle/DialogTitle.tsx +++ b/packages/manager/src/components/DialogTitle/DialogTitle.tsx @@ -9,6 +9,7 @@ import type { SxProps, Theme } from '@mui/material'; interface DialogTitleProps { className?: string; id?: string; + isFetching?: boolean; onClose?: () => void; subtitle?: string; sx?: SxProps; @@ -17,7 +18,7 @@ interface DialogTitleProps { const DialogTitle = (props: DialogTitleProps) => { const ref = React.useRef(null); - const { className, id, onClose, subtitle, sx, title } = props; + const { className, id, isFetching, onClose, subtitle, sx, title } = props; React.useEffect(() => { if (ref.current === null) { @@ -48,8 +49,8 @@ const DialogTitle = (props: DialogTitleProps) => { data-qa-dialog-title={title} data-qa-drawer-title={title} > - {title} - {onClose != null && ( + {!isFetching && title} + {onClose !== null && ( { const DrawerExampleWrapper = () => { const [open, setOpen] = React.useState(args.open); + return ( <> + updateArgs({ open: false })} + open={open} + > + + I smirked at their Kale chips banh-mi fingerstache brunch in + Williamsburg. + + + Meanwhile in my closet-style flat in Red-Hook, my pour-over coffee + glitched on my vinyl record player while I styled the bottom left + corner of my beard. Those artisan tacos I ordered were infused + with turmeric and locally sourced honey, a true farm-to-table + vibe. Pabst Blue Ribbon in hand, I sat on my reclaimed wood bench + next to the macramé plant holder. + + + Narwhal selfies dominated my Instagram feed, hashtagged with "slow + living" and "normcore aesthetics". My kombucha brewing kit arrived + just in time for me to ferment my own chai-infused blend. As I + adjusted my vintage round glasses, a tiny house documentary + started playing softly in the background. The retro typewriter + clacked as I typed out my minimalist poetry on sustainably sourced + paper. The sun glowed through the window, shining light on the + delightful cracks of my Apple watch. + + It was Saturday. + updateArgs({ open: false }), + }} + primaryButtonProps={{ label: 'Save' }} + /> + + + ); + }; + + return DrawerExampleWrapper(); + }, +}; + export default meta; diff --git a/packages/manager/src/components/Drawer.test.tsx b/packages/manager/src/components/Drawer.test.tsx new file mode 100644 index 00000000000..9d0245b43ec --- /dev/null +++ b/packages/manager/src/components/Drawer.test.tsx @@ -0,0 +1,77 @@ +import { Button } from '@linode/ui'; +import { fireEvent, waitFor } from '@testing-library/react'; +import * as React from 'react'; + +import { renderWithTheme } from 'src/utilities/testHelpers'; + +import { Drawer } from './Drawer'; + +import type { DrawerProps } from './Drawer'; + +describe('Drawer', () => { + const defaultArgs: DrawerProps = { + onClose: vi.fn(), + open: false, + title: 'This is a Drawer', + }; + + it.each([ + ['not render', false], + ['render', true], + ])('should %s a Dialog with title when open is %s', (_, isOpen) => { + const { queryByTestId, queryByText } = renderWithTheme( + + ); + + const title = queryByText('This is a Drawer'); + const drawer = queryByTestId('drawer'); + + if (isOpen) { + expect(title).toBeInTheDocument(); + expect(drawer).toBeInTheDocument(); + } else { + expect(title).not.toBeInTheDocument(); + expect(drawer).not.toBeInTheDocument(); + } + }); + + it('should render a Dialog with children if provided', () => { + const { getByText } = renderWithTheme( + +

Child items can go here!

+
+ ); + + expect(getByText('Child items can go here!')).toBeInTheDocument(); + }); + + it('should call onClose when the Dialog close button is clicked', async () => { + const { getByRole } = renderWithTheme( + +

Child items can go here!

+ +
+ ); + + const closeButton = getByRole('button', { name: 'Close' }); + fireEvent.click(closeButton); + + await waitFor(() => { + expect(defaultArgs.onClose).toHaveBeenCalled(); + }); + }); + + it('should render a Dialog with a loading spinner if isFetching is true', () => { + const { getByRole } = renderWithTheme( + + ); + + expect(getByRole('progressbar')).toBeVisible(); + }); +}); diff --git a/packages/manager/src/components/Drawer.tsx b/packages/manager/src/components/Drawer.tsx index 07da8da9af5..9f8694711da 100644 --- a/packages/manager/src/components/Drawer.tsx +++ b/packages/manager/src/components/Drawer.tsx @@ -1,4 +1,4 @@ -import { IconButton } from '@linode/ui'; +import { Box, CircleProgress, IconButton } from '@linode/ui'; import Close from '@mui/icons-material/Close'; import _Drawer from '@mui/material/Drawer'; import Grid from '@mui/material/Unstable_Grid2'; @@ -8,14 +8,16 @@ import { makeStyles } from 'tss-react/mui'; import { Typography } from 'src/components/Typography'; import { convertForAria } from 'src/utilities/stringUtils'; -import type { DrawerProps } from '@mui/material/Drawer'; +import type { DrawerProps as _DrawerProps } from '@mui/material/Drawer'; import type { Theme } from '@mui/material/styles'; -interface Props extends DrawerProps { +export interface DrawerProps extends _DrawerProps { /** - * Callback fired when the drawer closing animation has completed. + * Whether the drawer is fetching the entity's data. + * + * If true, the drawer will feature a loading spinner for its content. */ - onExited?: () => void; + isFetching?: boolean; /** * Title that appears at the top of the drawer */ @@ -27,56 +29,6 @@ interface Props extends DrawerProps { wide?: boolean; } -const useStyles = makeStyles()((theme: Theme) => ({ - button: { - '& :hover, & :focus': { - backgroundColor: theme.palette.primary.main, - color: theme.tokens.color.Neutrals.White, - }, - '& > span': { - padding: 2, - }, - minHeight: 'auto', - minWidth: 'auto', - padding: 0, - }, - common: { - '& .actionPanel': { - display: 'flex', - justifyContent: 'flex-end', - marginTop: theme.spacing(), - }, - '& .selectionCard': { - flexBasis: '100%', - maxWidth: '100%', - }, - padding: theme.spacing(4), - [theme.breakpoints.down('sm')]: { - padding: theme.spacing(2), - }, - }, - default: { - [theme.breakpoints.down('sm')]: { - maxWidth: 445, - width: '100%', - }, - width: 480, - }, - drawerHeader: { - '&&': { - marginBottom: theme.spacing(2), - }, - }, - title: { - marginRight: theme.spacing(4), - wordBreak: 'break-word', - }, - wide: { - maxWidth: 700, - width: '100%', - }, -})); - /** * ## Overview * - Drawers are essentially modal dialogs that appear on the right of the screen rather than the center. @@ -88,13 +40,22 @@ const useStyles = makeStyles()((theme: Theme) => ({ * - Clicking a button on the screen opens the drawer. * - Drawers can be closed by pressing the `esc` key, clicking the “X” icon, or clicking the “Cancel” button. */ -export const Drawer = (props: Props) => { +export const Drawer = (props: DrawerProps) => { const { classes, cx } = useStyles(); - - const { children, onClose, onExited, title, wide, ...rest } = props; - + const { children, isFetching, onClose, open, title, wide, ...rest } = props; const titleID = convertForAria(title); + // Store the last valid children and title in refs + // This is to prevent flashes of content during the drawer's closing transition, + // and its content becomes potentially undefined + const lastChildrenRef = React.useRef(children); + const lastTitleRef = React.useRef(title); + // Update refs when the drawer is open and content is matched + if (open && children) { + lastChildrenRef.current = children; + lastTitleRef.current = title; + } + return ( <_Drawer classes={{ @@ -103,17 +64,17 @@ export const Drawer = (props: Props) => { [classes.wide]: wide, }), }} - onClose={(event, reason) => { + onClose={(_, reason) => { if (onClose && reason !== 'backdropClick') { - onClose(event, reason); + onClose({}, 'escapeKeyDown'); } }} anchor="right" + open={open} {...rest} aria-labelledby={titleID} data-qa-drawer data-testid="drawer" - onTransitionExited={onExited} role="dialog" > { wrap="nowrap" > - - {title} - + {isFetching ? null : ( + + {title} + + )} { aria-label="Close drawer" color="primary" data-qa-close-drawer - onClick={onClose as (e: any) => void} + onClick={() => onClose?.({}, 'escapeKeyDown')} size="large" > - {children} + {isFetching ? ( + + + + ) : ( + children + )} ); }; + +const useStyles = makeStyles()((theme: Theme) => ({ + button: { + '& :hover, & :focus': { + backgroundColor: theme.palette.primary.main, + color: theme.tokens.color.Neutrals.White, + }, + '& > span': { + padding: 2, + }, + minHeight: 'auto', + minWidth: 'auto', + padding: 0, + }, + common: { + '& .actionPanel': { + display: 'flex', + justifyContent: 'flex-end', + marginTop: theme.spacing(), + }, + '& .selectionCard': { + flexBasis: '100%', + maxWidth: '100%', + }, + padding: theme.spacing(4), + [theme.breakpoints.down('sm')]: { + padding: theme.spacing(2), + }, + }, + default: { + [theme.breakpoints.down('sm')]: { + maxWidth: 445, + width: '100%', + }, + width: 480, + }, + drawerHeader: { + '&&': { + marginBottom: theme.spacing(2), + }, + }, + title: { + marginRight: theme.spacing(4), + wordBreak: 'break-word', + }, + wide: { + maxWidth: 700, + width: '100%', + }, +})); diff --git a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx index 55e555abe50..73a00ea2d21 100644 --- a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx +++ b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx @@ -37,10 +37,6 @@ interface EntityInfo { } interface TypeToConfirmDialogProps { - /** - * Chidlren are rendered above the TypeToConfirm input - */ - children?: React.ReactNode; /** * Props to be allow disabling the input */ @@ -69,10 +65,6 @@ interface TypeToConfirmDialogProps { * The click handler for the primary button */ onClick: () => void; - /** - * Optional callback to be executed when the closing animation has completed - */ - onExited?: () => void; /** * The open/closed state of the dialog */ @@ -95,7 +87,6 @@ export const TypeToConfirmDialog = (props: CombinedProps) => { loading, onClick, onClose, - onExited, open, textFieldStyle, title, @@ -133,7 +124,7 @@ export const TypeToConfirmDialog = (props: CombinedProps) => { secondaryButtonProps={{ 'data-testid': 'cancel', label: 'Cancel', - onClick: onClose, + onClick: onClose ? () => onClose({}, 'escapeKeyDown') : undefined, }} style={{ padding: 0 }} /> @@ -144,7 +135,6 @@ export const TypeToConfirmDialog = (props: CombinedProps) => { actions={actions} error={errors ? errors[0].reason : undefined} onClose={onClose} - onExited={onExited} open={open} title={title} > diff --git a/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx b/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx index aca09b3f886..d18c3686f87 100644 --- a/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx +++ b/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import * as React from 'react'; import { profileFactory } from 'src/factories/profile'; @@ -57,7 +57,7 @@ describe('SwitchAccountDrawer', () => { ); }); - it('should close when the close icon is clicked', () => { + it('should close when the close icon is clicked', async () => { const { getByLabelText } = renderWithTheme( ); @@ -65,6 +65,8 @@ describe('SwitchAccountDrawer', () => { const closeIconButton = getByLabelText('Close drawer'); fireEvent.click(closeIconButton); - expect(props.onClose).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(props.onClose).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx b/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx index 15abddf8b5b..f2d07646087 100644 --- a/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/EditImageDrawer.tsx @@ -58,7 +58,7 @@ export const EditImageDrawer = (props: Props) => { ...values, description: safeDescription, }) - .then(onClose) + .then(handleClose) .catch((errors: APIError[]) => { for (const error of errors) { if ( @@ -74,8 +74,13 @@ export const EditImageDrawer = (props: Props) => { }); }); + const handleClose = () => { + reset(); + onClose(); + }; + return ( - + {!canCreateImage && ( { secondaryButtonProps={{ disabled: !canCreateImage, label: 'Cancel', - onClick: onClose, + onClick: handleClose, }} style={{ marginTop: 16 }} /> diff --git a/packages/manager/src/features/Images/ImagesLanding/RebuildImageDrawer.tsx b/packages/manager/src/features/Images/ImagesLanding/RebuildImageDrawer.tsx index fc881f29992..9340c610165 100644 --- a/packages/manager/src/features/Images/ImagesLanding/RebuildImageDrawer.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/RebuildImageDrawer.tsx @@ -39,7 +39,7 @@ export const RebuildImageDrawer = (props: Props) => { return; } - onClose(); + handleClose(); history.push({ pathname: `/linodes/${values.linodeId}/rebuild`, @@ -49,10 +49,14 @@ export const RebuildImageDrawer = (props: Props) => { }); }); + const handleClose = () => { + reset(); + onClose(); + }; + return ( @@ -105,7 +109,7 @@ export const RebuildImageDrawer = (props: Props) => { }} secondaryButtonProps={{ label: 'Cancel', - onClick: onClose, + onClick: handleClose, }} style={{ marginTop: 16 }} /> diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx index d19782be804..b47498afd0b 100644 --- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx +++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/KubeControlPaneACLDrawer.tsx @@ -135,7 +135,7 @@ export const KubeControlPlaneACLDrawer = ( control_plane: payload, }); } - closeDrawer(); + handleClose(); } catch (errors) { for (const error of errors) { setError(error?.field ?? 'root', { message: error.reason }); @@ -144,14 +144,13 @@ export const KubeControlPlaneACLDrawer = ( } }; + const handleClose = () => { + reset(); + closeDrawer(); + }; + return ( - reset()} - open={open} - title={'Control Plane ACL'} - wide - > + diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx index c1f6e08f34d..e985eee2576 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshot.tsx @@ -47,6 +47,11 @@ export const CaptureSnapshot = (props: Props) => { }, }); + const handleClose = () => { + setIsSnapshotConfirmationDialogOpen(false); + reset(); + }; + const hasErrorFor = getErrorMap(['label'], snapshotError); return ( @@ -84,8 +89,7 @@ export const CaptureSnapshot = (props: Props) => { setIsSnapshotConfirmationDialogOpen(false)} - onExited={() => reset()} + onClose={handleClose} onSnapshot={() => snapshotForm.handleSubmit()} open={isSnapshotConfirmationDialogOpen} /> diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshotConfirmationDialog.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshotConfirmationDialog.tsx index d3ce406cae1..7568974343e 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshotConfirmationDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/CaptureSnapshotConfirmationDialog.tsx @@ -8,13 +8,12 @@ interface Props { error: string | undefined; loading: boolean; onClose: () => void; - onExited: () => void; onSnapshot: () => void; open: boolean; } export const CaptureSnapshotConfirmationDialog = (props: Props) => { - const { error, loading, onClose, onExited, onSnapshot, open } = props; + const { error, loading, onClose, onSnapshot, open } = props; const actions = ( { actions={actions} error={error} onClose={onClose} - onExited={onExited} open={open} title="Take a snapshot?" > diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx index 36ba47f6fc9..597112500e9 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditIPRDNSDrawer.tsx @@ -40,24 +40,20 @@ export const EditIPRDNSDrawer = (props: Props) => { enqueueSnackbar(`Successfully updated RDNS for ${ip?.address}`, { variant: 'success', }); - onClose(); + handleClose(); }, }); - const onExited = () => { + const handleClose = () => { formik.resetForm(); reset(); + onClose(); }; const errorMap = getErrorMap(['rdns'], error); return ( - +
{Boolean(errorMap.none) && ( {errorMap.none} @@ -80,7 +76,7 @@ export const EditIPRDNSDrawer = (props: Props) => { loading: isPending, type: 'submit', }} - secondaryButtonProps={{ label: 'Cancel', onClick: onClose }} + secondaryButtonProps={{ label: 'Cancel', onClick: handleClose }} style={{ marginTop: 16 }} />
diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx index aa91a6a728a..c1c532a3dfa 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/EditRangeRDNSDrawer.tsx @@ -63,26 +63,22 @@ export const EditRangeRDNSDrawer = (props: Props) => { enqueueSnackbar(`Successfully updated RDNS for ${range?.range}`, { variant: 'success', }); - onClose(); + handleClose(); }, }); const theme = useTheme(); - const onExited = () => { + const handleClose = () => { formik.resetForm(); reset(); + onClose(); }; const errorMap = getErrorMap(['rdns'], error); return ( - +
{Boolean(errorMap.none) && ( @@ -117,7 +113,7 @@ export const EditRangeRDNSDrawer = (props: Props) => { secondaryButtonProps={{ 'data-testid': 'cancel', label: 'Close', - onClick: onClose, + onClick: handleClose, }} style={{ marginTop: 16 }} /> diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/ResizeConfirmationDialog.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/ResizeConfirmationDialog.tsx index 8d7d91068a6..ae81679eb9c 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/ResizeConfirmationDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/ResizeConfirmationDialog.tsx @@ -6,7 +6,7 @@ import { Typography } from 'src/components/Typography'; export interface Props { currentPlan: string; - error?: JSX.Element | string; + error?: string; isOpen: boolean; onClose: () => void; onResize: () => void; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.test.tsx index 2b4e1b9dc80..fd2bc81d4d3 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.test.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.test.tsx @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -75,6 +75,8 @@ describe('HostNamesDrawer', () => { const closeButton = screen.getByRole('button', { name: 'Close drawer' }); await userEvent.click(closeButton); - expect(mockOnClose).toHaveBeenCalled(); + await waitFor(() => { + expect(mockOnClose).toHaveBeenCalled(); + }); }); }); diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx index d6aec436ec4..6c01457358f 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx @@ -109,7 +109,7 @@ export const CreateBucketDrawer = (props: Props) => { } } - onClose(); + handleClose(); } catch (errors) { for (const error of errors) { setError(error?.field ?? 'root', { message: error.reason }); @@ -137,13 +137,13 @@ export const CreateBucketDrawer = (props: Props) => { selectedRegionId: clusterRegion?.id ?? '', }); + const handleClose = () => { + reset(); + onClose(); + }; + return ( - + {isRestrictedUser && ( { : '', type: 'submit', }} - secondaryButtonProps={{ label: 'Cancel', onClick: onClose }} + secondaryButtonProps={{ label: 'Cancel', onClick: handleClose }} /> { } } - onClose(); + handleClose(); } catch (errors) { for (const error of errors) { setError(error?.field ?? 'root', { message: error.reason }); @@ -152,6 +152,11 @@ export const OMC_CreateBucketDrawer = (props: Props) => { } }; + const handleClose = () => { + reset(); + onClose(); + }; + const handleBucketFormSubmit = async ( e: React.FormEvent ) => { @@ -276,12 +281,7 @@ export const OMC_CreateBucketDrawer = (props: Props) => { }, [watchRegion]); return ( - + {isRestrictedUser && ( { : '', type: 'submit', }} - secondaryButtonProps={{ label: 'Cancel', onClick: onClose }} + secondaryButtonProps={{ label: 'Cancel', onClick: handleClose }} /> diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.tsx index 7cc7f432bc9..c89332c6e19 100644 --- a/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.tsx +++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsDetail/PlacementGroupsLinodes/PlacementGroupsLinodes.tsx @@ -71,10 +71,8 @@ export const PlacementGroupsLinodes = (props: Props) => { `/placement-groups/${placementGroup.id}/linodes/unassign/${linode.id}` ); }; - const handleExitedUnassignModal = () => { - setSelectedLinode(undefined); - }; const handleCloseDrawer = () => { + setSelectedLinode(undefined); history.replace(`/placement-groups/${placementGroup.id}/linodes`); }; const isAssignLinodesDrawerOpen = history.location.pathname.includes( @@ -127,7 +125,6 @@ export const PlacementGroupsLinodes = (props: Props) => { /> diff --git a/packages/manager/src/features/PlacementGroups/PlacementGroupsUnassignModal.tsx b/packages/manager/src/features/PlacementGroups/PlacementGroupsUnassignModal.tsx index 98a372e0ad2..1e9c99fb45a 100644 --- a/packages/manager/src/features/PlacementGroups/PlacementGroupsUnassignModal.tsx +++ b/packages/manager/src/features/PlacementGroups/PlacementGroupsUnassignModal.tsx @@ -18,13 +18,12 @@ import type { interface Props { onClose: () => void; - onExited?: () => void; open: boolean; selectedLinode: Linode | undefined; } export const PlacementGroupsUnassignModal = (props: Props) => { - const { onClose, onExited, open, selectedLinode } = props; + const { onClose, open, selectedLinode } = props; const { enqueueSnackbar } = useSnackbar(); const { id: placementGroupId, linodeId } = useParams<{ @@ -104,7 +103,6 @@ export const PlacementGroupsUnassignModal = (props: Props) => { }, }} onClose={onClose} - onExited={onExited} open={open} title="Delete Placement Group" > @@ -118,7 +116,6 @@ export const PlacementGroupsUnassignModal = (props: Props) => { actions={actions} error={error?.[0]?.reason} onClose={onClose} - onExited={onExited} open={open} title={linode?.label ? `Unassign ${linode.label}` : 'Unassign'} > diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx index ef9819f366d..ed7834df2fb 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx @@ -65,7 +65,7 @@ export const SubnetCreateDrawer = (props: Props) => { const onCreateSubnet = async (values: CreateSubnetPayload) => { try { await createSubnet(values); - onClose(); + handleClose(); } catch (errors) { for (const error of errors) { setError(error?.field ?? 'root', { message: error.reason }); @@ -73,16 +73,14 @@ export const SubnetCreateDrawer = (props: Props) => { } }; + const handleClose = () => { + resetForm(); + resetRequest(); + onClose(); + }; + return ( - { - resetForm(); - resetRequest(); - }} - onClose={onClose} - open={open} - title={'Create Subnet'} - > + {errors.root?.message && ( )} @@ -147,7 +145,7 @@ export const SubnetCreateDrawer = (props: Props) => { loading: isPending || isSubmitting, type: 'submit', }} - secondaryButtonProps={{ label: 'Cancel', onClick: onClose }} + secondaryButtonProps={{ label: 'Cancel', onClick: handleClose }} /> diff --git a/packages/ui/src/foundations/themes/light.ts b/packages/ui/src/foundations/themes/light.ts index 78381da15ab..622d44fbe78 100644 --- a/packages/ui/src/foundations/themes/light.ts +++ b/packages/ui/src/foundations/themes/light.ts @@ -666,6 +666,7 @@ export const lightTheme: ThemeOptions = { }, justifyContent: 'flex-start', margin: 0, + marginTop: 24, padding: 24, }, }, From c59c9430bac3d12115ac8a2b41b0c97e2a48e0a9 Mon Sep 17 00:00:00 2001 From: subsingh-akamai Date: Fri, 22 Nov 2024 10:07:10 +0530 Subject: [PATCH 24/92] test: [M3-8393] - Cypress test for Account Maintenance CSV downloads (#11168) * Updated test confirm maintenance details in the tables - to validate entry in downloaded csv files * Added changeset: Cypress test for Account Maintenance CSV downloads * Removed console.log * Removed library - papaparse to parse csv;Implemented script to parse the csv file * Removed it.only from test * Using specific locator for Download CSV and added code to delete downloaded file after assertion * Refeactoring as per review comment for delete files * Added assertion in readFile to ensure file content is not empty --- .../pr-11168-tests-1730108478631.md | 5 + .../core/account/account-maintenance.spec.ts | 124 +++++++++++++++++- packages/manager/cypress/support/util/csv.ts | 48 +++++++ 3 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 packages/manager/.changeset/pr-11168-tests-1730108478631.md create mode 100644 packages/manager/cypress/support/util/csv.ts diff --git a/packages/manager/.changeset/pr-11168-tests-1730108478631.md b/packages/manager/.changeset/pr-11168-tests-1730108478631.md new file mode 100644 index 00000000000..ac1f39b27cc --- /dev/null +++ b/packages/manager/.changeset/pr-11168-tests-1730108478631.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Cypress test for Account Maintenance CSV downloads ([#11168](https://github.com/linode/manager/pull/11168)) diff --git a/packages/manager/cypress/e2e/core/account/account-maintenance.spec.ts b/packages/manager/cypress/e2e/core/account/account-maintenance.spec.ts index 56292a96bd5..ba00f881332 100644 --- a/packages/manager/cypress/e2e/core/account/account-maintenance.spec.ts +++ b/packages/manager/cypress/e2e/core/account/account-maintenance.spec.ts @@ -1,5 +1,6 @@ import { mockGetMaintenance } from 'support/intercepts/account'; import { accountMaintenanceFactory } from 'src/factories'; +import { parseCsv } from 'support/util/csv'; describe('Maintenance', () => { /* @@ -7,6 +8,21 @@ describe('Maintenance', () => { * - When there is no pending maintenance, "No pending maintenance." is shown in the table. * - When there is no completed maintenance, "No completed maintenance." is shown in the table. */ + beforeEach(() => { + const downloadsFolder = Cypress.config('downloadsFolder'); + const filePatterns = '{pending-maintenance*,completed-maintenance*}'; + // Delete the file before the test + cy.exec(`rm -f ${downloadsFolder}/${filePatterns}`, { + failOnNonZeroExit: false, + }).then((result) => { + if (result.code === 0) { + cy.log(`Deleted file: ${filePatterns}`); + } else { + cy.log(`Failed to delete file: ${filePatterns}`); + } + }); + }); + it('table empty when no maintenance', () => { mockGetMaintenance([], []).as('getMaintenance'); @@ -118,12 +134,106 @@ describe('Maintenance', () => { }); }); - // Confirm download buttons work - cy.get('button') - .filter(':contains("Download CSV")') - .should('be.visible') - .should('be.enabled') - .click({ multiple: true }); - // TODO Need to add assertions to confirm CSV contains the expected contents on first trial (M3-8393) + // Validate content of the downloaded CSV for pending maintenance + cy.get('a[download*="pending-maintenance"]') + .invoke('attr', 'download') + .then((fileName) => { + const downloadsFolder = Cypress.config('downloadsFolder'); + + // Locate the element for pending-maintenance and then find its sibling