Skip to content

Commit d6c18ee

Browse files
Merge pull request #11378 from linode/staging
Release v1.133.0 - `staging` → `master`
2 parents 2a92113 + 500475c commit d6c18ee

File tree

1,185 files changed

+13129
-7720
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,185 files changed

+13129
-7720
lines changed

docs/PULL_REQUEST_TEMPLATE.md

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
## Description 📝
2+
23
Highlight the Pull Request's context and intentions.
34

45
## Changes 🔄
5-
List any change relevant to the reviewer.
6+
7+
List any change(s) relevant to the reviewer.
8+
69
- ...
710
- ...
811

912
## Target release date 🗓️
10-
Please specify a release date to guarantee timely review of this PR. If exact date is not known, please approximate and update it as needed.
13+
14+
Please specify a release date (and environment, if applicable) to guarantee timely review of this PR. If exact date is not known, please approximate and update it as needed.
1115

1216
## Preview 📷
17+
1318
**Include a screenshot or screen recording of the change.**
1419

1520
:lock: Use the [Mask Sensitive Data](https://cloud.linode.com/profile/settings) setting for security.
@@ -23,38 +28,58 @@ Please specify a release date to guarantee timely review of this PR. If exact da
2328
## How to test 🧪
2429

2530
### Prerequisites
31+
2632
(How to setup test environment)
33+
2734
- ...
2835
- ...
2936

3037
### Reproduction steps
38+
3139
(How to reproduce the issue, if applicable)
32-
- ...
33-
- ...
40+
41+
- [ ] ...
42+
- [ ] ...
3443

3544
### Verification steps
45+
3646
(How to verify changes)
37-
- ...
38-
- ...
3947

40-
## As an Author I have considered 🤔
48+
- [ ] ...
49+
- [ ] ...
50+
51+
<details>
52+
<summary> Author Checklists </summary>
53+
54+
## As an Author, to speed up the review process, I considered 🤔
55+
56+
👀 Doing a self review
57+
❔ Our [contribution guidelines](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md)
58+
🤏 Splitting feature into small PRs
59+
➕ Adding a [changeset](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md#writing-a-changeset)
60+
🧪 Providing/improving test coverage
61+
🔐 Removing all sensitive information from the code and PR description
62+
🚩 Using a feature flag to protect the release
63+
👣 Providing comprehensive reproduction steps
64+
📑 Providing or updating our documentation
65+
🕛 Scheduling a pair reviewing session
66+
📱 Providing mobile support
67+
♿ Providing accessibility support
4168

42-
*Check all that apply*
69+
<br/>
4370

44-
- [ ] 👀 Doing a self review
45-
- [ ] ❔ Our [contribution guidelines](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md)
46-
- [ ] 🤏 Splitting feature into small PRs
47-
- [ ] ➕ Adding a [changeset](https://github.com/linode/manager/blob/develop/docs/CONTRIBUTING.md#writing-a-changeset)
48-
- [ ] 🧪 Providing/Improving test coverage
49-
- [ ] 🔐 Removing all sensitive information from the code and PR description
50-
- [ ] 🚩 Using a feature flag to protect the release
51-
- [ ] 👣 Providing comprehensive reproduction steps
52-
- [ ] 📑 Providing or updating our documentation
53-
- [ ] 🕛 Scheduling a pair reviewing session
54-
- [ ] 📱 Providing mobile support
55-
- [ ] ♿ Providing accessibility support
71+
- [ ] I have read and considered all applicable items listed above.
72+
73+
## As an Author, before moving this PR from Draft to Open, I confirmed ✅
74+
75+
- [ ] All unit tests are passing
76+
- [ ] TypeScript compilation succeeded without errors
77+
- [ ] Code passes all linting rules
78+
79+
</details>
5680

5781
---
82+
5883
## Commit message and pull request title format standards
5984

6085
> **Note**: Remove this section before opening the pull request
@@ -63,6 +88,7 @@ Please specify a release date to guarantee timely review of this PR. If exact da
6388
`<commit type>: [JIRA-ticket-number] - <description>`
6489

6590
**Commit Types:**
91+
6692
- `feat`: New feature for the user (not a part of the code, or ci, ...).
6793
- `fix`: Bugfix for the user (not a fix to build something, ...).
6894
- `change`: Modifying an existing visual UI instance. Such as a component or a feature.

docs/development-guide/04-component-library.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ We use [Material-UI](https://mui.com/material-ui/getting-started/overview/) as t
77
All MUI components have abstractions in the Cloud Manager codebase, meaning you will use relative imports to use them instead of importing from MUI directly:
88

99
```ts
10-
import { Typography } from "src/components/Typography"; // NOT from '@mui/material/Typography'
10+
import { Typography } from "@linode/ui"; // NOT from '@mui/material/Typography'
1111
```
1212

1313
We do this because it gives us the ability to customize the component and still keep imports consistent. It also gives us flexibility if we ever wanted to change out the underlying component library.

docs/development-guide/08-testing.md

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,38 @@ The unit tests for Cloud Manager are written in Typescript using the [Vitest](ht
66

77
To run tests, first build the **api-v4** package:
88

9-
```
9+
```shell
1010
yarn install:all && yarn workspace @linode/api-v4 build
1111
```
1212

1313
Then you can start the tests:
1414

15-
```
15+
```shell
1616
yarn test
1717
```
1818

1919
Or you can run the tests in watch mode with:
2020

21-
```
21+
```shell
2222
yarn test:watch
2323
```
2424

2525
To run a specific file or files in a directory:
2626

27-
```
27+
```shell
2828
yarn test myFile.test.tsx
2929
yarn test src/some-folder
3030
```
3131

3232
Vitest has built-in pattern matching, so you can also do things like run all tests whose filename contains "Linode" with:
3333

34-
```
34+
```shell
3535
yarn test linode
3636
```
3737

3838
To run a test in debug mode, add a `debugger` breakpoint inside one of the test cases, then run:
3939

40-
```
40+
```shell
4141
yarn workspace linode-manager run test:debug
4242
```
4343

@@ -64,31 +64,25 @@ describe("My component", () => {
6464
Handling events such as clicks is a little more involved:
6565

6666
```js
67-
import { fireEvent } from "@testing-library/react";
67+
import { userEvent } from '@testing-library/user-event';
6868
import { renderWithTheme } from "src/utilities/testHelpers";
6969
import Component from "./wherever";
7070

7171
const props = { onClick: vi.fn() };
7272

7373
describe("My component", () => {
74-
it("should have some text", () => {
74+
it("should have some text", async () => {
7575
const { getByText } = renderWithTheme(<Component {...props} />);
7676
const button = getByText("Submit");
77-
fireEvent.click(button);
77+
await userEvent.click(button);
7878
expect(props.onClick).toHaveBeenCalled();
7979
});
8080
});
8181
```
8282

83-
If, while using the Testing Library, your tests trigger a warning in the console from React ("Warning: An update to Component inside a test was not wrapped in act(...)"), first check out the library author's [blog post](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning) about this. Depending on your situation, you probably will have to `wait` for something in your test:
84-
85-
```js
86-
import { fireEvent, wait } from '@testing-library/react';
83+
We recommend using `userEvent` rather than `fireEvent` where possible. This is a [React Testing Library best practice](https://testing-library.com/docs/user-event/intro#differences-from-fireevent), because `userEvent` more accurately simulates user interactions in a browser and makes the test more reliable in catching unintended event handler behavior.
8784

88-
...
89-
await wait(() => fireEvent.click(getByText('Delete')));
90-
...
91-
```
85+
If, while using the Testing Library, your tests trigger a warning in the console from React ("Warning: An update to Component inside a test was not wrapped in act(...)"), first check out the library author's [blog post](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning) about this. Depending on your situation, you probably will have to use [`findBy`](https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries) or [`waitFor`](https://testing-library.com/docs/dom-testing-library/api-async/) for something in your test to ensure asynchronous side-effects have completed.
9286

9387
### Mocking
9488

@@ -108,7 +102,9 @@ vi.mock('@linode/api-v4/lib/kubernetes', async () => {
108102

109103
Some components, such as our ActionMenu, don't lend themselves well to unit testing (they often have complex DOM structures from MUI and it's hard to target). We have mocks for most of these components in a `__mocks__` directory adjacent to their respective components. To make use of these, just tell Vitest to use the mock:
110104

105+
```js
111106
vi.mock('src/components/ActionMenu/ActionMenu');
107+
```
112108

113109
Any `<ActionMenu>`s rendered by the test will be simplified versions that are easier to work with.
114110

@@ -157,6 +153,7 @@ We use [Cypress](https://cypress.io) for end-to-end testing. Test files are foun
157153
* Select a reasonable expiry time (avoid "Never") and make sure that every permission is set to "Read/Write".
158154
3. Set the `MANAGER_OAUTH` environment variable in your `.env` file using your new personal access token.
159155
* Example of `.env` addition:
156+
160157
```shell
161158
# Manager OAuth token for Cypress tests:
162159
# (The real token will be a 64-digit string of hexadecimals).
@@ -174,16 +171,19 @@ We use [Cypress](https://cypress.io) for end-to-end testing. Test files are foun
174171
Cloud Manager UI tests can be configured using environment variables, which can be defined in `packages/manager/.env` or specified when running Cypress.
175172

176173
##### Cypress Environment Variables
174+
177175
These environment variables are used by Cypress out-of-the-box to override the default configuration. Cypress exposes many other options that can be configured with environment variables, but the items listed below are particularly relevant for Cloud Manager testing. More information can be found at [docs.cypress.io](https://docs.cypress.io/guides/guides/environment-variables).
178176

179177
| Environment Variable | Description | Example | Default |
180178
|----------------------|--------------------------------------------|----------------------------|-------------------------|
181179
| `CYPRESS_BASE_URL` | URL to Cloud Manager environment for tests | `https://cloud.linode.com` | `http://localhost:3000` |
182180

183181
##### Cloud Manager-specific Environment Variables
182+
184183
These environment variables are specific to Cloud Manager UI tests. They can be distinguished from out-of-the-box Cypress environment variables by their `CY_TEST_` prefix.
185184

186185
###### General
186+
187187
Environment variables related to the general operation of the Cloud Manager Cypress tests.
188188

189189
| Environment Variable | Description | Example | Default |
@@ -192,6 +192,7 @@ Environment variables related to the general operation of the Cloud Manager Cypr
192192
| `CY_TEST_TAGS` | Query identifying tests that should run by specifying allowed and disallowed tags. | `method:e2e` | Unset; all tests run by default |
193193

194194
###### Overriding Behavior
195+
195196
These environment variables can be used to override some behaviors of Cloud Manager's UI tests. This can be useful when testing Cloud Manager for nonstandard or work-in-progress functionality.
196197
197198
| Environment Variable | Description | Example | Default |
@@ -200,6 +201,7 @@ These environment variables can be used to override some behaviors of Cloud Mana
200201
| `CY_TEST_FEATURE_FLAGS` | JSON string containing feature flag data | `{}` | Unset; feature flag data is not overridden |
201202
202203
###### Run Splitting
204+
203205
These environment variables facilitate splitting the Cypress run between multiple runners without the use of any third party services. This can be useful for improving Cypress test performance in some circumstances. For additional performance gains, an optional test weights file can be specified using `CY_TEST_SPLIT_RUN_WEIGHTS` (see `CY_TEST_GENWEIGHTS` to generate test weights).
204206
205207
| Environment Variable | Description | Example | Default |
@@ -210,6 +212,7 @@ These environment variables facilitate splitting the Cypress run between multipl
210212
| `CY_TEST_SPLIT_RUN_WEIGHTS` | Path to test weights file | `./weights.json` | Unset; disabled by default |
211213
212214
###### Development, Logging, and Reporting
215+
213216
Environment variables related to Cypress logging and reporting, as well as report generation.
214217
215218
| Environment Variable | Description | Example | Default |
@@ -222,6 +225,7 @@ Environment variables related to Cypress logging and reporting, as well as repor
222225
| `CY_TEST_GENWEIGHTS` | Generate and output test weights to the given path | `./weights.json` | Unset; disabled by default |
223226
224227
###### Performance
228+
225229
Environment variables that can be used to improve test performance in some scenarios.
226230
227231
| Environment Variable | Description | Example | Default |
@@ -233,6 +237,7 @@ Environment variables that can be used to improve test performance in some scena
233237
234238
1. Look here for [Cypress Best Practices](https://docs.cypress.io/guides/references/best-practices)
235239
2. Test Example:
240+
236241
```tsx
237242
/* this test will not pass on cloud manager.
238243
it is only intended to show correct test structure, syntax,
@@ -293,13 +298,15 @@ Environment variables that can be used to improve test performance in some scena
293298
});
294299
});
295300
```
301+
296302
3. How to use intercepts:
303+
297304
```tsx
298305
// stub response syntax:
299306
cy.intercept('POST', ‘/path’, {response}) or cy.intercept(‘/path’, (req) => { req.reply({response})}).as('something');
300-
// edit and end response syntax:
307+
// edit and end response syntax:
301308
cy.intercept('GET', ‘/path’, (req) => { req.send({edit: something})}).as('something');
302-
// edit request syntax:
309+
// edit request syntax:
303310
cy.intercept('POST', ‘/path’, (req) => { req.body.storyName = 'some name'; req.continue().as('something');
304311
305312
// use alias syntax:

docs/development-guide/15-composition.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,74 @@ The Linode Create Page is a good example of a complex form that is built using r
5252
### Uncontrolled Forms
5353
Uncontrolled forms are a type of form that does not have a state for its values. It is often used for simple forms that do not need to be controlled, such as forms with a single input field or call to action.
5454

55+
## Form Validation (React Hook Form)
56+
### Best Practices
57+
1. Keep API validation in `@linode/validation` package
58+
2. Create extended schemas in `@linode/manager` package when you need validation beyond the API contract
59+
3. Use yup.concat() to extend existing schemas
60+
4. Add custom validation logic within the resolver function
61+
5. Include type definitions for form values and context
62+
63+
### Simple Schema Extension
64+
For basic form validation, extend the API schema directly:
65+
66+
```typescript
67+
import { CreateWidgetSchema } from '@linode/validation';
68+
import { object, string } from 'yup';
69+
import { yupResolver } from '@hookform/resolvers/yup';
70+
71+
const extendedSchema = CreateWidgetSchema.concat(
72+
object({
73+
customField: string().required('Required field'),
74+
})
75+
);
76+
77+
const form = useForm({
78+
resolver: yupResolver(extendedSchema)
79+
});
80+
```
81+
82+
### Complex Schema Extensions
83+
You may create a `resolver` function that handles the validation (see: [ManageImageRegionsForm.tsx](https://github.com/linode/manager/blob/develop/packages/manager/src/features/Images/ImagesLanding/ImageRegions/ManageImageRegionsForm.tsx#L189-L213)):
84+
85+
```typescript
86+
// Step 1: Create a Resolver Function
87+
// This function validates your form data against specific requirements
88+
89+
type Resolver<FormData, Context> = (values: FormData, context: Context) => {
90+
errors: Record<string, any>;
91+
values: FormData;
92+
};
93+
94+
// Example resolver that checks if at least one item from a list is selected
95+
const resolver: Resolver<YourFormData, YourContext> = (values, context) => {
96+
// Check if at least one valid option is selected
97+
const hasValidSelection = values.selectedItems.some(
98+
item => context.availableItems.includes(item)
99+
);
100+
101+
if (!hasValidSelection) {
102+
return {
103+
errors: {
104+
selectedItems: {
105+
message: 'Please select at least one valid option',
106+
type: 'validate'
107+
}
108+
},
109+
values
110+
};
111+
}
112+
113+
return { errors: {}, values };
114+
};
115+
116+
// Step 2: Use the Resolver in Your Form
117+
const form = useForm({
118+
resolver,
119+
defaultValues: { selectedItems: [] },
120+
context: { availableItems: ['item1', 'item2'] }
121+
});
122+
```
123+
124+
### Additional Complexity
125+
When working with multiple sequential schemas that require validation, you can create a resolver map and function (see: [LinodeCreate/resolvers.ts](https://github.com/linode/manager/blob/develop/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts])).

0 commit comments

Comments
 (0)