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

upcoming: [DI: 21998] - Added Service Type, Engine Option and Region Select component to Create Alert form #11286

Conversation

santoshp210-akamai
Copy link
Contributor

Description 📝

  • Added Service, Engine Option, Region Select components to the Create Alert form under General Information.
  • Added Unit tests for all three components

Changes 🔄

  • Added Autocomplete components for service type, engine option, region selection for the Create Alert form
  • Added Unit tests for the components
  • Modified the Alert Create POST query to include service_type as a parameter of the request
  • Minor changes to validation schema to improve the error messages
  • Minor changes to types and interfaces

Target release date 🗓️

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.

Preview 📷

Before After
Screenshot 2024-11-13 at 19 17 38 Screenshot 2024-11-19 at 21 38 14 Screenshot 2024-11-19 at 21 38 24

How to test 🧪

Prerequisites

(How to setup test environment)

  • The tabs are controlled by a featureFlag called aclpAlerting, the flag has been currently disabled. For testing enable the definitions part of the aclpAlerting flag to be true.
  • The API endpoints are not live yet, so use Legacy MSW Handlers for the mock responses.

Verification steps

(How to verify changes)

  • In Monitor (once the prerequisites are followed) , Alerts tab should be visible next to Dashboards.
  • Under that, Definition tab and a Create button should be visible.
  • On clicking Create, the Form Page should be visible.
  • The Engine Option component is designed to be visible only when the Service type is Databases, so selecting Databases in Service renders the Engine Option component

As an Author, I have considered 🤔

  • 👀 Doing a self review
  • ❔ Our contribution guidelines
  • 🤏 Splitting feature into small PRs
  • ➕ Adding a changeset
  • 🧪 Providing/improving test coverage
  • 🔐 Removing all sensitive information from the code and PR description
  • 🚩 Using a feature flag to protect the release
  • 👣 Providing comprehensive reproduction steps
  • 📑 Providing or updating our documentation
  • 🕛 Scheduling a pair reviewing session
  • 📱 Providing mobile support
  • ♿ Providing accessibility support

  • I have read and considered all applicable items listed above.

As an Author, before moving this PR from Draft to Open, I confirmed ✅

  • All unit tests are passing
  • TypeScript compilation succeeded without errors
  • Code passes all linting rules

