-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Service settings area to configure ZDD (#935)
- Loading branch information
Showing
9 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { hasDeployApp, selectAppById } from "@app/deploy"; | ||
import { | ||
server, | ||
stacksWithResources, | ||
testAccount, | ||
testApp, | ||
testEndpoint, | ||
testEnv, | ||
testServiceRails, | ||
verifiedUserHandlers, | ||
} from "@app/mocks"; | ||
import { appServiceSettingsPathUrl } from "@app/routes"; | ||
import { setupAppIntegrationTest, waitForBootup, waitForData } from "@app/test"; | ||
import { render, screen } from "@testing-library/react"; | ||
import { rest } from "msw"; | ||
|
||
describe("AppDetailServiceSettingsPage", () => { | ||
it("should successfully show app service scale page happy path", async () => { | ||
server.use( | ||
...verifiedUserHandlers(), | ||
...stacksWithResources({ | ||
accounts: [testAccount], | ||
apps: [testApp], | ||
services: [{ ...testServiceRails, force_zero_downtime: true }], | ||
}), | ||
); | ||
const { App, store } = setupAppIntegrationTest({ | ||
initEntries: [ | ||
appServiceSettingsPathUrl(`${testApp.id}`, `${testServiceRails.id}`), | ||
], | ||
}); | ||
|
||
await waitForBootup(store); | ||
|
||
render(<App />); | ||
await waitForData(store, (state) => { | ||
return hasDeployApp(selectAppById(state, { id: `${testApp.id}` })); | ||
}); | ||
|
||
await screen.findByText(/Service Settings/); | ||
const zddCheckbox = await screen.findByRole("checkbox", { | ||
name: "zero-downtime", | ||
}); | ||
const simpleHealthcheckCheckbox = await screen.findByRole("checkbox", { | ||
name: "simple-healthcheck", | ||
}); | ||
|
||
expect(zddCheckbox).toBeInTheDocument(); | ||
expect(simpleHealthcheckCheckbox).toBeInTheDocument(); | ||
expect(zddCheckbox).toBeChecked(); | ||
expect(simpleHealthcheckCheckbox).not.toBeChecked(); | ||
}); | ||
|
||
describe("when endpoints are configured", () => { | ||
it("should not show configuration, instead pointing at endpoints", async () => { | ||
server.use( | ||
rest.get(`${testEnv.apiUrl}/services/:id/vhosts`, (_, res, ctx) => { | ||
return res(ctx.json({ _embedded: { vhosts: [testEndpoint] } })); | ||
}), | ||
...verifiedUserHandlers(), | ||
...stacksWithResources({ | ||
accounts: [testAccount], | ||
apps: [testApp], | ||
services: [{ ...testServiceRails, force_zero_downtime: true }], | ||
}), | ||
); | ||
const { App, store } = setupAppIntegrationTest({ | ||
initEntries: [ | ||
appServiceSettingsPathUrl(`${testApp.id}`, `${testServiceRails.id}`), | ||
], | ||
}); | ||
|
||
await waitForBootup(store); | ||
|
||
render(<App />); | ||
await waitForData(store, (state) => { | ||
return hasDeployApp(selectAppById(state, { id: `${testApp.id}` })); | ||
}); | ||
|
||
await screen.findByText(/Service Settings/); | ||
await screen.findByText(/managed through the following Endpoints/); | ||
const zddCheckbox = screen.queryByRole("checkbox", { | ||
name: "zero-downtime", | ||
}); | ||
const simpleHealthcheckCheckbox = screen.queryByRole("checkbox", { | ||
name: "simple-healthcheck", | ||
}); | ||
expect(zddCheckbox).not.toBeInTheDocument(); | ||
expect(simpleHealthcheckCheckbox).not.toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { | ||
fetchApp, | ||
fetchService, | ||
getEndpointUrl, | ||
selectEndpointsByServiceId, | ||
selectServiceById, | ||
updateServiceById, | ||
} from "@app/deploy"; | ||
import { useDispatch, useLoader, useQuery, useSelector } from "@app/react"; | ||
import { endpointDetailUrl } from "@app/routes"; | ||
import { type SyntheticEvent, useEffect, useState } from "react"; | ||
import { useParams } from "react-router"; | ||
import { Link } from "react-router-dom"; | ||
import { | ||
Banner, | ||
BannerMessages, | ||
Box, | ||
Button, | ||
ButtonLinkDocs, | ||
CheckBox, | ||
Tooltip, | ||
} from "../shared"; | ||
|
||
export const AppDetailServiceSettingsPage = () => { | ||
const dispatch = useDispatch(); | ||
const { id = "", serviceId = "" } = useParams(); | ||
useQuery(fetchApp({ id })); | ||
useQuery(fetchService({ id: serviceId })); | ||
const service = useSelector((s) => selectServiceById(s, { id: serviceId })); | ||
const endpoints = useSelector((s) => | ||
selectEndpointsByServiceId(s, { serviceId: service.id }), | ||
); | ||
|
||
const [nextService, setNextService] = useState(service); | ||
useEffect(() => { | ||
setNextService(service); | ||
}, [service.id]); | ||
|
||
const action = updateServiceById({ ...nextService }); | ||
const modifyLoader = useLoader(action); | ||
const cancelChanges = () => setNextService(service); | ||
const changesExist = service !== nextService; | ||
|
||
const onSubmitForm = (e: SyntheticEvent) => { | ||
e.preventDefault(); | ||
dispatch(action); | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col gap-4"> | ||
<Box> | ||
<form onSubmit={onSubmitForm}> | ||
<BannerMessages {...modifyLoader} /> | ||
<div className="flex flex-col gap-2"> | ||
<div className="flex justify-between items-start"> | ||
<h1 className="text-lg text-gray-500 mb-4">Service Settings</h1> | ||
<ButtonLinkDocs href="https://www.aptible.com/docs/core-concepts/apps/deploying-apps/releases/overview" /> | ||
</div> | ||
{endpoints.length > 0 ? ( | ||
<Banner> | ||
Service settings are managed through the following Endpoints: | ||
{endpoints.map((endpoint, index) => { | ||
return ( | ||
<span key={index}> | ||
{index === 0 && " "} | ||
<Link to={endpointDetailUrl(endpoint.id)}> | ||
{getEndpointUrl(endpoint)} | ||
</Link> | ||
{index < endpoints.length - 1 && ", "} | ||
</span> | ||
); | ||
})} | ||
</Banner> | ||
) : ( | ||
<> | ||
<h2 className="text-md font-semibold">Deployment Strategy</h2> | ||
<CheckBox | ||
name="zero-downtime" | ||
label="Enable Zero-Downtime Deployment" | ||
checked={nextService.forceZeroDowntime} | ||
onChange={(e) => | ||
setNextService({ | ||
...nextService, | ||
forceZeroDowntime: e.currentTarget.checked, | ||
}) | ||
} | ||
/> | ||
<div className="flex gap-2"> | ||
<Tooltip text="When enabled, ignores Docker healthchecks and instead only waits to ensure the container stays up for at least 30 seconds."> | ||
<CheckBox | ||
name="simple-healthcheck" | ||
label="Use simple healthcheck (30s)" | ||
checked={nextService.naiveHealthCheck} | ||
onChange={(e) => | ||
setNextService({ | ||
...nextService, | ||
naiveHealthCheck: e.currentTarget.checked, | ||
}) | ||
} | ||
/> | ||
</Tooltip> | ||
</div> | ||
<div className="flex mt-4"> | ||
<Button | ||
name="autoscaling" | ||
className="w-40 flex font-semibold" | ||
type="submit" | ||
disabled={!changesExist} | ||
isLoading={modifyLoader.isLoading} | ||
> | ||
Save Changes | ||
</Button> | ||
{changesExist ? ( | ||
<Button | ||
className="w-40 ml-2 flex font-semibold" | ||
onClick={cancelChanges} | ||
variant="white" | ||
> | ||
Cancel | ||
</Button> | ||
) : null} | ||
</div> | ||
</> | ||
)} | ||
</div> | ||
</form> | ||
</Box> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.