Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [M3-6724]: Linode Config & Interface endpoints, validation, and React Query queries #9418

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/api-v4/.changeset/pr-9418-added-1690303394895.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Added
---

New methods for Linode Configs and new/updated Linode Config and interface types ([#9418](https://github.com/linode/manager/pull/9418))
155 changes: 153 additions & 2 deletions packages/api-v4/src/linodes/configs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
CreateLinodeConfigSchema,
UpdateConfigInterfaceOrderSchema,
UpdateConfigInterfaceSchema,
UpdateLinodeConfigSchema,
linodeInterfaceSchema,
} from '@linode/validation/lib/linodes.schema';
import { API_ROOT } from '../constants';
import Request, {
Expand All @@ -10,8 +13,15 @@ import Request, {
setURL,
setXFilter,
} from '../request';
import { Filter, Params, ResourcePage as Page } from '../types';
import { Config, LinodeConfigCreationData } from './types';
import { Filter, ResourcePage as Page, Params } from '../types';
import {
Config,
ConfigInterfaceOrderPayload,
Interface,
InterfacePayload,
LinodeConfigCreationData,
UpdateConfigInterfacePayload,
} from './types';

/**
* getLinodeConfigs
Expand Down Expand Up @@ -106,3 +116,144 @@ export const updateLinodeConfig = (
setMethod('PUT'),
setData(data, UpdateLinodeConfigSchema)
);

/**
* getConfigInterfaces
*
* Return non-paginated list in devnum order of all interfaces on the given config.
*
* @param linodeId { number } The id of a Linode.
* @param configId { number } The id of a config belonging to the specified Linode.
*/
export const getConfigInterfaces = (linodeId: number, configId: number) =>
Request<Interface[]>(
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(configId)}/interfaces`
),
setMethod('GET')
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ever going to get sorted and/or paginated? If so you need params and x-filter. I think x-filter should be the minimum if we're going to present this data in a table which I assume we wil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a non-paginated finite list returned in devnum order. I asked the API team about params and filters on Wednesday morning; there are no plans to add any at the moment, and based on our UI wireframes, we don't have a real need for them as there won't be a new table for this specific data.


/**
* getConfigInterface
*
* Get a single Linode config interface object using the interface's unique ID.
*
* @param linodeId { number } The id of a Linode.
* @param configId { number } The id of a config belonging to the specified Linode.
* @param interfaceId { number } The id of an interface belonging to the specified config.
*/
export const getConfigInterface = (
linodeId: number,
configId: number,
interfaceId: number
) =>
Request<Interface>(
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(
configId
)}/interfaces/${encodeURIComponent(interfaceId)}`
),
setMethod('GET')
);

/**
* appendConfigInterface
*
* Append a single new Linode config interface object to an existing config.
*
* @param linodeId { number } The id of a Linode to receive the new config interface.
* @param configId { number } The id of a config to receive the new interface.
*/
export const appendConfigInterface = (
linodeId: number,
configId: number,
data: InterfacePayload
) =>
Request<Interface>(
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(configId)}/interfaces`
),
setMethod('POST'),
setData(data, linodeInterfaceSchema)
);

/**
* updateConfigInterface
*
* Change an existing interface.
*
* @param linodeId { number } The id of a Linode.
* @param configId { number } The id of a config belonging to that Linode.
* @param interfaceId { number } The id of an interface belonging to the specified config.
*/
export const updateConfigInterface = (
linodeId: number,
configId: number,
interfaceId: number,
data: UpdateConfigInterfacePayload
) =>
Request<Interface>(
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(
configId
)}/interfaces/${encodeURIComponent(interfaceId)}`
),
setMethod('PUT'),
setData(data, UpdateConfigInterfaceSchema)
);

