From 1f3c6a878c7a58b08292b3cf24d25cfbe97d5559 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Thu, 27 Feb 2025 17:40:18 -0500 Subject: [PATCH 001/112] Saving... --- packages/manager/src/assets/icons/close.svg | 3 + .../DebouncedSearchTextField.tsx | 94 ++++++++++++------- .../src/features/Users/UsersLanding.tsx | 64 +++++++++++-- packages/ui/src/foundations/themes/light.ts | 2 +- 4 files changed, 117 insertions(+), 46 deletions(-) create mode 100644 packages/manager/src/assets/icons/close.svg diff --git a/packages/manager/src/assets/icons/close.svg b/packages/manager/src/assets/icons/close.svg new file mode 100644 index 00000000000..96dca5a4443 --- /dev/null +++ b/packages/manager/src/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx b/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx index 2c044a0d631..52873fad9c4 100644 --- a/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx +++ b/packages/manager/src/components/DebouncedSearchTextField/DebouncedSearchTextField.tsx @@ -5,11 +5,13 @@ import { TextField, } from '@linode/ui'; import Clear from '@mui/icons-material/Clear'; -import Search from '@mui/icons-material/Search'; import { styled } from '@mui/material/styles'; import * as React from 'react'; import { debounce } from 'throttle-debounce'; +import Search from 'src/assets/icons/search.svg'; +import Close from 'src/assets/icons/close.svg'; + import type { TextFieldProps } from '@linode/ui'; export interface DebouncedSearchProps extends TextFieldProps { @@ -76,38 +78,44 @@ export const DebouncedSearchTextField = React.memo( return ( - - - ) : ( - clearable && - textFieldValue && ( - { - setTextFieldValue(''); - onSearch(''); - }} - aria-label="Clear" - size="small" - > - ({ - '&&': { - color: theme.color.grey1, - }, - })} - /> - - ) - ), - startAdornment: ( - - - - ), - ...InputProps, + slotProps={{ + input: { + endAdornment: isSearching ? ( + + + + ) : ( + clearable && + textFieldValue && ( + { + setTextFieldValue(''); + onSearch(''); + }} + sx={{ + padding: 0, + }} + aria-label="Clear" + size="small" + > + + + ) + ), + startAdornment: ( + + + + ), + sx: (theme) => ({ + '& .MuiInput-input': { + padding: 0, + }, + font: theme.tokens.typography.Label.Regular.S, + padding: `${theme.tokens.spacing.S6} ${theme.tokens.spacing.S8}`, + }), + ...InputProps, + }, }} className={className} data-qa-debounced-search @@ -123,8 +131,22 @@ export const DebouncedSearchTextField = React.memo( } ); -const StyledSearchIcon = styled(Search)(({ theme }) => ({ - '&&, &&:hover': { - color: theme.color.grey1, +export const StyledClearIcon = styled(Close, { + label: 'StyledClearIcon', +})(({ theme }) => ({ + color: theme.tokens.search.Filled.Icon, + height: '16px', + width: '16px', +})); + +export const StyledSearchIcon = styled(Search, { + label: 'StyledSearchIcon', +})(({ theme }) => ({ + '&&': { + '&:hover': { + color: theme.tokens.search.Disabled.SearchIcon, + }, + + color: theme.tokens.search.Default.SearchIcon, }, })); diff --git a/packages/manager/src/features/Users/UsersLanding.tsx b/packages/manager/src/features/Users/UsersLanding.tsx index 38b7efb4a7c..fed986a21ce 100644 --- a/packages/manager/src/features/Users/UsersLanding.tsx +++ b/packages/manager/src/features/Users/UsersLanding.tsx @@ -1,8 +1,11 @@ +import { getAPIFilterFromQuery } from '@linode/search'; import { Box, Button, Typography } from '@linode/ui'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import * as React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter'; import { Table } from 'src/components/Table'; @@ -36,17 +39,28 @@ export const UsersLanding = () => { const pagination = usePagination(1, 'account-users'); const order = useOrder(); + const location = useLocation(); + const history = useHistory(); + + const queryParams = new URLSearchParams(location.search); + const query = queryParams.get('query') ?? ''; + + const { error: searchError, filter } = getAPIFilterFromQuery(query, { + searchableFieldsWithoutOperator: ['username', 'email'], + }); + const showProxyUserTable = profile?.user_type === 'child' || profile?.user_type === 'proxy'; const usersFilter: Filter = { ['+order']: order.order, ['+order_by']: order.orderBy, - ['user_type']: showProxyUserTable ? 'child' : undefined, + ...filter, + // ['user_type']: showProxyUserTable ? 'child' : undefined, }; - // Since this query is disabled for restricted users, use isInitialLoading. - const { data: users, error, isInitialLoading, refetch } = useAccountUsers({ + // Since this query is disabled for restricted users, use isLoading. + const { data: users, error, isLoading, refetch } = useAccountUsers({ filters: usersFilter, params: { page: pagination.page, @@ -56,14 +70,20 @@ export const UsersLanding = () => { const isRestrictedUser = profile?.restricted; - // Since this query is disabled for restricted users, use isInitialLoading. + // Since this query is disabled for restricted users, use isLoading. const { data: proxyUser, error: proxyUserError, - isInitialLoading: isLoadingProxyUser, + isFetching, + isLoading: isLoadingProxyUser, } = useAccountUsers({ enabled: showProxyUserTable && !isRestrictedUser, - filters: { user_type: 'proxy' }, + filters: usersFilter, + params: { + page: pagination.page, + page_size: pagination.pageSize, + }, + // filters: { user_type: 'proxy' }, }); const isChildAccountAccessRestricted = useRestrictedGlobalGrantCheck({ @@ -91,6 +111,16 @@ export const UsersLanding = () => { setSelectedUsername(username); }; + const handleSearch = (value: string) => { + queryParams.set('page', '1'); + if (value) { + queryParams.set('query', value); + } else { + queryParams.delete('query'); + } + history.push({ search: queryParams.toString() }); + }; + return ( @@ -127,7 +157,7 @@ export const UsersLanding = () => { sx={(theme) => ({ alignItems: 'center', display: 'flex', - justifyContent: showProxyUserTable ? 'space-between' : 'flex-end', + justifyContent: 'space-between', marginBottom: theme.spacing(2), marginTop: theme.spacing(3), })} @@ -144,6 +174,22 @@ export const UsersLanding = () => { User Settings )} + + + ); } diff --git a/packages/manager/src/features/CloudPulse/Alerts/AlertsResources/DisplayAlertResources.tsx b/packages/manager/src/features/CloudPulse/Alerts/AlertsResources/DisplayAlertResources.tsx index 7be38b48811..e9498d43b0a 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/AlertsResources/DisplayAlertResources.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/AlertsResources/DisplayAlertResources.tsx @@ -1,4 +1,4 @@ -import { Checkbox } from '@linode/ui'; +import { Box, Checkbox, Tooltip } from '@linode/ui'; import React from 'react'; import { sortData } from 'src/components/OrderBy'; @@ -13,6 +13,7 @@ import { TableRowError } from 'src/components/TableRowError/TableRowError'; import { TableSortCell } from 'src/components/TableSortCell'; import { isAllPageSelected, isSomeSelected } from '../Utils/AlertResourceUtils'; +import { AlertMaxSelectionText } from './AlertMaxSelectionText'; import { serviceTypeBasedColumns } from './constants'; import type { AlertServiceType } from '@linode/api-v4'; @@ -69,11 +70,21 @@ export interface DisplayAlertResourceProp { */ isSelectionsNeeded?: boolean; + /** + * The maximum number of elements that can be selected + */ + maxSelectionCount?: number; + /** * Callback to scroll till the element required on page change change or sorting change */ scrollToElement: () => void; + /** + * The number of elements that can be selected based on selected resources and maximum selections + */ + selectionsRemaining?: number; + /** * The service type associated with the alert */ @@ -87,7 +98,9 @@ export const DisplayAlertResources = React.memo( handleSelection, isDataLoadingError, isSelectionsNeeded, + maxSelectionCount, scrollToElement, + selectionsRemaining, serviceType, } = props; const pageSize = 25; @@ -149,6 +162,22 @@ export const DisplayAlertResources = React.memo( }, [handleSelection] ); + + const isCheckboxDisabled = ( + isChecked?: boolean, + uncheckedCount?: number + ) => { + if (selectionsRemaining === undefined) { + return false; + } + + if (uncheckedCount === undefined) { + // if uncheckedCount is not passed, just rely on isChecked and selectionsRemaining + return !isChecked && selectionsRemaining === 0; + } + + return selectionsRemaining < uncheckedCount; // find if there is appropriate space for root checkbox to be enabled + }; const columns = serviceTypeBasedColumns[serviceType ?? '']; const colSpanCount = isSelectionsNeeded ? columns.length + 1 @@ -162,120 +191,186 @@ export const DisplayAlertResources = React.memo( handlePageSizeChange, page, pageSize, - }) => ( - <> - - - - {isSelectionsNeeded && ( - - - handleSelectionChange( - paginatedData.map(({ id }) => id), - !isAllPageSelected(paginatedData) - ) - } + }) => { + const isRootCheckBoxDisabled = isCheckboxDisabled( + false, + paginatedData.filter(({ checked }) => !checked).length + ); + return ( + <> +
+ + + {isSelectionsNeeded && ( + - - )} - {columns.map(({ label, sortingKey }) => ( - - handleSort(orderBy, order, handlePageChange) - } - active={sorting.orderBy === sortingKey} - data-qa-header={label.toLowerCase()} - data-testid={label.toLowerCase()} - direction={sorting.order} - key={label} - label={sortingKey ?? ''} - > - {label} - - ))} - - - - {!isDataLoadingError && - paginatedData.map((resource, index) => { - const { checked, id } = resource; - return ( - - {isSelectionsNeeded && ( - + padding="checkbox" + > + + ) : undefined + } + placement="right-start" + > + { - handleSelectionChange([id], !checked); - }} + indeterminate={ + isSomeSelected(paginatedData) && + !isAllPageSelected(paginatedData) + } + onClick={() => + handleSelectionChange( + paginatedData.map(({ id }) => id), + !isAllPageSelected(paginatedData) + ) + } sx={{ p: 0, }} - checked={checked} - data-testid={`select_item_${id}`} + checked={isAllPageSelected(paginatedData)} + data-testid={`select_all_in_page_${page}`} + disabled={isRootCheckBoxDisabled} /> - - )} - {columns.map(({ accessor, label }) => ( - - {accessor(resource)} - - ))} - - ); - })} - {isDataLoadingError && ( - - )} - {paginatedData.length === 0 && ( - - - No data to display. - + + + + )} + {columns.map(({ label, sortingKey }) => ( + + handleSort(orderBy, order, handlePageChange) + } + active={sorting.orderBy === sortingKey} + data-qa-header={label.toLowerCase()} + data-testid={label.toLowerCase()} + direction={sorting.order} + key={label} + label={sortingKey ?? ''} + > + {label} + + ))} - )} - -
- {!isDataLoadingError && paginatedData.length !== 0 && ( - { - handlePageNumberChange(handlePageChange, page); - }} - handleSizeChange={(pageSize) => { - handlePageSizeChange(pageSize); - handlePageNumberChange(handlePageChange, 1); // Moves to the first page after page size change - scrollToGivenElement(); - }} - count={count} - eventCategory="alerts_resources" - page={page} - pageSize={pageSize} - /> - )} - - )} + + + {!isDataLoadingError && + paginatedData.map((resource, index) => { + const { checked, id } = resource; + const isItemCheckboxDisabled = isCheckboxDisabled( + checked + ); + return ( + + {isSelectionsNeeded && ( + + + ) : undefined + } + placement="right-start" + > + + { + handleSelectionChange([id], !checked); + }} + sx={{ + p: 0, + }} + checked={checked} + data-testid={`select_item_${id}`} + disabled={isItemCheckboxDisabled} + /> + + + + )} + {columns.map(({ accessor, label }) => ( + + {accessor(resource)} + + ))} + + ); + })} + {isDataLoadingError && ( + + )} + {paginatedData.length === 0 && ( + + + No data to display. + + + )} + + + {!isDataLoadingError && paginatedData.length !== 0 && ( + { + handlePageNumberChange(handlePageChange, page); + }} + handleSizeChange={(pageSize) => { + handlePageSizeChange(pageSize); + handlePageNumberChange(handlePageChange, 1); // Moves to the first page after page size change + scrollToGivenElement(); + }} + count={count} + eventCategory="alerts_resources" + page={page} + pageSize={pageSize} + /> + )} + + ); + }} ); } diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Resources/CloudPulseModifyAlertResources.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Resources/CloudPulseModifyAlertResources.test.tsx index dd800c40c3d..f481fde660c 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Resources/CloudPulseModifyAlertResources.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Resources/CloudPulseModifyAlertResources.test.tsx @@ -140,9 +140,10 @@ describe('CreateAlertResources component tests', () => { expect(queryByTestId('alert_message_notice')).not.toBeInTheDocument(); }); - it('should be able to see the error notice if the forms field state has error', () => { + it('should be able to see the error notice if the forms field state has error', async () => { const { getAllByTestId, + getByTestId, getByText, } = renderWithThemeAndHookFormContext({ component: , @@ -172,5 +173,12 @@ describe('CreateAlertResources component tests', () => { expect(getAllByTestId('alert_message_notice').length).toBe(2); // one for error and one for selection warning expect(getByText('You can select up to 2 resources.')).toBeInTheDocument(); expect(getByText('More than 2 resources selected')).toBeInTheDocument(); + const resourceFour = getByTestId('select_item_4'); + expect(resourceFour).toBeInTheDocument(); + expect(resourceFour).toHaveAttribute('aria-disabled', 'true'); + + await userEvent.click(getByText('Deselect All')); + + expect(getByText('Select All')).toHaveAttribute('aria-disabled', 'true'); }); }); From c6fd08d26d29e6b6a6808a9e5f2344bdca426932 Mon Sep 17 00:00:00 2001 From: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:36:28 -0400 Subject: [PATCH 027/112] test: [M3-9716] - Disallow some regions from being used for Image upload tests (#11961) * Disallow `au-mel`, `gb-lon`, and `sg-sin-2` from being used when uploading Images during Cypress tests * Added changeset: Avoid selecting regions that do not support Machine Images in Image upload tests --- .../.changeset/pr-11961-tests-1743634308900.md | 5 +++++ .../e2e/core/images/machine-image-upload.spec.ts | 11 ++++++++++- packages/manager/cypress/support/util/regions.ts | 13 +++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 packages/manager/.changeset/pr-11961-tests-1743634308900.md diff --git a/packages/manager/.changeset/pr-11961-tests-1743634308900.md b/packages/manager/.changeset/pr-11961-tests-1743634308900.md new file mode 100644 index 00000000000..d165a7b28fa --- /dev/null +++ b/packages/manager/.changeset/pr-11961-tests-1743634308900.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Avoid selecting regions that do not support Machine Images in Image upload tests ([#11961](https://github.com/linode/manager/pull/11961)) 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 5d78e70bfcf..715d7885f45 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 @@ -113,7 +113,16 @@ const assertProcessing = (label: string, id: string) => { * @param label - Label to apply to uploaded image. */ const uploadImage = (label: string) => { - const region = chooseRegion({ capabilities: ['Object Storage'] }); + // Disallow these regions from being returned by `chooseRegion` because they do not support Machine Images: + // - au-mel + // - gb-lon + // - sg-sin-2 + // + // See also BAC-862. + const region = chooseRegion({ + capabilities: ['Object Storage'], + exclude: ['au-mel', 'gb-lon', 'sg-sin-2'], + }); const upload = 'machine-images/test-image.gz'; cy.visitWithLogin('/images/create/upload'); diff --git a/packages/manager/cypress/support/util/regions.ts b/packages/manager/cypress/support/util/regions.ts index 26b80b4ace0..d48677e5c05 100644 --- a/packages/manager/cypress/support/util/regions.ts +++ b/packages/manager/cypress/support/util/regions.ts @@ -236,6 +236,11 @@ interface ChooseRegionOptions { * Regions from which to choose. If unspecified, Regions exposed by the API will be used. */ regions?: Region[]; + + /** + * Array of region IDs to exclude from results, in addition to `disallowedRegionIds` regions. + */ + exclude?: string[]; } /** @@ -289,6 +294,10 @@ const resolveSearchRegions = ( ): Region[] => { const requiredCapabilities = options?.capabilities ?? []; const overrideRegion = getOverrideRegion(); + const allDisallowedRegionIds = [ + ...disallowedRegionIds, + ...(options?.exclude ?? []), + ]; // If the user has specified an override region for this run, it takes precedent // over any other specified criteria. @@ -303,7 +312,7 @@ const resolveSearchRegions = ( )}` ); } - if (disallowedRegionIds.includes(overrideRegion.id)) { + if (allDisallowedRegionIds.includes(overrideRegion.id)) { throw new Error( `Override region ${overrideRegion.id} (${overrideRegion.label}) is disallowed for testing due to capacity limitations.` ); @@ -314,7 +323,7 @@ const resolveSearchRegions = ( const capableRegions = regionsWithCapabilities( options?.regions ?? regions, requiredCapabilities - ).filter((region: Region) => !disallowedRegionIds.includes(region.id)); + ).filter((region: Region) => !allDisallowedRegionIds.includes(region.id)); if (!capableRegions.length) { throw new Error( From 3c68cf535f9b9e09267f4dcfb05ea7d415288420 Mon Sep 17 00:00:00 2001 From: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:43:20 -0400 Subject: [PATCH 028/112] test: [M3-9481 & more] - Apply environment-specific tags to tests (#11958) * Apply environment-specific tags to tests * Fix issue with tags persisting between tests in certain cases * Remove assertion that may fail depending on randomly-generated mock data * Added changeset: Add `env:marketplaceApps`, `env:multipleRegions`, and `env:stackScripts` tags for Cypress tests --- .../pr-11958-tests-1743610427083.md | 5 + .../migrate-linode-with-firewall.spec.ts | 177 +++++++++--------- .../e2e/core/linodes/rebuild-linode.spec.ts | 2 +- .../core/oneClickApps/one-click-apps.spec.ts | 10 +- .../smoke-community-stackscripts.spec.ts | 5 +- .../cypress/support/setup/test-tagging.ts | 10 +- packages/manager/cypress/support/util/tag.ts | 3 + 7 files changed, 116 insertions(+), 96 deletions(-) create mode 100644 packages/manager/.changeset/pr-11958-tests-1743610427083.md diff --git a/packages/manager/.changeset/pr-11958-tests-1743610427083.md b/packages/manager/.changeset/pr-11958-tests-1743610427083.md new file mode 100644 index 00000000000..f1805971d36 --- /dev/null +++ b/packages/manager/.changeset/pr-11958-tests-1743610427083.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Add `env:marketplaceApps`, `env:multipleRegions`, and `env:stackScripts` tags for Cypress tests ([#11958](https://github.com/linode/manager/pull/11958)) diff --git a/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts b/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts index 0070a252647..4ad04ce9844 100644 --- a/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts @@ -144,98 +144,103 @@ describe('Migrate Linode With Firewall', () => { * - Uses real API data to create a Firewall, attach a Linode to it, then migrate the Linode. */ it.skip('migrates linode with firewall - real data', () => { - cy.tag('method:e2e', 'purpose:dcTesting'); - const [migrationRegionStart, migrationRegionEnd] = chooseRegions(2); - const firewallLabel = randomLabel(); - const linodePayload = createLinodeRequestFactory.build({ - label: randomLabel(), - region: migrationRegionStart.id, - }); + cy.tag('method:e2e', 'purpose:dcTesting', 'env:multipleRegions'); + + // Execute the body of the test inside Cypress's command queue to ensure + // that logic that requires multiple regions only executes after tags are evaluated. + cy.defer(async () => {}).then(() => { + const [migrationRegionStart, migrationRegionEnd] = chooseRegions(2); + const firewallLabel = randomLabel(); + const linodePayload = createLinodeRequestFactory.build({ + label: randomLabel(), + region: migrationRegionStart.id, + }); - interceptCreateFirewall().as('createFirewall'); - interceptGetFirewalls().as('getFirewalls'); - - // Create a Linode, then navigate to the Firewalls landing page. - cy.defer(() => - createTestLinode(linodePayload, { securityMethod: 'powered_off' }) - ).then((linode: Linode) => { - interceptMigrateLinode(linode.id).as('migrateLinode'); - cy.visitWithLogin('/firewalls'); - cy.wait('@getFirewalls'); - - ui.button - .findByTitle('Create Firewall') - .should('be.visible') - .should('be.enabled') - .click(); - - ui.drawer - .findByTitle('Create Firewall') - .should('be.visible') - .within(() => { - cy.findByText('Label').should('be.visible').click(); - cy.focused().type(firewallLabel); - - cy.findByText('Linodes').should('be.visible').click(); - cy.focused().type(linode.label); - - ui.autocompletePopper - .findByTitle(linode.label) - .should('be.visible') - .click(); - - // Click on the Select again to dismiss the autocomplete popper. - cy.findByLabelText('Linodes').should('be.visible').click(); - - ui.buttonGroup - .findButtonByTitle('Create Firewall') - .should('be.visible') - .should('be.enabled') - .click(); - }); + interceptCreateFirewall().as('createFirewall'); + interceptGetFirewalls().as('getFirewalls'); - cy.wait('@createFirewall'); - cy.visitWithLogin(`/linodes/${linode.id}`); - cy.get('[data-qa-link-text="true"]') - .should('be.visible') - .within(() => { - cy.findByText('linodes').should('be.visible'); - }); + // Create a Linode, then navigate to the Firewalls landing page. + cy.defer(() => + createTestLinode(linodePayload, { securityMethod: 'powered_off' }) + ).then((linode: Linode) => { + interceptMigrateLinode(linode.id).as('migrateLinode'); + cy.visitWithLogin('/firewalls'); + cy.wait('@getFirewalls'); - // Make sure Linode is running before attempting to migrate. - cy.get('[data-qa-linode-status]').within(() => { - cy.findByText('OFFLINE'); - }); + ui.button + .findByTitle('Create Firewall') + .should('be.visible') + .should('be.enabled') + .click(); - ui.actionMenu - .findByTitle(`Action menu for Linode ${linode.label}`) - .should('be.visible') - .click(); - - ui.actionMenuItem.findByTitle('Migrate').should('be.visible').click(); - - ui.dialog - .findByTitle(`Migrate Linode ${linode.label} to another region`) - .should('be.visible') - .within(() => { - // Click "Accept" check box. - cy.findByText('Accept').should('be.visible').click(); - - // Select region for migration. - ui.regionSelect.find().click(); - ui.regionSelect - .findItemByRegionLabel(migrationRegionEnd.label) - .click(); - - // Initiate migration. - ui.button - .findByTitle('Enter Migration Queue') - .should('be.visible') - .should('be.enabled') - .click(); + ui.drawer + .findByTitle('Create Firewall') + .should('be.visible') + .within(() => { + cy.findByText('Label').should('be.visible').click(); + cy.focused().type(firewallLabel); + + cy.findByText('Linodes').should('be.visible').click(); + cy.focused().type(linode.label); + + ui.autocompletePopper + .findByTitle(linode.label) + .should('be.visible') + .click(); + + // Click on the Select again to dismiss the autocomplete popper. + cy.findByLabelText('Linodes').should('be.visible').click(); + + ui.buttonGroup + .findButtonByTitle('Create Firewall') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@createFirewall'); + cy.visitWithLogin(`/linodes/${linode.id}`); + cy.get('[data-qa-link-text="true"]') + .should('be.visible') + .within(() => { + cy.findByText('linodes').should('be.visible'); + }); + + // Make sure Linode is running before attempting to migrate. + cy.get('[data-qa-linode-status]').within(() => { + cy.findByText('OFFLINE'); }); - cy.wait('@migrateLinode').its('response.statusCode').should('eq', 200); + ui.actionMenu + .findByTitle(`Action menu for Linode ${linode.label}`) + .should('be.visible') + .click(); + + ui.actionMenuItem.findByTitle('Migrate').should('be.visible').click(); + + ui.dialog + .findByTitle(`Migrate Linode ${linode.label} to another region`) + .should('be.visible') + .within(() => { + // Click "Accept" check box. + cy.findByText('Accept').should('be.visible').click(); + + // Select region for migration. + ui.regionSelect.find().click(); + ui.regionSelect + .findItemByRegionLabel(migrationRegionEnd.label) + .click(); + + // Initiate migration. + ui.button + .findByTitle('Enter Migration Queue') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.wait('@migrateLinode').its('response.statusCode').should('eq', 200); + }); }); }); }); diff --git a/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts index e3a4d5fa827..0841ebc23e4 100644 --- a/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts @@ -176,7 +176,7 @@ describe('rebuild linode', () => { * - Confirms that a Linode can be rebuilt using a Community StackScript. */ it('rebuilds a linode from Community StackScript', () => { - cy.tag('method:e2e'); + cy.tag('method:e2e', 'env:stackScripts'); const stackScriptId = 443929; const stackScriptName = 'OpenLiteSpeed-WordPress'; const image = 'AlmaLinux 9'; diff --git a/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts b/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts index c1e069cec3f..67f44a6b5a1 100644 --- a/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts +++ b/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts @@ -20,7 +20,7 @@ import type { StackScript } from '@linode/api-v4'; describe('OneClick Apps (OCA)', () => { it('Lists all the OneClick Apps', () => { - cy.tag('method:e2e'); + cy.tag('method:e2e', 'env:marketplaceApps'); interceptGetStackScripts().as('getStackScripts'); cy.visitWithLogin(`/linodes/create?type=One-Click`); @@ -57,7 +57,7 @@ describe('OneClick Apps (OCA)', () => { }); it('Can view app details of a marketplace app', () => { - cy.tag('method:e2e'); + cy.tag('method:e2e', 'env:marketplaceApps'); interceptGetStackScripts().as('getStackScripts'); cy.visitWithLogin(`/linodes/create?type=One-Click`); @@ -180,7 +180,7 @@ describe('OneClick Apps (OCA)', () => { cy.findByText('New apps').should('be.visible'); // Check that the app is listed and select it - cy.get('[data-qa-selection-card="true"]').should('have.length', 2); + // The app may be listed 2 or 3 times. cy.findAllByText(stackscript.label).first().should('be.visible').click(); }); @@ -249,8 +249,8 @@ describe('OneClick Apps (OCA)', () => { }); // leave test disabled by default - xit('Validate the summaries of all the OneClick Apps', () => { - cy.tag('method:e2e'); + it.skip('Validate the summaries of all the OneClick Apps', () => { + cy.tag('method:e2e', 'env:marketplaceApps'); interceptGetStackScripts().as('getStackScripts'); cy.visitWithLogin(`/linodes/create?type=One-Click`); diff --git a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts index d6343ceeec4..2407527532e 100644 --- a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts +++ b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts @@ -190,7 +190,7 @@ describe('Community Stackscripts integration tests', () => { * - Confirms that pagination works as expected. */ it('pagination works with infinite scrolling', () => { - cy.tag('method:e2e'); + cy.tag('method:e2e', 'env:stackScripts'); interceptGetStackScripts().as('getStackScripts'); // Fetch all public Images to later use while filtering StackScripts. @@ -236,7 +236,7 @@ describe('Community Stackscripts integration tests', () => { * - Confirms that search can filter the expected results. */ it('search function filters results correctly', () => { - cy.tag('method:e2e'); + cy.tag('method:e2e', 'env:stackScripts'); const stackScript = mockStackScripts[0]; interceptGetStackScripts().as('getStackScripts'); @@ -267,6 +267,7 @@ describe('Community Stackscripts integration tests', () => { * - Confirms that the deployment flow works. */ it('deploys a new linode as expected', () => { + cy.tag('method:e2e', 'env:stackScripts'); const stackScriptId = '37239'; const stackScriptName = 'setup-ipsec-vpn'; const sharedKey = randomString(); diff --git a/packages/manager/cypress/support/setup/test-tagging.ts b/packages/manager/cypress/support/setup/test-tagging.ts index 5dbd48c8405..914ae3dcf36 100644 --- a/packages/manager/cypress/support/setup/test-tagging.ts +++ b/packages/manager/cypress/support/setup/test-tagging.ts @@ -19,7 +19,7 @@ const query = Cypress.env('CY_TEST_TAGS') ?? ''; */ Cypress.on('test:before:run', (_test: Test, _runnable: Runnable) => { /* - * Looks for the first command that does not belong in a hook and evalutes tags. + * Looks for the first command that does not belong in a hook and evaluates tags. * * Waiting for the first command to begin executing ensures that test context * is set up and that tags have been assigned to the test. @@ -27,7 +27,13 @@ Cypress.on('test:before:run', (_test: Test, _runnable: Runnable) => { const commandHandler = () => { const context = cy.state('ctx'); if (context && context.test?.type !== 'hook') { - const tags = context?.tags ?? []; + const tags = context?.tags ? [...context.tags] : []; + + // Remove tags from context now that we've read them and can evaluate them. + // This prevents tags from persisting between tests in certain situations. + if (tags.length) { + context.tags = []; + } if (!evaluateQuery(query, tags)) { context.skip(); diff --git a/packages/manager/cypress/support/util/tag.ts b/packages/manager/cypress/support/util/tag.ts index db105961963..203291be8f0 100644 --- a/packages/manager/cypress/support/util/tag.ts +++ b/packages/manager/cypress/support/util/tag.ts @@ -9,7 +9,10 @@ const queryRegex = /(?:-|\+)?([^\s]+)/g; export type TestTag = // Environment-related tags. // Used to identify tests where certain environment-specific features are required. + | 'env:marketplaceApps' + | 'env:multipleRegions' | 'env:premiumPlans' + | 'env:stackScripts' // Feature-related tags. // Used to identify tests which deal with a certain feature or features. From 07e6dc49136cf7375ea052103298b1f2ecc307e4 Mon Sep 17 00:00:00 2001 From: Nikhil Agrawal <165884194+nikhagra-akamai@users.noreply.github.com> Date: Mon, 7 Apr 2025 11:10:18 +0530 Subject: [PATCH 029/112] refactor: [DI-24450] - Added logic to update the alert in cache when it is created (#11969) * refactor: [DI-24450] - Added logic to update the alert in cache when it is created * test: [DI-24450] - Updated cypress tests * removed unused variable * added changeset --- .../pr-11969-added-1743779981445.md | 5 +++ .../core/cloudpulse/create-user-alert.spec.ts | 2 +- .../core/cloudpulse/edit-user-alert.spec.ts | 17 +--------- .../cypress/support/intercepts/cloudpulse.ts | 5 ++- .../manager/src/queries/cloudpulse/alerts.ts | 32 ++++++++++++++++--- 5 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 packages/manager/.changeset/pr-11969-added-1743779981445.md diff --git a/packages/manager/.changeset/pr-11969-added-1743779981445.md b/packages/manager/.changeset/pr-11969-added-1743779981445.md new file mode 100644 index 00000000000..5ec79486e71 --- /dev/null +++ b/packages/manager/.changeset/pr-11969-added-1743779981445.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Add `cache update` logic in alerts.ts query file ([#11969](https://github.com/linode/manager/pull/11969)) diff --git a/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts index 6545e59d9c5..97d48c5a7a5 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts @@ -165,7 +165,7 @@ describe('Create Alert', () => { mockGetDatabases(databaseMock); mockGetAllAlertDefinitions([mockAlerts]).as('getAlertDefinitionsList'); mockGetAlertChannels([notificationChannels]); - mockCreateAlertDefinition(serviceType, customAlertDefinition).as( + mockCreateAlertDefinition(serviceType, mockAlerts).as( 'createAlertDefinition' ); }); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts index 5167a7a9edd..ccb7427af30 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts @@ -30,7 +30,6 @@ import { ui } from 'support/ui'; import { accountFactory, - alertDefinitionFactory, alertFactory, cpuRulesFactory, dashboardMetricFactory, @@ -49,20 +48,6 @@ import type { Flags } from 'src/featureFlags'; const flags: Partial = { aclp: { beta: true, enabled: true } }; const mockAccount = accountFactory.build(); -// Mock alert definition -const customAlertDefinition = alertDefinitionFactory.build({ - channel_ids: [1], - description: 'update-description', - entity_ids: ['1', '2', '3', '4', '5'], - label: 'Alert-1', - rule_criteria: { - rules: [cpuRulesFactory.build(), memoryRulesFactory.build()], - }, - severity: 0, - tags: [''], - trigger_conditions: triggerConditionFactory.build(), -}); - // Mock alert details const alertDetails = alertFactory.build({ alert_channels: [{ id: 1 }], @@ -150,7 +135,7 @@ describe('Integration Tests for Edit Alert', () => { mockUpdateAlertDefinitions(service_type, id, alertDetails).as( 'updateDefinitions' ); - mockCreateAlertDefinition(service_type, customAlertDefinition).as( + mockCreateAlertDefinition(service_type, alertDetails).as( 'createAlertDefinition' ); mockGetCloudPulseMetricDefinitions(service_type, metricDefinitions); diff --git a/packages/manager/cypress/support/intercepts/cloudpulse.ts b/packages/manager/cypress/support/intercepts/cloudpulse.ts index b2c86d73270..0dc4df75750 100644 --- a/packages/manager/cypress/support/intercepts/cloudpulse.ts +++ b/packages/manager/cypress/support/intercepts/cloudpulse.ts @@ -14,7 +14,6 @@ import { makeResponse } from 'support/util/response'; import type { Alert, CloudPulseMetricsResponse, - CreateAlertDefinitionPayload, Dashboard, MetricDefinition, NotificationChannel, @@ -355,12 +354,12 @@ export const mockGetAlertChannels = ( export const mockCreateAlertDefinition = ( serviceType: string, - createAlertRequest: CreateAlertDefinitionPayload + alert: Alert ): Cypress.Chainable => { return cy.intercept( 'POST', apiMatcher(`/monitor/services/${serviceType}/alert-definitions`), - paginateResponse(createAlertRequest) + makeResponse(alert) ); }; /** diff --git a/packages/manager/src/queries/cloudpulse/alerts.ts b/packages/manager/src/queries/cloudpulse/alerts.ts index c7d9df20150..569de45cde7 100644 --- a/packages/manager/src/queries/cloudpulse/alerts.ts +++ b/packages/manager/src/queries/cloudpulse/alerts.ts @@ -28,8 +28,29 @@ export const useCreateAlertDefinition = (serviceType: AlertServiceType) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => createAlertDefinition(data, serviceType), - onSuccess() { - queryClient.invalidateQueries(queryFactory.alerts); + onSuccess(newAlert) { + queryClient.cancelQueries({ + queryKey: queryFactory.alerts._ctx.all().queryKey, + }); + + queryClient.setQueryData( + queryFactory.alerts._ctx.all().queryKey, + (oldData) => (oldData ? [...oldData, newAlert] : [newAlert]) + ); + + queryClient.setQueryData( + queryFactory.alerts._ctx.alertByServiceTypeAndId( + newAlert.service_type, + String(newAlert.id) + ).queryKey, + newAlert + ); + + queryClient.invalidateQueries({ + queryKey: queryFactory.alerts._ctx.alertsByServiceType( + newAlert.service_type + ).queryKey, + }); }, }); }; @@ -91,12 +112,13 @@ export const useEditAlertDefinition = () => { ); }); - queryClient.invalidateQueries({ - queryKey: queryFactory.alerts._ctx.alertByServiceTypeAndId( + queryClient.setQueryData( + queryFactory.alerts._ctx.alertByServiceTypeAndId( data.service_type, String(data.id) ).queryKey, - }); + data + ); queryClient.invalidateQueries({ queryKey: queryFactory.alerts._ctx.alertsByServiceType( From 82c00469aa42f67507158a5246a4969a38eb5ca0 Mon Sep 17 00:00:00 2001 From: Purvesh Makode Date: Mon, 7 Apr 2025 13:47:42 +0530 Subject: [PATCH 030/112] refactor: [M3-9614] - Move `getUserTimeZone` and its associated profile factories (#11955) * Move `getUserTimeZone` and its associated `profile` factories * Added changeset: Move `getUserTimeZone` and its associated profile factories to `@linode/utilities` * Added changeset: Move `getUserTimeZone` and its associated profile factories to `@linode/utilities` * Update mocks --- .../.changeset/pr-11955-removed-1743596163534.md | 5 +++++ .../e2e/core/account/account-cancellation.spec.ts | 2 +- .../e2e/core/account/account-linode-managed.spec.ts | 3 +-- .../e2e/core/account/account-login-history.spec.ts | 2 +- .../e2e/core/account/display-settings.spec.ts | 2 +- .../e2e/core/account/personal-access-tokens.spec.ts | 2 +- .../e2e/core/account/security-questions.spec.ts | 4 +--- .../e2e/core/account/sms-verification.spec.ts | 2 +- .../cypress/e2e/core/account/ssh-keys.spec.ts | 3 +-- .../e2e/core/account/two-factor-auth.spec.ts | 6 +----- .../e2e/core/account/user-permissions.spec.ts | 2 +- .../core/account/user-verification-banner.spec.ts | 2 +- .../e2e/core/account/users-landing-page.spec.ts | 2 +- .../core/billing/restricted-user-billing.spec.ts | 3 ++- .../e2e/core/billing/smoke-billing-activity.spec.ts | 2 +- .../core/cloudpulse/timerange-verification.spec.ts | 3 +-- .../e2e/core/databases/create-database.spec.ts | 2 +- .../core/images/images-empty-landing-page.spec.ts | 2 +- .../images/images-non-empty-landing-page.spec.ts | 4 ++-- .../e2e/core/images/smoke-create-image.spec.ts | 9 ++------- .../e2e/core/kubernetes/smoke-lke-create.spec.ts | 2 +- .../core/linodes/create-linode-with-ssh-key.spec.ts | 4 ++-- .../cypress/e2e/core/linodes/create-linode.spec.ts | 2 +- .../core/linodes/smoke-linode-landing-table.spec.ts | 7 +++++-- .../e2e/core/managed/managed-navigation.spec.ts | 2 +- .../objectStorage/enable-object-storage.spec.ts | 3 +-- .../bucket-access-keys-gen2.spec.ts | 2 +- .../objectStorageGen2/bucket-create-gen2.spec.ts | 3 +-- .../e2e/core/parentChild/account-switching.spec.ts | 2 +- .../e2e/core/parentChild/token-expiration.spec.ts | 7 ++----- .../e2e/core/parentChild/token-scopes.spec.ts | 7 ++----- .../cypress/e2e/core/volumes/create-volume.spec.ts | 8 ++------ .../components/AccessPanel/UserSSHKeyPanel.test.tsx | 2 +- .../manager/src/components/Avatar/Avatar.test.tsx | 2 +- .../DateTimeDisplay/DateTimeDisplay.test.tsx | 13 +++++++++++-- .../components/DateTimeDisplay/DateTimeDisplay.tsx | 2 +- .../src/dev-tools/components/ExtraPresetProfile.tsx | 2 +- packages/manager/src/factories/grants.ts | 3 ++- packages/manager/src/factories/index.ts | 1 - .../features/Account/CloseAccountSetting.test.tsx | 2 +- .../features/Account/ObjectStorageSettings.test.tsx | 3 ++- .../src/features/Account/Quotas/utils.test.tsx | 2 +- .../features/Account/SwitchAccountDrawer.test.tsx | 2 +- .../SwitchAccounts/ChildAccountList.test.tsx | 3 ++- .../src/features/Backups/BackupsCTA.test.tsx | 4 ++-- .../BillingActivityPanel.test.tsx | 11 +++++++++-- .../ContactInfoPanel/ContactInformation.test.tsx | 2 +- .../PaymentInfoPanel/PaymentInformation.test.tsx | 3 ++- .../IAM/Users/UserDetails/DeleteUserPanel.test.tsx | 3 ++- .../IAM/Users/UserDetails/UserEmailPanel.test.tsx | 3 ++- .../features/IAM/Users/UsersTable/UserRow.test.tsx | 2 +- .../IAM/Users/UsersTable/UsersActionMenu.test.tsx | 2 +- .../Images/ImagesLanding/ImagesLanding.test.tsx | 3 ++- .../ImagesLanding/ImagesLandingEmptyState.test.tsx | 3 ++- .../Linodes/LinodeCreate/Addons/Backups.test.tsx | 4 ++-- .../Linodes/LinodeCreate/Addons/PrivateIP.test.tsx | 3 +-- .../Linodes/LinodeCreate/Details/Details.test.tsx | 3 ++- .../features/Linodes/LinodeCreate/Region.test.tsx | 3 ++- .../features/Linodes/LinodeCreate/Security.test.tsx | 8 ++++++-- .../LinodeBackup/ScheduleSettings.test.tsx | 3 +-- .../LinodesDetail/LinodeBackup/ScheduleSettings.tsx | 4 +--- .../Longview/LongviewLanding/LongviewPlans.test.tsx | 11 ++++------- .../ManagedDashboardCard/ManagedChartPanel.tsx | 4 ++-- .../src/features/Managed/Monitors/IssueCalendar.tsx | 7 ++++--- .../NodeBalancerSummary/TablesPanel.tsx | 3 +-- .../BucketDetail/ObjectDetailsDrawer.test.tsx | 2 +- .../BucketLanding/BucketDetailsDrawer.test.tsx | 2 +- .../Profile/APITokens/CreateAPITokenDrawer.test.tsx | 2 +- .../Profile/APITokens/ViewAPITokenDrawer.test.tsx | 3 ++- .../Profile/DisplaySettings/EmailForm.test.tsx | 2 +- .../Profile/DisplaySettings/TimezoneForm.test.tsx | 2 +- .../Profile/DisplaySettings/UsernameForm.test.tsx | 2 +- .../Profile/SSHKeys/CreateSSHKeyDrawer.test.tsx | 4 ++-- .../src/features/Profile/SSHKeys/SSHKeys.test.tsx | 2 +- .../Profile/Settings/Notifications.test.tsx | 2 +- .../src/features/TopMenu/UserMenu/UserMenu.test.tsx | 3 ++- .../src/features/TopMenu/UserMenu/utils.test.ts | 6 +++--- .../Users/UserProfile/UserEmailPanel.test.tsx | 3 ++- .../manager/src/features/Users/UserRow.test.tsx | 2 +- .../Volumes/VolumesLandingEmptyState.test.tsx | 3 ++- .../mocks/presets/extra/account/customProfile.ts | 2 +- packages/manager/src/mocks/serverHandlers.ts | 4 ++-- packages/manager/src/request.test.tsx | 2 +- packages/manager/src/utilities/formatDate.test.ts | 10 +++++++++- packages/manager/src/utilities/formatDate.ts | 3 +-- .../.changeset/pr-11955-added-1743596189029.md | 5 +++++ packages/utilities/src/factories/index.ts | 1 + .../{manager => utilities}/src/factories/profile.ts | 2 +- .../src/helpers}/getUserTimezone.test.ts | 8 +++++--- .../src/helpers}/getUserTimezone.ts | 2 +- packages/utilities/src/helpers/index.ts | 3 ++- 91 files changed, 172 insertions(+), 147 deletions(-) create mode 100644 packages/manager/.changeset/pr-11955-removed-1743596163534.md create mode 100644 packages/utilities/.changeset/pr-11955-added-1743596189029.md rename packages/{manager => utilities}/src/factories/profile.ts (98%) rename packages/{manager/src/utilities => utilities/src/helpers}/getUserTimezone.test.ts (86%) rename packages/{manager/src/utilities => utilities/src/helpers}/getUserTimezone.ts (88%) diff --git a/packages/manager/.changeset/pr-11955-removed-1743596163534.md b/packages/manager/.changeset/pr-11955-removed-1743596163534.md new file mode 100644 index 00000000000..b004250bd76 --- /dev/null +++ b/packages/manager/.changeset/pr-11955-removed-1743596163534.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Removed +--- + +Move `getUserTimeZone` and its associated profile factories to `@linode/utilities` ([#11955](https://github.com/linode/manager/pull/11955)) diff --git a/packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts b/packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts index 728ea5695d7..78797cd18bb 100644 --- a/packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts +++ b/packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for Cloud Manager account cancellation flows. */ +import { profileFactory } from '@linode/utilities'; import { cancellationDataLossWarning, cancellationDialogTitle, @@ -22,7 +23,6 @@ import { } from 'support/util/random'; import { accountFactory } from 'src/factories/account'; -import { profileFactory } from 'src/factories/profile'; import { CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT, PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT, diff --git a/packages/manager/cypress/e2e/core/account/account-linode-managed.spec.ts b/packages/manager/cypress/e2e/core/account/account-linode-managed.spec.ts index 118eb70095d..8115f263497 100644 --- a/packages/manager/cypress/e2e/core/account/account-linode-managed.spec.ts +++ b/packages/manager/cypress/e2e/core/account/account-linode-managed.spec.ts @@ -2,7 +2,7 @@ * @file Integration tests for Cloud Manager account enable Linode Managed flows. */ -import { linodeFactory } from '@linode/utilities'; +import { linodeFactory, profileFactory } from '@linode/utilities'; import { visitUrlWithManagedDisabled, visitUrlWithManagedEnabled, @@ -22,7 +22,6 @@ import { ui } from 'support/ui'; import { chooseRegion } from 'support/util/regions'; import { accountFactory } from 'src/factories/account'; -import { profileFactory } from 'src/factories/profile'; import type { Linode } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/account/account-login-history.spec.ts b/packages/manager/cypress/e2e/core/account/account-login-history.spec.ts index b53a18ef7d0..e18840149e9 100644 --- a/packages/manager/cypress/e2e/core/account/account-login-history.spec.ts +++ b/packages/manager/cypress/e2e/core/account/account-login-history.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for Cloud Manager account login history flows. */ +import { profileFactory } from '@linode/utilities'; import { loginEmptyStateMessageText, loginHelperText, @@ -9,7 +10,6 @@ import { import { mockGetAccountLogins } from 'support/intercepts/account'; import { mockGetProfile } from 'support/intercepts/profile'; -import { profileFactory } from 'src/factories'; import { accountLoginFactory } from 'src/factories/accountLogin'; import { PARENT_USER } from 'src/features/Account/constants'; import { formatDate } from 'src/utilities/formatDate'; diff --git a/packages/manager/cypress/e2e/core/account/display-settings.spec.ts b/packages/manager/cypress/e2e/core/account/display-settings.spec.ts index 64f950dd149..e5b0e1655c6 100644 --- a/packages/manager/cypress/e2e/core/account/display-settings.spec.ts +++ b/packages/manager/cypress/e2e/core/account/display-settings.spec.ts @@ -1,4 +1,4 @@ -import { profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; import { getProfile } from 'support/api/account'; import { mockUpdateUsername } from 'support/intercepts/account'; import { interceptGetProfile } from 'support/intercepts/profile'; diff --git a/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts b/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts index c133b491d55..9e5554532c1 100644 --- a/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts +++ b/packages/manager/cypress/e2e/core/account/personal-access-tokens.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for personal access token CRUD operations. */ +import { profileFactory } from '@linode/utilities'; import { mockCreatePersonalAccessToken, mockGetAppTokens, @@ -14,7 +15,6 @@ import { ui } from 'support/ui'; import { randomLabel, randomString } from 'support/util/random'; import { appTokenFactory } from 'src/factories/oauth'; -import { profileFactory } from 'src/factories/profile'; import { PROXY_USER_RESTRICTED_TOOLTIP_TEXT } from 'src/features/Account/constants'; import type { Token } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/account/security-questions.spec.ts b/packages/manager/cypress/e2e/core/account/security-questions.spec.ts index 3d161c672b4..f2746a5377e 100644 --- a/packages/manager/cypress/e2e/core/account/security-questions.spec.ts +++ b/packages/manager/cypress/e2e/core/account/security-questions.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for account security questions. */ +import { profileFactory, securityQuestionsFactory } from '@linode/utilities'; import { mockGetProfile, mockGetSecurityQuestions, @@ -9,9 +10,6 @@ import { } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { securityQuestionsFactory } from 'src/factories/profile'; -import { profileFactory } from 'src/factories/profile'; - /** * Finds the "Security Questions" section on the profile auth page. * diff --git a/packages/manager/cypress/e2e/core/account/sms-verification.spec.ts b/packages/manager/cypress/e2e/core/account/sms-verification.spec.ts index 6a5ed1aed43..74f59ac48a7 100644 --- a/packages/manager/cypress/e2e/core/account/sms-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/account/sms-verification.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for SMS phone verification. */ +import { profileFactory } from '@linode/utilities'; import { mockGetProfile, mockSendVerificationCode, @@ -15,7 +16,6 @@ import { randomPhoneNumber, } from 'support/util/random'; -import { profileFactory } from 'src/factories/profile'; import { getFormattedNumber } from 'src/features/Profile/AuthenticationSettings/PhoneVerification/helpers'; describe('SMS phone verification', () => { diff --git a/packages/manager/cypress/e2e/core/account/ssh-keys.spec.ts b/packages/manager/cypress/e2e/core/account/ssh-keys.spec.ts index 5afeac8936c..cf24e9644d9 100644 --- a/packages/manager/cypress/e2e/core/account/ssh-keys.spec.ts +++ b/packages/manager/cypress/e2e/core/account/ssh-keys.spec.ts @@ -1,3 +1,4 @@ +import { sshKeyFactory } from '@linode/utilities'; import { sshFormatErrorMessage } from 'support/constants/account'; import { mockCreateSSHKey, @@ -9,8 +10,6 @@ import { import { ui } from 'support/ui'; import { randomLabel, randomString } from 'support/util/random'; -import { sshKeyFactory } from 'src/factories'; - describe('SSH keys', () => { /* * - Vaildates SSH key creation flow using mock data. diff --git a/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts b/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts index 9f1e945cb5a..75001e64ccf 100644 --- a/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts +++ b/packages/manager/cypress/e2e/core/account/two-factor-auth.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for account two-factor authentication functionality. */ +import { profileFactory, securityQuestionsFactory } from '@linode/utilities'; import { mockConfirmTwoFactorAuth, mockDisableTwoFactorAuth, @@ -17,11 +18,6 @@ import { randomString, } from 'support/util/random'; -import { - profileFactory, - securityQuestionsFactory, -} from 'src/factories/profile'; - import type { SecurityQuestionsData } from '@linode/api-v4'; /** diff --git a/packages/manager/cypress/e2e/core/account/user-permissions.spec.ts b/packages/manager/cypress/e2e/core/account/user-permissions.spec.ts index ccaa315dfa9..098053018b4 100644 --- a/packages/manager/cypress/e2e/core/account/user-permissions.spec.ts +++ b/packages/manager/cypress/e2e/core/account/user-permissions.spec.ts @@ -1,4 +1,4 @@ -import { profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; import { userPermissionsGrants } from 'support/constants/user-permissions'; diff --git a/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts b/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts index d2547970203..d3b5cc0a771 100644 --- a/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts +++ b/packages/manager/cypress/e2e/core/account/user-verification-banner.spec.ts @@ -1,4 +1,4 @@ -import { profileFactory, securityQuestionsFactory } from '@src/factories'; +import { profileFactory, securityQuestionsFactory } from '@linode/utilities'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; import { verificationBannerNotice } from 'support/constants/user'; diff --git a/packages/manager/cypress/e2e/core/account/users-landing-page.spec.ts b/packages/manager/cypress/e2e/core/account/users-landing-page.spec.ts index af4179b74c5..762310df69f 100644 --- a/packages/manager/cypress/e2e/core/account/users-landing-page.spec.ts +++ b/packages/manager/cypress/e2e/core/account/users-landing-page.spec.ts @@ -1,4 +1,4 @@ -import { profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; import { diff --git a/packages/manager/cypress/e2e/core/billing/restricted-user-billing.spec.ts b/packages/manager/cypress/e2e/core/billing/restricted-user-billing.spec.ts index 4c55cefadfa..e47db4f1307 100644 --- a/packages/manager/cypress/e2e/core/billing/restricted-user-billing.spec.ts +++ b/packages/manager/cypress/e2e/core/billing/restricted-user-billing.spec.ts @@ -2,7 +2,8 @@ * @file Integration tests for restricted user billing flows. */ -import { paymentMethodFactory, profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; +import { paymentMethodFactory } from '@src/factories'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; import { mockGetPaymentMethods, mockGetUser } from 'support/intercepts/account'; diff --git a/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts b/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts index 9189a234d17..ad32f3f778d 100644 --- a/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts +++ b/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts @@ -1,5 +1,5 @@ import { getProfile } from '@linode/api-v4'; -import { profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; import { formatDate } from '@src/utilities/formatDate'; import { DateTime } from 'luxon'; import { authenticate } from 'support/api/authentication'; diff --git a/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts index 17d5a0d5f84..af475bfb926 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts @@ -1,7 +1,7 @@ /** * @file Integration Tests for CloudPulse Custom and Preset Verification */ -import { regionFactory } from '@linode/utilities'; +import { profileFactory, regionFactory } from '@linode/utilities'; import { DateTime } from 'luxon'; import { widgetDetails } from 'support/constants/widgets'; import { mockGetAccount } from 'support/intercepts/account'; @@ -29,7 +29,6 @@ import { dashboardFactory, dashboardMetricFactory, databaseFactory, - profileFactory, widgetFactory, } from 'src/factories'; import { convertToGmt } from 'src/features/CloudPulse/Utils/CloudPulseDateTimePickerUtils'; diff --git a/packages/manager/cypress/e2e/core/databases/create-database.spec.ts b/packages/manager/cypress/e2e/core/databases/create-database.spec.ts index d053ff241fb..b2405d44b2d 100644 --- a/packages/manager/cypress/e2e/core/databases/create-database.spec.ts +++ b/packages/manager/cypress/e2e/core/databases/create-database.spec.ts @@ -1,3 +1,4 @@ +import { profileFactory } from '@linode/utilities'; import { databaseConfigurations, mockDatabaseEngineTypes, @@ -25,7 +26,6 @@ import { databaseFactory, eventFactory, grantsFactory, - profileFactory, } from 'src/factories'; import type { Database } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/images/images-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/images/images-empty-landing-page.spec.ts index 49a4a3e3665..139652ad412 100644 --- a/packages/manager/cypress/e2e/core/images/images-empty-landing-page.spec.ts +++ b/packages/manager/cypress/e2e/core/images/images-empty-landing-page.spec.ts @@ -1,4 +1,4 @@ -import { profileFactory } from '@src/factories'; +import { profileFactory } from '@linode/utilities'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; import { mockGetUser } from 'support/intercepts/account'; diff --git a/packages/manager/cypress/e2e/core/images/images-non-empty-landing-page.spec.ts b/packages/manager/cypress/e2e/core/images/images-non-empty-landing-page.spec.ts index 394ebe42563..b9a0274f22f 100644 --- a/packages/manager/cypress/e2e/core/images/images-non-empty-landing-page.spec.ts +++ b/packages/manager/cypress/e2e/core/images/images-non-empty-landing-page.spec.ts @@ -1,3 +1,4 @@ +import { profileFactory } from '@linode/utilities'; import { mockGetUser } from 'support/intercepts/account'; import { mockGetAllImages } from 'support/intercepts/images'; import { @@ -7,10 +8,9 @@ import { import { ui } from 'support/ui'; import { randomLabel } from 'support/util/random'; +import { imageFactory } from 'src/factories'; import { grantsFactory } from 'src/factories'; -import { profileFactory } from 'src/factories'; import { accountUserFactory } from 'src/factories'; -import { imageFactory } from 'src/factories'; import type { Image } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts index 00d9f4c319a..d46157d4c6c 100644 --- a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts +++ b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts @@ -1,4 +1,4 @@ -import { linodeFactory } from '@linode/utilities'; +import { linodeFactory, profileFactory } from '@linode/utilities'; import { mockGetUser } from 'support/intercepts/account'; import { mockGetEvents } from 'support/intercepts/events'; import { mockCreateImage } from 'support/intercepts/images'; @@ -10,12 +10,7 @@ import { import { ui } from 'support/ui'; import { randomLabel, randomNumber, randomPhrase } from 'support/util/random'; -import { - accountUserFactory, - eventFactory, - grantsFactory, - profileFactory, -} from 'src/factories'; +import { accountUserFactory, eventFactory, grantsFactory } from 'src/factories'; import { linodeDiskFactory } from 'src/factories/disk'; import { imageFactory } from 'src/factories/images'; diff --git a/packages/manager/cypress/e2e/core/kubernetes/smoke-lke-create.spec.ts b/packages/manager/cypress/e2e/core/kubernetes/smoke-lke-create.spec.ts index c32b7eb1d4e..f6b99c5ab16 100644 --- a/packages/manager/cypress/e2e/core/kubernetes/smoke-lke-create.spec.ts +++ b/packages/manager/cypress/e2e/core/kubernetes/smoke-lke-create.spec.ts @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { accountUserFactory, grantsFactory, kubernetesClusterFactory, - profileFactory, } from '@src/factories'; import { mockGetUser } from 'support/intercepts/account'; import { mockCreateCluster } from 'support/intercepts/lke'; diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts index f3812319191..fac8dd00c69 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts @@ -1,4 +1,4 @@ -import { linodeFactory } from '@linode/utilities'; +import { linodeFactory, sshKeyFactory } from '@linode/utilities'; import { mockGetUser, mockGetUsers } from 'support/intercepts/account'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockCreateSSHKey } from 'support/intercepts/profile'; @@ -7,7 +7,7 @@ import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { accountUserFactory, sshKeyFactory } from 'src/factories'; +import { accountUserFactory } from 'src/factories'; describe('Create Linode with SSH Key', () => { /* diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts index 357cb202f2b..f5f0cff79a4 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts @@ -5,6 +5,7 @@ import { linodeFactory, linodeTypeFactory, + profileFactory, regionFactory, } from '@linode/utilities'; import { authenticate } from 'support/api/authentication'; @@ -34,7 +35,6 @@ import { accountFactory, accountUserFactory, grantsFactory, - profileFactory, } from 'src/factories'; let username: string; diff --git a/packages/manager/cypress/e2e/core/linodes/smoke-linode-landing-table.spec.ts b/packages/manager/cypress/e2e/core/linodes/smoke-linode-landing-table.spec.ts index b67e42ff902..7c878913a8c 100644 --- a/packages/manager/cypress/e2e/core/linodes/smoke-linode-landing-table.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/smoke-linode-landing-table.spec.ts @@ -1,6 +1,9 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { linodeFactory } from '@linode/utilities'; -import { profileFactory, userPreferencesFactory } from '@src/factories'; +import { + linodeFactory, + profileFactory, + userPreferencesFactory, +} from '@linode/utilities'; import { accountSettingsFactory } from '@src/factories/accountSettings'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; diff --git a/packages/manager/cypress/e2e/core/managed/managed-navigation.spec.ts b/packages/manager/cypress/e2e/core/managed/managed-navigation.spec.ts index 9df4cefa15e..07ea0214e64 100644 --- a/packages/manager/cypress/e2e/core/managed/managed-navigation.spec.ts +++ b/packages/manager/cypress/e2e/core/managed/managed-navigation.spec.ts @@ -2,6 +2,7 @@ * @file Integration tests for Managed navigation. */ +import { userPreferencesFactory } from '@linode/utilities'; import { managedAccount, nonManagedAccount, @@ -26,7 +27,6 @@ import { managedIssueFactory, monitorFactory, } from 'src/factories/managed'; -import { userPreferencesFactory } from 'src/factories/profile'; import type { UserPreferences } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/objectStorage/enable-object-storage.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/enable-object-storage.spec.ts index 78dd831ccd1..fbf74587e72 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/enable-object-storage.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/enable-object-storage.spec.ts @@ -1,13 +1,12 @@ /** * @file Cypress integration tests for OBJ enrollment and cancellation. */ -import { regionFactory } from '@linode/utilities'; +import { profileFactory, regionFactory } from '@linode/utilities'; import { accountFactory, accountSettingsFactory, objectStorageClusterFactory, objectStorageKeyFactory, - profileFactory, } from '@src/factories'; import { mockGetAccount, diff --git a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-access-keys-gen2.spec.ts b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-access-keys-gen2.spec.ts index 1141986b61b..b1b7be3ce68 100644 --- a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-access-keys-gen2.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-access-keys-gen2.spec.ts @@ -1,3 +1,4 @@ +import { profileFactory } from '@linode/utilities'; import { mockGetAccount } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockGetAccessKeys } from 'support/intercepts/object-storage'; @@ -5,7 +6,6 @@ import { mockGetProfile } from 'support/intercepts/profile'; import { ui } from 'support/ui'; import { accountFactory, objectStorageKeyFactory } from 'src/factories'; -import { profileFactory } from 'src/factories/profile'; describe('Object Storage gen2 access keys tests', () => { /** diff --git a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts index 02d531df972..5592400f299 100644 --- a/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageGen2/bucket-create-gen2.spec.ts @@ -1,4 +1,4 @@ -import { regionFactory } from '@linode/utilities'; +import { profileFactory, regionFactory } from '@linode/utilities'; import { mockGetAccount } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { @@ -21,7 +21,6 @@ import { objectStorageBucketFactoryGen2, objectStorageEndpointsFactory, } from 'src/factories'; -import { profileFactory } from 'src/factories/profile'; import type { ACLType, ObjectStorageEndpoint } from '@linode/api-v4'; diff --git a/packages/manager/cypress/e2e/core/parentChild/account-switching.spec.ts b/packages/manager/cypress/e2e/core/parentChild/account-switching.spec.ts index 1f5383399ad..271235aa07f 100644 --- a/packages/manager/cypress/e2e/core/parentChild/account-switching.spec.ts +++ b/packages/manager/cypress/e2e/core/parentChild/account-switching.spec.ts @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { accountFactory, appTokenFactory, paymentMethodFactory, - profileFactory, } from '@src/factories'; import { accountUserFactory } from '@src/factories/accountUsers'; import { grantsFactory } from '@src/factories/grants'; diff --git a/packages/manager/cypress/e2e/core/parentChild/token-expiration.spec.ts b/packages/manager/cypress/e2e/core/parentChild/token-expiration.spec.ts index 00b1652fa2a..18227d3bce4 100644 --- a/packages/manager/cypress/e2e/core/parentChild/token-expiration.spec.ts +++ b/packages/manager/cypress/e2e/core/parentChild/token-expiration.spec.ts @@ -1,3 +1,4 @@ +import { profileFactory } from '@linode/utilities'; import { DateTime } from 'luxon'; import { mockGetAccount, @@ -8,11 +9,7 @@ import { mockGetProfile } from 'support/intercepts/profile'; import { ui } from 'support/ui'; import { randomLabel, randomString } from 'support/util/random'; -import { - accountFactory, - accountUserFactory, - profileFactory, -} from 'src/factories'; +import { accountFactory, accountUserFactory } from 'src/factories'; const mockChildAccount = accountFactory.build({ company: 'Partner Company', diff --git a/packages/manager/cypress/e2e/core/parentChild/token-scopes.spec.ts b/packages/manager/cypress/e2e/core/parentChild/token-scopes.spec.ts index 48ab9549bc6..5547bd1a262 100644 --- a/packages/manager/cypress/e2e/core/parentChild/token-scopes.spec.ts +++ b/packages/manager/cypress/e2e/core/parentChild/token-scopes.spec.ts @@ -1,8 +1,5 @@ -import { - accountFactory, - appTokenFactory, - profileFactory, -} from '@src/factories'; +import { profileFactory } from '@linode/utilities'; +import { accountFactory, appTokenFactory } from '@src/factories'; import { accountUserFactory } from '@src/factories/accountUsers'; import { DateTime } from 'luxon'; import { diff --git a/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts index 8a12ff4f8ee..df55d1fbab8 100644 --- a/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts @@ -1,9 +1,5 @@ -import { createLinodeRequestFactory } from '@linode/utilities'; -import { - accountUserFactory, - grantsFactory, - profileFactory, -} from '@src/factories'; +import { createLinodeRequestFactory, profileFactory } from '@linode/utilities'; +import { accountUserFactory, grantsFactory } from '@src/factories'; import { authenticate } from 'support/api/authentication'; import { entityTag } from 'support/constants/cypress'; import { mockGetUser } from 'support/intercepts/account'; diff --git a/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.test.tsx b/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.test.tsx index d271b141c43..6cc350e05aa 100644 --- a/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.test.tsx +++ b/packages/manager/src/components/AccessPanel/UserSSHKeyPanel.test.tsx @@ -1,8 +1,8 @@ +import { profileFactory, sshKeyFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { profileFactory, sshKeyFactory } from 'src/factories'; import { accountUserFactory } from 'src/factories/accountUsers'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/components/Avatar/Avatar.test.tsx b/packages/manager/src/components/Avatar/Avatar.test.tsx index bbd256aa30e..2ef7c70fd7d 100644 --- a/packages/manager/src/components/Avatar/Avatar.test.tsx +++ b/packages/manager/src/components/Avatar/Avatar.test.tsx @@ -1,6 +1,6 @@ +import { profileFactory } from '@linode/utilities'; import * as React from 'react'; -import { profileFactory } from 'src/factories/profile'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { Avatar } from './Avatar'; diff --git a/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.test.tsx b/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.test.tsx index 86c393f0c56..25b977b5876 100644 --- a/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.test.tsx +++ b/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.test.tsx @@ -4,8 +4,17 @@ import * as React from 'react'; import { ISO_DATETIME_NO_TZ_FORMAT } from 'src/constants'; import { renderWithTheme } from 'src/utilities/testHelpers'; -import { DateTimeDisplay, DateTimeDisplayProps } from './DateTimeDisplay'; -vi.mock('../../utilities/getUserTimezone'); +import { DateTimeDisplay } from './DateTimeDisplay'; + +import type { DateTimeDisplayProps } from './DateTimeDisplay'; + +vi.mock('@linode/utilities', async () => { + const actual = await vi.importActual('@linode/utilities'); + return { + ...actual, + getUserTimezone: vi.fn().mockReturnValue('utc'), + }; +}); const APIDate = '2018-07-20T04:23:17'; diff --git a/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.tsx b/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.tsx index 467a8ec9053..c2fdfaeee14 100644 --- a/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.tsx +++ b/packages/manager/src/components/DateTimeDisplay/DateTimeDisplay.tsx @@ -1,7 +1,7 @@ +import { useProfile } from '@linode/queries'; import { Typography } from '@linode/ui'; import * as React from 'react'; -import { useProfile } from '@linode/queries'; import { formatDate } from 'src/utilities/formatDate'; import type { TimeInterval } from 'src/utilities/formatDate'; diff --git a/packages/manager/src/dev-tools/components/ExtraPresetProfile.tsx b/packages/manager/src/dev-tools/components/ExtraPresetProfile.tsx index 317810062f3..7d16ed10b41 100644 --- a/packages/manager/src/dev-tools/components/ExtraPresetProfile.tsx +++ b/packages/manager/src/dev-tools/components/ExtraPresetProfile.tsx @@ -1,7 +1,7 @@ import { Dialog } from '@linode/ui'; +import { profileFactory } from '@linode/utilities'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { extraMockPresets } from 'src/mocks/presets'; import { setCustomProfileData } from 'src/mocks/presets/extra/account/customProfile'; diff --git a/packages/manager/src/factories/grants.ts b/packages/manager/src/factories/grants.ts index 2e296077eab..ed74bb0eab5 100644 --- a/packages/manager/src/factories/grants.ts +++ b/packages/manager/src/factories/grants.ts @@ -1,6 +1,7 @@ -import { Grant, Grants } from '@linode/api-v4/lib/account'; import { Factory } from '@linode/utilities'; +import type { Grant, Grants } from '@linode/api-v4/lib/account'; + export const grantFactory = Factory.Sync.makeFactory({ id: Factory.each((i) => i), label: Factory.each((i) => `entity-${i}`), diff --git a/packages/manager/src/factories/index.ts b/packages/manager/src/factories/index.ts index 1532384e897..b9eb65f419c 100644 --- a/packages/manager/src/factories/index.ts +++ b/packages/manager/src/factories/index.ts @@ -34,7 +34,6 @@ export * from './oauth'; export * from './objectStorage'; export * from './placementGroups'; export * from './preferences'; -export * from './profile'; export * from './promotionalOffer'; export * from './stackscripts'; export * from './statusPage'; diff --git a/packages/manager/src/features/Account/CloseAccountSetting.test.tsx b/packages/manager/src/features/Account/CloseAccountSetting.test.tsx index bedbedcc8f9..813693cc55b 100644 --- a/packages/manager/src/features/Account/CloseAccountSetting.test.tsx +++ b/packages/manager/src/features/Account/CloseAccountSetting.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { fireEvent, waitFor } from '@testing-library/react'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { renderWithTheme } from 'src/utilities/testHelpers'; import CloseAccountSetting from './CloseAccountSetting'; diff --git a/packages/manager/src/features/Account/ObjectStorageSettings.test.tsx b/packages/manager/src/features/Account/ObjectStorageSettings.test.tsx index 752770ef889..d505f431982 100644 --- a/packages/manager/src/features/Account/ObjectStorageSettings.test.tsx +++ b/packages/manager/src/features/Account/ObjectStorageSettings.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { accountSettingsFactory, profileFactory } from 'src/factories'; +import { accountSettingsFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Account/Quotas/utils.test.tsx b/packages/manager/src/features/Account/Quotas/utils.test.tsx index 1defc2fd551..b2d936e64ae 100644 --- a/packages/manager/src/features/Account/Quotas/utils.test.tsx +++ b/packages/manager/src/features/Account/Quotas/utils.test.tsx @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react'; import * as React from 'react'; -import { profileFactory } from 'src/factories/profile'; import { quotaFactory, quotaUsageFactory } from 'src/factories/quotas'; import { diff --git a/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx b/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx index d18c3686f87..4454594073f 100644 --- a/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx +++ b/packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { fireEvent, waitFor } from '@testing-library/react'; import * as React from 'react'; -import { profileFactory } from 'src/factories/profile'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Account/SwitchAccounts/ChildAccountList.test.tsx b/packages/manager/src/features/Account/SwitchAccounts/ChildAccountList.test.tsx index 3e3416e2426..537e0b2b54c 100644 --- a/packages/manager/src/features/Account/SwitchAccounts/ChildAccountList.test.tsx +++ b/packages/manager/src/features/Account/SwitchAccounts/ChildAccountList.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor, within } from '@testing-library/react'; import * as React from 'react'; -import { accountFactory, profileFactory } from 'src/factories'; +import { accountFactory } from 'src/factories'; import { ChildAccountList } from 'src/features/Account/SwitchAccounts/ChildAccountList'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/features/Backups/BackupsCTA.test.tsx b/packages/manager/src/features/Backups/BackupsCTA.test.tsx index a09eff4c069..cf39fe107e9 100644 --- a/packages/manager/src/features/Backups/BackupsCTA.test.tsx +++ b/packages/manager/src/features/Backups/BackupsCTA.test.tsx @@ -1,7 +1,7 @@ -import { linodeFactory } from '@linode/utilities'; +import { linodeFactory, profileFactory } from '@linode/utilities'; import * as React from 'react'; -import { accountSettingsFactory, profileFactory } from 'src/factories'; +import { accountSettingsFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Billing/BillingPanels/BillingActivityPanel/BillingActivityPanel.test.tsx b/packages/manager/src/features/Billing/BillingPanels/BillingActivityPanel/BillingActivityPanel.test.tsx index f0529271171..5d193f2cfd7 100644 --- a/packages/manager/src/features/Billing/BillingPanels/BillingActivityPanel/BillingActivityPanel.test.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/BillingActivityPanel/BillingActivityPanel.test.tsx @@ -13,13 +13,20 @@ import { paymentToActivityFeedItem, transactionDateOptions, } from './BillingActivityPanel'; -vi.mock('../../../../utilities/getUserTimezone'); + +vi.mock('@linode/utilities', async () => { + const actual = await vi.importActual('@linode/utilities'); + return { + ...actual, + getUserTimezone: vi.fn().mockReturnValue('utc'), + }; +}); // Mock global Date object so Transaction Date tests are deterministic. global.Date.now = vi.fn(() => new Date('2020-01-02T00:00:00').getTime()); vi.mock('@linode/api-v4/lib/account', async () => { - const actual = await vi.importActual('@linode/api-v4/lib/account'); + const actual = await vi.importActual('@linode/api-v4/lib/account'); const invoices = [ // eslint-disable-next-line invoiceFactory.build({ date: '2020-01-01T00:00:00' }), 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 bb2c501a26e..007fc991e7c 100644 --- a/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/ContactInfoPanel/ContactInformation.test.tsx @@ -1,6 +1,6 @@ +import { profileFactory } from '@linode/utilities'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/PaymentInformation.test.tsx b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/PaymentInformation.test.tsx index c83dce711f2..d448a99a7d2 100644 --- a/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/PaymentInformation.test.tsx +++ b/packages/manager/src/features/Billing/BillingPanels/PaymentInfoPanel/PaymentInformation.test.tsx @@ -1,9 +1,10 @@ +import { profileFactory } from '@linode/utilities'; import { PayPalScriptProvider } from '@paypal/react-paypal-js'; import { fireEvent } from '@testing-library/react'; import * as React from 'react'; import { PAYPAL_CLIENT_ID } from 'src/constants'; -import { profileFactory, paymentMethodFactory } from 'src/factories'; +import { paymentMethodFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { renderWithTheme, wrapWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/IAM/Users/UserDetails/DeleteUserPanel.test.tsx b/packages/manager/src/features/IAM/Users/UserDetails/DeleteUserPanel.test.tsx index 03429a26a97..5126852229f 100644 --- a/packages/manager/src/features/IAM/Users/UserDetails/DeleteUserPanel.test.tsx +++ b/packages/manager/src/features/IAM/Users/UserDetails/DeleteUserPanel.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { accountUserFactory, profileFactory } from 'src/factories'; +import { accountUserFactory } from 'src/factories'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { DeleteUserPanel } from './DeleteUserPanel'; diff --git a/packages/manager/src/features/IAM/Users/UserDetails/UserEmailPanel.test.tsx b/packages/manager/src/features/IAM/Users/UserDetails/UserEmailPanel.test.tsx index ec5f11e2915..56d680f9c21 100644 --- a/packages/manager/src/features/IAM/Users/UserDetails/UserEmailPanel.test.tsx +++ b/packages/manager/src/features/IAM/Users/UserDetails/UserEmailPanel.test.tsx @@ -1,6 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import React from 'react'; -import { accountUserFactory, profileFactory } from 'src/factories'; +import { accountUserFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/IAM/Users/UsersTable/UserRow.test.tsx b/packages/manager/src/features/IAM/Users/UsersTable/UserRow.test.tsx index 1522117850d..48297641a19 100644 --- a/packages/manager/src/features/IAM/Users/UsersTable/UserRow.test.tsx +++ b/packages/manager/src/features/IAM/Users/UsersTable/UserRow.test.tsx @@ -1,6 +1,6 @@ +import { profileFactory } from '@linode/utilities'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { accountUserFactory } from 'src/factories/accountUsers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { diff --git a/packages/manager/src/features/IAM/Users/UsersTable/UsersActionMenu.test.tsx b/packages/manager/src/features/IAM/Users/UsersTable/UsersActionMenu.test.tsx index 13b59a9a7c3..f194e87622b 100644 --- a/packages/manager/src/features/IAM/Users/UsersTable/UsersActionMenu.test.tsx +++ b/packages/manager/src/features/IAM/Users/UsersTable/UsersActionMenu.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { UsersActionMenu } from './UsersActionMenu'; diff --git a/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.test.tsx b/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.test.tsx index 14e7381047f..1914fff0156 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.test.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImagesLanding.test.tsx @@ -1,8 +1,9 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { grantsFactory, imageFactory, profileFactory } from 'src/factories'; +import { grantsFactory, imageFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { diff --git a/packages/manager/src/features/Images/ImagesLanding/ImagesLandingEmptyState.test.tsx b/packages/manager/src/features/Images/ImagesLanding/ImagesLandingEmptyState.test.tsx index 036449b78bf..68535118cbd 100644 --- a/packages/manager/src/features/Images/ImagesLanding/ImagesLandingEmptyState.test.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/ImagesLandingEmptyState.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { grantsFactory, profileFactory } from 'src/factories'; +import { grantsFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithThemeAndRouter } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.test.tsx index 537aa716d70..80e85ac5ea0 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.test.tsx @@ -1,8 +1,8 @@ -import { regionFactory } from '@linode/utilities'; +import { profileFactory, regionFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { accountSettingsFactory, profileFactory } from 'src/factories'; +import { accountSettingsFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.test.tsx index a57756aee8a..3e2dfa064ef 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.test.tsx @@ -1,8 +1,7 @@ -import { regionFactory } from '@linode/utilities'; +import { profileFactory, regionFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.test.tsx index 46e007f0958..98fcf5c6a0d 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Details/Details.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { grantsFactory, profileFactory } from 'src/factories'; +import { grantsFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Region.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Region.test.tsx index 7c2f3f26d45..2dbda81d991 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Region.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Region.test.tsx @@ -1,13 +1,14 @@ import { linodeFactory, linodeTypeFactory, + profileFactory, regionFactory, } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import React from 'react'; -import { grantsFactory, imageFactory, profileFactory } from 'src/factories'; +import { grantsFactory, imageFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Security.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Security.test.tsx index 3938483b0cc..a74ee1ad629 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Security.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Security.test.tsx @@ -1,8 +1,12 @@ -import { regionFactory } from '@linode/utilities'; +import { + profileFactory, + regionFactory, + sshKeyFactory, +} from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { accountFactory, profileFactory, sshKeyFactory } from 'src/factories'; +import { accountFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.test.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.test.tsx index 432ce683b0f..b9f9fbc2243 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.test.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.test.tsx @@ -1,9 +1,8 @@ -import { linodeFactory } from '@linode/utilities'; +import { linodeFactory, profileFactory } from '@linode/utilities'; import { userEvent } from '@testing-library/user-event'; import { Settings } from 'luxon'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.tsx index aeffde9fb56..c29fe74091b 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeBackup/ScheduleSettings.tsx @@ -12,14 +12,12 @@ import { Paper, Typography, } from '@linode/ui'; -import { initWindows } from '@linode/utilities'; +import { getUserTimezone, initWindows } from '@linode/utilities'; import { styled } from '@mui/material/styles'; import { useFormik } from 'formik'; import { useSnackbar } from 'notistack'; import * as React from 'react'; -import { getUserTimezone } from 'src/utilities/getUserTimezone'; - interface Props { isReadOnly: boolean; linodeId: number; diff --git a/packages/manager/src/features/Longview/LongviewLanding/LongviewPlans.test.tsx b/packages/manager/src/features/Longview/LongviewLanding/LongviewPlans.test.tsx index 07db574b1d5..5786900a228 100644 --- a/packages/manager/src/features/Longview/LongviewLanding/LongviewPlans.test.tsx +++ b/packages/manager/src/features/Longview/LongviewLanding/LongviewPlans.test.tsx @@ -1,3 +1,4 @@ +import { profileFactory } from '@linode/utilities'; import { screen, waitFor, @@ -10,16 +11,12 @@ import { withDocumentTitleProvider } from 'src/components/DocumentTitle'; import { accountSettingsFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { longviewSubscriptionFactory } from 'src/factories/longviewSubscription'; -import { profileFactory } from 'src/factories/profile'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; -import { - LONGVIEW_FREE_ID, - LongviewPlans, - LongviewPlansProps, - formatPrice, -} from './LongviewPlans'; +import { LONGVIEW_FREE_ID, LongviewPlans, formatPrice } from './LongviewPlans'; + +import type { LongviewPlansProps } from './LongviewPlans'; const mockLongviewSubscriptions = longviewSubscriptionFactory.buildList(4); diff --git a/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx b/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx index 1a3603d7096..35fc5899124 100644 --- a/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx +++ b/packages/manager/src/features/Managed/ManagedDashboardCard/ManagedChartPanel.tsx @@ -1,4 +1,6 @@ +import { useProfile } from '@linode/queries'; import { Box, CircleProgress, ErrorState, Typography } from '@linode/ui'; +import { getUserTimezone } from '@linode/utilities'; import { useTheme } from '@mui/material/styles'; import * as React from 'react'; @@ -9,9 +11,7 @@ import { generateNetworkUnits, } from 'src/features/Longview/shared/utilities'; import { useManagedStatsQuery } from 'src/queries/managed/managed'; -import { useProfile } from '@linode/queries'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; -import { getUserTimezone } from 'src/utilities/getUserTimezone'; import { StyledGraphControlsDiv, diff --git a/packages/manager/src/features/Managed/Monitors/IssueCalendar.tsx b/packages/manager/src/features/Managed/Monitors/IssueCalendar.tsx index d1f610c73cf..4fc7f3884a4 100644 --- a/packages/manager/src/features/Managed/Monitors/IssueCalendar.tsx +++ b/packages/manager/src/features/Managed/Monitors/IssueCalendar.tsx @@ -1,13 +1,14 @@ -import { ManagedIssue } from '@linode/api-v4'; +import { useProfile } from '@linode/queries'; +import { getUserTimezone } from '@linode/utilities'; import { DateTime } from 'luxon'; import * as React from 'react'; -import { useProfile } from '@linode/queries'; import { parseAPIDate } from 'src/utilities/date'; -import { getUserTimezone } from 'src/utilities/getUserTimezone'; import IssueDay from './IssueDay'; +import type { ManagedIssue } from '@linode/api-v4'; + const TOTAL_DAYS = 10; interface IssueCalendarProps { diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/TablesPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/TablesPanel.tsx index 38efeaafe54..f2fe70eeac1 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/TablesPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/TablesPanel.tsx @@ -4,7 +4,7 @@ import { useProfile, } from '@linode/queries'; import { Box, CircleProgress, ErrorState, Paper, Typography } from '@linode/ui'; -import { formatNumber, getMetrics } from '@linode/utilities'; +import { formatNumber, getMetrics, getUserTimezone } from '@linode/utilities'; import { styled, useTheme } from '@mui/material/styles'; import { useParams } from '@tanstack/react-router'; import * as React from 'react'; @@ -13,7 +13,6 @@ import PendingIcon from 'src/assets/icons/pending.svg'; import { AreaChart } from 'src/components/AreaChart/AreaChart'; import { formatBitsPerSecond } from 'src/features/Longview/shared/utilities'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; -import { getUserTimezone } from 'src/utilities/getUserTimezone'; import type { Theme } from '@mui/material/styles'; import type { diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectDetailsDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectDetailsDrawer.test.tsx index e359f1425d5..41b105baf4a 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectDetailsDrawer.test.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectDetailsDrawer.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { act, waitFor } from '@testing-library/react'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.test.tsx index 97e62f9bf99..88486866dba 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.test.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.test.tsx @@ -1,4 +1,5 @@ import { + profileFactory, readableBytes, regionFactory, truncateMiddle, @@ -10,7 +11,6 @@ import { vi } from 'vitest'; import { objectStorageBucketFactory, objectStorageBucketFactoryGen2, - profileFactory, } from 'src/factories'; import { formatDate } from 'src/utilities/formatDate'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.test.tsx b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.test.tsx index cf02a92a23d..77356b600f6 100644 --- a/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.test.tsx +++ b/packages/manager/src/features/Profile/APITokens/CreateAPITokenDrawer.test.tsx @@ -1,10 +1,10 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { appTokenFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; -import { profileFactory } from 'src/factories/profile'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/APITokens/ViewAPITokenDrawer.test.tsx b/packages/manager/src/features/Profile/APITokens/ViewAPITokenDrawer.test.tsx index 16ef2448375..5b372fff0b1 100644 --- a/packages/manager/src/features/Profile/APITokens/ViewAPITokenDrawer.test.tsx +++ b/packages/manager/src/features/Profile/APITokens/ViewAPITokenDrawer.test.tsx @@ -1,6 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import * as React from 'react'; -import { appTokenFactory, profileFactory } from 'src/factories'; +import { appTokenFactory } from 'src/factories'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { basePerms } from './utils'; diff --git a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx index f381a503950..40506219b7d 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/EmailForm.test.tsx @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx index 2093cd8887e..28a8306b0ff 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import * as React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx index d0cd4bd5a8f..ad33398fe11 100644 --- a/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx +++ b/packages/manager/src/features/Profile/DisplaySettings/UsernameForm.test.tsx @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.test.tsx b/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.test.tsx index e8101838b46..89f25a8679e 100644 --- a/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.test.tsx +++ b/packages/manager/src/features/Profile/SSHKeys/CreateSSHKeyDrawer.test.tsx @@ -1,9 +1,9 @@ +import { sshKeyFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import { sshKeyFactory } from 'src/factories'; -import { http, HttpResponse, server } from 'src/mocks/testServer'; +import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { CreateSSHKeyDrawer } from './CreateSSHKeyDrawer'; diff --git a/packages/manager/src/features/Profile/SSHKeys/SSHKeys.test.tsx b/packages/manager/src/features/Profile/SSHKeys/SSHKeys.test.tsx index 296b121a1b0..d06641c1ca7 100644 --- a/packages/manager/src/features/Profile/SSHKeys/SSHKeys.test.tsx +++ b/packages/manager/src/features/Profile/SSHKeys/SSHKeys.test.tsx @@ -1,7 +1,7 @@ +import { sshKeyFactory } from '@linode/utilities'; import { waitForElementToBeRemoved } from '@testing-library/react'; import * as React from 'react'; -import { sshKeyFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { http, HttpResponse, server } from 'src/mocks/testServer'; import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Profile/Settings/Notifications.test.tsx b/packages/manager/src/features/Profile/Settings/Notifications.test.tsx index bb721d54120..bc4d9e90736 100644 --- a/packages/manager/src/features/Profile/Settings/Notifications.test.tsx +++ b/packages/manager/src/features/Profile/Settings/Notifications.test.tsx @@ -1,7 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/TopMenu/UserMenu/UserMenu.test.tsx b/packages/manager/src/features/TopMenu/UserMenu/UserMenu.test.tsx index c8596ff8b42..9f3dccc9d9f 100644 --- a/packages/manager/src/features/TopMenu/UserMenu/UserMenu.test.tsx +++ b/packages/manager/src/features/TopMenu/UserMenu/UserMenu.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { fireEvent, within } from '@testing-library/react'; import * as React from 'react'; -import { accountFactory, profileFactory } from 'src/factories'; +import { accountFactory } from 'src/factories'; import { grantsFactory } from 'src/factories/grants'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/TopMenu/UserMenu/utils.test.ts b/packages/manager/src/features/TopMenu/UserMenu/utils.test.ts index 8a960cac142..8857c95b81f 100644 --- a/packages/manager/src/features/TopMenu/UserMenu/utils.test.ts +++ b/packages/manager/src/features/TopMenu/UserMenu/utils.test.ts @@ -1,9 +1,9 @@ -import { UserType } from '@linode/api-v4'; - -import { profileFactory } from 'src/factories'; +import { profileFactory } from '@linode/utilities'; import { getCompanyNameOrEmail } from './utils'; +import type { UserType } from '@linode/api-v4'; + const MOCK_COMPANY_NAME = 'Test Company, LLC'; describe('getCompanyNameOrEmail', () => { diff --git a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.test.tsx b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.test.tsx index ec5f11e2915..56d680f9c21 100644 --- a/packages/manager/src/features/Users/UserProfile/UserEmailPanel.test.tsx +++ b/packages/manager/src/features/Users/UserProfile/UserEmailPanel.test.tsx @@ -1,6 +1,7 @@ +import { profileFactory } from '@linode/utilities'; import React from 'react'; -import { accountUserFactory, profileFactory } from 'src/factories'; +import { accountUserFactory } from 'src/factories'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithTheme } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/features/Users/UserRow.test.tsx b/packages/manager/src/features/Users/UserRow.test.tsx index 3841f20d1d3..beee5b15549 100644 --- a/packages/manager/src/features/Users/UserRow.test.tsx +++ b/packages/manager/src/features/Users/UserRow.test.tsx @@ -1,6 +1,6 @@ +import { profileFactory } from '@linode/utilities'; import React from 'react'; -import { profileFactory } from 'src/factories'; import { accountUserFactory } from 'src/factories/accountUsers'; import { grantsFactory } from 'src/factories/grants'; import { HttpResponse, http, server } from 'src/mocks/testServer'; diff --git a/packages/manager/src/features/Volumes/VolumesLandingEmptyState.test.tsx b/packages/manager/src/features/Volumes/VolumesLandingEmptyState.test.tsx index 5db19c1234f..3fe37c35f32 100644 --- a/packages/manager/src/features/Volumes/VolumesLandingEmptyState.test.tsx +++ b/packages/manager/src/features/Volumes/VolumesLandingEmptyState.test.tsx @@ -1,7 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { grantsFactory, profileFactory } from 'src/factories'; +import { grantsFactory } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; import { HttpResponse, http, server } from 'src/mocks/testServer'; import { renderWithThemeAndRouter } from 'src/utilities/testHelpers'; diff --git a/packages/manager/src/mocks/presets/extra/account/customProfile.ts b/packages/manager/src/mocks/presets/extra/account/customProfile.ts index 8d936604ea0..7f37a323f0f 100644 --- a/packages/manager/src/mocks/presets/extra/account/customProfile.ts +++ b/packages/manager/src/mocks/presets/extra/account/customProfile.ts @@ -1,6 +1,6 @@ +import { profileFactory } from '@linode/utilities'; import { http } from 'msw'; -import { profileFactory } from 'src/factories'; import { makeResponse } from 'src/mocks/utilities/response'; import type { Profile } from '@linode/api-v4'; diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index fdac266145c..c3eea73bd93 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -19,8 +19,10 @@ import { nodeBalancerFactory, pickRandom, proDedicatedTypeFactory, + profileFactory, regionAvailabilityFactory, regions, + securityQuestionsFactory, } from '@linode/utilities'; import { DateTime } from 'luxon'; import { HttpResponse, http } from 'msw'; @@ -92,9 +94,7 @@ import { placementGroupFactory, possibleMySQLReplicationTypes, possiblePostgresReplicationTypes, - profileFactory, promoFactory, - securityQuestionsFactory, serviceTypesFactory, stackScriptFactory, staticObjects, diff --git a/packages/manager/src/request.test.tsx b/packages/manager/src/request.test.tsx index 4abd6252b72..33bfa853adc 100644 --- a/packages/manager/src/request.test.tsx +++ b/packages/manager/src/request.test.tsx @@ -1,8 +1,8 @@ +import { profileFactory } from '@linode/utilities'; import { AxiosHeaders } from 'axios'; import { handleStartSession } from 'src/store/authentication/authentication.actions'; -import { profileFactory } from './factories'; import { getURL, handleError, injectAkamaiAccountHeader } from './request'; import { storeFactory } from './store'; diff --git a/packages/manager/src/utilities/formatDate.test.ts b/packages/manager/src/utilities/formatDate.test.ts index f5cf3e121f6..3ee3d1017cb 100644 --- a/packages/manager/src/utilities/formatDate.test.ts +++ b/packages/manager/src/utilities/formatDate.test.ts @@ -6,7 +6,14 @@ import { } from 'src/constants'; import { formatDate, shouldHumanize } from './formatDate'; -vi.mock('./getUserTimezone'); + +vi.mock('@linode/utilities', async () => { + const actual = await vi.importActual('@linode/utilities'); + return { + ...actual, + getUserTimezone: vi.fn().mockReturnValue('utc'), + }; +}); describe('shouldHumanize', () => { it('should NOT humanize few days duration with day cutoff', () => { @@ -22,6 +29,7 @@ describe('shouldHumanize', () => { expect(shouldHumanize(time, 'month')).toBeTruthy(); }); }); + describe('formatDate utility', () => { describe('Non-humanized dates', () => { it('should be displayed in 24-hour ISO format', () => { diff --git a/packages/manager/src/utilities/formatDate.ts b/packages/manager/src/utilities/formatDate.ts index bd07fccd418..719fab754b2 100644 --- a/packages/manager/src/utilities/formatDate.ts +++ b/packages/manager/src/utilities/formatDate.ts @@ -1,11 +1,10 @@ +import { getUserTimezone } from '@linode/utilities'; import { DateTime, Duration } from 'luxon'; import { DATETIME_DISPLAY_FORMAT, ISO_DATE_FORMAT } from 'src/constants'; import { reportException } from 'src/exceptionReporting'; import { parseAPIDate } from 'src/utilities/date'; -import { getUserTimezone } from './getUserTimezone'; - export type TimeInterval = 'day' | 'month' | 'never' | 'week' | 'year'; const durationMap = { diff --git a/packages/utilities/.changeset/pr-11955-added-1743596189029.md b/packages/utilities/.changeset/pr-11955-added-1743596189029.md new file mode 100644 index 00000000000..74f0782d536 --- /dev/null +++ b/packages/utilities/.changeset/pr-11955-added-1743596189029.md @@ -0,0 +1,5 @@ +--- +"@linode/utilities": Added +--- + +Move `getUserTimeZone` and its associated profile factories to `@linode/utilities` ([#11955](https://github.com/linode/manager/pull/11955)) diff --git a/packages/utilities/src/factories/index.ts b/packages/utilities/src/factories/index.ts index ea88b488e7c..7994c8c46a0 100644 --- a/packages/utilities/src/factories/index.ts +++ b/packages/utilities/src/factories/index.ts @@ -5,4 +5,5 @@ export * from './linodes'; export * from './linodeConfigInterface'; export * from './linodeInterface'; export * from './nodebalancer'; +export * from './profile'; export * from './regions'; diff --git a/packages/manager/src/factories/profile.ts b/packages/utilities/src/factories/profile.ts similarity index 98% rename from packages/manager/src/factories/profile.ts rename to packages/utilities/src/factories/profile.ts index adf79e23b5f..1aa5aaf5607 100644 --- a/packages/manager/src/factories/profile.ts +++ b/packages/utilities/src/factories/profile.ts @@ -1,4 +1,4 @@ -import { Factory } from '@linode/utilities'; +import { Factory } from './factoryProxy'; import type { Profile, diff --git a/packages/manager/src/utilities/getUserTimezone.test.ts b/packages/utilities/src/helpers/getUserTimezone.test.ts similarity index 86% rename from packages/manager/src/utilities/getUserTimezone.test.ts rename to packages/utilities/src/helpers/getUserTimezone.test.ts index 61e04f05f5a..4713958d419 100644 --- a/packages/manager/src/utilities/getUserTimezone.test.ts +++ b/packages/utilities/src/helpers/getUserTimezone.test.ts @@ -1,9 +1,11 @@ -import { Profile } from '@linode/api-v4/lib/profile'; -import { DateTime } from 'luxon'; +import { describe, expect, it } from 'vitest'; -import { profileFactory } from 'src/factories/profile'; +import { DateTime } from 'luxon'; import { getUserTimezone } from './getUserTimezone'; +import { profileFactory } from '../factories/profile'; + +import type { Profile } from '@linode/api-v4/lib/profile'; const mockProfile: Profile = profileFactory.build(); diff --git a/packages/manager/src/utilities/getUserTimezone.ts b/packages/utilities/src/helpers/getUserTimezone.ts similarity index 88% rename from packages/manager/src/utilities/getUserTimezone.ts rename to packages/utilities/src/helpers/getUserTimezone.ts index 8cada0b91ad..ea6f687a9f6 100644 --- a/packages/manager/src/utilities/getUserTimezone.ts +++ b/packages/utilities/src/helpers/getUserTimezone.ts @@ -2,7 +2,7 @@ import { DateTime, IANAZone } from 'luxon'; export const getUserTimezone = (profileTimezone?: string) => { return profileTimezone && - profileTimezone != '' && + profileTimezone !== '' && IANAZone.isValidZone(profileTimezone) ? profileTimezone : DateTime.local().zoneName; diff --git a/packages/utilities/src/helpers/index.ts b/packages/utilities/src/helpers/index.ts index 976db2d3ac6..56889df0aa4 100644 --- a/packages/utilities/src/helpers/index.ts +++ b/packages/utilities/src/helpers/index.ts @@ -17,10 +17,11 @@ export * from './formatDuration'; export * from './formatStorageUnits'; export * from './formatUptime'; export * from './getAll'; +export * from './getDisplayName'; export * from './getIsLegacyInterfaceArray'; export * from './getNewRegionLabel'; +export * from './getUserTimezone'; export * from './groupByTags'; -export * from './getDisplayName'; export * from './initWindows'; export * from './isNilOrEmpty'; export * from './isNumber'; From 46788f599b234f995a069c712f64efca5207eccf Mon Sep 17 00:00:00 2001 From: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> Date: Mon, 7 Apr 2025 07:08:52 -0700 Subject: [PATCH 031/112] fix: [M3-9719] - Fix a couple of errors with v4beta requests on the NodeBalancer details page (#11966) * Fix the nodebalancer query and the extra erroring kube request * Clean up * Add changeset --- .../pr-11966-fixed-1743718029581.md | 5 +++++ .../NodeBalancerSummary/SummaryPanel.tsx | 20 +++++++++++++------ packages/manager/src/queries/kubernetes.ts | 4 ++-- .../src/nodebalancers/nodebalancers.ts | 18 +++++++++++------ 4 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 packages/manager/.changeset/pr-11966-fixed-1743718029581.md diff --git a/packages/manager/.changeset/pr-11966-fixed-1743718029581.md b/packages/manager/.changeset/pr-11966-fixed-1743718029581.md new file mode 100644 index 00000000000..5ae611d9e0f --- /dev/null +++ b/packages/manager/.changeset/pr-11966-fixed-1743718029581.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +LKE-E related network requests on the NodeBalancer details page ([#11966](https://github.com/linode/manager/pull/11966)) diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx index 069311161ce..175a3373d90 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx @@ -13,16 +13,26 @@ import * as React from 'react'; import { Link } from 'src/components/Link'; import { TagCell } from 'src/components/TagCell/TagCell'; -import { useKubernetesBetaEndpoint } from 'src/features/Kubernetes/kubeUtils'; +import { + useIsLkeEnterpriseEnabled, + useKubernetesBetaEndpoint, +} from 'src/features/Kubernetes/kubeUtils'; import { IPAddress } from 'src/features/Linodes/LinodesLanding/IPAddress'; import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted'; import { useKubernetesClusterQuery } from 'src/queries/kubernetes'; export const SummaryPanel = () => { + const { isUsingBetaEndpoint } = useKubernetesBetaEndpoint(); + const { isLkeEnterpriseLAFeatureEnabled } = useIsLkeEnterpriseEnabled(); + const { id } = useParams({ from: '/nodebalancers/$id/summary', }); - const { data: nodebalancer } = useNodeBalancerQuery(Number(id), Boolean(id)); + const { data: nodebalancer } = useNodeBalancerQuery( + Number(id), + Boolean(id), + isLkeEnterpriseLAFeatureEnabled + ); const { data: configs } = useAllNodeBalancerConfigsQuery(Number(id)); const { data: regions } = useRegionsQuery(); const { data: attachedFirewallData } = useNodeBalancersFirewallsQuery( @@ -42,19 +52,17 @@ export const SummaryPanel = () => { id: nodebalancer?.id, }); - const { isUsingBetaEndpoint } = useKubernetesBetaEndpoint(); - // If we can't get the cluster (status === 'error'), we can assume it's been deleted const { status: clusterStatus } = useKubernetesClusterQuery({ - enabled: nodebalancer && Boolean(nodebalancer.lke_cluster), + enabled: Boolean(nodebalancer?.lke_cluster), id: nodebalancer?.lke_cluster?.id ?? -1, + isUsingBetaEndpoint, options: { refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, retry: false, }, - isUsingBetaEndpoint, }); const configPorts = configs?.reduce((acc, config) => { diff --git a/packages/manager/src/queries/kubernetes.ts b/packages/manager/src/queries/kubernetes.ts index 5293bdf09ae..0051f56800a 100644 --- a/packages/manager/src/queries/kubernetes.ts +++ b/packages/manager/src/queries/kubernetes.ts @@ -184,8 +184,8 @@ export const kubernetesQueries = createQueryKeys('kubernetes', { export const useKubernetesClusterQuery = ({ enabled = true, id = -1, - options = {}, isUsingBetaEndpoint = false, + options = {}, }) => { return useQuery({ ...kubernetesQueries.cluster(id)._ctx.cluster(isUsingBetaEndpoint), @@ -215,8 +215,8 @@ export const useKubernetesClustersInfiniteQuery = ( interface KubernetesClustersQueryOptions { enabled: boolean; filter: Filter; - params: Params; isUsingBetaEndpoint: boolean; + params: Params; } export const useKubernetesClustersQuery = ({ diff --git a/packages/queries/src/nodebalancers/nodebalancers.ts b/packages/queries/src/nodebalancers/nodebalancers.ts index 7a1462300db..580d517f6f5 100644 --- a/packages/queries/src/nodebalancers/nodebalancers.ts +++ b/packages/queries/src/nodebalancers/nodebalancers.ts @@ -68,10 +68,10 @@ export const nodebalancerQueries = createQueryKeys('nodebalancers', { queryFn: () => getNodeBalancerFirewalls(id), queryKey: null, }, - nodebalancer: () => ({ - queryFn: (useBetaEndpoint) => - useBetaEndpoint ? getNodeBalancerBeta(id) : getNodeBalancer(id), - queryKey: ['v4'], + nodebalancer: (isUsingBetaEndpoint: boolean = false) => ({ + queryFn: () => + isUsingBetaEndpoint ? getNodeBalancerBeta(id) : getNodeBalancer(id), + queryKey: [isUsingBetaEndpoint ? 'v4beta' : 'v4'], }), stats: { queryFn: () => getNodeBalancerStats(id), @@ -122,9 +122,15 @@ export const useNodeBalancersQuery = (params: Params, filter: Filter) => placeholderData: keepPreviousData, }); -export const useNodeBalancerQuery = (id: number, enabled = true) => { +export const useNodeBalancerQuery = ( + id: number, + enabled = true, + isUsingBetaEndpoint = false +) => { return useQuery({ - ...nodebalancerQueries.nodebalancer(id)._ctx.nodebalancer(), + ...nodebalancerQueries + .nodebalancer(id) + ._ctx.nodebalancer(isUsingBetaEndpoint), enabled, }); }; From f0e69baf60d58b3d18080ffe4a10da0ca9e8c0e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 12:27:23 -0400 Subject: [PATCH 032/112] chore(deps-dev): Bump vite from 6.2.4 to 6.2.5 (#11971) --- packages/manager/package.json | 2 +- packages/search/package.json | 2 +- pnpm-lock.yaml | 240 +++++++++++++++++----------------- 3 files changed, 122 insertions(+), 122 deletions(-) diff --git a/packages/manager/package.json b/packages/manager/package.json index 53236af61b5..c8db25be97b 100644 --- a/packages/manager/package.json +++ b/packages/manager/package.json @@ -214,7 +214,7 @@ "redux-mock-store": "^1.5.3", "storybook": "^8.6.7", "storybook-dark-mode": "4.0.1", - "vite": "^6.2.4", + "vite": "^6.2.5", "vite-plugin-svgr": "^3.2.0" }, "browserslist": [ diff --git a/packages/search/package.json b/packages/search/package.json index bb948e209c1..01b7c8b1518 100644 --- a/packages/search/package.json +++ b/packages/search/package.json @@ -16,6 +16,6 @@ "@linode/api-v4": "workspace:*" }, "peerDependencies": { - "vite": "^6.2.4" + "vite": "^6.2.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e7968dc60c..626c42f845a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -349,7 +349,7 @@ importers: version: 8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3) '@storybook/react-vite': specifier: ^8.6.7 - version: 8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.38.0)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.39.0)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) '@storybook/theming': specifier: ^8.6.7 version: 8.6.9(storybook@8.6.9(prettier@2.2.1)) @@ -454,7 +454,7 @@ importers: version: 6.21.0(eslint@7.32.0)(typescript@5.7.3) '@vitejs/plugin-react-swc': specifier: ^3.7.2 - version: 3.7.2(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 3.7.2(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) '@vitest/coverage-v8': specifier: ^3.0.7 version: 3.0.7(vitest@3.0.7) @@ -493,7 +493,7 @@ importers: version: 1.14.0(cypress@14.0.1) cypress-vite: specifier: ^1.6.0 - version: 1.6.0(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 1.6.0(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) dotenv: specifier: ^16.0.3 version: 16.4.5 @@ -573,11 +573,11 @@ importers: specifier: 4.0.1 version: 4.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.9(prettier@2.2.1)) vite: - specifier: ^6.2.4 - version: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + specifier: ^6.2.5 + version: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) vite-plugin-svgr: specifier: ^3.2.0 - version: 3.3.0(rollup@4.38.0)(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 3.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) packages/queries: dependencies: @@ -682,8 +682,8 @@ importers: specifier: ^4.0.3 version: 4.2.0 vite: - specifier: ^6.2.4 - version: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + specifier: ^6.2.5 + version: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) packages/shared: dependencies: @@ -795,7 +795,7 @@ importers: version: 2.2.1 vite-plugin-svgr: specifier: ^3.2.0 - version: 3.3.0(rollup@4.38.0)(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 3.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) packages/ui: dependencies: @@ -889,7 +889,7 @@ importers: version: 2.2.1 vite-plugin-svgr: specifier: ^3.2.0 - version: 3.3.0(rollup@4.38.0)(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + version: 3.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) packages/utilities: dependencies: @@ -2016,8 +2016,8 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.38.0': - resolution: {integrity: sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==} + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} cpu: [arm] os: [android] @@ -2026,8 +2026,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.38.0': - resolution: {integrity: sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==} + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} cpu: [arm64] os: [android] @@ -2036,8 +2036,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.38.0': - resolution: {integrity: sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==} + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} cpu: [arm64] os: [darwin] @@ -2046,8 +2046,8 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.38.0': - resolution: {integrity: sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==} + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} cpu: [x64] os: [darwin] @@ -2056,8 +2056,8 @@ packages: cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.38.0': - resolution: {integrity: sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==} + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} cpu: [arm64] os: [freebsd] @@ -2066,8 +2066,8 @@ packages: cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.38.0': - resolution: {integrity: sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==} + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} cpu: [x64] os: [freebsd] @@ -2076,8 +2076,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.38.0': - resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==} + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} cpu: [arm] os: [linux] @@ -2086,8 +2086,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.38.0': - resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==} + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} cpu: [arm] os: [linux] @@ -2096,8 +2096,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.38.0': - resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==} + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} cpu: [arm64] os: [linux] @@ -2106,8 +2106,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.38.0': - resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==} + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} cpu: [arm64] os: [linux] @@ -2116,8 +2116,8 @@ packages: cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.38.0': - resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==} + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} cpu: [loong64] os: [linux] @@ -2126,8 +2126,8 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.38.0': - resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} cpu: [ppc64] os: [linux] @@ -2136,13 +2136,13 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.38.0': - resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==} + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.38.0': - resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==} + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} cpu: [riscv64] os: [linux] @@ -2151,8 +2151,8 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.38.0': - resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==} + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} cpu: [s390x] os: [linux] @@ -2161,8 +2161,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.38.0': - resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==} + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} cpu: [x64] os: [linux] @@ -2171,8 +2171,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.38.0': - resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==} + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} cpu: [x64] os: [linux] @@ -2181,8 +2181,8 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.38.0': - resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==} + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} cpu: [arm64] os: [win32] @@ -2191,8 +2191,8 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.38.0': - resolution: {integrity: sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==} + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} cpu: [ia32] os: [win32] @@ -2201,8 +2201,8 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.38.0': - resolution: {integrity: sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==} + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} cpu: [x64] os: [win32] @@ -6113,8 +6113,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.38.0: - resolution: {integrity: sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==} + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -6834,8 +6834,8 @@ packages: peerDependencies: vite: ^2.6.0 || 3 || 4 - vite@6.2.4: - resolution: {integrity: sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==} + vite@6.2.5: + resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -7805,12 +7805,12 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': dependencies: glob: 10.4.5 magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.7.3) - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) optionalDependencies: typescript: 5.7.3 @@ -8077,129 +8077,129 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rollup/pluginutils@5.1.3(rollup@4.38.0)': + '@rollup/pluginutils@5.1.3(rollup@4.39.0)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.38.0 + rollup: 4.39.0 '@rollup/rollup-android-arm-eabi@4.34.8': optional: true - '@rollup/rollup-android-arm-eabi@4.38.0': + '@rollup/rollup-android-arm-eabi@4.39.0': optional: true '@rollup/rollup-android-arm64@4.34.8': optional: true - '@rollup/rollup-android-arm64@4.38.0': + '@rollup/rollup-android-arm64@4.39.0': optional: true '@rollup/rollup-darwin-arm64@4.34.8': optional: true - '@rollup/rollup-darwin-arm64@4.38.0': + '@rollup/rollup-darwin-arm64@4.39.0': optional: true '@rollup/rollup-darwin-x64@4.34.8': optional: true - '@rollup/rollup-darwin-x64@4.38.0': + '@rollup/rollup-darwin-x64@4.39.0': optional: true '@rollup/rollup-freebsd-arm64@4.34.8': optional: true - '@rollup/rollup-freebsd-arm64@4.38.0': + '@rollup/rollup-freebsd-arm64@4.39.0': optional: true '@rollup/rollup-freebsd-x64@4.34.8': optional: true - '@rollup/rollup-freebsd-x64@4.38.0': + '@rollup/rollup-freebsd-x64@4.39.0': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.38.0': + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': optional: true '@rollup/rollup-linux-arm-musleabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.38.0': + '@rollup/rollup-linux-arm-musleabihf@4.39.0': optional: true '@rollup/rollup-linux-arm64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm64-gnu@4.38.0': + '@rollup/rollup-linux-arm64-gnu@4.39.0': optional: true '@rollup/rollup-linux-arm64-musl@4.34.8': optional: true - '@rollup/rollup-linux-arm64-musl@4.38.0': + '@rollup/rollup-linux-arm64-musl@4.39.0': optional: true '@rollup/rollup-linux-loongarch64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.38.0': + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.38.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': optional: true '@rollup/rollup-linux-riscv64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.38.0': + '@rollup/rollup-linux-riscv64-gnu@4.39.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.38.0': + '@rollup/rollup-linux-riscv64-musl@4.39.0': optional: true '@rollup/rollup-linux-s390x-gnu@4.34.8': optional: true - '@rollup/rollup-linux-s390x-gnu@4.38.0': + '@rollup/rollup-linux-s390x-gnu@4.39.0': optional: true '@rollup/rollup-linux-x64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-x64-gnu@4.38.0': + '@rollup/rollup-linux-x64-gnu@4.39.0': optional: true '@rollup/rollup-linux-x64-musl@4.34.8': optional: true - '@rollup/rollup-linux-x64-musl@4.38.0': + '@rollup/rollup-linux-x64-musl@4.39.0': optional: true '@rollup/rollup-win32-arm64-msvc@4.34.8': optional: true - '@rollup/rollup-win32-arm64-msvc@4.38.0': + '@rollup/rollup-win32-arm64-msvc@4.39.0': optional: true '@rollup/rollup-win32-ia32-msvc@4.34.8': optional: true - '@rollup/rollup-win32-ia32-msvc@4.38.0': + '@rollup/rollup-win32-ia32-msvc@4.39.0': optional: true '@rollup/rollup-win32-x64-msvc@4.34.8': optional: true - '@rollup/rollup-win32-x64-msvc@4.38.0': + '@rollup/rollup-win32-x64-msvc@4.39.0': optional: true '@sentry-internal/feedback@7.120.0': @@ -8376,13 +8376,13 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@8.6.9(storybook@8.6.9(prettier@2.2.1))(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': + '@storybook/builder-vite@8.6.9(storybook@8.6.9(prettier@2.2.1))(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': dependencies: '@storybook/csf-plugin': 8.6.9(storybook@8.6.9(prettier@2.2.1)) browser-assert: 1.2.1 storybook: 8.6.9(prettier@2.2.1) ts-dedent: 2.2.0 - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) '@storybook/components@8.4.5(storybook@8.6.9(prettier@2.2.1))': dependencies: @@ -8449,11 +8449,11 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.9(prettier@2.2.1) - '@storybook/react-vite@8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.38.0)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': + '@storybook/react-vite@8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.39.0)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) - '@rollup/pluginutils': 5.1.3(rollup@4.38.0) - '@storybook/builder-vite': 8.6.9(storybook@8.6.9(prettier@2.2.1))(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + '@rollup/pluginutils': 5.1.3(rollup@4.39.0) + '@storybook/builder-vite': 8.6.9(storybook@8.6.9(prettier@2.2.1))(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) '@storybook/react': 8.6.9(@storybook/test@8.6.9(storybook@8.6.9(prettier@2.2.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.9(prettier@2.2.1))(typescript@5.7.3) find-up: 5.0.0 magic-string: 0.30.17 @@ -8463,7 +8463,7 @@ snapshots: resolve: 1.22.8 storybook: 8.6.9(prettier@2.2.1) tsconfig-paths: 4.2.0 - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) optionalDependencies: '@storybook/test': 8.6.9(storybook@8.6.9(prettier@2.2.1)) transitivePeerDependencies: @@ -9118,10 +9118,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@3.7.2(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': + '@vitejs/plugin-react-swc@3.7.2(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': dependencies: '@swc/core': 1.10.11 - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) transitivePeerDependencies: - '@swc/helpers' @@ -9157,14 +9157,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.7(msw@2.6.5(@types/node@20.17.6)(typescript@5.7.3))(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': + '@vitest/mocker@3.0.7(msw@2.6.5(@types/node@20.17.6)(typescript@5.7.3))(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1))': dependencies: '@vitest/spy': 3.0.7 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.6.5(@types/node@20.17.6)(typescript@5.7.3) - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) '@vitest/pretty-format@2.0.5': dependencies: @@ -9890,11 +9890,11 @@ snapshots: dependencies: cypress: 14.0.1 - cypress-vite@1.6.0(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)): + cypress-vite@1.6.0(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)): dependencies: chokidar: 3.6.0 debug: 4.4.0(supports-color@8.1.1) - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) transitivePeerDependencies: - supports-color @@ -12828,30 +12828,30 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.8 fsevents: 2.3.3 - rollup@4.38.0: + rollup@4.39.0: dependencies: '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.38.0 - '@rollup/rollup-android-arm64': 4.38.0 - '@rollup/rollup-darwin-arm64': 4.38.0 - '@rollup/rollup-darwin-x64': 4.38.0 - '@rollup/rollup-freebsd-arm64': 4.38.0 - '@rollup/rollup-freebsd-x64': 4.38.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.38.0 - '@rollup/rollup-linux-arm-musleabihf': 4.38.0 - '@rollup/rollup-linux-arm64-gnu': 4.38.0 - '@rollup/rollup-linux-arm64-musl': 4.38.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.38.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.38.0 - '@rollup/rollup-linux-riscv64-gnu': 4.38.0 - '@rollup/rollup-linux-riscv64-musl': 4.38.0 - '@rollup/rollup-linux-s390x-gnu': 4.38.0 - '@rollup/rollup-linux-x64-gnu': 4.38.0 - '@rollup/rollup-linux-x64-musl': 4.38.0 - '@rollup/rollup-win32-arm64-msvc': 4.38.0 - '@rollup/rollup-win32-ia32-msvc': 4.38.0 - '@rollup/rollup-win32-x64-msvc': 4.38.0 + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -13633,7 +13633,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) transitivePeerDependencies: - '@types/node' - jiti @@ -13648,22 +13648,22 @@ snapshots: - tsx - yaml - vite-plugin-svgr@3.3.0(rollup@4.38.0)(typescript@5.7.3)(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)): + vite-plugin-svgr@3.3.0(rollup@4.39.0)(typescript@5.7.3)(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)): dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.38.0) + '@rollup/pluginutils': 5.1.3(rollup@4.39.0) '@svgr/core': 8.1.0(typescript@5.7.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.3)) - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1): + vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1): dependencies: esbuild: 0.25.2 postcss: 8.5.3 - rollup: 4.38.0 + rollup: 4.39.0 optionalDependencies: '@types/node': 20.17.6 fsevents: 2.3.3 @@ -13675,7 +13675,7 @@ snapshots: vitest@3.0.7(@types/debug@4.1.12)(@types/node@20.17.6)(@vitest/ui@3.0.7)(jiti@1.21.6)(jsdom@24.1.3)(msw@2.6.5(@types/node@20.17.6)(typescript@5.7.3))(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1): dependencies: '@vitest/expect': 3.0.7 - '@vitest/mocker': 3.0.7(msw@2.6.5(@types/node@20.17.6)(typescript@5.7.3))(vite@6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) + '@vitest/mocker': 3.0.7(msw@2.6.5(@types/node@20.17.6)(typescript@5.7.3))(vite@6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1)) '@vitest/pretty-format': 3.0.7 '@vitest/runner': 3.0.7 '@vitest/snapshot': 3.0.7 @@ -13691,7 +13691,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.2.4(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) + vite: 6.2.5(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) vite-node: 3.0.7(@types/node@20.17.6)(jiti@1.21.6)(terser@5.36.0)(tsx@4.19.3)(yaml@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: From 45e841953b919bffab0fef860da5e82699559541 Mon Sep 17 00:00:00 2001 From: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:03:47 -0700 Subject: [PATCH 033/112] fix: Update `pnpm clean` command to include all new packages (#11983) * Update pnpm clean command * Address feedback: future-proof the clean command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a15c097c27..fdc18b3a98c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "package-versions": "pnpm run --filter @linode/scripts package-versions", "junit:summary": "pnpm run --filter @linode/scripts --silent junit:summary", "generate-tod": "pnpm run --filter @linode/scripts --silent generate-tod", - "clean": "rm -rf node_modules && rm -rf packages/manager/node_modules && rm -rf packages/api-v4/node_modules && rm -rf packages/validation/node_modules && rm -rf packages/api-v4/lib && rm -rf packages/validation/lib && rm -rf packages/ui/node_modules && rm -rf packages/utilities/node_modules", + "clean": "concurrently \"rm -rf node_modules\" \"pnpm -r exec rm -rf node_modules lib dist\" \"pnpm store prune\"", "prepare": "husky" }, "resolutions": { From 77d5135b70427dfa011ec9b2b527430c74f214dd Mon Sep 17 00:00:00 2001 From: Connie Liu <139280159+coliu-akamai@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:54:35 -0700 Subject: [PATCH 034/112] upcoming: [M3-9682] - Disable Upgrade Interfaces functionality for LKE Linodes and other conditions (#11934) * hide upgrade chip/button in certain cases * switch to hook for shared logic * address feedback * disable and add tooltip pt1 * yikes * add forgotten condition * changeset * maybe a bit cleaner? * maybe this? * Revert "maybe this?" This reverts commit 416961a4cf1e8fbe7d6ae40f9762e557d0bc48b6. * test timeout? * UX copy feedback * initial inclusion of tooltip icon * UX feedback * update spacing + shorten timeout (if it works) --- ...r-11934-upcoming-features-1743443080180.md | 5 ++ .../Linodes/LinodeEntityDetail.test.tsx | 13 ++-- .../Linodes/LinodeEntityDetailBody.tsx | 61 +++++++++++++------ .../LinodeConfigs/LinodeConfigs.tsx | 22 ++++++- .../UpgradeInterfaces/constants.ts | 3 + .../LinodeConfigs/UpgradeInterfaces/utils.tsx | 43 +++++++++++++ .../Linodes/LinodesDetail/LinodesDetail.tsx | 11 +++- .../src/hooks/useCanUpgradeInterfaces.ts | 61 +++++++++++++++++++ packages/ui/src/components/Button/Button.tsx | 2 +- 9 files changed, 192 insertions(+), 29 deletions(-) create mode 100644 packages/manager/.changeset/pr-11934-upcoming-features-1743443080180.md create mode 100644 packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils.tsx create mode 100644 packages/manager/src/hooks/useCanUpgradeInterfaces.ts diff --git a/packages/manager/.changeset/pr-11934-upcoming-features-1743443080180.md b/packages/manager/.changeset/pr-11934-upcoming-features-1743443080180.md new file mode 100644 index 00000000000..191e72b6ed1 --- /dev/null +++ b/packages/manager/.changeset/pr-11934-upcoming-features-1743443080180.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Disable Upgrade Interfaces feature for LKE Linodes and other conditions ([#11934](https://github.com/linode/manager/pull/11934)) diff --git a/packages/manager/src/features/Linodes/LinodeEntityDetail.test.tsx b/packages/manager/src/features/Linodes/LinodeEntityDetail.test.tsx index 52c27929e3a..101096f3af1 100644 --- a/packages/manager/src/features/Linodes/LinodeEntityDetail.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeEntityDetail.test.tsx @@ -119,10 +119,15 @@ describe('Linode Entity Detail', () => { } ); - await waitFor(() => { - expect(getByTestId(vpcSectionTestId)).toBeInTheDocument(); - expect(getByTestId(assignedVPCLabelTestId).innerHTML).toEqual('test-vpc'); - }); + await waitFor( + () => { + expect(getByTestId(vpcSectionTestId)).toBeInTheDocument(); + expect(getByTestId(assignedVPCLabelTestId).innerHTML).toEqual( + 'test-vpc' + ); + }, + { timeout: 15_000 } + ); }); it('should not display the LKE section if the linode is not associated with an LKE cluster', async () => { diff --git a/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx b/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx index 6b3ffc3a294..aeaac84b3ac 100644 --- a/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx +++ b/packages/manager/src/features/Linodes/LinodeEntityDetailBody.tsx @@ -1,5 +1,5 @@ import { usePreferences, useProfile } from '@linode/queries'; -import { Box, Chip, Typography } from '@linode/ui'; +import { Box, Chip, TooltipIcon, Typography } from '@linode/ui'; import { pluralize } from '@linode/utilities'; import { useMediaQuery } from '@mui/material'; import Grid from '@mui/material/Grid2'; @@ -17,11 +17,12 @@ import { useIsDiskEncryptionFeatureEnabled } from 'src/components/Encryption/uti import { Link } from 'src/components/Link'; import { useKubernetesBetaEndpoint } from 'src/features/Kubernetes/kubeUtils'; import { AccessTable } from 'src/features/Linodes/AccessTable'; +import { useCanUpgradeInterfaces } from 'src/hooks/useCanUpgradeInterfaces'; import { useKubernetesClusterQuery } from 'src/queries/kubernetes'; import { useIsLinodeInterfacesEnabled } from 'src/utilities/linodes'; -import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; import { EncryptedStatus } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; +import { encryptionStatusTestId } from '../Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable'; import { HighPerformanceVolumeIcon } from './HighPerformanceVolumeIcon'; import { StyledBodyGrid, @@ -35,6 +36,7 @@ import { StyledVPCBox, sxLastListItem, } from './LinodeEntityDetail.styles'; +import { getUnableToUpgradeTooltipText } from './LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils'; import { ipTableId } from './LinodesDetail/LinodeNetworking/LinodeIPAddresses'; import { lishLink, sshLink } from './LinodesDetail/utilities'; @@ -113,6 +115,10 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { const location = useLocation(); const history = useHistory(); + const openUpgradeInterfacesDialog = () => { + history.replace(`${location.pathname}/upgrade-interfaces`); + }; + const { data: profile } = useProfile(); const { data: maskSensitiveDataPreference } = usePreferences( @@ -128,6 +134,15 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { const { isLinodeInterfacesEnabled } = useIsLinodeInterfacesEnabled(); const isLinodeInterface = interfaceGeneration === 'linode'; + const { + canUpgradeInterfaces, + unableToUpgradeReasons, + } = useCanUpgradeInterfaces(linodeLkeClusterId, region, interfaceGeneration); + + const unableToUpgradeTooltipText = getUnableToUpgradeTooltipText( + unableToUpgradeReasons + ); + // Take the first firewall to display. Linodes with legacy config interfaces can only be assigned to one firewall (currently). We'll only display // the attached firewall for Linodes with legacy config interfaces - Linodes with new Linode interfaces can be associated with multiple firewalls // since each interface can have a firewall. @@ -142,10 +157,6 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { (subnet) => subnet.linodes.some((linode) => linode.id === linodeId) ); - const openUpgradeInterfacesDialog = () => { - history.replace(`${location.pathname}/upgrade-interfaces`); - }; - const numIPAddresses = ipv4.length + (ipv6 ? 1 : 0); const firstAddress = ipv4[0]; @@ -416,9 +427,9 @@ export const LinodeEntityDetailBody = React.memo((props: BodyProps) => { { sx={{ alignItems: 'center', display: 'flex' }} > Configuration Profile - ({ - backgroundColor: theme.color.tagButtonBg, - color: theme.tokens.color.Neutrals[80], - marginLeft: theme.spacing(0.5), - })} - component="span" - label="UPGRADE" - onClick={openUpgradeInterfacesDialog} - size="small" - /> + + ({ + backgroundColor: theme.color.tagButtonBg, + color: theme.tokens.color.Neutrals[80], + marginLeft: theme.spacingFunction(12), + })} + component="span" + disabled={!canUpgradeInterfaces} + label="UPGRADE" + onClick={openUpgradeInterfacesDialog} + size="small" + /> + {!canUpgradeInterfaces && unableToUpgradeTooltipText && ( + + )} + )} diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigs.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigs.tsx index 179eaa61fba..d97b71154ff 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigs.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigs.tsx @@ -19,6 +19,7 @@ import { TableContentWrapper } from 'src/components/TableContentWrapper/TableCon import { TableHead } from 'src/components/TableHead'; import { TableRow } from 'src/components/TableRow'; import { TableSortCell } from 'src/components/TableSortCell'; +import { useCanUpgradeInterfaces } from 'src/hooks/useCanUpgradeInterfaces'; import { sendLinodeConfigurationDocsEvent } from 'src/utilities/analytics/customEventAnalytics'; import { useIsLinodeInterfacesEnabled } from 'src/utilities/linodes'; @@ -26,6 +27,8 @@ import { BootConfigDialog } from './BootConfigDialog'; import { ConfigRow } from './ConfigRow'; import { DeleteConfigDialog } from './DeleteConfigDialog'; import { LinodeConfigDialog } from './LinodeConfigDialog'; +import { DEFAULT_UPGRADE_BUTTON_HELPER_TEXT } from './UpgradeInterfaces/constants'; +import { getUnableToUpgradeTooltipText } from './UpgradeInterfaces/utils'; const LinodeConfigs = () => { const theme = useTheme(); @@ -37,8 +40,21 @@ const LinodeConfigs = () => { const id = Number(linodeId); const { data: linode } = useLinodeQuery(id); - const { isLinodeInterfacesEnabled } = useIsLinodeInterfacesEnabled(); + const { + canUpgradeInterfaces, + unableToUpgradeReasons, + } = useCanUpgradeInterfaces( + linode?.lke_cluster_id, + linode?.region, + linode?.interface_generation + ); + + const unableToUpgradeTooltip = getUnableToUpgradeTooltipText( + unableToUpgradeReasons + ); + const upgradeInterfacesTooltipText = + unableToUpgradeTooltip ?? DEFAULT_UPGRADE_BUTTON_HELPER_TEXT; const isLegacyConfigInterface = linode?.interface_generation !== 'linode'; @@ -117,9 +133,9 @@ const LinodeConfigs = () => { diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/constants.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/constants.ts index 0b8cedb2f78..ebe7b1ed720 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/constants.ts +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/constants.ts @@ -13,3 +13,6 @@ export const SUCCESS_UPGRADE_COPY = 'The configuration upgrade was successful.'; export const ERROR_DRY_RUN_COPY = 'The configuration dry run found the following issues. Please correct the issues and perform another dry run before upgrading the interface configuration.'; + +export const DEFAULT_UPGRADE_BUTTON_HELPER_TEXT = + 'Upgrade to Linode interfaces to connect the interface to the Linode not the Configuration Profile. You can perform a dry run to identify any issues before upgrading.'; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils.tsx new file mode 100644 index 00000000000..fc62b68d5f8 --- /dev/null +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils.tsx @@ -0,0 +1,43 @@ +import { List, ListItem, Stack, Typography } from '@linode/ui'; +import React from 'react'; + +import type { CannotUpgradeInterfaceReasons } from 'src/hooks/useCanUpgradeInterfaces'; + +const UPGRADE_REASON_TO_COPY: Record = { + isLegacyConfigOnlyAccount: + 'Linode Interfaces are not enabled in your account settings.', + isLkeLinode: + 'Linode Interfaces are not available for LKE Cluster Linodes yet.', + regionUnsupported: 'Linode Interfaces are not available in this region yet.', +}; + +export const getUnableToUpgradeTooltipText = ( + reasons: CannotUpgradeInterfaceReasons[] +) => { + if (reasons.length === 0) { + return null; + } + + return ( + + {reasons.length === 1 ? ( + {UPGRADE_REASON_TO_COPY[reasons[0]]} + ) : ( + <> + Cannot upgrade interfaces: + + {reasons.map((entity) => ( + + {UPGRADE_REASON_TO_COPY[entity]} + + ))} + + + )} + + ); +}; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetail.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetail.tsx index b4ed7224c25..be9cf2ad04c 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetail.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodesDetail.tsx @@ -14,6 +14,7 @@ import { } from 'react-router-dom'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; +import { useCanUpgradeInterfaces } from 'src/hooks/useCanUpgradeInterfaces'; import { UpgradeInterfacesDialog } from './LinodeConfigs/UpgradeInterfaces/UpgradeInterfacesDialog'; @@ -57,6 +58,11 @@ export const LinodeDetail = () => { const id = Number(linodeId); const { data: linode, error, isLoading } = useLinodeQuery(id); + const { canUpgradeInterfaces } = useCanUpgradeInterfaces( + linode?.lke_cluster_id, + linode?.region, + linode?.interface_generation + ); if (error) { return ; @@ -95,9 +101,12 @@ export const LinodeDetail = () => {
)} diff --git a/packages/manager/src/hooks/useCanUpgradeInterfaces.ts b/packages/manager/src/hooks/useCanUpgradeInterfaces.ts new file mode 100644 index 00000000000..02f03c13a6e --- /dev/null +++ b/packages/manager/src/hooks/useCanUpgradeInterfaces.ts @@ -0,0 +1,61 @@ +import { useAccountSettings, useRegionQuery } from '@linode/queries'; + +import { useIsLinodeInterfacesEnabled } from 'src/utilities/linodes'; + +import type { InterfaceGenerationType } from '@linode/api-v4'; + +export type CannotUpgradeInterfaceReasons = + | 'isLegacyConfigOnlyAccount' + | 'isLkeLinode' + | 'regionUnsupported'; + +/** + * Hook to determine whether to show Upgrade Interface features for a Linode with the given + * LKE cluster, region, and interface type. + * + * The Linode must be using legacy interfaces to be upgraded. Additionally, + * if the Linode is part of a cluster, is in a region that doesn't allow Linode + * Interfaces, or if the customer's account settings specify only legacy + * interfaces can be used, then the Linode cannot be upgraded. + */ +export const useCanUpgradeInterfaces = ( + linodeLkeId: null | number | undefined, + linodeRegion: string | undefined, + interfaceType: InterfaceGenerationType | undefined +) => { + const { isLinodeInterfacesEnabled } = useIsLinodeInterfacesEnabled(); + const { data: region } = useRegionQuery(linodeRegion ?? ''); + const { data: accountSettings } = useAccountSettings(); + + const regionSupportsLinodeInterfaces = + region?.capabilities.includes('Linode Interfaces') ?? false; + + const isLegacyConfigOnlyAccount = + accountSettings?.interfaces_for_new_linodes === 'legacy_config_only'; + + const canUpgradeInterfaces = + // show the Upgrade Interfaces button if our Linode is not part of an LKE cluster, is + // using Legacy config profile interfaces in a region that supports the new Interfaces + // and our account can have Linodes using new interfaces + isLinodeInterfacesEnabled && + interfaceType !== 'linode' && + !linodeLkeId && + !isLegacyConfigOnlyAccount && + regionSupportsLinodeInterfaces; + + const unableToUpgradeReasons: CannotUpgradeInterfaceReasons[] = []; + if (!!linodeLkeId) { + unableToUpgradeReasons.push('isLkeLinode'); + } + if (isLegacyConfigOnlyAccount) { + unableToUpgradeReasons.push('isLegacyConfigOnlyAccount'); + } + if (!regionSupportsLinodeInterfaces) { + unableToUpgradeReasons.push('regionUnsupported'); + } + + return { + canUpgradeInterfaces, + unableToUpgradeReasons, + }; +}; diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index ebd5d0cbfb8..1602133a68b 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -52,7 +52,7 @@ export interface ButtonProps extends _ButtonProps { /** Tooltip analytics event */ tooltipAnalyticsEvent?: () => void; /** Tooltip text */ - tooltipText?: string; + tooltipText?: string | JSX.Element; } const StyledButton = styled(_Button, { From 2aa06af63b9d9b6b08137231f29070d5ad92d0c0 Mon Sep 17 00:00:00 2001 From: santoshp210-akamai <159890961+santoshp210-akamai@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:31:33 +0530 Subject: [PATCH 035/112] upcoming: [DI-24238] - UI changes to Create/Edit form (#11963) * upcoming: [DI-24238] - UI changes to Create/Edit form * upcoming: [DI-23238] - Added changeset --------- Co-authored-by: venkatmano-akamai --- ...r-11963-upcoming-features-1743680039001.md | 5 + .../CreateAlertDefinition.test.tsx | 99 +++++++++++++++---- .../CreateAlert/CreateAlertDefinition.tsx | 11 +-- .../CreateAlert/Criteria/Metric.test.tsx | 3 + .../Alerts/CreateAlert/Criteria/Metric.tsx | 2 + .../Criteria/TriggerConditions.tsx | 2 + .../GeneralInformation/ServiceTypeSelect.tsx | 2 +- .../pr-11963-changed-1743680086813.md | 5 + packages/validation/src/cloudpulse.schema.ts | 2 +- 9 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 packages/manager/.changeset/pr-11963-upcoming-features-1743680039001.md create mode 100644 packages/validation/.changeset/pr-11963-changed-1743680086813.md diff --git a/packages/manager/.changeset/pr-11963-upcoming-features-1743680039001.md b/packages/manager/.changeset/pr-11963-upcoming-features-1743680039001.md new file mode 100644 index 00000000000..95f920cae93 --- /dev/null +++ b/packages/manager/.changeset/pr-11963-upcoming-features-1743680039001.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +UI bugfixes: Resetting Trigger Occurences, Resources values when service type is cleared, Disabling Trigger Occurences, Threshold values unless Service Type is selected. Added Max value for Trigger Occurences and Threshold TextField components ([#11963](https://github.com/linode/manager/pull/11963)) 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 559c5f49406..795fa650249 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.test.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { renderWithTheme } from 'src/utilities/testHelpers'; import { CreateAlertDefinition } from './CreateAlertDefinition'; + vi.mock('src/queries/cloudpulse/resources', () => ({ ...vi.importActual('src/queries/cloudpulse/resources'), useResourcesQuery: queryMocks.useResourcesQuery, @@ -16,12 +17,29 @@ vi.mock('@linode/queries', async (importOriginal) => ({ })); const queryMocks = vi.hoisted(() => ({ + useCloudPulseServiceTypes: vi.fn().mockReturnValue({}), + useGetCloudPulseMetricDefinitionsByServiceType: vi.fn().mockReturnValue({}), useRegionsQuery: vi.fn(), useResourcesQuery: vi.fn(), })); +vi.mock('src/queries/cloudpulse/services', async () => { + const actual = await vi.importActual('src/queries/cloudpulse/services'); + return { + ...actual, + useCloudPullMetricDefinitionsByServiceType: + queryMocks.useGetCloudPulseMetricDefinitionsByServiceType, + useCloudPulseServiceTypes: queryMocks.useCloudPulseServiceTypes, + }; +}); + beforeEach(() => { Element.prototype.scrollIntoView = vi.fn(); + queryMocks.useGetCloudPulseMetricDefinitionsByServiceType.mockReturnValue({ + data: [], + isError: false, + isLoading: false, + }); queryMocks.useResourcesQuery.mockReturnValue({ data: [], isError: false, @@ -32,6 +50,12 @@ beforeEach(() => { isError: false, isFetching: false, }); + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: { data: [{ label: 'Linode', service_type: 'linode' }] }, + isError: false, + isLoading: false, + status: 'success', + }); }); describe('AlertDefinition Create', () => { @@ -72,37 +96,72 @@ describe('AlertDefinition Create', () => { expect(specificInput).toHaveAttribute('value', 'text'); }); - it('should render client side validation errors', async () => { + queryMocks.useGetCloudPulseMetricDefinitionsByServiceType.mockReturnValue({ + data: { + data: [ + { + available_aggregate_functions: ['min'], + dimensions: [], + is_alertable: true, + label: 'CPU utilization', + metric: 'system_cpu_utilization_percent', + metric_type: 'gauge', + scrape_interval: '2m', + unit: 'percent', + }, + ], + }, + }); + it('should render client side validation errors for threshold and trigger occurences text field', async () => { + const user = userEvent.setup(); + const container = renderWithTheme(); + + const serviceTypeInput = container.getByPlaceholderText('Select a Service'); + await user.click(serviceTypeInput); + + await user.click(container.getByText('Linode')); + + const dataFieldContainer = container.getByPlaceholderText( + 'Select a Data Field' + ); + + await user.click(dataFieldContainer); + await user.click(container.getByText('CPU utilization')); + + const submitButton = container.getByText('Submit'); + + await user.click(submitButton); + + expect(container.getAllByText('Enter a positive value.').length).toBe(2); + + const thresholdInput = container.getByLabelText('Threshold'); + const triggerOccurrences = container.getByTestId('trigger-occurences'); + await user.clear(thresholdInput); + await user.clear(within(triggerOccurrences).getByTestId('textfield-input')); + await user.click(submitButton!); + + expect(container.getAllByText('The value should be a number.').length).toBe( + 2 + ); + }); + + it('should render the client side validation error messages for the form', async () => { const errorMessage = 'This field is required.'; const user = userEvent.setup(); const container = renderWithTheme(); - const input = container.getByLabelText('Threshold'); + + const submitButton = container.getByText('Submit'); + await user.click( container.getByRole('button', { name: 'Add dimension filter' }) ); - const submitButton = container.getByText('Submit'); + await user.click(submitButton!); - expect(container.getAllByText('This field is required.').length).toBe(11); + expect(container.getAllByText(errorMessage).length).toBe(11); container.getAllByText(errorMessage).forEach((element) => { expect(element).toBeVisible(); }); - await user.clear(input); - await user.type(input, '-3'); - await userEvent.click(submitButton!); - - expect( - await container.findByText("The value can't be negative.") - ).toBeVisible(); - - await user.clear(input); - await user.type(input, 'sdgf'); - await userEvent.click(submitButton!); - - expect( - await container.findByText('The value should be a number.') - ).toBeInTheDocument(); - expect( await container.findByText( 'At least one notification channel is required.' diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index 37240c8bca9..5478e1ba184 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx @@ -151,16 +151,9 @@ export const CreateAlertDefinition = () => { const handleServiceTypeChange = React.useCallback(() => { // Reset the criteria to initial state - setValue('rule_criteria.rules', [ - { - aggregate_function: null, - dimension_filters: [], - metric: null, - operator: null, - threshold: 0, - }, - ]); + setValue('rule_criteria.rules', [{ ...criteriaInitialValues }]); setValue('entity_ids', []); + setValue('trigger_conditions', triggerConditionInitialValues); }, [setValue]); React.useEffect(() => { diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.test.tsx index 3c51d779c7a..7f6a6e6e837 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.test.tsx @@ -222,6 +222,9 @@ describe('Metric component tests', () => { ), useFormOptions: { defaultValues: { + rule_criteria: { + rules: [mockData[0]], + }, serviceType: 'linode', }, }, diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.tsx index c8ff0e0058c..dde19a1b6e5 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/Metric.tsx @@ -263,8 +263,10 @@ export const Metric = (props: MetricCriteriaProps) => { data-qa-metric-threshold={`${name}-threshold`} data-qa-threshold="threshold" data-testid="threshold" + disabled={!metricWatcher} errorText={fieldState.error?.message} label="Threshold" + max={Number.MAX_SAFE_INTEGER} min={0} name={`${name}.threshold`} noMarginTop diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/TriggerConditions.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/TriggerConditions.tsx index b5804a79dc0..18864ae14ad 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/TriggerConditions.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/Criteria/TriggerConditions.tsx @@ -173,8 +173,10 @@ export const TriggerConditions = (props: TriggerConditionProps) => { }} data-qa-trigger-occurrences data-testid="trigger-occurences" + disabled={!serviceTypeWatcher} errorText={fieldState.error?.message} label="" + max={Number.MAX_SAFE_INTEGER} min={0} name={`${name}.trigger_occurrences`} noMarginTop 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 a8716cb0b6f..85143140dc0 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/GeneralInformation/ServiceTypeSelect.tsx @@ -40,7 +40,7 @@ export const CloudPulseServiceSelect = ( string, AlertServiceType >[] => { - return serviceOptions && serviceOptions.data.length > 0 + return serviceOptions?.data?.length ? serviceOptions.data.map((service) => ({ label: service.label, value: service.service_type as AlertServiceType, diff --git a/packages/validation/.changeset/pr-11963-changed-1743680086813.md b/packages/validation/.changeset/pr-11963-changed-1743680086813.md new file mode 100644 index 00000000000..cb52d5cbb61 --- /dev/null +++ b/packages/validation/.changeset/pr-11963-changed-1743680086813.md @@ -0,0 +1,5 @@ +--- +"@linode/validation": Changed +--- + +Validation message for threshold field in Metric Threshold ([#11963](https://github.com/linode/manager/pull/11963)) diff --git a/packages/validation/src/cloudpulse.schema.ts b/packages/validation/src/cloudpulse.schema.ts index 2781880a33b..3ba827a345f 100644 --- a/packages/validation/src/cloudpulse.schema.ts +++ b/packages/validation/src/cloudpulse.schema.ts @@ -14,7 +14,7 @@ const metricCriteria = object({ operator: string().required(fieldErrorMessage), threshold: number() .required(fieldErrorMessage) - .min(0, "The value can't be negative.") + .positive("Enter a positive value.") .typeError('The value should be a number.'), dimension_filters: array().of(dimensionFilters).notRequired(), }); From d70dc963789756900d9eb3e8a002e6df9a09e35f Mon Sep 17 00:00:00 2001 From: Ankita Date: Tue, 8 Apr 2025 10:32:44 +0530 Subject: [PATCH 036/112] upcoming:[DI-24445] - Remove or condition when empty (#11967) * upcoming:[DI-24445] - Remove or condition when empty * upcoming:[DI-24445] - PR comment * upcoming:[DI-24445] - Add changeset --------- Co-authored-by: venkatmano-akamai --- .../pr-11967-upcoming-features-1743747392300.md | 5 +++++ .../src/features/CloudPulse/Utils/FilterBuilder.test.ts | 8 +++----- .../src/features/CloudPulse/Utils/FilterBuilder.ts | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 packages/manager/.changeset/pr-11967-upcoming-features-1743747392300.md diff --git a/packages/manager/.changeset/pr-11967-upcoming-features-1743747392300.md b/packages/manager/.changeset/pr-11967-upcoming-features-1743747392300.md new file mode 100644 index 00000000000..4c6efebfce9 --- /dev/null +++ b/packages/manager/.changeset/pr-11967-upcoming-features-1743747392300.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Remove `or` condition in filtering of `/instances` call at CloudPulse Metrics ([#11967](https://github.com/linode/manager/pull/11967)) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts index 775bde172bc..0269aef7a27 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts @@ -142,9 +142,7 @@ it('test getResourceSelectionProperties method', () => { expect(handleResourcesSelection).toBeDefined(); expect(savePreferences).toEqual(false); expect(disabled).toEqual(false); - expect(JSON.stringify(xFilter)).toEqual( - '{"+and":[{"region":"us-east"}],"+or":[]}' - ); + expect(JSON.stringify(xFilter)).toEqual('{"+and":[{"region":"us-east"}]}'); expect(label).toEqual(name); } }); @@ -176,7 +174,7 @@ it('test getResourceSelectionProperties method with disabled true', () => { expect(handleResourcesSelection).toBeDefined(); expect(savePreferences).toEqual(false); expect(disabled).toEqual(true); - expect(JSON.stringify(xFilter)).toEqual('{"+and":[],"+or":[]}'); + expect(JSON.stringify(xFilter)).toEqual('{"+and":[]}'); expect(label).toEqual(name); } }); @@ -303,7 +301,7 @@ it('test buildXfilter method', () => { result = buildXFilter(resourceSelectionConfig, {}); - expect(JSON.stringify(result)).toEqual('{"+and":[],"+or":[]}'); + expect(JSON.stringify(result)).toEqual('{"+and":[]}'); } }); diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index a62f6376afa..c9ab01a8251 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -313,8 +313,10 @@ export const buildXFilter = ( } }); } - - return { '+and': filters, '+or': orCondition }; + if (orCondition.length) { + return { '+and': filters, '+or': orCondition }; + } + return { '+and': filters }; }; /** From 6fa94d11034112570ac38401686c60196a5f7b39 Mon Sep 17 00:00:00 2001 From: Connie Liu <139280159+coliu-akamai@users.noreply.github.com> Date: Tue, 8 Apr 2025 07:18:18 -0700 Subject: [PATCH 037/112] upcoming: [M3-9706] - Fix Subnets Table for Linodes using new interfaces (#11953) * make firewall show up in subnet linode row * update columns * get interface directly? (need to fix tests) * fix query * fix test * switch to getting single config * update tests * add check for range * changesets * simplify reboot logic * fix test * unrecommended setup changes? * update quotes * fix integration tests * whoops * Update packages/manager/src/features/VPCs/utils.test.ts Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com> * Update packages/manager/src/features/VPCs/utils.test.ts Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com> * address feedback pt1 * maybe a hook? * fix imports and add comments * move to util function --------- Co-authored-by: Dajahi Wiley <114682940+dwiley-akamai@users.noreply.github.com> --- ...r-11953-upcoming-features-1743614906461.md | 5 + .../linodes/create-linode-with-vpc.spec.ts | 38 +++-- .../e2e/core/vpc/vpc-details-page.spec.ts | 63 ++++++-- .../cypress/support/intercepts/configs.ts | 53 +++++- .../VPCInterfaceDetailsContent.tsx | 2 +- .../VPCs/VPCDetail/SubnetLinodeRow.test.tsx | 104 +++++++++--- .../VPCs/VPCDetail/SubnetLinodeRow.tsx | 152 +++++++++++------- .../VPCDetail/SubnetUnassignLinodesDrawer.tsx | 2 +- .../VPCs/VPCDetail/VPCSubnetsTable.tsx | 1 + .../manager/src/features/VPCs/utils.test.ts | 127 ++++++++++----- packages/manager/src/features/VPCs/utils.ts | 34 ++-- .../useInterfaceAndFirewallDataForLinode.ts | 86 ++++++++++ .../pr-11953-added-1743614960791.md | 5 + packages/queries/src/linodes/configs.ts | 31 +++- packages/queries/src/linodes/interfaces.ts | 13 +- packages/queries/src/linodes/linodes.ts | 25 ++- .../src/factories/linodeInterface.ts | 2 +- 17 files changed, 568 insertions(+), 175 deletions(-) create mode 100644 packages/manager/.changeset/pr-11953-upcoming-features-1743614906461.md create mode 100644 packages/manager/src/hooks/useInterfaceAndFirewallDataForLinode.ts create mode 100644 packages/queries/.changeset/pr-11953-added-1743614960791.md diff --git a/packages/manager/.changeset/pr-11953-upcoming-features-1743614906461.md b/packages/manager/.changeset/pr-11953-upcoming-features-1743614906461.md new file mode 100644 index 00000000000..26f9bab4a56 --- /dev/null +++ b/packages/manager/.changeset/pr-11953-upcoming-features-1743614906461.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Fix SubnetLinodeRow for Linodes using new interfaces ([#11953](https://github.com/linode/manager/pull/11953)) diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts index b7ffa62f9d2..09b916eaf10 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts @@ -3,7 +3,7 @@ import { linodeFactory, regionFactory, } from '@linode/utilities'; -import { mockGetLinodeConfigs } from 'support/intercepts/configs'; +import { mockGetLinodeConfig } from 'support/intercepts/configs'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockCreateLinode, @@ -82,7 +82,13 @@ describe('Create Linode with VPCs', () => { linodes: [ { id: mockLinode.id, - interfaces: [{ active: true, config_id: 1, id: mockInterface.id }], + interfaces: [ + { + active: true, + config_id: mockLinodeConfig.id, + id: mockInterface.id, + }, + ], }, ], }; @@ -150,13 +156,15 @@ describe('Create Linode with VPCs', () => { mockGetVPC(mockVPC).as('getVPC'); mockGetSubnets(mockVPC.id, [mockUpdatedSubnet]).as('getSubnets'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); - mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as( - 'getLinodeConfigs' - ); + mockGetLinodeConfig({ + config: mockLinodeConfig, + configId: mockLinodeConfig.id, + linodeId: mockLinode.id, + }).as('getLinodeConfig'); cy.visit(`/vpcs/${mockVPC.id}`); cy.findByLabelText(`expand ${mockSubnet.label} row`).click(); - cy.wait('@getLinodeConfigs'); + cy.wait('@getLinodeConfig'); cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist'); }); @@ -209,7 +217,13 @@ describe('Create Linode with VPCs', () => { linodes: [ { id: mockLinode.id, - interfaces: [{ active: true, config_id: 1, id: mockInterface.id }], + interfaces: [ + { + active: true, + config_id: mockLinodeConfig.id, + id: mockInterface.id, + }, + ], }, ], }; @@ -314,13 +328,15 @@ describe('Create Linode with VPCs', () => { mockGetVPC(mockVPC).as('getVPC'); mockGetSubnets(mockVPC.id, [mockUpdatedSubnet]).as('getSubnets'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); - mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as( - 'getLinodeConfigs' - ); + mockGetLinodeConfig({ + config: mockLinodeConfig, + configId: mockLinodeConfig.id, + linodeId: mockLinode.id, + }).as('getLinodeConfig'); cy.visit(`/vpcs/${mockVPC.id}`); cy.findByLabelText(`expand ${mockSubnet.label} row`).click(); - cy.wait('@getLinodeConfigs'); + cy.wait('@getLinodeConfig'); cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist'); }); diff --git a/packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts b/packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts index 55031530cd4..812427afbaf 100644 --- a/packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts +++ b/packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts @@ -4,7 +4,7 @@ import { linodeFactory, } from '@linode/utilities'; import { linodeConfigFactory, subnetFactory, vpcFactory } from '@src/factories'; -import { mockGetLinodeConfigs } from 'support/intercepts/configs'; +import { mockGetLinodeConfig } from 'support/intercepts/configs'; import { mockGetLinodeDetails } from 'support/intercepts/linodes'; import { mockCreateSubnet, @@ -277,6 +277,7 @@ describe('VPC details page', () => { const linodeRegion = chooseRegion({ capabilities: ['VPCs'] }); const mockInterfaceId = randomNumber(); + const mockConfigId = randomNumber(); const mockLinode = linodeFactory.build({ id: randomNumber(), label: randomLabel(), @@ -290,7 +291,13 @@ describe('VPC details page', () => { linodes: [ { id: mockLinode.id, - interfaces: [{ active: true, id: mockInterfaceId }], + interfaces: [ + { + active: true, + config_id: mockConfigId, + id: mockInterfaceId, + }, + ], }, ], }); @@ -304,25 +311,29 @@ describe('VPC details page', () => { const mockInterface = linodeConfigInterfaceFactoryWithVPC.build({ active: true, + id: mockInterfaceId, primary: true, subnet_id: mockSubnet.id, vpc_id: mockVPC.id, }); const mockLinodeConfig = linodeConfigFactory.build({ + id: mockConfigId, interfaces: [mockInterface], }); mockGetVPC(mockVPC).as('getVPC'); mockGetSubnets(mockVPC.id, [mockSubnet]).as('getSubnets'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); - mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as( - 'getLinodeConfigs' - ); + mockGetLinodeConfig({ + config: mockLinodeConfig, + configId: mockLinodeConfig.id, + linodeId: mockLinode.id, + }).as('getLinodeConfig'); cy.visitWithLogin(`/vpcs/${mockVPC.id}`); cy.findByLabelText(`expand ${mockSubnet.label} row`).click(); - cy.wait('@getLinodeConfigs'); + cy.wait('@getLinodeConfig'); cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist'); }); @@ -333,6 +344,7 @@ describe('VPC details page', () => { const linodeRegion = chooseRegion({ capabilities: ['VPCs'] }); const mockInterfaceId = randomNumber(); + const mockConfigId = randomNumber(); const mockLinode = linodeFactory.build({ id: randomNumber(), label: randomLabel(), @@ -346,7 +358,13 @@ describe('VPC details page', () => { linodes: [ { id: mockLinode.id, - interfaces: [{ active: true, id: mockInterfaceId }], + interfaces: [ + { + active: true, + config_id: mockConfigId, + id: mockInterfaceId, + }, + ], }, ], }); @@ -367,19 +385,22 @@ describe('VPC details page', () => { }); const mockLinodeConfig = linodeConfigFactory.build({ + id: mockConfigId, interfaces: [mockInterface], }); mockGetVPC(mockVPC).as('getVPC'); mockGetSubnets(mockVPC.id, [mockSubnet]).as('getSubnets'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); - mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as( - 'getLinodeConfigs' - ); + mockGetLinodeConfig({ + config: mockLinodeConfig, + configId: mockLinodeConfig.id, + linodeId: mockLinode.id, + }).as('getLinodeConfig'); cy.visitWithLogin(`/vpcs/${mockVPC.id}`); cy.findByLabelText(`expand ${mockSubnet.label} row`).click(); - cy.wait('@getLinodeConfigs'); + cy.wait('@getLinodeConfig'); cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('not.exist'); }); @@ -390,6 +411,7 @@ describe('VPC details page', () => { const linodeRegion = chooseRegion({ capabilities: ['VPCs'] }); const mockInterfaceId = randomNumber(); + const mockConfigId = randomNumber(); const mockLinode = linodeFactory.build({ id: randomNumber(), label: randomLabel(), @@ -403,7 +425,13 @@ describe('VPC details page', () => { linodes: [ { id: mockLinode.id, - interfaces: [{ active: true, id: mockInterfaceId }], + interfaces: [ + { + active: true, + config_id: mockConfigId, + id: mockInterfaceId, + }, + ], }, ], }); @@ -430,19 +458,22 @@ describe('VPC details page', () => { }); const mockLinodeConfig = linodeConfigFactory.build({ + id: mockConfigId, interfaces: [mockInterface, mockPrimaryInterface], }); mockGetVPC(mockVPC).as('getVPC'); mockGetSubnets(mockVPC.id, [mockSubnet]).as('getSubnets'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); - mockGetLinodeConfigs(mockLinode.id, [mockLinodeConfig]).as( - 'getLinodeConfigs' - ); + mockGetLinodeConfig({ + config: mockLinodeConfig, + configId: mockLinodeConfig.id, + linodeId: mockLinode.id, + }).as('getLinodeConfig'); cy.visitWithLogin(`/vpcs/${mockVPC.id}`); cy.findByLabelText(`expand ${mockSubnet.label} row`).click(); - cy.wait('@getLinodeConfigs'); + cy.wait('@getLinodeConfig'); cy.findByTestId(WARNING_ICON_UNRECOMMENDED_CONFIG).should('exist'); }); }); diff --git a/packages/manager/cypress/support/intercepts/configs.ts b/packages/manager/cypress/support/intercepts/configs.ts index 3a6396050e9..1671b9227e0 100644 --- a/packages/manager/cypress/support/intercepts/configs.ts +++ b/packages/manager/cypress/support/intercepts/configs.ts @@ -4,9 +4,10 @@ import { apiMatcher } from 'support/util/intercepts'; import { paginateResponse } from 'support/util/paginate'; -import { Config } from '@linode/api-v4'; import { makeResponse } from 'support/util/response'; +import type { Config, Interface } from '@linode/api-v4'; + /** * Intercepts GET request to fetch all configs for a given linode. * @@ -102,7 +103,7 @@ export const mockDeleteLinodeConfigInterface = ( * Mocks GET request to retrieve Linode configs. * * @param linodeId - ID of Linode for mocked request. - * @param configs - a list of Linode configswith which to mocked response. + * @param configs - a list of Linode configs with which to mocked response. * * @returns Cypress chainable. */ @@ -117,6 +118,54 @@ export const mockGetLinodeConfigs = ( ); }; +/** + * Mocks GET request to retrieve a Linode Config + * + * @param linodeId - ID of Linode for mocked request. + * @param configId - ID of Config for mocked request. + * @param config - the config with which to mocked response. + * + * @returns Cypress chainable. + */ +export const mockGetLinodeConfig = (inputs: { + config: Config; + configId: number; + linodeId: number; +}): Cypress.Chainable => { + const { config, configId, linodeId } = inputs; + return cy.intercept( + 'GET', + apiMatcher(`linode/instances/${linodeId}/configs/${configId}`), + config + ); +}; + +/** + * Mocks GET request to retrieve an interface of a Linode config + * + * @param linodeId - ID of Linode for mocked request. + * @param configId - ID of Config for mocked request. + * @param linodeId - ID of Config Interface for mocked request. + * @param configInterface - the interface with which to mocked response. + * + * @returns Cypress chainable. + */ +export const mockGetLinodeConfigInterface = (inputs: { + configId: number; + configInterface: Interface; + interfaceId: number; + linodeId: number; +}): Cypress.Chainable => { + const { configId, configInterface, interfaceId, linodeId } = inputs; + return cy.intercept( + 'GET', + apiMatcher( + `linode/instances/${linodeId}/configs/${configId}/interfaces/${interfaceId}` + ), + configInterface + ); +}; + /** * Mocks PUT request to update a linode config. * diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/InterfaceDetailsDrawer/VPCInterfaceDetailsContent.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/InterfaceDetailsDrawer/VPCInterfaceDetailsContent.tsx index 9bed4dcac59..ccdfcc4d3ca 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/InterfaceDetailsDrawer/VPCInterfaceDetailsContent.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/InterfaceDetailsDrawer/VPCInterfaceDetailsContent.tsx @@ -39,7 +39,7 @@ export const VPCInterfaceDetailsContent = (props: VPCInterfaceData) => { ))} diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.test.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.test.tsx index 26c935f2d63..4178a1a6c0e 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.test.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.test.tsx @@ -2,12 +2,10 @@ import { linodeConfigInterfaceFactory, linodeConfigInterfaceFactoryWithVPC, linodeFactory, + linodeInterfaceFactoryVPC, } from '@linode/utilities'; -import { - fireEvent, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; +import { waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { @@ -72,10 +70,12 @@ describe('SubnetLinodeRow', () => { it('should display linode label, reboot status, VPC IPv4 address, associated firewalls, IPv4 chip, and Reboot and Unassign buttons', async () => { const linodeFactory1 = linodeFactory.build({ id: 1, label: 'linode-1' }); + const config = linodeConfigFactory.build({ + interfaces: [linodeConfigInterfaceFactoryWithVPC.build({ id: 1 })], + }); server.use( - http.get('*/instances/*/configs', async () => { - const configs = linodeConfigFactory.buildList(3); - return HttpResponse.json(makeResourcePage(configs)); + http.get('*/instances/*/configs/:configId', async () => { + return HttpResponse.json(config); }) ); @@ -96,6 +96,7 @@ describe('SubnetLinodeRow', () => { linodeId={linodeFactory1.id} subnet={subnetFactory.build()} subnetId={1} + subnetInterfaces={[{ active: true, config_id: config.id, id: 1 }]} /> ) ); @@ -119,21 +120,79 @@ describe('SubnetLinodeRow', () => { const rebootLinodeButton = getAllByRole('button')[2]; expect(rebootLinodeButton).toHaveTextContent('Reboot'); - fireEvent.click(rebootLinodeButton); + await userEvent.click(rebootLinodeButton); expect(handlePowerActionsLinode).toHaveBeenCalled(); const unassignLinodeButton = getAllByRole('button')[3]; expect(unassignLinodeButton).toHaveTextContent('Unassign Linode'); - fireEvent.click(unassignLinodeButton); + await userEvent.click(unassignLinodeButton); expect(handleUnassignLinode).toHaveBeenCalled(); }); + it('should display the ip, range, and firewall for a Linode using Linode Interfaces', async () => { + const linodeFactory1 = linodeFactory.build({ id: 1, label: 'linode-1' }); + server.use( + http.get('*/instances/*/interfaces/:interfaceId', async () => { + const vpcLinodeInterface = linodeInterfaceFactoryVPC.build(); + return HttpResponse.json(vpcLinodeInterface); + }), + http.get('*/instances/*/interfaces/:interfaceId/firewalls', async () => { + return HttpResponse.json( + makeResourcePage( + firewallFactory.buildList(1, { label: mockFirewall0 }) + ) + ); + }) + ); + + const handlePowerActionsLinode = vi.fn(); + const handleUnassignLinode = vi.fn(); + + const { + getAllByRole, + getAllByText, + getByTestId, + getByText, + } = renderWithTheme( + wrapWithTableBody( + + ) + ); + + // Loading state should render + expect(getByTestId(loadingTestId)).toBeInTheDocument(); + + await waitForElementToBeRemoved(getByTestId(loadingTestId)); + + const linodeLabelLink = getAllByRole('link')[0]; + expect(linodeLabelLink).toHaveAttribute( + 'href', + `/linodes/${linodeFactory1.id}/networking/interfaces/1` + ); + + getAllByText('10.0.0.0'); + getAllByText('10.0.0.1'); + getByText(mockFirewall0); + }); + it('should not display reboot linode button if the linode has all active interfaces', async () => { const linodeFactory1 = linodeFactory.build({ id: 1, label: 'linode-1' }); const vpcInterface = linodeConfigInterfaceFactoryWithVPC.build({ active: true, + ip_ranges: [], primary: true, }); + const config = linodeConfigFactory.build({ + interfaces: [vpcInterface], + }); server.use( http.get('*/linodes/instances/:linodeId', () => { return HttpResponse.json(linodeFactory1); @@ -145,11 +204,8 @@ describe('SubnetLinodeRow', () => { ) ); }), - http.get('*/instances/*/configs', () => { - const configs = linodeConfigFactory.build({ - interfaces: [vpcInterface], - }); - return HttpResponse.json(makeResourcePage([configs])); + http.get('*/instances/*/configs/:configId', async () => { + return HttpResponse.json(config); }) ); @@ -159,6 +215,9 @@ describe('SubnetLinodeRow', () => { const { getAllByRole, getByTestId } = renderWithTheme( wrapWithTableBody( { expect(buttons.length).toEqual(2); const powerOffButton = buttons[0]; expect(powerOffButton).toHaveTextContent('Power Off'); - fireEvent.click(powerOffButton); + await userEvent.click(powerOffButton); expect(handlePowerActionsLinode).toHaveBeenCalled(); const unassignLinodeButton = buttons[1]; expect(unassignLinodeButton).toHaveTextContent('Unassign Linode'); - fireEvent.click(unassignLinodeButton); + await userEvent.click(unassignLinodeButton); expect(handleUnassignLinode).toHaveBeenCalled(); }); @@ -213,8 +272,8 @@ describe('SubnetLinodeRow', () => { }); server.use( - http.get('*/instances/*/configs', async () => { - return HttpResponse.json(makeResourcePage([configurationProfile])); + http.get('*/instances/*/configs/*', async () => { + return HttpResponse.json(configurationProfile); }) ); @@ -227,6 +286,7 @@ describe('SubnetLinodeRow', () => { linodeId={linodeFactory2.id} subnet={subnet} subnetId={subnet.id} + subnetInterfaces={[{ active: true, config_id: 1, id: 1 }]} /> ) ); @@ -251,8 +311,8 @@ describe('SubnetLinodeRow', () => { }) ); server.use( - http.get('*/instances/*/configs', async () => { - return HttpResponse.json(makeResourcePage([configurationProfile])); + http.get('*/instances/*/configs/*', async () => { + return HttpResponse.json(configurationProfile); }) ); @@ -268,6 +328,7 @@ describe('SubnetLinodeRow', () => { linodeId={linodeFactory1.id} subnet={subnetFactory.build()} subnetId={0} + subnetInterfaces={[{ active: true, config_id: 1, id: 1 }]} /> ) ); @@ -302,6 +363,7 @@ describe('SubnetLinodeRow', () => { linodeId={linodeFactory2.id} subnet={subnet} subnetId={subnet.id} + subnetInterfaces={[{ active: true, config_id: 1, id: 1 }]} /> ) ); diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.tsx index bf2557ccde5..846a5275116 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.tsx @@ -1,8 +1,4 @@ -import { - useAllLinodeConfigsQuery, - useLinodeFirewallsQuery, - useLinodeQuery, -} from '@linode/queries'; +import { useLinodeQuery } from '@linode/queries'; import { Box, CircleProgress, TooltipIcon, Typography } from '@linode/ui'; import { capitalizeAllWords } from '@linode/utilities'; import ErrorOutline from '@mui/icons-material/ErrorOutline'; @@ -17,19 +13,26 @@ import { TableRow } from 'src/components/TableRow'; import { getLinodeIconStatus } from 'src/features/Linodes/LinodesLanding/utils'; import { determineNoneSingleOrMultipleWithChip } from 'src/utilities/noneSingleOrMultipleWithChip'; +import { useInterfaceAndFirewallDataForLinode } from '../../../hooks/useInterfaceAndFirewallDataForLinode'; import { VPC_REBOOT_MESSAGE, WARNING_ICON_UNRECOMMENDED_CONFIG, } from '../constants'; import { hasUnrecommendedConfiguration as _hasUnrecommendedConfiguration, - getSubnetInterfaceFromConfigs, + hasUnrecommendedConfigurationLinodeInterface, } from '../utils'; import { StyledWarningIcon } from './SubnetLinodeRow.styles'; -import type { APIError, Firewall, Linode } from '@linode/api-v4'; -import type { Config, Interface } from '@linode/api-v4/lib/linodes/types'; -import type { Subnet } from '@linode/api-v4/lib/vpcs/types'; +import type { + APIError, + Firewall, + Interface, + Linode, + LinodeInterface, + Subnet, + SubnetLinodeInterfaceData, +} from '@linode/api-v4'; import type { Action } from 'src/features/Linodes/PowerActionsDialogOrDrawer'; interface Props { @@ -44,6 +47,7 @@ interface Props { linodeId: number; subnet: Subnet; subnetId: number; + subnetInterfaces: SubnetLinodeInterfaceData[]; } export const SubnetLinodeRow = (props: Props) => { @@ -55,30 +59,56 @@ export const SubnetLinodeRow = (props: Props) => { linodeId, subnet, subnetId, + subnetInterfaces, } = props; + const subnetInterfaceData = + subnetInterfaces.find((interfaceData) => interfaceData.active) ?? + subnetInterfaces[0]; + const { + active: isInterfaceActive, + config_id: configId, + id: interfaceId, + } = subnetInterfaceData; + const isLinodeInterface = configId === null; + const { data: linode, error: linodeError, isLoading: linodeLoading, } = useLinodeQuery(linodeId); + /** + * We need to handle support for both legacy interfaces and Linode interfaces. + * The below hook gives us the relevant firewall and interface data depending on + * interface type. + */ const { - data: attachedFirewalls, - error: firewallsError, - isLoading: firewallsLoading, - } = useLinodeFirewallsQuery(linodeId); + firewallsInfo, + interfacesInfo, + } = useInterfaceAndFirewallDataForLinode({ + configId, // subnet.linodes.interfaces data now includes config_id, so we no longer have to fetch all configs + interfaceId, + isLinodeInterface, + linodeId, + }); + const { attachedFirewalls, firewallsError, firewallsLoading } = firewallsInfo; const { - data: configs, - error: configsError, - isLoading: configsLoading, - } = useAllLinodeConfigsQuery(linodeId); - - const hasUnrecommendedConfiguration = _hasUnrecommendedConfiguration( - configs ?? [], - subnet?.id ?? -1 - ); + config, // undefined if this Linode is using Linode Interfaces. Used to determine an unrecommended configuration + configInterface, // undefined if this Linode is using Linode Interfaces + interfaceData, + interfaceError, + interfaceLoading, + linodeInterface, // undefined if this Linode is using Config Profile Interfaces + } = interfacesInfo; + + const hasUnrecommendedConfiguration = isLinodeInterface + ? hasUnrecommendedConfigurationLinodeInterface( + linodeInterface, + isInterfaceActive + ) + : _hasUnrecommendedConfiguration(config, subnetId); if (linodeLoading || !linode) { return ( @@ -110,7 +140,15 @@ export const SubnetLinodeRow = (props: Props) => { } const linkifiedLinodeLabel = ( - {linode.label} + + {linode.label} + ); const labelCell = @@ -122,9 +160,9 @@ export const SubnetLinodeRow = (props: Props) => { - This Linode is using a configuration profile with a Networking - setting that is not recommended. To avoid potential connectivity - issues, edit the Linode’s configuration. + {isLinodeInterface + ? '@TODO Linode Interfaces - confirm copy? This Linode’s Network Interfaces setup is not recommended. To avoid potential connectivity issues, set this Linode’s VPC interface as the default IPv4 route.' + : 'This Linode is using a configuration profile with a Networking setting that is not recommended. To avoid potential connectivity issues, edit the Linode’s configuration.'} } icon={} @@ -141,13 +179,7 @@ export const SubnetLinodeRow = (props: Props) => { const isRunning = linode.status === 'running'; const isOffline = linode.status === 'stopped' || linode.status === 'offline'; const isRebootNeeded = - isRunning && - configs?.some((config) => - config.interfaces?.some( - (linodeInterface) => - linodeInterface.purpose === 'vpc' && !linodeInterface.active - ) - ); + isRunning && !isLinodeInterface && !configInterface?.active; const showPowerButton = !isRebootNeeded && (isRunning || isOffline); @@ -177,20 +209,18 @@ export const SubnetLinodeRow = (props: Props) => { {getSubnetLinodeIPv4CellString( - configs ?? [], - configsLoading, - subnetId, - configsError ?? undefined + interfaceData, + interfaceLoading, + interfaceError ?? undefined )} {getIPRangesCellContents( - configs ?? [], - configsLoading, - subnetId, - configsError ?? undefined + interfaceData, + interfaceLoading, + interfaceError ?? undefined )} @@ -261,9 +291,8 @@ const getFirewallsCellString = ( }; const getSubnetLinodeIPv4CellString = ( - configs: Config[], + interfaceData: Interface | LinodeInterface | undefined, loading: boolean, - subnetId: number, error?: APIError[] ): JSX.Element | string => { if (loading) { @@ -274,15 +303,23 @@ const getSubnetLinodeIPv4CellString = ( return 'Error retrieving VPC IPv4s'; } - if (configs.length === 0) { + if (!interfaceData) { return 'None'; } - const configInterface = getSubnetInterfaceFromConfigs(configs, subnetId); - return getIPv4Link(configInterface); + if ('purpose' in interfaceData) { + return getIPv4LinkForConfigInterface(interfaceData); + } else { + const primaryIPv4 = interfaceData.vpc?.ipv4.addresses.find( + (address) => address.primary + )?.address; + return {primaryIPv4 ?? 'None'}; + } }; -const getIPv4Link = (configInterface: Interface | undefined): JSX.Element => { +const getIPv4LinkForConfigInterface = ( + configInterface: Interface | undefined +): JSX.Element => { return ( // eslint-disable-next-line react/jsx-no-useless-fragment <> @@ -294,9 +331,8 @@ const getIPv4Link = (configInterface: Interface | undefined): JSX.Element => { }; const getIPRangesCellContents = ( - configs: Config[], + interfaceData: Interface | LinodeInterface | undefined, loading: boolean, - subnetId: number, error?: APIError[] ): JSX.Element | string => { if (loading) { @@ -307,14 +343,22 @@ const getIPRangesCellContents = ( return 'Error retrieving VPC IPv4s'; } - if (configs.length === 0) { + if (!interfaceData) { return 'None'; } - const configInterface = getSubnetInterfaceFromConfigs(configs, subnetId); - return determineNoneSingleOrMultipleWithChip( - configInterface?.ip_ranges ?? [] - ); + if ('purpose' in interfaceData) { + return determineNoneSingleOrMultipleWithChip( + interfaceData?.ip_ranges ?? [] + ); + } else { + const linodeInterfaceVPCRanges = interfaceData.vpc?.ipv4.ranges.map( + (range) => range.range + ); + return determineNoneSingleOrMultipleWithChip( + linodeInterfaceVPCRanges ?? [] + ); + } }; const getFirewallLinks = (data: Firewall[]): JSX.Element => { diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetUnassignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetUnassignLinodesDrawer.tsx index 9acc0af2f20..ee5bc25e75e 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetUnassignLinodesDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetUnassignLinodesDrawer.tsx @@ -127,7 +127,7 @@ export const SubnetUnassignLinodesDrawer = React.memo( const updatedConfigInterfaces = await Promise.all( selectedLinodes.map(async (linode) => { const response = await queryClient.fetchQuery( - linodeQueries.linode(linode.id)._ctx.configs + linodeQueries.linode(linode.id)._ctx.configs._ctx.configs ); if (response) { diff --git a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx index aa2dded83da..b86149978b4 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx @@ -335,6 +335,7 @@ export const VPCSubnetsTable = (props: Props) => { linodeId={linodeInfo.id} subnet={subnet} subnetId={subnet.id} + subnetInterfaces={linodeInfo.interfaces} /> )) ) : ( diff --git a/packages/manager/src/features/VPCs/utils.test.ts b/packages/manager/src/features/VPCs/utils.test.ts index 9bd13e5de12..aba782e71b1 100644 --- a/packages/manager/src/features/VPCs/utils.test.ts +++ b/packages/manager/src/features/VPCs/utils.test.ts @@ -1,6 +1,7 @@ import { linodeConfigInterfaceFactory, linodeConfigInterfaceFactoryWithVPC, + linodeInterfaceFactoryVPC, } from '@linode/utilities'; import { linodeConfigFactory } from 'src/factories/linodeConfigs'; @@ -10,9 +11,9 @@ import { } from 'src/factories/subnets'; import { - getSubnetInterfaceFromConfigs, getUniqueLinodesFromSubnets, hasUnrecommendedConfiguration, + hasUnrecommendedConfigurationLinodeInterface, } from './utils'; const subnetLinodeInfoList1 = subnetAssignedLinodeDataFactory.buildList(4); @@ -56,34 +57,8 @@ describe('getUniqueLinodesFromSubnets', () => { }); }); -describe('getSubnetInterfaceFromConfigs', () => { - it('returns the interface associated with the given subnet id', () => { - const interfaces = linodeConfigInterfaceFactoryWithVPC.buildList(5); - const singleConfig = linodeConfigFactory.build({ interfaces }); - const configs = [linodeConfigFactory.build(), singleConfig]; - - const subnetInterface1 = getSubnetInterfaceFromConfigs(configs, 2); - expect(subnetInterface1).toEqual(interfaces[0]); - const subnetInterface2 = getSubnetInterfaceFromConfigs(configs, 3); - expect(subnetInterface2).toEqual(interfaces[1]); - const subnetInterface3 = getSubnetInterfaceFromConfigs(configs, 4); - expect(subnetInterface3).toEqual(interfaces[2]); - const subnetInterface4 = getSubnetInterfaceFromConfigs(configs, 5); - expect(subnetInterface4).toEqual(interfaces[3]); - const subnetInterface5 = getSubnetInterfaceFromConfigs(configs, 6); - expect(subnetInterface5).toEqual(interfaces[4]); - }); - - it('should return undefined if an interface with the given subnet ID is not found', () => { - const configs = linodeConfigFactory.buildList(4); - - const subnetInterfaceUndefined = getSubnetInterfaceFromConfigs(configs, 5); - expect(subnetInterfaceUndefined).toBeUndefined(); - }); -}); - describe('hasUnrecommendedConfiguration function', () => { - it('returns true when a config has an active VPC interface and a non-VPC primary interface', () => { + it('returns true if the given config has an active VPC interface and a non-VPC primary interface', () => { const publicInterface = linodeConfigInterfaceFactory.build({ id: 10, primary: true, @@ -99,8 +74,6 @@ describe('hasUnrecommendedConfiguration function', () => { interfaces: [publicInterface, vpcInterface], }); - const config2 = linodeConfigFactory.build(); - const subnet = subnetFactory.build({ id: 1, linodes: [ @@ -120,12 +93,10 @@ describe('hasUnrecommendedConfiguration function', () => { ], }); - expect(hasUnrecommendedConfiguration([config1, config2], subnet.id)).toBe( - true - ); + expect(hasUnrecommendedConfiguration(config1, subnet.id)).toBe(true); }); - it('returns false when a config has an active VPC interface that is the primary interface', () => { + it('returns false if the given config has an active VPC interface that is the primary interface', () => { const publicInterface = linodeConfigInterfaceFactory.build({ id: 10 }); const vpcInterface = linodeConfigInterfaceFactoryWithVPC.build({ active: true, @@ -134,10 +105,6 @@ describe('hasUnrecommendedConfiguration function', () => { subnet_id: 1, }); - const config1 = linodeConfigFactory.build({ - interfaces: [publicInterface], - }); - const config2 = linodeConfigFactory.build({ interfaces: [publicInterface, vpcInterface], }); @@ -161,8 +128,86 @@ describe('hasUnrecommendedConfiguration function', () => { ], }); - expect(hasUnrecommendedConfiguration([config1, config2], subnet.id)).toBe( - false - ); + expect(hasUnrecommendedConfiguration(config2, subnet.id)).toBe(false); + }); +}); + +describe('hasUnrecommendedConfigurationLinodeInterface function', () => { + it('returns false if the given interface is not active', () => { + expect( + hasUnrecommendedConfigurationLinodeInterface( + linodeInterfaceFactoryVPC.build(), + false + ) + ).toBe(false); + }); + + it('returns false if the given interface is active and the default route', () => { + const vpcInterfaceWithNat = linodeInterfaceFactoryVPC.build({ + vpc: { + ipv4: { + addresses: [ + { + address: '10.0.0.0', + nat_1_1_address: '172.65.28.10', + primary: true, + }, + ], + }, + subnet_id: 1, + vpc_id: 1, + }, + }); + const vpcInterfaceWithoutNat = linodeInterfaceFactoryVPC.build(); + expect( + hasUnrecommendedConfigurationLinodeInterface(vpcInterfaceWithNat, true) + ).toBe(false); + + expect( + hasUnrecommendedConfigurationLinodeInterface(vpcInterfaceWithoutNat, true) + ).toBe(false); + }); + + it('returns true if the given active VPC interface is not the default route but has a nat_1_1 address', () => { + const vpcInterfaceWithNat = linodeInterfaceFactoryVPC.build({ + default_route: { ipv4: undefined }, + vpc: { + ipv4: { + addresses: [ + { + address: '10.0.0.0', + nat_1_1_address: '172.65.28.10', + primary: true, + }, + ], + }, + subnet_id: 1, + vpc_id: 1, + }, + }); + expect( + hasUnrecommendedConfigurationLinodeInterface(vpcInterfaceWithNat, true) + ).toEqual(true); + }); + + it('returns false if the given active VPC interface is not the default route and has no nat_1_1 address', () => { + const vpcInterfaceWithoutNat = linodeInterfaceFactoryVPC.build({ + default_route: { ipv4: undefined }, + vpc: { + ipv4: { + addresses: [ + { + address: '10.0.0.0', + primary: true, + }, + ], + }, + subnet_id: 1, + vpc_id: 1, + }, + }); + expect( + hasUnrecommendedConfigurationLinodeInterface(vpcInterfaceWithoutNat, true) + ).toEqual(false); }); }); diff --git a/packages/manager/src/features/VPCs/utils.ts b/packages/manager/src/features/VPCs/utils.ts index 10ea3ede81b..6a2964795cf 100644 --- a/packages/manager/src/features/VPCs/utils.ts +++ b/packages/manager/src/features/VPCs/utils.ts @@ -1,6 +1,6 @@ import { getPrimaryInterfaceIndex } from '../Linodes/LinodesDetail/LinodeConfigs/utilities'; -import type { Config, Subnet, VPC } from '@linode/api-v4'; +import type { Config, LinodeInterface, Subnet, VPC } from '@linode/api-v4'; export const getUniqueLinodesFromSubnets = (subnets: Subnet[]) => { const linodes: number[] = []; @@ -14,31 +14,25 @@ export const getUniqueLinodesFromSubnets = (subnets: Subnet[]) => { return linodes.length; }; -export const getSubnetInterfaceFromConfigs = ( - configs: Config[], - subnetId: number +// Linode Interfaces: show unrecommended notice if (active) VPC interface has an IPv4 nat_1_1 address but isn't the default IPv4 route +export const hasUnrecommendedConfigurationLinodeInterface = ( + linodeInterface: LinodeInterface | undefined, + isInterfaceActive: boolean ) => { - for (const config of configs) { - if (config.interfaces) { - for (const linodeInterface of config.interfaces) { - if ( - linodeInterface.ipv4?.vpc && - linodeInterface.subnet_id === subnetId - ) { - return linodeInterface; - } - } - } - } - - return undefined; + return ( + isInterfaceActive && + linodeInterface?.vpc?.ipv4.addresses.some( + (address) => address.nat_1_1_address + ) && + !linodeInterface?.default_route.ipv4 + ); }; export const hasUnrecommendedConfiguration = ( - configs: Config[], + config: Config | undefined, subnetId: number ) => { - for (const config of configs) { + if (config) { const configInterfaces = config.interfaces; /* diff --git a/packages/manager/src/hooks/useInterfaceAndFirewallDataForLinode.ts b/packages/manager/src/hooks/useInterfaceAndFirewallDataForLinode.ts new file mode 100644 index 00000000000..ccbad77912d --- /dev/null +++ b/packages/manager/src/hooks/useInterfaceAndFirewallDataForLinode.ts @@ -0,0 +1,86 @@ +import { + useLinodeConfigQuery, + useLinodeFirewallsQuery, + useLinodeInterfaceFirewallsQuery, + useLinodeInterfaceQuery, +} from '@linode/queries'; + +/** + * In some files, We need to handle support for both legacy interfaces and Linode interfaces. + * + * To fetch data, depending if the Linode with the given ID is a Linode Interface, we use Linode Interface related queries. + * Otherwise, we use config profile interface related queries. + */ +export const useInterfaceAndFirewallDataForLinode = (inputs: { + configId: null | number; + interfaceId: number; + isLinodeInterface: boolean; + linodeId: number; +}) => { + const { configId, interfaceId, isLinodeInterface, linodeId } = inputs; + + // query to fetch firewalls if this Linode uses config profile interfaces + const { + data: attachedFirewallsConfig, + error: firewallsErrorConfig, + isLoading: firewallsLoadingConfig, + } = useLinodeFirewallsQuery(linodeId, !isLinodeInterface); + + // query to fetch firewalls for a Linode Interface (firewalls are attached at the interface level) + const { + data: attachedFirewallsLinodeInterface, + error: firewallsErrorLinodeInterface, + isLoading: firewallsLoadingLinodeInterface, + } = useLinodeInterfaceFirewallsQuery( + linodeId, + interfaceId, + isLinodeInterface + ); + + // Depending on which query was fired to fetch firewalls, return appropriate data + const attachedFirewalls = + attachedFirewallsConfig ?? attachedFirewallsLinodeInterface; + const firewallsError = firewallsErrorConfig ?? firewallsErrorLinodeInterface; + const firewallsLoading = + firewallsLoadingConfig || firewallsLoadingLinodeInterface; + + const { + data: linodeInterface, + error: linodeInterfaceError, + isLoading: linodeInterfaceLoading, + } = useLinodeInterfaceQuery(linodeId, interfaceId, isLinodeInterface); + + const { + data: config, + error: configError, + isLoading: configLoading, + } = useLinodeConfigQuery({ + configId: configId ?? -1, + enabled: !isLinodeInterface, + linodeId, + }); + + // Depending on which query was fired to fetch the Linode's interfaces, return appropriate data + const configInterface = config?.interfaces?.find( + (iface) => iface.id === interfaceId + ); + const interfaceData = linodeInterface ?? configInterface; + const interfaceError = linodeInterfaceError ?? configError; + const interfaceLoading = linodeInterfaceLoading ?? configLoading; + + return { + firewallsInfo: { + attachedFirewalls, + firewallsError, + firewallsLoading, + }, + interfacesInfo: { + config, // undefined if this Linode is using Linode Interfaces. Used to determine an unrecommended configuration + configInterface, // undefined if this Linode is using Linode Interfaces + interfaceData, + interfaceError, + interfaceLoading, + linodeInterface, // undefined if this Linode is using Config Profile Interfaces + }, + }; +}; diff --git a/packages/queries/.changeset/pr-11953-added-1743614960791.md b/packages/queries/.changeset/pr-11953-added-1743614960791.md new file mode 100644 index 00000000000..149212f70bb --- /dev/null +++ b/packages/queries/.changeset/pr-11953-added-1743614960791.md @@ -0,0 +1,5 @@ +--- +"@linode/queries": Added +--- + +Linode Config related queries to get a single Config and a single Config Profile Interface ([#11953](https://github.com/linode/manager/pull/11953)) diff --git a/packages/queries/src/linodes/configs.ts b/packages/queries/src/linodes/configs.ts index 636024d153e..842a0df3aa4 100644 --- a/packages/queries/src/linodes/configs.ts +++ b/packages/queries/src/linodes/configs.ts @@ -10,12 +10,41 @@ import { linodeQueries } from './linodes'; import type { APIError, Config, + Interface, LinodeConfigCreationData, } from '@linode/api-v4'; export const useAllLinodeConfigsQuery = (id: number, enabled = true) => { return useQuery({ - ...linodeQueries.linode(id)._ctx.configs, + ...linodeQueries.linode(id)._ctx.configs._ctx.configs, + enabled, + }); +}; + +export const useLinodeConfigQuery = (options: { + configId: number; + enabled: boolean; + linodeId: number; +}) => { + const { configId, enabled, linodeId } = options; + return useQuery({ + ...linodeQueries.linode(linodeId)._ctx.configs._ctx.config(configId), + enabled, + }); +}; + +export const useLinodeConfigInterfaceQuery = (options: { + configId: number; + enabled: boolean; + interfaceId: number; + linodeId: number; +}) => { + const { configId, enabled, interfaceId, linodeId } = options; + return useQuery({ + ...linodeQueries + .linode(linodeId) + ._ctx.configs._ctx.config(configId) + ._ctx.interface(interfaceId), enabled, }); }; diff --git a/packages/queries/src/linodes/interfaces.ts b/packages/queries/src/linodes/interfaces.ts index 56120fd33f0..8aaa0050256 100644 --- a/packages/queries/src/linodes/interfaces.ts +++ b/packages/queries/src/linodes/interfaces.ts @@ -40,12 +40,15 @@ export const useLinodeInterfaceQuery = ( export const useLinodeInterfaceFirewallsQuery = ( linodeId: number, - interfaceId: number + interfaceId: number, + enabled: boolean = true ) => { - return useQuery, APIError[]>( - linodeQueries.linode(linodeId)._ctx.interfaces._ctx.interface(interfaceId) - ._ctx.firewalls - ); + return useQuery, APIError[]>({ + ...linodeQueries + .linode(linodeId) + ._ctx.interfaces._ctx.interface(interfaceId)._ctx.firewalls, + enabled, + }); }; export const useCreateLinodeInterfaceMutation = (linodeId: number) => { diff --git a/packages/queries/src/linodes/linodes.ts b/packages/queries/src/linodes/linodes.ts index 3b254020c5f..1b324ca4d67 100644 --- a/packages/queries/src/linodes/linodes.ts +++ b/packages/queries/src/linodes/linodes.ts @@ -3,8 +3,11 @@ import { cloneLinode, createLinode, deleteLinode, + getConfigInterface, + getConfigInterfaces, getLinode, getLinodeBackups, + getLinodeConfig, getLinodeFirewalls, getLinodeIPs, getLinodeInterface, @@ -89,7 +92,27 @@ export const linodeQueries = createQueryKeys('linodes', { queryKey: null, }, configs: { - queryFn: () => getAllLinodeConfigs(id), + contextQueries: { + config: (configId: number) => ({ + contextQueries: { + interface: (interfaceId: number) => ({ + queryFn: () => getConfigInterface(id, configId, interfaceId), + queryKey: [interfaceId], + }), + interfaces: { + queryFn: () => getConfigInterfaces(id, configId), + queryKey: null, + }, + queryKey: null, + }, + queryFn: () => getLinodeConfig(id, configId), + queryKey: [configId], + }), + configs: { + queryFn: () => getAllLinodeConfigs(id), + queryKey: null, + }, + }, queryKey: null, }, disks: { diff --git a/packages/utilities/src/factories/linodeInterface.ts b/packages/utilities/src/factories/linodeInterface.ts index 2e7ae010b73..7c6a422f2ee 100644 --- a/packages/utilities/src/factories/linodeInterface.ts +++ b/packages/utilities/src/factories/linodeInterface.ts @@ -59,7 +59,7 @@ export const linodeInterfaceFactoryVPC = Factory.Sync.makeFactory Date: Tue, 8 Apr 2025 17:15:43 +0200 Subject: [PATCH 038/112] fix: [UIE-8674] - advanced config fix (#11987) * fix: [UIE-8447] - refresh drawer after config add/update * fix: [UIE-8447] - fix loading state * fix: [UIE-8447] - fix delay in drawer * fix: [UIE-8674] - fix config label * fix: [UIE-8674] - add loading state for the drawer, rename Monitor tab * Added changeset: Update config label to follow the category.label format, rename Monitor tab --- .../pr-11987-changed-1744123144788.md | 5 +++ .../CreateAlert/CreateAlertDefinition.tsx | 2 +- .../DatabaseAdvancedConfiguration.tsx | 5 +-- .../DatabaseAdvancedConfigurationDrawer.tsx | 39 ++++++++++++------- .../DatabaseConfigurationItem.tsx | 7 ++-- .../Databases/DatabaseDetail/index.tsx | 4 +- .../src/features/Databases/utilities.test.ts | 8 ++-- .../src/features/Databases/utilities.ts | 8 ++-- packages/validation/src/cloudpulse.schema.ts | 2 +- 9 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 packages/manager/.changeset/pr-11987-changed-1744123144788.md diff --git a/packages/manager/.changeset/pr-11987-changed-1744123144788.md b/packages/manager/.changeset/pr-11987-changed-1744123144788.md new file mode 100644 index 00000000000..0a8b802052b --- /dev/null +++ b/packages/manager/.changeset/pr-11987-changed-1744123144788.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Update config label to follow the category.label format, rename Monitor tab ([#11987](https://github.com/linode/manager/pull/11987)) diff --git a/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx b/packages/manager/src/features/CloudPulse/Alerts/CreateAlert/CreateAlertDefinition.tsx index 5478e1ba184..8c328d3cfc1 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 { CREATE_ALERT_ERROR_FIELD_MAP, MULTILINE_ERROR_SEPARATOR, SINGLELINE_ERROR_SEPARATOR, - CREATE_ALERT_SUCCESS_MESSAGE + CREATE_ALERT_SUCCESS_MESSAGE, } from '../constants'; import { enhanceValidationSchemaWithEntityIdValidation, diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfiguration.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfiguration.tsx index b2bbc24618f..3439bf209db 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfiguration.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfiguration.tsx @@ -25,7 +25,6 @@ export const DatabaseAdvancedConfiguration = ({ database }: Props) => { setAdvancedConfigurationDrawerOpen, ] = React.useState(false); - const engine = database.engine; const engineConfigs = database.engine_config; return ( @@ -62,7 +61,7 @@ export const DatabaseAdvancedConfiguration = ({ database }: Props) => { Object.entries(value!).map(([configLabel, configValue]) => ( - {`${engine}.${configLabel}`} + {`${key}.${configLabel}`} {formatConfigValue(String(configValue))} @@ -72,7 +71,7 @@ export const DatabaseAdvancedConfiguration = ({ database }: Props) => { ) : ( - {`${engine}.${key}`} + {`${key}`} {formatConfigValue(String(value))} diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfigurationDrawer.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfigurationDrawer.tsx index a435c9fd20d..876a18f6fbd 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfigurationDrawer.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseAdvancedConfiguration/DatabaseAdvancedConfigurationDrawer.tsx @@ -2,9 +2,11 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { ActionsPanel, Button, + CircleProgress, Divider, Drawer, Notice, + Stack, Typography, } from '@linode/ui'; import { createDynamicAdvancedConfigSchema } from '@linode/validation'; @@ -51,7 +53,7 @@ interface FormValues { export const DatabaseAdvancedConfigurationDrawer = (props: Props) => { const { database, onClose, open } = props; - const { engine, engine_config: existingConfigurations, id } = database; + const { engine, engine_config: existingConfiguration, id } = database; const [ selectedConfig, @@ -64,13 +66,16 @@ export const DatabaseAdvancedConfigurationDrawer = (props: Props) => { mutateAsync: updateDatabase, } = useDatabaseMutation(engine, id); - const { data: databaseConfig } = useDatabaseEngineConfig(engine, true); + const { data: databaseConfig, isLoading } = useDatabaseEngineConfig( + engine, + true + ); const configurations = convertEngineConfigToOptions(databaseConfig); - const existingConfigsArray = useMemo( - () => convertExistingConfigsToArray(existingConfigurations, databaseConfig), - [existingConfigurations, databaseConfig] + const existingConfigurations = useMemo( + () => convertExistingConfigsToArray(existingConfiguration, databaseConfig), + [existingConfiguration, databaseConfig] ); const { @@ -80,7 +85,7 @@ export const DatabaseAdvancedConfigurationDrawer = (props: Props) => { reset, watch, } = useForm({ - defaultValues: { configs: existingConfigsArray }, + defaultValues: { configs: existingConfigurations }, mode: 'onBlur', resolver: yupResolver( createDynamicAdvancedConfigSchema( @@ -97,10 +102,10 @@ export const DatabaseAdvancedConfigurationDrawer = (props: Props) => { const configs = watch('configs'); useEffect(() => { - if (existingConfigsArray) { - reset({ configs: existingConfigsArray }); + if (existingConfigurations.length > 0) { + reset({ configs: existingConfigurations }); } - }, [existingConfigsArray]); + }, [existingConfigurations]); const usedConfigs = useMemo( () => new Set(fields.map((config) => config.label)), @@ -201,13 +206,22 @@ export const DatabaseAdvancedConfigurationDrawer = (props: Props) => { + {isLoading && ( + + + + )} + {!isLoading && configs.length === 0 && ( + + No advanced configurations have been added. + + )} {configs.map((config, index) => ( { return ( { name={`configs.${index}.value`} /> ))} - {configs.length === 0 && ( - - No advanced configurations have been added. - - )} void; onChange: (config: ConfigValue) => void; @@ -33,7 +32,7 @@ interface Props { } export const DatabaseConfigurationItem = (props: Props) => { - const { configItem, engine, errorText, onBlur, onChange, onRemove } = props; + const { configItem, errorText, onBlur, onChange, onRemove } = props; const configLabel = configItem?.label || ''; const renderInputField = () => { @@ -127,7 +126,9 @@ export const DatabaseConfigurationItem = (props: Props) => { mr: 0.5, })} > - {`${engine}.${configLabel}`} + {configItem?.category === 'other' + ? configLabel + : `${configItem?.category}.${configLabel}`} {configItem?.requires_restart && ( diff --git a/packages/manager/src/features/Databases/DatabaseDetail/index.tsx b/packages/manager/src/features/Databases/DatabaseDetail/index.tsx index 2f59cb0e70c..5f1316d601d 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/index.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/index.tsx @@ -115,8 +115,8 @@ export const DatabaseDetail = () => { if (isMonitorEnabled) { tabs.splice(1, 0, { chip: flags.dbaasV2MonitorMetrics?.beta ? : null, - routeName: `/databases/${engine}/${id}/monitor`, - title: 'Monitor', + routeName: `/databases/${engine}/${id}/metrics`, + title: 'Metrics', }); } diff --git a/packages/manager/src/features/Databases/utilities.test.ts b/packages/manager/src/features/Databases/utilities.test.ts index 4ffcf30709c..77c4a245a5e 100644 --- a/packages/manager/src/features/Databases/utilities.test.ts +++ b/packages/manager/src/features/Databases/utilities.test.ts @@ -599,6 +599,7 @@ describe('formatConfigValue', () => { describe('findConfigItem', () => { const mockConfigs: DatabaseEngineConfig = databaseEngineConfigFactory.build(); const expectedConfig = { + category: 'other', description: 'The minimum amount of time in seconds to keep binlog entries before deletion. This may be extended for services that require binlog entries for longer than the default for example if using the MySQL Debezium Kafka connector.', example: 600, @@ -609,6 +610,7 @@ describe('findConfigItem', () => { }; const expectedNestedConfig = { + category: 'mysql', description: 'The number of seconds that the mysqld server waits for a connect packet before responding with Bad handshake', example: 10, @@ -646,7 +648,7 @@ describe('convertExistingConfigsToArray', () => { const expectedOptions: ConfigurationOption[] = [ { - category: '', + category: 'mysql', description: 'The number of seconds that the mysqld server waits for a connect packet before responding with Bad handshake', example: 10, @@ -658,7 +660,7 @@ describe('convertExistingConfigsToArray', () => { value: 10, }, { - category: '', + category: 'mysql', description: "Default server time zone as an offset from UTC (from -12:00 to +12:00), a time zone name, or 'SYSTEM' to use the MySQL server default.", example: '+03:00', @@ -671,7 +673,7 @@ describe('convertExistingConfigsToArray', () => { value: '+03:00', }, { - category: '', + category: 'other', description: 'The minimum amount of time in seconds to keep binlog entries before deletion. This may be extended for services that require binlog entries for longer than the default for example if using the MySQL Debezium Kafka connector.', example: 600, diff --git a/packages/manager/src/features/Databases/utilities.ts b/packages/manager/src/features/Databases/utilities.ts index 42effa10ac5..a13c19453a9 100644 --- a/packages/manager/src/features/Databases/utilities.ts +++ b/packages/manager/src/features/Databases/utilities.ts @@ -335,7 +335,7 @@ export const findConfigItem = ( const value = configs[key]; if (key === targetKey) { - return value as ConfigurationOption; + return { ...value, category: 'other' } as ConfigurationOption; } if (typeof value === 'object' && value !== null) { @@ -343,7 +343,7 @@ export const findConfigItem = ( value as Record, targetKey ); - if (found) return found; + if (found) return { ...found, category: key }; } } @@ -374,7 +374,7 @@ export const convertExistingConfigsToArray = ( if (foundConfig) { options.push({ ...foundConfig, - category: '', + category: foundConfig.category || '', label: subKey, value: subValue, }); @@ -385,7 +385,7 @@ export const convertExistingConfigsToArray = ( if (foundConfig) { options.push({ ...foundConfig, - category: '', + category: foundConfig.category || '', label: key, value: value, }); diff --git a/packages/validation/src/cloudpulse.schema.ts b/packages/validation/src/cloudpulse.schema.ts index 3ba827a345f..6a848039d2d 100644 --- a/packages/validation/src/cloudpulse.schema.ts +++ b/packages/validation/src/cloudpulse.schema.ts @@ -14,7 +14,7 @@ const metricCriteria = object({ operator: string().required(fieldErrorMessage), threshold: number() .required(fieldErrorMessage) - .positive("Enter a positive value.") + .positive('Enter a positive value.') .typeError('The value should be a number.'), dimension_filters: array().of(dimensionFilters).notRequired(), }); From 903fe444f7994edd1621d8cfc551a5ace713eb03 Mon Sep 17 00:00:00 2001 From: Hussain Khalil <122488130+hkhalil-akamai@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:34:49 -0400 Subject: [PATCH 039/112] upcoming: [M3-9646] - Create VM Host Maintenance flag (#11974) * Add feature flag * Use beta feature flag * Added changeset: Feature flag for VM Host Maintenance policy --- .../.changeset/pr-11974-upcoming-features-1743784910488.md | 5 +++++ packages/manager/src/dev-tools/FeatureFlagTool.tsx | 4 ++++ packages/manager/src/featureFlags.ts | 1 + 3 files changed, 10 insertions(+) create mode 100644 packages/manager/.changeset/pr-11974-upcoming-features-1743784910488.md diff --git a/packages/manager/.changeset/pr-11974-upcoming-features-1743784910488.md b/packages/manager/.changeset/pr-11974-upcoming-features-1743784910488.md new file mode 100644 index 00000000000..65a1f280cd4 --- /dev/null +++ b/packages/manager/.changeset/pr-11974-upcoming-features-1743784910488.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Feature flag for VM Host Maintenance policy ([#11974](https://github.com/linode/manager/pull/11974)) diff --git a/packages/manager/src/dev-tools/FeatureFlagTool.tsx b/packages/manager/src/dev-tools/FeatureFlagTool.tsx index 9800ca40be9..7ece6fb9ccf 100644 --- a/packages/manager/src/dev-tools/FeatureFlagTool.tsx +++ b/packages/manager/src/dev-tools/FeatureFlagTool.tsx @@ -44,6 +44,10 @@ const options: { flag: keyof Flags; label: string }[] = [ flag: 'linodeCloneFirewall', label: 'Linode Clone Firewall', }, + { + flag: 'vmHostMaintenance', + label: 'VM Host Maintenance Policy', + }, ]; interface RenderFlagItemProps { diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts index b5fa4c50365..51e095391ab 100644 --- a/packages/manager/src/featureFlags.ts +++ b/packages/manager/src/featureFlags.ts @@ -152,6 +152,7 @@ export interface Flags { taxes: Taxes; tpaProviders: Provider[]; udp: boolean; + vmHostMaintenance: BetaFeatureFlag; } interface MarketplaceAppOverride { From dbca81819a431dd22c07d8de0b1900a4fdf73406 Mon Sep 17 00:00:00 2001 From: bill-akamai Date: Tue, 8 Apr 2025 11:11:13 -0500 Subject: [PATCH 040/112] change: [M3-9655] - [Akamai Design System] Toast Component (#11962) * Begin v2 of branch * Finish implementing new design * Added changeset: Update toast styling to Akamai Design System specs * Added changeset: Include outlined SVG icons for toast design * Address code review feedback --- .../pr-11962-changed-1743652792312.md | 5 +++ .../src/components/Snackbar/CloseSnackbar.tsx | 5 +-- .../src/components/Snackbar/Snackbar.tsx | 40 +++++++++++++++++-- .../Snackbar/ToastNotifications.stories.tsx | 15 +++++-- packages/manager/src/index.tsx | 2 +- .../pr-11962-added-1743652880587.md | 5 +++ .../ui/src/assets/icons/error-outlined.svg | 3 ++ packages/ui/src/assets/icons/index.ts | 5 +++ .../ui/src/assets/icons/info-outlined.svg | 3 ++ .../ui/src/assets/icons/success-outlined.svg | 4 ++ packages/ui/src/assets/icons/tip-outlined.svg | 4 ++ .../ui/src/assets/icons/warning-outlined.svg | 5 +++ packages/ui/src/foundations/themes/dark.ts | 14 ++++--- packages/ui/src/foundations/themes/light.ts | 14 ++++--- 14 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 packages/manager/.changeset/pr-11962-changed-1743652792312.md create mode 100644 packages/ui/.changeset/pr-11962-added-1743652880587.md create mode 100644 packages/ui/src/assets/icons/error-outlined.svg create mode 100644 packages/ui/src/assets/icons/info-outlined.svg create mode 100644 packages/ui/src/assets/icons/success-outlined.svg create mode 100644 packages/ui/src/assets/icons/tip-outlined.svg create mode 100644 packages/ui/src/assets/icons/warning-outlined.svg diff --git a/packages/manager/.changeset/pr-11962-changed-1743652792312.md b/packages/manager/.changeset/pr-11962-changed-1743652792312.md new file mode 100644 index 00000000000..2116ae7e2b7 --- /dev/null +++ b/packages/manager/.changeset/pr-11962-changed-1743652792312.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +Update toast styling to Akamai Design System specs ([#11962](https://github.com/linode/manager/pull/11962)) diff --git a/packages/manager/src/components/Snackbar/CloseSnackbar.tsx b/packages/manager/src/components/Snackbar/CloseSnackbar.tsx index 7a2ae79136f..b5be1ed6653 100644 --- a/packages/manager/src/components/Snackbar/CloseSnackbar.tsx +++ b/packages/manager/src/components/Snackbar/CloseSnackbar.tsx @@ -1,5 +1,4 @@ -import { IconButton } from '@linode/ui'; -import Close from '@mui/icons-material/Close'; +import { CloseIcon, IconButton } from '@linode/ui'; import * as React from 'react'; interface Props { @@ -20,7 +19,7 @@ export const CloseSnackbar = (props: Props) => { size="large" title={text} > - + ); }; diff --git a/packages/manager/src/components/Snackbar/Snackbar.tsx b/packages/manager/src/components/Snackbar/Snackbar.tsx index d111476a7f9..c34633f19d5 100644 --- a/packages/manager/src/components/Snackbar/Snackbar.tsx +++ b/packages/manager/src/components/Snackbar/Snackbar.tsx @@ -5,14 +5,36 @@ import * as React from 'react'; import { CloseSnackbar } from './CloseSnackbar'; +import { + InfoOutlinedIcon, + TipOutlinedIcon, + WarningOutlinedIcon, + ErrorOutlinedIcon, + SuccessOutlinedIcon, +} from '@linode/ui'; + import type { Theme } from '@mui/material/styles'; import type { SnackbarProviderProps } from 'notistack'; +// Add override for "tip" variant which is Akamai specific and not built into Notistack +declare module 'notistack' { + interface VariantOverrides { + tip: true; + } +} + const StyledMaterialDesignContent = styled(MaterialDesignContent)( ({ theme }: { theme: Theme }) => ({ + '#notistack-snackbar > svg': { + position: 'absolute', + left: '14px', + }, '&.notistack-MuiContent': { color: theme.notificationToast.default.color, flexWrap: 'unset', + borderRadius: 0, + paddingLeft: theme.spacingFunction(12), + paddingRight: theme.spacingFunction(12), [theme.breakpoints.up('md')]: { maxWidth: '400px', }, @@ -25,7 +47,7 @@ const StyledMaterialDesignContent = styled(MaterialDesignContent)( backgroundColor: theme.notificationToast.error.backgroundColor, borderLeft: theme.notificationToast.error.borderLeft, }, - '&.notistack-MuiContent-info': { + '&.notistack-MuiContent-info, &.notistack-MuiContent-tip': { backgroundColor: theme.notificationToast.info.backgroundColor, borderLeft: theme.notificationToast.info.borderLeft, }, @@ -37,6 +59,9 @@ const StyledMaterialDesignContent = styled(MaterialDesignContent)( backgroundColor: theme.notificationToast.warning.backgroundColor, borderLeft: theme.notificationToast.warning.borderLeft, }, + '& #notistack-snackbar + div': { + paddingLeft: theme.spacingFunction(12), + }, }) ); @@ -50,13 +75,22 @@ export const Snackbar = (props: SnackbarProviderProps) => { return ( , + info: , + tip: , + warning: , + error: , + success: , + }} {...rest} Components={{ default: StyledMaterialDesignContent, - error: StyledMaterialDesignContent, info: StyledMaterialDesignContent, - success: StyledMaterialDesignContent, + tip: StyledMaterialDesignContent, warning: StyledMaterialDesignContent, + error: StyledMaterialDesignContent, + success: StyledMaterialDesignContent, }} action={(snackbarId) => ( ; export const Default: Story = { args: { anchorOrigin: { horizontal: 'right', vertical: 'bottom' }, - hideIconVariant: true, + hideIconVariant: false, maxSnack: 5, }, render: (args) =>