Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
879e308
feat: [M3-7577] - Add invoice byline for powered down instances (#10208)
jaalah-akamai Feb 29, 2024
1045e22
refactor: [M3-7440] - Upgrade to TanStack Query v4 (#10236)
bnussman-akamai Feb 29, 2024
c4080ba
test: [M3-7452] - Add Cypress test to check empty state in Images lan…
cliu-akamai Feb 29, 2024
e192145
upcoming: [M3-7816] - Hide "Switch Account" buttons if child_account_…
jaalah-akamai Mar 1, 2024
b8c1e45
upcoming: [M3-7727] - Only support Edge to Edge Migrations (#10238)
hana-akamai Mar 4, 2024
cb8232a
fix: [M3-7832] - Update root eslint parser to @typescript-eslint/pars…
abailly-akamai Mar 4, 2024
f138153
refactor: [M3-7650] - Refactor Cypress region utils, address region c…
jdamore-linode Mar 4, 2024
661f074
test: [M3-7497] - Add tests for child user verification banner (#10204)
cliu-akamai Mar 4, 2024
6d22d36
fix: React Query `updateInPaginatedStore` helper function not working…
bnussman-akamai Mar 4, 2024
e1c13bb
refactor: [M3-7437] - Use `@lukemorales/query-key-factory` for Profil…
bnussman-akamai Mar 4, 2024
fbf6e39
Merge branch 'master' into develop
carrillo-erik Mar 5, 2024
e8b3716
upcoming: [M3 7738] - Update Placement Group Create & Edit Drawers (#…
abailly-akamai Mar 5, 2024
2243adf
upcoming: [M3-7835] - Adjust user table column count (#10252)
jaalah-akamai Mar 5, 2024
f8ce5e6
upcoming: [M3-7839] - Change Business Partner to Parent User (#10259)
jaalah-akamai Mar 6, 2024
d4bb18e
change: [M3-7813] - Allow the disabling of the TypeToConfirm input (#…
abailly-akamai Mar 6, 2024
c0a181a
chore(deps): Bump sanitize-html from 2.11.0 to 2.12.1 (#10247)
dependabot[bot] Mar 6, 2024
89499b3
upcoming: [M3-7723] - Placement Group feature flag as object (#10256)
abailly-akamai Mar 6, 2024
caf10cb
test: Resolve OBJ create and delete E2E test flake (#10245)
jdamore-linode Mar 7, 2024
491c106
refactor: [M3-7687] - Linodes Restricted User Experience 2/2 (#10227)
jaalah-akamai Mar 7, 2024
6127288
fix: [M3-7824] - ACLB TCP Rule Creation and other fixes (#10264)
bnussman-akamai Mar 7, 2024
499da54
test: Fix test flake for Images landing page test (#10267)
jdamore-linode Mar 7, 2024
95ba193
upcoming: [M3-7704] - Disable Cloning, Private IP, Backups for edge r…
hana-akamai Mar 7, 2024
e7659e4
upcoming: [M3-7842] - Update Assign Linode Drawer and improve query s…
abailly-akamai Mar 8, 2024
45a52cf
fix: [M3-7831] - Persisting error messages in ACLB delete dialogs (#1…
bnussman-akamai Mar 8, 2024
0e4c7c2
upcoming: [M3-7816-v2] - Adjust logic for when to show Switch Account…
jaalah-akamai Mar 8, 2024
433f3fd
upcoming: [M3-7616] - Add Placement Groups Events and Notifications (…
carrillo-erik Mar 8, 2024
0a6c1ad
fix: [M3-7817] - Show correct status of Child Account Enabled column …
mjac0bs Mar 8, 2024
20c0869
fix: [M3-7269] - Display parent email in user menu when no company na…
mjac0bs Mar 11, 2024
48bf7e4
upcoming: [M3-7697] - Add scrolling for S3 hostnames in the Access Ke…
cpathipa Mar 12, 2024
0830c44
upcoming: [M3-7724] - Linode plan table updates for Edge regions (#10…
hana-akamai Mar 12, 2024
9917c8e
refactor: [M3-6708] – Implement new useId() hook from React 18 (#10261)
dwiley-akamai Mar 12, 2024
b2c985c
change: [M3-7856] - Update ACLB Match Condition Tooltips and Placehol…
bnussman-akamai Mar 12, 2024
74695aa
upcoming: [M3-7857] - Account Management Copy Updates & Improvements …
jaalah-akamai Mar 12, 2024
aa59941
feat: [M3-7806] - Linode Create Refactor - Part 1 (#10268)
bnussman-akamai Mar 12, 2024
83f7898
feat: Add March 2024 apps to Marketplace (#10272)
tbaka-dev Mar 13, 2024
306868d
upcoming: [M3-7798] - Update Buckets landing page to use regions inst…
cpathipa Mar 13, 2024
0914e6d
change: [M3-7860] - Source ACLB region info from API data and use Jak…
bnussman-akamai Mar 13, 2024
3cd0132
fix: [M3-7826-v2] - Fix email displaying in top menu for all users wi…
mjac0bs Mar 13, 2024
b59e794
fix: [M3-7867] -Ensure IP / Mask for firewall rules drawer correctly …
jaalah-akamai Mar 13, 2024
62229f0
upcoming: [M3-7866] - Improve Proxy Account Visibility with Distinct …
jaalah-akamai Mar 13, 2024
cfc12d4
upcoming: [M3-7855] - Updates to PATs for Restricted Parent Users (#1…
jaalah-akamai Mar 14, 2024
9f1aae8
fix: [M3-7880] - Properly reset rebuild modal state (#10287)
abailly-akamai Mar 14, 2024
09d8402
feat: [M3-7829] - Linode Clone UI refinements (#10280)
hana-akamai Mar 14, 2024
fded40a
feat: [M3-7789] – Disable 512GB plans (#10228)
dwiley-akamai Mar 18, 2024
ccc6833
Cloud version 1.115.0, API v4 version 0.113.0, and Validation version…
abailly-akamai Mar 18, 2024
c59ee09
Remove extra space from invoice detail notice
jaalah Mar 18, 2024
a2a74e8
Update APIv4 and Validation double bumps in changelogs
abailly-akamai Mar 18, 2024
496ef42
Merge pull request #10292 from linode/release-v1.115.0
abailly-akamai Mar 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
root: true,
ignorePatterns: ["**/node_modules/", "**/build/"],
parser: "@babel/eslint-parser",
parser: "@typescript-eslint/parser",
};
42 changes: 28 additions & 14 deletions docs/development-guide/05-fetching-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,33 +79,47 @@ This works, but has a few disadvantages:

A better way to fetch data is to use React Query. It address the issues listed above and has many additional features.

To fetch data with React Query, check to see if the API method you want to use has a query written for it in `packages/manager/src/queries`. If not, feel free to write one. It should look something like this:
To fetch data with React Query:

```ts
import { getProfile, Profile } from "@linode/api-v4/lib/profile";
import { APIError } from "@linode/api-v4/lib/types";
import { useQuery } from "react-query";
- Create an `@linode/api-v4` function that calls the intended Linode API endpoint.
- Create a query key factory that uses the newly created `@linode/api-v4` function.
- Create a hook that wraps `useQuery` and uses the query key factory.

const queryKey = "profile";
```ts
import { useQuery } from "@tanstack/react-query";
import { getProfile } from "@linode/api-v4";
import type { APIError, Profile } from "@linode/api-v4";

const profileQueries = createQueryKeys('profile', {
profile: {
queryFn: getProfile,
queryKey: null,
},
});

export const useProfile = () =>
useQuery<Profile, APIError[]>(queryKey, getProfile);
useQuery<Profile, APIError[]>(profileQueries.profile);
```

The first time `useProfile()` is called, the data is fetched from the API. On subsequent calls, the data is retrieved from the in-memory cache.

`useQuery` accepts a third "options" parameter, which can be used to specify cache time (among others things). For example, to specify that the cache should never expire for this query:
`useQuery` accepts options which can be used to specify cache time (among others things). For example, to specify that the cache should never expire for this query:

```ts
import { queryPresets } from "src/queries/base";
// ...other imports

const profileQueries = createQueryKeys('profile', {
profile: {
queryFn: getProfile,
queryKey: null,
},
})

export const useProfile = () =>
useQuery<Profile, APIError[]>(
queryKey,
getProfile,
queryPresets.oneTimeFetch
);
useQuery<Profile, APIError[]>({
...profileQueries.profile,
...queryPresets.oneTimeFetch,
});
```

Loading and error states are managed by React Query. The earlier username display example becomes greatly simplified:
Expand Down
16 changes: 16 additions & 0 deletions docs/development-guide/11-feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ Feature flags are served by [LaunchDarkly](https://launchdarkly.com/). On app lo

Feature flag values themselves can be booleans (most common), strings, numbers, or JSON (also common).

We often need to control more than one setting for a given feature. For instance, a feature might have a boolean flag to enable/disable it, and a secondary flag to control its "beta" status. In LaunchDarkly, if using a JSON object and still wanting to control the on/off status of a feature, **all variations must contain an `enabled` key**. ex:

```json
{
"enabled": true,
"beta": true
}
```

Feature flags variations should also be labelled as clearly as possible to avoid confusion for a potential third party managing the flag you created.
For instance, for the example above, the variations could be labelled:

- Variation 1: Feature ON, Beta ON
- Variation 2: Feature ON, Beta OFF
- Variation 3: Everything OFF

## Creating a feature flag

Feature flags are created in the LaunchDarkly dashboard. Give your flag a name (like "Images Pricing Banner") and key (like "imagesPricingBanner") and select the flag type (boolean, etc). Configure the desired variations and targeting options.
Expand Down
25 changes: 22 additions & 3 deletions docs/development-guide/13-coding-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@ If you are using VSCode it is highly recommended to use the ESlint extension. Th

## React

- When conditionally rendering JSX, use ternaries instead of `&&`.
- Example: `condition ? <Component /> : null` instead of `condition && <Component />`
- This is to avoid hard-to-catch bugs ([read more](https://kentcdodds.com/blog/use-ternaries-rather-than-and-and-in-jsx)).
[Several new hooks were introduced with the release of React 18](https://react.dev/blog/2022/03/29/react-v18#new-hooks).

It should be noted that the `useId()` hook is particularly useful for generating unique IDs for accessibility attributes. For this use case, `useId()` is preferred over hardcoding the ID because components may be rendered more than once on a page, but IDs must be unique.

As an example from `DisplayLinodes.tsx`, early in the file we invoke the hook: `const displayViewDescriptionId = React.useId()`

And make use of the unique ID by passing it as the value for a component's `aria-describedby` attribute in the `return` value:

```
<StyledToggleButton
aria-describedby={displayViewDescriptionId}
aria-label="Toggle display"
disableRipple
isActive={true}
onClick={toggleLinodeView}
size="large"
>
<GridView />
</StyledToggleButton>
```

Per the [docs](https://react.dev/reference/react/useId#usage), the hook should not be used for generating keys in a list.

## Event Handler Naming Convention

Expand Down
135 changes: 135 additions & 0 deletions docs/tooling/react-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# React Query

[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.

## 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.

Because of Cloud Manager's complexity, we use [`@lukemorales/query-key-factory`](https://github.com/lukemorales/query-key-factory) to manage our query keys. This package allows us to define query key _factories_ that enable typesafe standardized query keys that can be reused and referenced throughout the application.

### Examples

#### Simple Query

```ts
import { useQuery } from "@tanstack/react-query";
import { getProfile } from "@linode/api-v4";
import type { APIError, Profile } from "@linode/api-v4";

const profileQueries = createQueryKeys('profile', {
profile: {
queryFn: getProfile,
queryKey: null,
},
});

export const useProfile = () =>
useQuery<Profile, APIError[]>(profileQueries.profile);
```

#### Query with parameters

> [!important]
> Queries that have parameters should always include the parameters in the `queryKey`

```ts
import { useQuery } from "@tanstack/react-query";
import { getLinode } from "@linode/api-v4";
import type { APIError, Linode } from "@linode/api-v4";

const linodeQueries = createQueryKeys('linodes', {
linode: (id: number) => ({
queryFn: () => getLinode(id),
queryKey: [id],
}),
});

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

## Maintaining the Cache

> 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

### `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.

Use `invalidateQueries` when:
- You are dealing with any *paginated data* (because order may have changed)
- You want fresh data from the API

> [!note]
> When using `invalidateQueries`, use a query key factory to ensure you are invalidating the data at the correct query key.

### `setQueryData`

Use this if you have data readily available to put in the cache. This often happens when you make a PUT request.

### Example

This example shows how we keep the cache up to date when performing create / update / delete operations
on an entity.

```ts
import { useQuery, useMutation } from "@tanstack/react-query";
import { getLinode, getLinodes, updateLinode, deleteLinode, createLinode } from "@linode/api-v4";
import type { APIError, Linode, ResourcePage } from "@linode/api-v4";

const linodeQueries = createQueryKeys('linodes', {
linode: (id: number) => ({
queryFn: () => getLinode(id),
queryKey: [id],
}),
linodes: (params: Params = {}, filter: Filter = {}) => ({
queryFn: () => getLinodes(params, filter),
queryKey: [params, filter],
}),
});

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

export const useLinodeUpdateMutation = (id: number) => {
const queryClient = useQueryClient();
return useMutation<Linode, APIError[], Partial<Linode>>({
mutationFn: (data) => updateLinode(id, data),
onSuccess(linode) {
// Invalidate all paginated pages in the cache.
queryClient.invalidateQueries(linodeQueries.linodes._def);
// Because we have the updated Linode, we can manually set the cache for the `useLinode` query.
queryClient.setQueryData(linodeQueries.linode(id).queryKey, linode);
},
});
}

export const useDeleteLinodeMutation = (id: number) => {
const queryClient = useQueryClient();
return useMutation<{}, APIError[]>({
mutationFn: () => deleteLinode(id),
onSuccess() {
queryClient.removeQueries(linodeQueries.linode(id).queryKey);
queryClient.invalidateQueries(linodeQueries.linodes._def);
},
});
};

export const useCreateLinodeMutation = () => {
const queryClient = useQueryClient();
return useMutation<Linode, APIError[], CreateLinodeRequest>({
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);
// Because we have the new Linode, we can manually set the cache for the `useLinode` query.
queryClient.setQueryData(linodeQueries.linode(id).queryKey, linode);
},
});
}
```
13 changes: 13 additions & 0 deletions packages/api-v4/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## [2024-03-18] - v0.112.0


### Changed:

- Make `match_condition` optional in Rule types to support TCP rules ([#10264](https://github.com/linode/manager/pull/10264))
- Make `type` and `region` required in `CreateLinodeRequest` ([#10268](https://github.com/linode/manager/pull/10268))

### Upcoming Features:

- Add Placement Groups events types ([#10221](https://github.com/linode/manager/pull/10221))
- Add temporary deleteBucketWithRegion method for OBJ Multicluster ([#10244](https://github.com/linode/manager/pull/10244))

## [2024-03-04] - v0.111.0

### Changed:
Expand Down
2 changes: 1 addition & 1 deletion 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.111.0",
"version": "0.112.0",
"homepage": "https://github.com/linode/manager/tree/develop/packages/api-v4",
"bugs": {
"url": "https://github.com/linode/manager/issues"
Expand Down
6 changes: 6 additions & 0 deletions packages/api-v4/src/account/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,12 @@ export type EventAction =
| 'nodebalancer_delete'
| 'nodebalancer_update'
| 'password_reset'
| 'placement_group_assign'
| 'placement_group_created'
| 'placement_group_assigned'
| 'placement_group_unassigned'
| 'placement_group_updated'
| 'placement_group_deleted'
| 'profile_update'
| 'stackscript_create'
| 'stackscript_delete'
Expand Down
6 changes: 3 additions & 3 deletions packages/api-v4/src/aclb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface Route {
label: string;
protocol: RouteProtocol;
rules: {
match_condition: MatchCondition;
match_condition?: MatchCondition;
service_targets: {
id: number;
label: string;
Expand All @@ -83,7 +83,7 @@ export interface CreateRoutePayload {
}

export interface Rule {
match_condition: MatchCondition;
match_condition?: MatchCondition;
service_targets: {
id: number;
label: string;
Expand All @@ -92,7 +92,7 @@ export interface Rule {
}

export interface RulePayload {
match_condition: MatchCondition;
match_condition?: MatchCondition;
service_targets: {
id: number;
label: string;
Expand Down
4 changes: 2 additions & 2 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ export interface CreateLinodePlacementGroupPayload {
}

export interface CreateLinodeRequest {
type?: string;
region?: string;
type: string;
region: string;
stackscript_id?: number;
backup_id?: number;
swap_size?: number;
Expand Down
29 changes: 29 additions & 0 deletions packages/api-v4/src/object-storage/buckets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@ export const deleteBucket = ({
setMethod('DELETE')
);

/**
* deleteBucketWithRegion
*
* Removes a Bucket from your account with region.
*
* NOTE: Attempting to delete a non-empty bucket will result in an error.
*/
/*
@TODO OBJ Multicluster: deleteBucketWithRegion is a function,
once feature is rolled out we replace it with existing deleteBucket
by updating it with region instead of cluster.
*/

export const deleteBucketWithRegion = ({
region,
label,
}: {
region: string;
label: string;
}) =>
Request<ObjectStorageBucket>(
setURL(
`${API_ROOT}/object-storage/buckets/${encodeURIComponent(
region
)}/${encodeURIComponent(label)}`
),
setMethod('DELETE')
);

/**
* Returns a list of Objects in a given Bucket.
*/
Expand Down
Loading