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-7031] - Add AGLB Create Service Target Drawer #9657

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
24e41bd
initial drawer
bnussman Sep 8, 2023
14be7e8
start building healthchecks
bnussman Sep 8, 2023
03af11f
finish health check inputs
bnussman Sep 8, 2023
d6605e5
add client side validation
bnussman Sep 8, 2023
3f18031
fix healthcheck validation
bnussman Sep 8, 2023
12ae865
add most robust error handling
bnussman Sep 11, 2023
c078649
wire up goofy cert select
bnussman Sep 11, 2023
e37e3cd
fix weird style
bnussman Sep 11, 2023
43c1b68
order props
bnussman Sep 11, 2023
2fd6a52
handle general errors
bnussman Sep 11, 2023
2f72a76
make `ca_certificate` nullable
bnussman Sep 11, 2023
41edc89
Merge branch 'develop' into M3-7031-aglb-service-target-create-drawer
bnussman Sep 11, 2023
8094f50
Add Linode or IP input
bnussman Sep 11, 2023
9be3e9a
add new Selects
bnussman Sep 11, 2023
addeee4
Added changeset: Add AGLB Create Service Target Drawer
bnussman Sep 11, 2023
79cd77f
feedback @abailly-akamai
bnussman Sep 12, 2023
5b11c1b
add basic unit test and minor improvments
bnussman Sep 12, 2023
9c1d7bf
clean up and comment
bnussman Sep 12, 2023
231c619
Add QA data attributes for service target create drawer
Sep 14, 2023
20ccd40
Add Load Balancer service target integration tests
Sep 14, 2023
923a25f
add query invalidation `onSuccess`
bnussman Sep 14, 2023
fe95601
build out new UI
bnussman Sep 14, 2023
034705d
add some unit testing
bnussman Sep 15, 2023
e4f36a3
adapt e2e test to new UI
bnussman Sep 15, 2023
206ed6a
feedback @abailly-akamai
bnussman Sep 15, 2023
91f7b40
implement UX feedback
bnussman Sep 15, 2023
e38df3c
more feedback
bnussman Sep 15, 2023
d7a2a3a
move port to own line and improve validation
bnussman Sep 15, 2023
6acad64
try to match mockup closer
bnussman Sep 15, 2023
53fe3e5
fix e2e now that we show linode label in table
bnussman Sep 15, 2023
10756a6
use e2e to test form
bnussman Sep 15, 2023
ded5ec1
Merge branch 'develop' into M3-7031-aglb-service-target-create-drawer
bnussman Sep 19, 2023
0bf845e
Merge branch 'develop' into M3-7031-aglb-service-target-create-drawer
bnussman Sep 20, 2023
3e49db7
revert unintended change
bnussman Sep 20, 2023
4098d99
feedback @mjac0bs
bnussman Sep 20, 2023
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
3 changes: 2 additions & 1 deletion packages/api-v4/src/aglb/service-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Request, {
import { Filter, Params, ResourcePage } from '../types';
import { BETA_API_ROOT } from '../constants';
import type { ServiceTarget, ServiceTargetPayload } from './types';
import { CreateServiceTargetSchema } from '@linode/validation';

/**
* getLoadbalancerServiceTargets
Expand Down Expand Up @@ -63,7 +64,7 @@ export const createLoadbalancerServiceTarget = (
loadbalancerId
)}/service-targets`
),
setData(data),
setData(data, CreateServiceTargetSchema),
setMethod('POST')
);

Expand Down
3 changes: 2 additions & 1 deletion packages/api-v4/src/aglb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export interface ServiceTargetPayload {
}

interface HealthCheck {
protocol: 'tcp' | 'http';
interval: number;
timeout: number;
unhealthy_threshold: number;
Expand All @@ -128,7 +129,7 @@ export interface ServiceTarget extends ServiceTargetPayload {
}

export interface Endpoint {
ip?: string;
ip: string;
host?: string;
port: number;
rate_capacity: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add AGLB Create Service Target Drawer ([#9657](https://github.com/linode/manager/pull/9657))
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/**
* @file Integration tests related to Cloud Manager AGLB Service Target management.
*/

import {
linodeFactory,
loadbalancerFactory,
serviceTargetFactory,
certificateFactory,
} from '@src/factories';
import {
mockAppendFeatureFlags,
mockGetFeatureFlagClientstream,
} from 'support/intercepts/feature-flags';
import {
mockGetLoadBalancer,
mockGetLoadBalancerCertificates,
mockGetServiceTargets,
mockCreateServiceTarget,
} from 'support/intercepts/load-balancers';
import { makeFeatureFlagData } from 'support/util/feature-flags';
import type { Linode, ServiceTarget } from '@linode/api-v4';
import { randomLabel, randomIp, randomNumber } from 'support/util/random';
import { ui } from 'support/ui';
import { chooseRegion } from 'support/util/regions';
import { mockGetLinodes } from 'support/intercepts/linodes';

describe('Akamai Global Load Balancer service targets', () => {
// TODO Remove this `beforeEach()` hook and related `cy.wait()` calls when `aglb` feature flag goes away.
beforeEach(() => {
mockAppendFeatureFlags({
aglb: makeFeatureFlagData(true),
}).as('getFeatureFlags');
mockGetFeatureFlagClientstream().as('getClientStream');
});

/*
* - Confirms that Load Balancer service targets are listed in the "Service Targets" tab.
*/
it('lists Load Balancer service targets', () => {
const mockLoadBalancer = loadbalancerFactory.build();
// const mockServiceTargets = serviceTargetFactory.buildList(5);
const mockServiceTargets = new Array(5).fill(null).map(
(_item: null, i: number): ServiceTarget => {
return serviceTargetFactory.build({
label: randomLabel(),
});
}
);

mockGetLoadBalancer(mockLoadBalancer).as('getLoadBalancer');
mockGetServiceTargets(mockLoadBalancer, mockServiceTargets).as(
'getServiceTargets'
);

cy.visitWithLogin(`/loadbalancers/${mockLoadBalancer.id}/service-targets`);
cy.wait([
'@getFeatureFlags',
'@getClientStream',
'@getLoadBalancer',
'@getServiceTargets',
]);

// Confirm that each service target is listed as expected.
mockServiceTargets.forEach((serviceTarget: ServiceTarget) => {
cy.findByText(serviceTarget.label).should('be.visible');
// TODO Assert that endpoints, health checks, algorithm, and certificates are listed.
});
});

/**
* - Confirms that service target page shows empty state when there are no service targets.
* - Confirms that clicking "Create Service Target" opens "Add a Service Target" drawer.
* - Confirms that "Add a Service Target" drawer can be cancelled.
* - Confirms that endpoints can be selected via Linode label and via IP address.
* - Confirms that health check can be disabled or re-enabled, and UI responds to toggle.
* - [TODO] Confirms that service target list updates to reflect created service target.
*/
it('can create a Load Balancer service target', () => {
const loadBalancerRegion = chooseRegion();
const mockLinodes = new Array(2).fill(null).map(
(_item: null, _i: number): Linode => {
return linodeFactory.build({
label: randomLabel(),
region: loadBalancerRegion.id,
ipv4: [randomIp()],
});
}
);

const mockLoadBalancer = loadbalancerFactory.build({
regions: [loadBalancerRegion.id],
});
const mockServiceTarget = serviceTargetFactory.build({
label: randomLabel(),
});
const mockCertificate = certificateFactory.build({
label: randomLabel(),
});

mockGetLinodes(mockLinodes);
mockGetLoadBalancer(mockLoadBalancer).as('getLoadBalancer');
mockGetServiceTargets(mockLoadBalancer, []).as('getServiceTargets');
mockGetLoadBalancerCertificates(mockLoadBalancer.id, [mockCertificate]);

cy.visitWithLogin(`/loadbalancers/${mockLoadBalancer.id}/service-targets`);
cy.wait([
'@getFeatureFlags',
'@getClientStream',
'@getLoadBalancer',
'@getServiceTargets',
]);

cy.findByText('No items to display.');

ui.button
.findByTitle('Create Service Target')
.should('be.visible')
.should('be.enabled')
.click();

// Confirm that drawer can be closed.
ui.drawer
.findByTitle('Add a Service Target')
.should('be.visible')
.within(() => {
ui.button
.findByTitle('Cancel')
.scrollIntoView()
.should('be.visible')
.should('be.enabled')
.click();
});

cy.get('[data-qa-drawer]').should('not.exist');

// Re-open "Add a Service Target" drawer.
ui.button
.findByTitle('Create Service Target')
.should('be.visible')
.should('be.enabled')
.click();

// Fill out service target drawer, click "Create Service Target".
mockCreateServiceTarget(mockLoadBalancer, mockServiceTarget).as(
'createServiceTarget'
);

ui.drawer
.findByTitle('Add a Service Target')
.should('be.visible')
.within(() => {
cy.findByText('Service Target Label')
.should('be.visible')
.click()
.type(mockServiceTarget.label);

// Fill out the first endpoint using the mocked Linode's label.
cy.findByText('Linode or Public IP Address')
.should('be.visible')
.click()
.type(mockLinodes[0].label);

ui.autocompletePopper
.findByTitle(mockLinodes[0].label)
.should('be.visible')
.click();

ui.button.findByTitle('Add Endpoint').should('be.visible').click();

// Verify the first endpoint was added to the table
cy.findByText(mockLinodes[0].label, { exact: false })
.scrollIntoView()
.should('be.visible');

// Create another endpoint
cy.findByText('Linode or Public IP Address')
.should('be.visible')
.click()
.type(mockLinodes[1].ipv4[0]);

ui.autocompletePopper
.findByTitle(mockLinodes[1].label)
.should('be.visible')
.click();

ui.button.findByTitle('Add Endpoint').should('be.visible').click();

// Verify the second endpoint was added to the table
cy.findByText(mockLinodes[1].label, { exact: false })
.scrollIntoView()
.should('be.visible');

// Select the certificate mocked for this load balancer.
cy.findByText('Certificate').click().type(mockCertificate.label);

ui.autocompletePopper
.findByTitle(mockCertificate.label)
.should('be.visible')
.click();

// Confirm that health check options are hidden when health check is disabled.
cy.findByText('Use Health Checks').should('be.visible').click();

cy.get('[data-qa-healthcheck-options]').should('not.exist');

// Re-enable health check, fill out form.
cy.findByText('Use Health Checks')
.scrollIntoView()
.should('be.visible')
.click();

cy.get('[data-qa-healthcheck-options]')
.scrollIntoView()
.should('be.visible');

ui.button
.findByTitle('Create Service Target')
.scrollIntoView()
.should('be.visible')
.should('be.enabled')
.click();
});
cy.wait('@createServiceTarget');

// TODO Assert that new service target is listed.
// cy.findByText('No items to display.').should('not.exist');
// cy.findByText(mockServiceTarget.label).should('not.exist');
});
});
26 changes: 24 additions & 2 deletions packages/manager/cypress/support/intercepts/load-balancers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,13 @@ export const mockUploadLoadBalancerCertificate = (
*
* @returns Cypress chainable.
*/
export const mockGetServiceTargets = (serviceTargets: ServiceTarget[]) => {
export const mockGetServiceTargets = (
loadBalancer: Loadbalancer,
serviceTargets: ServiceTarget[]
) => {
return cy.intercept(
'GET',
apiMatcher('/aglb/service-targets*'),
apiMatcher(`/aglb/${loadBalancer.id}/service-targets*`),
paginateResponse(serviceTargets)
);
};
Expand All @@ -125,3 +128,22 @@ export const mockGetServiceTargetsError = (message?: string) => {
makeErrorResponse(message ?? defaultMessage, 500)
);
};

/**
* Intercepts POST request to create a service target and mocks response.
*
* @param loadBalancer - Load balancer for mocked service target.
* @param serviceTarget - Service target with which to mock response.
*
* @returns Cypress chainable.
*/
export const mockCreateServiceTarget = (
loadBalancer: Loadbalancer,
serviceTarget: ServiceTarget
) => {
return cy.intercept(
'POST',
apiMatcher(`/aglb/${loadBalancer.id}/service-targets`),
makeResponse(serviceTarget)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const CustomPopper = (props: PopperProps) => {
? `calc(${style.width} + 2px)`
: style.width + 2
: undefined,
zIndex: 1,
Copy link
Member Author

Choose a reason for hiding this comment

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

Fixes zIndex issue with new Autocomplete

Screen.Recording.2023-09-14.at.5.04.21.PM.mov

};

return (
Expand Down
6 changes: 3 additions & 3 deletions packages/manager/src/components/TextField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import { Box } from 'src/components/Box';
import {
default as _TextField,
StandardTextFieldProps,
Expand All @@ -9,6 +8,7 @@ import { clamp } from 'ramda';
import * as React from 'react';
import { makeStyles } from 'tss-react/mui';

import { Box } from 'src/components/Box';
import { CircleProgress } from 'src/components/CircleProgress';
import { FormHelperText } from 'src/components/FormHelperText';
import { InputAdornment } from 'src/components/InputAdornment';
Expand Down Expand Up @@ -296,7 +296,7 @@ export const TextField = (props: TextFieldProps) => {
inputId || (label ? convertToKebabCase(`${label}`) : undefined);

return (
<div
<Box
className={cx({
[classes.helpWrapper]: Boolean(tooltipText),
[errorScrollClassName]: !!errorText,
Expand Down Expand Up @@ -443,6 +443,6 @@ export const TextField = (props: TextFieldProps) => {
{helperText}
</FormHelperText>
)}
</div>
</Box>
);
};
Loading