@santoshp210-akamai santoshp210-akamai requested a review from a team as a code owner November 19, 2024 16:11
@santoshp210-akamai santoshp210-akamai requested review from mjac0bs and hkhalil-akamai and removed request for a team November 19, 2024 16:11
@santoshp210-akamai santoshp210-akamai self-assigned this Nov 19, 2024
@@ -158,8 +158,8 @@ export interface CreateAlertDefinitionPayload {
export interface CreateAlertDefinitionForm
extends CreateAlertDefinitionPayload {
region: string;
service_type: string;
engine_type: string;
service_type: string | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type for the service_type has been kept string as we're getting the data from the API. Once we're aware of the possible service_types , we will use specific strings as a type for it

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we had an understanding as to what these will be: enum['linode','lke','aclb','dbaas']?

CreateAlertDefinitionPayload,
} from '@linode/api-v4';

export const getCreateAlertPayload = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Omitting the props here as we require these attributes for the form but are not part of the payload for the POST request

Copy link
Contributor

Choose a reason for hiding this comment

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

This name caused me to pause as I reading it in CreateAlertDefinition, I would say we should rename this to include the word filter or omit since that better communicates that the function's purpose is to remove specific fields rather than just "getting" something.

Copy link

github-actions bot commented Nov 19, 2024

Coverage Report:
Base Coverage: 86.72%
Current Coverage: 86.72%

Copy link
Contributor

@mjac0bs mjac0bs left a comment

Choose a reason for hiding this comment

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

Thanks @santoshp210-akamai. Verified the correct form fields appear depending on service type and validation is present.

Left a question about the mocked form submission and a first pass of general feedback.

Copy link
Contributor

Choose a reason for hiding this comment

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

We've run into test issues in the past with pickRandom introducing non-determinism in our factories. Because we use factories in our tests, we're expecting them to consistently generate the same values and so our best practice is to recommend against them (docs reference).

Can we avoid random assignment here? (cc @jdamore-linode for visibility)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mjac0bs can we use Math.Random to pick out the values ?

Copy link
Member

@bnussman-akamai bnussman-akamai Nov 20, 2024

Choose a reason for hiding this comment

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

We are recommending we don't use any randomness in the factories themselves. If you want randomness in the results returned from the Mock Service Worker, you can implement the randomness at that level rather than at the factory level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have pushed a commit to remove pickRandom. The only randomness is the Math.random for the id. Should I remove that too @bnussman-akamai ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@santoshp210-akamai I would believe so, would doing something similar to what you're doing with the label work for the id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaalah-akamai , Not sure. Will try something out.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is better, just be aware when writing tests assertions checking for any specific alert properties that the data is dependent on the alert id, and write the assertion to expect the correct value accordingly (or force a value via mocking in test).

Here's an example of what Banks mentioned about randomness in the mock endpoint vs the factory.

serverHandlers.ts:

  http.post(
    '*/monitor/services/:service_type/alert-definitions',
    async ({ request }) => {
      const types: AlertDefinitionType[] = ['custom', 'default'];
      const status: AlertStatusType[] = ['enabled', 'disabled'];
      const severity: AlertSeverityType[] = [0, 1, 2, 3];
      const users = ['user1', 'user2', 'user3'];
      const serviceTypes: AlertServiceType[] = ['linode', 'dbaas'];

      const reqBody = await request.json();
      const response = alertFactory.build({
        ...(reqBody as CreateAlertDefinitionPayload),
        created_by: pickRandom(users),
        serviceTypes: pickRandom(serviceTypes),
        severity: pickRandom(severity),
        status: pickRandom(status),
        type: pickRandom(types),
        updated_by: pickRandom(users),
      });
      return HttpResponse.json(response);
    }
  ),

factories/cloudpulse/alerts.ts:

export const alertFactory = Factory.Sync.makeFactory<Alert>({
  channels: [],
  created: new Date().toISOString(),
  created_by: 'user1',
  description: '',
  id: Factory.each((i) => i),
  label: Factory.each((id) => `Alert-${id}`),
  resource_ids: ['0', '1', '2', '3'],
  rule_criteria: {
    rules: [],
  },
  service_type: 'linode',
  severity: 0,
  status: 'enabled',
  triggerCondition: {
    evaluation_period_seconds: 0,
    polling_interval_seconds: 0,
    trigger_occurrences: 0,
  },
  type: 'default',
  updated: new Date().toISOString(),
  updated_by: 'user1',
});

packages/manager/src/mocks/serverHandlers.ts Outdated Show resolved Hide resolved
};
return HttpResponse.json(response);
}),
http.post(
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we have a mock request here... should we see this firing when the Submit button is clicked? I was not seeing any request in the network tab.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mjac0bs , The request won't fire as the validation schema is written for all the fields that are there in the entire form. Those components will be added in further PR's.

CreateAlertDefinitionPayload,
} from '@linode/api-v4';