/**
* updateLinodeConfigOrder
*
* Change the order of interfaces.
*
* @param linodeId { number } The id of a Linode.
* @param configId { number } The id of a config belonging to the specified Linode.
*/
export const updateLinodeConfigOrder = (
linodeId: number,
configId: number,
data: ConfigInterfaceOrderPayload
) =>
Request<{}>(
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(configId)}/interfaces/order`
),
setMethod('POST'),
setData(data, UpdateConfigInterfaceOrderSchema)
);

/**
* deleteLinodeConfigInterface
*
* Delete a Linode config interface.
*
* @param linodeId { number } The id of a Linode the specified config is attached to.
* @param configId { number } The id of a config belonging to the specified Linode.
* @param interfaceId { number } The id of the interface to be deleted.
*/
export const deleteLinodeConfigInterface = (
linodeId: number,
configId: number,
interfaceId: number
) =>
Request<{}>(
setMethod('DELETE'),
setURL(
`${API_ROOT}/linode/instances/${encodeURIComponent(
linodeId
)}/configs/${encodeURIComponent(
configId
)}/interfaces/${encodeURIComponent(interfaceId)}`
)
);
25 changes: 24 additions & 1 deletion packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,40 @@ export type LinodeStatus =
| 'restoring'
| 'stopped';

export type InterfacePurpose = 'public' | 'vlan';
export type InterfacePurpose = 'public' | 'vlan' | 'vpc';

export interface ConfigInterfaceIPv4 {
vpc?: string;
nat_1_1?: string;
}

export interface ConfigInterfaceIPv6 {
vpc?: string;
}

export interface Interface {
id: number;
label: string | null;
purpose: InterfacePurpose;
ipam_address: string | null;
primary?: boolean;
subnet?: number | null;
ipv4?: ConfigInterfaceIPv4;
ipv6?: ConfigInterfaceIPv6;
ip_ranges?: string[];
Comment on lines +166 to +170
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we create a new LinodeConfigInterface that extends Interface instead of having all these optional props?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on how Interface is used across the codebase, when going through the files this seems clearer to me

}

export type InterfacePayload = Omit<Interface, 'id'>;

export interface ConfigInterfaceOrderPayload {
ids: number[];
}

export type UpdateConfigInterfacePayload = Pick<
Interface,
'primary' | 'ipv4' | 'ipv6' | 'ip_ranges'
>;

export interface Config {
id: number;
kernel: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

React Query queries for Linode Configs ([#9418](https://github.com/linode/manager/pull/9418))
9 changes: 9 additions & 0 deletions packages/manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { tokenEventHandler } from './queries/tokens';
import { volumeEventsHandler } from './queries/volumes';
import { ApplicationState } from './store';
import { getNextThemeValue } from './utilities/theme';
// import { useConfigInterfacesQuery } from 'src/queries/linodes/configs';

// Ensure component's display name is 'App'
export const App = () => <BaseApp />;
Expand All @@ -63,6 +64,14 @@ const BaseApp = withDocumentTitleProvider(

const { enqueueSnackbar } = useSnackbar();

// const testLinodeID = your linode ID here
// const testConfigID = your linode config ID here
// const { data: configInterfaces } = useConfigInterfacesQuery(
// testLinodeID,
// testConfigID
// );
// console.log(configInterfaces);

const [goToOpen, setGoToOpen] = React.useState(false);

const theme = preferences?.theme;
Expand Down
103 changes: 99 additions & 4 deletions packages/manager/src/queries/linodes/configs.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import {
APIError,
Config,
ConfigInterfaceOrderPayload,
Interface,
InterfacePayload,
LinodeConfigCreationData,
UpdateConfigInterfacePayload,
appendConfigInterface,
createLinodeConfig,
deleteLinodeConfig,
deleteLinodeConfigInterface,
getConfigInterface,
getConfigInterfaces,
updateConfigInterface,
updateLinodeConfig,
updateLinodeConfigOrder,
} from '@linode/api-v4';
import { useMutation, useQueryClient } from 'react-query';
import { useMutation, useQuery, useQueryClient } from 'react-query';

import { queryKey } from './linodes';

const configQueryKey = 'configs';
const interfaceQueryKey = 'interfaces';

// Config queries
export const useLinodeConfigDeleteMutation = (
linodeId: number,
configId: number
Expand All @@ -23,7 +37,7 @@ export const useLinodeConfigDeleteMutation = (
queryKey,
'linode',
linodeId,
'configs',
configQueryKey,
]);
},
}
Expand All @@ -40,7 +54,7 @@ export const useLinodeConfigCreateMutation = (linodeId: number) => {
queryKey,
'linode',
linodeId,
'configs',
configQueryKey,
]);
},
}
Expand All @@ -60,9 +74,90 @@ export const useLinodeConfigUpdateMutation = (
queryKey,
'linode',
linodeId,
'configs',
configQueryKey,
]);
},
}
);
};

// Config Interface queries
export const useConfigInterfacesQuery = (linodeID: number, configID: number) => {
return useQuery<Interface[], APIError[]>(
[queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey],
() => getConfigInterfaces(linodeID, configID),
{ keepPreviousData: true }
);
};

export const useConfigInterfaceQuery = (linodeID: number, configID: number, interfaceID: number) => {
return useQuery<Interface, APIError[]>(
[queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey, 'interface', interfaceID],
() => getConfigInterface(linodeID, configID, interfaceID),
{ keepPreviousData: true }
);
};

export const useConfigInterfacesOrderMutation = (
linodeID: number,
configID: number,
) => {
const queryClient = useQueryClient();
return useMutation<{}, APIError[], ConfigInterfaceOrderPayload>(
(data) => updateLinodeConfigOrder(linodeID, configID, data),
{
onSuccess() {
queryClient.invalidateQueries([queryKey, 'linode', linodeID, configQueryKey, 'config', interfaceQueryKey]);
}
}
)
};

export const useAppendConfigInterfaceMutation = (
linodeID: number,
configID: number,
) => {
const queryClient = useQueryClient();
return useMutation<Interface, APIError[], InterfacePayload>(
(data) => appendConfigInterface(linodeID, configID, data),
{
onSuccess() {
queryClient.invalidateQueries([queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey]);
}
}
)
}

export const useUpdateConfigInterfaceMutation = (
linodeID: number,
configID: number,
interfaceID: number,
) => {
const queryClient = useQueryClient();
return useMutation<Interface, APIError[], UpdateConfigInterfacePayload>(
(data) => updateConfigInterface(linodeID, configID, interfaceID, data),
{
onSuccess: (InterfaceObj) => {
queryClient.invalidateQueries([queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey]);
queryClient.setQueryData<Interface>([queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey, 'interface', InterfaceObj.id], InterfaceObj);
}
}
)
}

export const useDeleteConfigInterfaceMutation = (
linodeID: number,
configID: number,
interfaceID: number,
) => {
const queryClient = useQueryClient();
return useMutation<{}, APIError[]>(
() => deleteLinodeConfigInterface(linodeID, configID, interfaceID),
{
onSuccess() {
queryClient.invalidateQueries([queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey]);
queryClient.removeQueries([queryKey, 'linode', linodeID, configQueryKey, 'config', configID, interfaceQueryKey, 'interface', interfaceID]);
}
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/validation": Added
---

Linode Config and interface validation ([#9418](https://github.com/linode/manager/pull/9418))
Loading