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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11767-fixed-1740775625877.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Filter out Linodes in distributed regions from Create flow Backups & Clone tab ([#11767](https://github.com/linode/manager/pull/11767))
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,40 @@ import {
renderWithThemeAndHookFormContext,
} from 'src/utilities/testHelpers';

import { LinodeSelectTable } from './LinodeSelectTable';
import { LinodeSelectTable, getLinodeXFilter } from './LinodeSelectTable';

beforeAll(() => mockMatchMedia());

describe('Linode Select Table', () => {
it('should filter out Linodes in distributed regions', () => {
const { filter } = getLinodeXFilter(undefined, '');

expect(filter).toHaveProperty('site_type', 'core');
});

it('should search for label, id, ipv4, tags', () => {
const { filter } = getLinodeXFilter(undefined, '12345678');

expect(filter).toStrictEqual({
'+or': [
{ label: { '+contains': '12345678' } },
{ id: { '+contains': '12345678' } },
{ ipv4: { '+contains': '12345678' } },
{ tags: { '+contains': '12345678' } },
],
site_type: 'core',
});
});

it('should return an error if the x-filter is invalid', () => {
const { filterError } = getLinodeXFilter(undefined, '123 456');

expect(filterError).toHaveProperty(
'message',
`Expected "!=", "<", "<=", "=", ">", ">=", [:~], or whitespace but "4" found.`
);
});

it('should render Linodes from the API', async () => {
const linodes = linodeFactory.buildList(10);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getAPIFilterFromQuery } from '@linode/search';
import { Box, Notice, Stack, Typography } from '@linode/ui';
import Grid from '@mui/material/Grid2';
import useMediaQuery from '@mui/material/useMediaQuery';
Expand All @@ -22,7 +23,6 @@ import { usePagination } from 'src/hooks/usePagination';
import { useLinodesQuery } from 'src/queries/linodes/linodes';
import { sendLinodePowerOffEvent } from 'src/utilities/analytics/customEventAnalytics';
import { isPrivateIP } from 'src/utilities/ipUtils';
import { isNumeric } from 'src/utilities/stringUtils';

import {
getGeneratedLinodeLabel,
Expand All @@ -34,6 +34,7 @@ import { SelectLinodeCard } from './SelectLinodeCard';
import type { LinodeCreateFormValues } from '../utilities';
import type { Linode } from '@linode/api-v4';
import type { Theme } from '@mui/material';
import type { UseOrder } from 'src/hooks/useOrder';

interface Props {
/**
Expand Down Expand Up @@ -79,17 +80,11 @@ export const LinodeSelectTable = (props: Props) => {
const pagination = usePagination();
const order = useOrder();

const filter = preselectedLinodeId
? { id: preselectedLinodeId }
: {
'+or': [
{ label: { '+contains': query } },
...(isNumeric(query) ? [{ id: Number(query) }] : []), // let users filter by Linode id
],
'+order': order.order,
'+order_by': order.orderBy,
// backups: { enabled: true }, womp womp! We can't filter on values within objects
};
const { filter, filterError } = getLinodeXFilter(
preselectedLinodeId,
query,
order
);

const { data, error, isFetching, isLoading } = useLinodesQuery(
{
Expand Down Expand Up @@ -146,6 +141,7 @@ export const LinodeSelectTable = (props: Props) => {
}}
clearable
debounceTime={250}
errorText={filterError?.message}
hideLabel
isSearching={isFetching}
label="Search"
Expand Down Expand Up @@ -240,3 +236,37 @@ export const LinodeSelectTable = (props: Props) => {
</Stack>
);
};

export const getLinodeXFilter = (
preselectedLinodeId: number | undefined,
query: string,
order?: UseOrder
) => {
if (preselectedLinodeId) {
return {
id: preselectedLinodeId,
};
}

const { error: filterError, filter: apiFilter } = getAPIFilterFromQuery(
query,
{
searchableFieldsWithoutOperator: ['label', 'id', 'ipv4', 'tags'],
}
);

const filter = {
...apiFilter,
site_type: 'core', // backups and cloning are not supported for distributed regions
// backups: { enabled: true }, womp womp! We can't filter on values within objects
};

if (order) {
return {
filter: { ...filter, '+order': order.order, '+order_by': order.orderBy },
filterError,
};
}

return { filter, filterError };
};
6 changes: 5 additions & 1 deletion packages/manager/src/hooks/useOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import type { BaseQueryParams } from 'src/utilities/queryParams';

export type Order = 'asc' | 'desc';

export interface UseOrder extends OrderSet {
handleOrderChange: (newOrderBy: string, newOrder: Order) => void;
}

/**
* useOrder is a hook that allows you to handle ordering tables. It takes into account
* the following items when determining initial order
Expand All @@ -31,7 +35,7 @@ export const useOrder = (
initial?: OrderSet,
preferenceKey?: string,
prefix?: string
) => {
): UseOrder => {
const { data: sortPreferences } = usePreferences(
(preferences) => preferences?.sortKeys
);
Expand Down