Skip to content

Commit

Permalink
Merge pull request #10327 from linode/release-v1.116.0
Browse files Browse the repository at this point in the history
Release v1.116.0 - release → staging
  • Loading branch information
hana-akamai authored Apr 1, 2024
2 parents 496ef42 + 510bb1a commit 1eb4e18
Show file tree
Hide file tree
Showing 509 changed files with 8,663 additions and 5,378 deletions.
4 changes: 2 additions & 2 deletions docs/development-guide/09-mocking-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ To enable the MSW, open the Local Dev Tools and check the "Mock Service Worker"
import { rest } from "msw";

const handlers = [
rest.get("*/profile", (req, res, ctx) => {
http.get("*/profile", () => {
//
const profile = profileFactory.build({ restricted: true });
return res(ctx.json(profile));
return HttpResponse.json((profile));
}),
// ... other handlers
];
Expand Down
29 changes: 28 additions & 1 deletion docs/development-guide/13-coding-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The styles for Cloud Manager are located in three places:
- The breakpoints can be modified at `/foundations/breakpoints/index.ts`.
- Component-specific styles may be defined either at the end of the component file or in a dedicated file, named `ComponentName.styles.tsx`. Refer to the guidelines outlined in the "Styles" section of [Component Structure](02-component-structure.md#styles).

## Typesript Unions, Const Enums and Objects
## Typescript Unions, Const Enums, Objects and Intersections
In our development process, we often encounter scenarios where we need to handle various messages or descriptions in our application. These messages can range from short, pithy statements to longer, more descriptive texts. To ensure a consistent and maintainable approach, we can use union types for lists of pithy data and const enums or plain old JavaScript objects (POJOs) for longer descriptions.

### Union Types for Pithy Data
Expand Down Expand Up @@ -132,6 +132,33 @@ myFunction(CreateTypes.Backup); // Works
myFunction('fromBackup'); // Works
```

### Preferring Interfaces Over Intersections

Much of the time, a simple type alias to an object type acts very similarly to an interface.

```Typescript
interface Foo { prop: string }

type Bar = { prop: string };
```

However, and as soon as you need to compose two or more types, you have the option of extending those types with an interface, or intersecting them in a type alias, and that's when the differences start to matter.

Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never. Interfaces also display consistently better, whereas type aliases to intersections can't be displayed in part of other intersections. Type relationships between interfaces are also cached, as opposed to intersection types as a whole. A final noteworthy difference is that when checking against a target intersection type, every constituent is checked before checking against the "effective"/"flattened" type.

For this reason, extending types with interfaces/extends is suggested over creating intersection types.

```Typescript
- type Foo = Bar & Baz & {
- someProp: string;
- }
+ interface Foo extends Bar, Baz {
+ someProp: string;
+ }
```
Source: [TypeScript Wiki](https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections)
## Adobe Analytics
### Writing a Custom Event
Expand Down
53 changes: 43 additions & 10 deletions docs/tooling/react-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[TanStack Query](https://tanstack.com/query/latest) (formerly React Query) is Cloud Manager's primary tool for fetching and caching API data. For a quick introduction, read our [Fetching Data](../development-guide/05-fetching-data.md#react-query) development guide.

You can find all of Cloud Manager's queries and mutations in `packages/manager/src/queries`.

## Query Keys

React Query's cache is a simple key-value store. Query Keys are serializable strings that uniquely identify a query's data in the cache. You can read more about the concept [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) in the TanStack Query docs.
Expand All @@ -12,6 +14,12 @@ Because of Cloud Manager's complexity, we use [`@lukemorales/query-key-factory`]

#### Simple Query

> [!note]
> Queries that have no parameters _must_ specify `null` for the query key.
> Using `null` tells the query key factory to not append any params to the query key.
> (See the profile example below)

```ts
import { useQuery } from "@tanstack/react-query";
import { getProfile } from "@linode/api-v4";
Expand All @@ -20,7 +28,7 @@ import type { APIError, Profile } from "@linode/api-v4";
const profileQueries = createQueryKeys('profile', {
profile: {
queryFn: getProfile,
queryKey: null,
queryKey: null, // queryKey will be ["profile", "profile"]
},
});

Expand All @@ -41,22 +49,31 @@ import type { APIError, Linode } from "@linode/api-v4";
const linodeQueries = createQueryKeys('linodes', {
linode: (id: number) => ({
queryFn: () => getLinode(id),
queryKey: [id],
queryKey: [id], // queryKey will be ["linodes", "linode", id]
}),
});

export const useLinodeQuery = (id: number) =>
useQuery<Linode, APIError[]>(linodeQueries.linode(1));
```

### Additional Reading on Query Keys

- https://tkdodo.eu/blog/effective-react-query-keys#effective-react-query-keys
- https://tanstack.com/query/latest/docs/framework/react/guides/query-keys

## Maintaining the Cache

> A significant challenge of React Query is keeping the client state in sync with the server.
A significant challenge of React Query is keeping the client state in sync with the server.

The two easiest ways of updating the cache using React Query are
- Using `invalidateQueries` to mark data as stale (which will trigger a refetch the next time the query is mounted)
- Using `setQueryData` to manually update the cache

> [!note]
> Place `invalidateQueries` and `setQueryData` calls in the `onSuccess` of `useMutation` hooks
> whenever possible.
### `invalidateQueries`

This will mark data as stale in the React Query cache, which will cause Cloud Manager to refetch the data if the corresponding query is mounted.
Expand Down Expand Up @@ -85,11 +102,11 @@ import type { APIError, Linode, ResourcePage } from "@linode/api-v4";
const linodeQueries = createQueryKeys('linodes', {
linode: (id: number) => ({
queryFn: () => getLinode(id),
queryKey: [id],
queryKey: [id], // queryKey will be ["linodes", "linode", id]
}),
linodes: (params: Params = {}, filter: Filter = {}) => ({
paginated: (params: Params = {}, filter: Filter = {}) => ({
queryFn: () => getLinodes(params, filter),
queryKey: [params, filter],
queryKey: [params, filter], // queryKey will be ["linodes", "paginated", params, filter]
}),
});

Expand All @@ -102,7 +119,7 @@ export const useLinodeUpdateMutation = (id: number) => {
mutationFn: (data) => updateLinode(id, data),
onSuccess(linode) {
// Invalidate all paginated pages in the cache.
queryClient.invalidateQueries(linodeQueries.linodes._def);
queryClient.invalidateQueries(linodeQueries.paginated._def);
// Because we have the updated Linode, we can manually set the cache for the `useLinode` query.
queryClient.setQueryData(linodeQueries.linode(id).queryKey, linode);
},
Expand All @@ -114,8 +131,10 @@ export const useDeleteLinodeMutation = (id: number) => {
return useMutation<{}, APIError[]>({
mutationFn: () => deleteLinode(id),
onSuccess() {
// Invalidate all paginated pages in the cache.
queryClient.invalidateQueries(linodeQueries.paginated._def);
// Remove the deleted linode from the cache
queryClient.removeQueries(linodeQueries.linode(id).queryKey);
queryClient.invalidateQueries(linodeQueries.linodes._def);
},
});
};
Expand All @@ -126,10 +145,24 @@ export const useCreateLinodeMutation = () => {
mutationFn: createLinode,
onSuccess(linode) {
// Invalidate all paginated pages in the cache. We don't know what page the new Linode will be on.
queryClient.invalidateQueries(linodeQueries.linodes._def);
queryClient.invalidateQueries(linodeQueries.paginated._def);
// Because we have the new Linode, we can manually set the cache for the `useLinode` query.
queryClient.setQueryData(linodeQueries.linode(id).queryKey, linode);
},
});
}
```
```

## Frequently Asked Questions

### Are we storing dupdate data in the cache? Why?

Yes, there is potential for the same data to exist many times in the cache.

For example, we have a query `useVolumesQuery` with the query key `["volumes", "paginated", { page: 1 }]` that contains the first 100 volumes on your account.
One of those same volumes could also be stored in the cache by using `useVolumeQuery` with query key `["linodes", "linode", 5]`.
This creates a senerio where the same volume is cached by React Query under multiple query keys.

This is a legitimate disadvantage of React Query's caching strategy. **We must be aware of this when we perform cache updates (using invalidations or manually updating the cache) so that the entity is update everywhere in the cache.**

Some data fetching tools like Apollo Client are able to intelligently detect duplicate entities and merge them. React Query does not do this. See [this tweet](https://twitter.com/tannerlinsley/status/1557395389531074560) from the creator of React Query.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"resolutions": {
"@babel/traverse": "^7.23.3",
"minimist": "^1.2.3",
"yargs-parser": "^18.1.3",
"yargs-parser": "^21.1.1",
"kind-of": "^6.0.3",
"node-fetch": "^2.6.7",
"ua-parser-js": "^0.7.33",
Expand Down
16 changes: 16 additions & 0 deletions packages/api-v4/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## [2024-04-01] - v0.113.0

### Added:

- Event type for database resize create (#10262)
- jsdoc style comments to `CreateLinodeRequest` based on API documentation ([#10319](https://github.com/linode/manager/pull/10319))

### Changed:

- Allow `image` to be `null` in `CreateLinodeRequest` ([#10281](https://github.com/linode/manager/pull/10281))
- Allow `firewall_id` to be `null` in `CreateLinodeRequest` ([#10319](https://github.com/linode/manager/pull/10319))

### Tech Stories:

- Update `axios` to resolve `follow-redirects` CVE-2024-28849 ([#10291](https://github.com/linode/manager/pull/10291))

## [2024-03-18] - v0.112.0


Expand Down
4 changes: 2 additions & 2 deletions packages/api-v4/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@linode/api-v4",
"version": "0.112.0",
"version": "0.113.0",
"homepage": "https://github.com/linode/manager/tree/develop/packages/api-v4",
"bugs": {
"url": "https://github.com/linode/manager/issues"
Expand Down Expand Up @@ -41,7 +41,7 @@
"unpkg": "./lib/index.global.js",
"dependencies": {
"@linode/validation": "*",
"axios": "~1.6.5",
"axios": "~1.6.8",
"ipaddr.js": "^2.0.0",
"yup": "^0.32.9"
},
Expand Down
8 changes: 6 additions & 2 deletions packages/api-v4/src/account/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ export const getOAuthClient = (clientId: number) =>
*
*/

interface OAuthClientWithSecret extends OAuthClient {
secret: string;
}

export const createOAuthClient = (data: OAuthClientRequest) =>
Request<OAuthClient & { secret: string }>(
Request<OAuthClientWithSecret>(
setURL(`${API_ROOT}/account/oauth-clients`),
setMethod('POST'),
setData(data, createOAuthClientSchema)
Expand All @@ -69,7 +73,7 @@ export const createOAuthClient = (data: OAuthClientRequest) =>
*
*/
export const resetOAuthClientSecret = (clientId: number | string) =>
Request<OAuthClient & { secret: string }>(
Request<OAuthClientWithSecret>(
setURL(
`${API_ROOT}/account/oauth-clients/${encodeURIComponent(
clientId
Expand Down
1 change: 1 addition & 0 deletions packages/api-v4/src/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export type EventAction =
| 'credit_card_updated'
| 'database_low_disk_space'
| 'database_resize'
| 'database_resize_create'
| 'database_backup_restore'
| 'database_create'
| 'database_credentials_reset'
Expand Down
3 changes: 2 additions & 1 deletion packages/api-v4/src/databases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export type DatabaseStatus =
| 'resuming'
| 'restoring'
| 'failed'
| 'degraded';
| 'degraded'
| 'resizing';

export type DatabaseBackupType = 'snapshot' | 'auto';

Expand Down
84 changes: 82 additions & 2 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,24 +350,104 @@ export interface CreateLinodePlacementGroupPayload {
}

export interface CreateLinodeRequest {
/**
* The Linode Type of the Linode you are creating.
*/
type: string;
/**
* The Region where the Linode will be located.
*/
region: string;
/**
* A StackScript ID that will cause the referenced StackScript to be run during deployment of this Linode.
*
* This field cannot be used when deploying from a Backup or a Private Image.
*/
stackscript_id?: number;
/**
* A Backup ID from another Linode’s available backups.
*
* Your User must have read_write access to that Linode,
* the Backup must have a status of successful,
* and the Linode must be deployed to the same region as the Backup.
*
* This field and the image field are mutually exclusive.
*/
backup_id?: number;
/**
* When deploying from an Image, this field is optional, otherwise it is ignored.
* This is used to set the swap disk size for the newly-created Linode.
* @default 512
*/
swap_size?: number;
image?: string;
/**
* An Image ID to deploy the Linode Disk from.
*/
image?: string | null;
/**
* This sets the root user’s password on a newly-created Linode Disk when deploying from an Image.
*/
root_pass?: string;
/**
* A list of public SSH keys that will be automatically appended to the root user’s
* `~/.ssh/authorized_keys`file when deploying from an Image.
*/
authorized_keys?: string[];
/**
* If this field is set to true, the created Linode will automatically be enrolled in the Linode Backup service.
* This will incur an additional charge. The cost for the Backup service is dependent on the Type of Linode deployed.
*
* This option is always treated as true if the account-wide backups_enabled setting is true.
*
* @default false
*/
backups_enabled?: boolean;
/**
* This field is required only if the StackScript being deployed requires input data from the User for successful completion
*/
stackscript_data?: any;
/**
* If it is deployed from an Image or a Backup and you wish it to remain offline after deployment, set this to false.
* @default true if the Linode is created with an Image or from a Backup.
*/
booted?: boolean;
/**
* The Linode’s label is for display purposes only.
* If no label is provided for a Linode, a default will be assigned.
*/
label?: string;
/**
* An array of tags applied to this object.
*
* Tags are for organizational purposes only.
*/
tags?: string[];
/**
* If true, the created Linode will have private networking enabled and assigned a private IPv4 address.
* @default false
*/
private_ip?: boolean;
/**
* A list of usernames. If the usernames have associated SSH keys,
* the keys will be appended to the root users `~/.ssh/authorized_keys`
* file automatically when deploying from an Image.
*/
authorized_users?: string[];
/**
* An array of Network Interfaces to add to this Linode’s Configuration Profile.
*/
interfaces?: InterfacePayload[];
/**
* An object containing user-defined data relevant to the creation of Linodes.
*/
metadata?: UserData;
firewall_id?: number;
/**
* The `id` of the Firewall to attach this Linode to upon creation.
*/
firewall_id?: number | null;
/**
* An object that assigns this the Linode to a placment group upon creation.
*/
placement_group?: CreateLinodePlacementGroupPayload;
}

Expand Down
Loading

0 comments on commit 1eb4e18

Please sign in to comment.