export const getCreateAlertPayload = (
Copy link
Contributor

Choose a reason for hiding this comment

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

This name caused me to pause as I reading it in CreateAlertDefinition, I would say we should rename this to include the word filter or omit since that better communicates that the function's purpose is to remove specific fields rather than just "getting" something.

@@ -158,8 +158,8 @@ export interface CreateAlertDefinitionPayload {
export interface CreateAlertDefinitionForm
extends CreateAlertDefinitionPayload {
region: string;
service_type: string;
engine_type: string;
service_type: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we had an understanding as to what these will be: enum['linode','lke','aclb','dbaas']?

const { enqueueSnackbar } = useSnackbar();
const { mutateAsync: createAlert } = useCreateAlertDefinition();
const { mutateAsync: createAlert } = useCreateAlertDefinition(
getValues('service_type')!
Copy link
Contributor

Choose a reason for hiding this comment

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

I know we have validation in place, and service types should be some sort of enum in the future, but to me this is a potential runtime issue. @bnussman-akamai do you have any thoughts on this particularly?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. This is tough.

Because the endpoint path includes the service type (POST /monitor/services/{service_type}/alert-definitions) but we are getting the service type from the form data, this could cause issues. I think at the worst, it will just cause an API error, which is fine.

But... Why does the createAlertDefinitionSchema have service_type. Is service_type part of the request body or is it in the path? Some things are not making sense to me here

api-v4 and validation should always be aligned API not UI. If needed we can extend the validation schema inside of Cloud Manager

Copy link
Contributor Author

@santoshp210-akamai santoshp210-akamai Nov 20, 2024

Choose a reason for hiding this comment

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

@bnussman-akamai , It is in the path. The reason why service_type is there in createAlertDefinitionSchema is because we need to validate it as it is mandatory for the selection of resources for the Alert.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea that's definitely an oversight, it should be removed from the validation in the schema and added at the UI level. cc: @santoshp210-akamai

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bnussman-akamai Are there any similar Components , to extend the validation schema ? The reason we proceeded this way is because of the need for validation for service_type.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we're using a Controller we can utilize the rules prop

Copy link
Member

@bnussman-akamai bnussman-akamai Nov 21, 2024

Choose a reason for hiding this comment

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

You can extend a @linode/validation schema like this:

export const CreateLinodeFromStackScriptSchema = CreateLinodeSchema.concat(
object({
stackscript_id: number().required('You must select a StackScript.'),
})
);

…Payload to filterFormValues and replaced the type of service_typeto a union type of relevant services instead of string
@@ -158,8 +159,8 @@ export interface CreateAlertDefinitionPayload {
export interface CreateAlertDefinitionForm
extends CreateAlertDefinitionPayload {
region: string;
service_type: string;
engine_type: string;
service_type: AlertServiceType | null;
Copy link
Member

Choose a reason for hiding this comment

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

It looks like service_type is in the path and not the request body. Should this be removed?

Suggested change
service_type: AlertServiceType | null;

Copy link
Contributor

Choose a reason for hiding this comment

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

From my understanding this is just for the form itself, it's getting stripped before being sent as part of the request

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jaalah-akamai , Yes that's right.

service_type: string;
engine_type: string;
service_type: AlertServiceType | null;
engine_type: string | null;
Copy link
Member

Choose a reason for hiding this comment

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

Is this reflected in the API spec?

Also wondering if the API really needs this. Don't all Databases regardless of engine have unique IDs? Maybe the backend could determine this without us having to pass it.

Copy link
Contributor

Choose a reason for hiding this comment

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

@santoshp210-akamai This is a good question, in addition we may want to do the same when it comes to the schema and add it at the UI level if it's something we need.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are not there as part of the request, so we are not sending these attributes but we have this interface extended so we can have this part of the form for the to take the user's input and for validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@santoshp210-akamai This is a good question, in addition we may want to do the same when it comes to the schema and add it at the UI level if it's something we need.

So just for these components, we just add it in UI and then validate separately ?

Copy link
Contributor

@jaalah-akamai jaalah-akamai left a comment

Choose a reason for hiding this comment

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

Thanks, this is looking much better and I think adding a schema in manager will make this much more organized with how much form work you have to do. 👍

@@ -1,14 +1,15 @@
export type AlertSeverityType = 0 | 1 | 2 | 3 | null;
type MetricAggregationType = 'avg' | 'sum' | 'min' | 'max' | 'count' | null;
type MetricOperatorType = 'eq' | 'gt' | 'lt' | 'gte' | 'lte' | null;
export type AlertServiceType = 'linode' | 'dbaas' | null;
Copy link
Member

Choose a reason for hiding this comment

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

Why null? Seems like the should not be nullable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Autocomplete component is controlled, so we have it nullable in the cases where it is being initialized , when user clears an option. We have validations in place so the user won't be able to submit any null value we are making sure appropriate values are being selected.

Copy link
Member

Choose a reason for hiding this comment

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

If that's the case, we should probably be extending the type within Cloud Manager. The @linode/api-v4 package is suppose to be 1:1 with the API

export const createAlertDefinition = (data: CreateAlertDefinitionPayload) =>
export const createAlertDefinition = (
data: CreateAlertDefinitionPayload,
service_type: string
Copy link
Member

Choose a reason for hiding this comment

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

Optional: Maybe we could use AlertServiceType here.

const { enqueueSnackbar } = useSnackbar();
const { mutateAsync: createAlert } = useCreateAlertDefinition();
const { mutateAsync: createAlert } = useCreateAlertDefinition(
getValues('service_type')!
Copy link
Member

@bnussman-akamai bnussman-akamai Nov 21, 2024

Choose a reason for hiding this comment

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

You can extend a @linode/validation schema like this:

export const CreateLinodeFromStackScriptSchema = CreateLinodeSchema.concat(
object({
stackscript_id: number().required('You must select a StackScript.'),
})
);

Copy link
Contributor

@mjac0bs mjac0bs left a comment

Choose a reason for hiding this comment

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

Approving with my initial feedback addressed, pending Banks' last round of feedback is also addressed.

"@linode/api-v4": Changed
---

Added service_type as parameter for the Create Alert POST request ([#11286](https://github.com/linode/manager/pull/11286))
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be better as an 'Added' changeset, and without the 'Added' at the beginning of the description.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it

@santoshp210-akamai
Copy link
Contributor Author

santoshp210-akamai commented Nov 25, 2024

@bnussman-akamai , we are extending in a way that's similar to linode validation, I'm not sure what the changes you're requesting here #11286 (comment).

… from string to AlertServiceType wherever applicable, moved randomness to the serverHandler from the alert factory
@santoshp210-akamai
Copy link
Contributor Author

@jaalah-akamai , Thank you for updating the docs with Form Validation Best Practices, examples and references. We are now following the same way to extend additional validation from form validation for our use-cases.

… them to manager , appropriately omitted and added null checks to ensure type safety
@santoshp210-akamai
Copy link
Contributor Author

@bnussman-akamai , @jaalah-akamai As per the discussion. Removed the null types from the interfaces and types in api-v4 and moved them to manager. And appropriately omitting props, adding null checks to ensure proper type safety.

@linode-gh-bot
Copy link
Collaborator

Cloud Manager UI test results

🔺 1 failing test on test run #11 ↗︎

❌ Failing✅ Passing↪️ Skipped🕐 Duration
1 Failing454 Passing2 Skipped106m 25s

Details

Failing Tests
SpecTest
machine-image-upload.spec.tsmachine image » uploads machine image, mock expired upload event

Troubleshooting

Use this command to re-run the failing tests:

yarn cy:run -s "cypress/e2e/core/images/machine-image-upload.spec.ts"

@jaalah-akamai jaalah-akamai merged commit d8b7978 into linode:develop Nov 25, 2024
21 of 23 checks passed
Copy link

cypress bot commented Nov 25, 2024

Cloud Manager E2E    Run #6876

Run Properties:  status check failed Failed #6876  •  git commit d8b79783c9: Merge pull request #11286 from santoshp210-akamai/feature/create-alert-flow-gene...
Project Cloud Manager E2E
Branch Review develop
Run status status check failed Failed #6876
Run duration 38m 04s
Commit git commit d8b79783c9: Merge pull request #11286 from santoshp210-akamai/feature/create-alert-flow-gene...
Committer Jaalah Ramos
View all properties for this run ↗︎

Test results
Tests that failed  Failures 5
Tests that were flaky  Flaky 6
Tests that did not run due to a developer annotating a test with .skip  Pending 2
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 450
View all changes introduced in this branch ↗︎

Tests for review

Failed  images/machine-image-upload.spec.ts • 4 failed tests

View Output Video

Test Artifacts
machine image > uploads machine image, mock finish event Screenshots Video
machine image > uploads machine image, mock upload canceled failed event Screenshots Video
machine image > uploads machine image, mock failed to decompress failed event Screenshots Video
machine image > uploads machine image, mock expired upload event Screenshots Video
Failed  linodes/rebuild-linode.spec.ts • 1 failed test

View Output Video

Test Artifacts
rebuild linode > rebuilds a linode from Account StackScript Screenshots Video
Flakiness  switch-linode-state.spec.ts • 1 flaky test

View Output Video

Test Artifacts
switch linode state > powers off a linode from landing page Screenshots Video
Flakiness  clone-linode.spec.ts • 1 flaky test

View Output Video

Test Artifacts
clone linode > can clone a Linode from Linode details page Screenshots Video
Flakiness  resize-linode.spec.ts • 1 flaky test

View Output Video

Test Artifacts
resize linode > resizes a linode by increasing size when offline: cold migration Screenshots Video
Flakiness  linode-config.spec.ts • 2 flaky tests

View Output Video

Test Artifacts
Linode Config management > End-to-End > Edits a config Screenshots Video
Linode Config management > End-to-End > Clones a config Screenshots Video
Flakiness  create-linode-with-firewall.spec.ts • 1 flaky test

View Output Video

Test Artifacts
Create Linode with Firewall > can generate and assign a compliant Firewall during Linode Create flow Screenshots Video

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants