Skip to content

Commit 03239a0

Browse files
committed
If runner feature disabled, don't show 'runner' navlink or pages
1 parent 1ca780a commit 03239a0

11 files changed

Lines changed: 162 additions & 27 deletions

File tree

api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This API is built with [Spring boot framework](https://spring.io)
1010
## Run dependencies
1111

1212
Before you can start packit you need to run `./scripts/run-dependencies` from the project root
13-
to start database and `outpack_server` instances.
13+
to start database, `orderly.runner` server and worker, and `outpack_server` instances.
1414

1515
## Starting App
1616

api/app/src/main/kotlin/packit/controllers/RunnerController.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import packit.service.RunnerService
1818
@RequestMapping("/runner")
1919
@PreAuthorize("hasAuthority('packet.run')")
2020
class RunnerController(private val runnerService: RunnerService) {
21+
@GetMapping("/enabled")
22+
fun getEnabled(): ResponseEntity<Boolean> {
23+
return ResponseEntity.ok(runnerService.getEnabled())
24+
}
25+
2126
@GetMapping("/version")
2227
fun getVersion(): ResponseEntity<OrderlyRunnerVersion> {
2328
return ResponseEntity.ok(runnerService.getVersion())

api/app/src/main/kotlin/packit/service/RunnerService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import packit.model.dto.*
1414
import packit.repository.RunInfoRepository
1515

1616
interface RunnerService {
17+
fun getEnabled(): Boolean = true
1718
fun getVersion(): OrderlyRunnerVersion
1819
fun gitFetch()
1920
fun getBranches(): GitBranches
@@ -167,6 +168,7 @@ class DisabledRunnerService : RunnerService {
167168
throw PackitException("runnerDisabled", HttpStatus.FORBIDDEN)
168169
}
169170

171+
override fun getEnabled(): Boolean = false
170172
override fun getVersion(): OrderlyRunnerVersion = error()
171173
override fun gitFetch() = error()
172174
override fun getBranches(): GitBranches = error()

api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ class RunnerControllerTest : IntegrationTest() {
9696
userRepository.delete(testUser)
9797
}
9898

99+
@Test
100+
@WithAuthenticatedUser(authorities = ["packet.run"])
101+
fun `reports enabled status`() {
102+
val res: ResponseEntity<JsonNode> = restTemplate.exchange(
103+
"/runner/enabled",
104+
HttpMethod.GET,
105+
getTokenizedHttpEntity()
106+
)
107+
108+
assertSuccess(res)
109+
110+
assertEquals(true, res.body!!.asBoolean())
111+
}
112+
99113
@Test
100114
@WithAuthenticatedUser(authorities = ["packet.run"])
101115
fun `can get orderly runner version`() {
@@ -334,6 +348,20 @@ class UnknownRepoRunnerControllerTest : IntegrationTest() {
334348

335349
@TestPropertySource(properties = ["orderly.runner.enabled=false"])
336350
class DisabledRunnerControllerTest : IntegrationTest() {
351+
@Test
352+
@WithAuthenticatedUser(authorities = ["packet.run"])
353+
fun `reports disabled status`() {
354+
val res: ResponseEntity<JsonNode> = restTemplate.exchange(
355+
"/runner/enabled",
356+
HttpMethod.GET,
357+
getTokenizedHttpEntity()
358+
)
359+
360+
assertSuccess(res)
361+
362+
assertEquals(false, res.body!!.asBoolean())
363+
}
364+
337365
@Test
338366
@WithAuthenticatedUser(authorities = ["packet.run"])
339367
fun `cannot get orderly runner version`() {

api/app/src/test/kotlin/packit/unit/service/RunnerServiceTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ class RunnerServiceTest {
7676
userService
7777
)
7878

79+
@Test
80+
fun `can report enabled status`() {
81+
val result = sut.getEnabled()
82+
assertEquals(result, true)
83+
}
84+
7985
@Test
8086
fun `can get version`() {
8187
val result = sut.getVersion()

app/src/app/components/contents/runner/PacketRunnerLayout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SidebarItem } from "@lib/types/SidebarItem";
44
import { useUser } from "../../providers/UserProvider";
55
import { Sidebar } from "../common/Sidebar";
66
import { Unauthorized } from "../common/Unauthorized";
7+
import { useGetRunnerEnabled } from "@components/header/hooks/useGetRunnerEnabled";
78

89
const sidebarItems: SidebarItem[] = [
910
{
@@ -18,7 +19,9 @@ const sidebarItems: SidebarItem[] = [
1819

1920
export const PacketRunnerLayout = () => {
2021
const { authorities } = useUser();
21-
if (!hasPacketRunPermission(authorities)) {
22+
const { isRunnerEnabled } = useGetRunnerEnabled(hasPacketRunPermission(authorities));
23+
24+
if (!hasPacketRunPermission(authorities) || isRunnerEnabled === false) {
2225
return <Unauthorized />;
2326
}
2427
return (

app/src/app/components/header/NavMenu.tsx

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,30 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuIte
88
import { Menu } from "lucide-react";
99
import { NavLink } from "react-router-dom";
1010
import { Button, buttonVariants } from "../Base/Button";
11-
11+
import { useGetRunnerEnabled } from "./hooks/useGetRunnerEnabled";
1212
interface NavMenuProps extends React.HTMLAttributes<HTMLElement> {
1313
authorities: string[];
1414
}
1515
export const NavMenu = ({ className, authorities, ...props }: NavMenuProps) => {
16-
const NavItems: { [key: string]: string } = {
17-
runner: "Runner"
18-
// accessibility: "Accessibility",
19-
};
16+
const canRunPackets = hasPacketRunPermission(authorities);
17+
const { isRunnerEnabled, isLoading: isRunnerEnabledLoading } = useGetRunnerEnabled(canRunPackets);
18+
19+
const NavItems: { [key: string]: string } = {};
2020

21-
const displayableItems = Object.keys(NavItems).filter((to) => {
22-
if (to === "runner" && !hasPacketRunPermission(authorities)) {
23-
return false;
24-
} else {
25-
return true;
26-
}
27-
});
21+
if (canRunPackets && !isRunnerEnabledLoading && isRunnerEnabled) {
22+
NavItems.runner = "Runner";
23+
}
2824

2925
// Special case: route "Admin" to appropriate tab depending on user perms
3026
if (hasUserManagePermission(authorities) || hasGlobalPacketManagePermission(authorities)) {
3127
const key = hasUserManagePermission(authorities) ? "manage-roles" : "resync-packets";
3228
NavItems[key] = "Admin";
33-
displayableItems.push(key);
3429
}
3530

3631
return (
3732
<div className="flex-1">
3833
<nav className={cn("flex items-center space-x-2 pr-4 lg:pr-6 justify-end", className)} {...props}>
39-
{displayableItems.map((to) => (
34+
{Object.entries(NavItems).map(([to, label]) => (
4035
<NavLink
4136
to={to}
4237
key={to}
@@ -51,7 +46,7 @@ export const NavMenu = ({ className, authorities, ...props }: NavMenuProps) => {
5146
)
5247
}
5348
>
54-
{NavItems[to]}
49+
{label}
5550
</NavLink>
5651
))}
5752
</nav>
@@ -63,9 +58,9 @@ export const NavMenu = ({ className, authorities, ...props }: NavMenuProps) => {
6358
</Button>
6459
</DropdownMenuTrigger>
6560
<DropdownMenuContent className="w-48">
66-
{displayableItems.map((to) => (
61+
{Object.entries(NavItems).map(([to, label]) => (
6762
<DropdownMenuItem key={to} asChild>
68-
<NavLink to={to}>{NavItems[to]}</NavLink>
63+
<NavLink to={to}>{label}</NavLink>
6964
</DropdownMenuItem>
7065
))}
7166
</DropdownMenuContent>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import useSWR from "swr";
2+
import appConfig from "@config/appConfig";
3+
import { fetcher } from "@lib/fetch";
4+
5+
export const useGetRunnerEnabled = (shouldFetch = true) => {
6+
const { data, error, isLoading } = useSWR<boolean>(
7+
shouldFetch ? `${appConfig.apiUrl()}/runner/enabled` : null,
8+
(url: string) => fetcher({ url }),
9+
{
10+
revalidateOnFocus: false
11+
}
12+
);
13+
14+
return {
15+
isRunnerEnabled: data === true,
16+
isLoading,
17+
error
18+
};
19+
};

app/src/tests/components/contents/runner/PacketRunnerLayout.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ import userEvent from "@testing-library/user-event";
33
import { MemoryRouter, Route, Routes } from "react-router-dom";
44
import { PacketRunnerLayout } from "@components/contents/runner";
55
import * as UserProvider from "@components/providers/UserProvider";
6+
import { useGetRunnerEnabled } from "@components/header/hooks/useGetRunnerEnabled";
67

78
const mockUseUser = vitest.spyOn(UserProvider, "useUser");
9+
10+
vitest.mock("@components/header/hooks/useGetRunnerEnabled", () => ({
11+
useGetRunnerEnabled: vitest.fn()
12+
}));
13+
14+
const mockedUseGetRunnerEnabled = vitest.mocked(useGetRunnerEnabled);
15+
816
describe("packet runner component", () => {
917
const renderElement = () => {
1018
return render(
@@ -21,6 +29,11 @@ describe("packet runner component", () => {
2129

2230
it("should allow navigation between run and logs in from sidebar given packet.run permission", async () => {
2331
mockUseUser.mockReturnValue({ authorities: ["packet.run"] } as any);
32+
mockedUseGetRunnerEnabled.mockReturnValue({
33+
isRunnerEnabled: true,
34+
isLoading: false,
35+
error: undefined
36+
});
2437
renderElement();
2538

2639
// ensure correct url route is loaded
@@ -36,9 +49,27 @@ describe("packet runner component", () => {
3649
});
3750

3851
it("should show unauthorized when user does not have packet.run authority", () => {
52+
mockedUseGetRunnerEnabled.mockReturnValue({
53+
isRunnerEnabled: true,
54+
isLoading: false,
55+
error: undefined
56+
});
3957
mockUseUser.mockReturnValue({ authorities: [""] } as any);
4058
renderElement();
4159

4260
expect(screen.getByText(/Unauthorized/)).toBeVisible();
4361
});
62+
63+
it("should show unauthorized when orderly runner is not enabled", () => {
64+
mockUseUser.mockReturnValue({ authorities: ["packet.run"] } as any);
65+
mockedUseGetRunnerEnabled.mockReturnValue({
66+
isRunnerEnabled: false,
67+
isLoading: false,
68+
error: undefined
69+
});
70+
71+
renderElement();
72+
73+
expect(screen.getByText(/Unauthorized/)).toBeVisible();
74+
});
4475
});

app/src/tests/components/header/Header.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,24 @@ import { BrandingProvider } from "@components/providers/BrandingProvider";
1010
import { expectThemeClass, handleRequestWithEnabledThemes } from "../../testUtils";
1111
import { LocalStorageKeys } from "@lib/types/LocalStorageKeys";
1212
import { SWRConfig } from "swr";
13+
import { useGetRunnerEnabled } from "@components/header/hooks/useGetRunnerEnabled";
1314

1415
const mockUseUser = vitest.spyOn(UserProvider, "useUser");
1516

17+
vitest.mock("@components/header/hooks/useGetRunnerEnabled", () => ({
18+
useGetRunnerEnabled: vitest.fn()
19+
}));
20+
21+
const mockedUseGetRunnerEnabled = vitest.mocked(useGetRunnerEnabled);
22+
1623
describe("header component", () => {
1724
const renderElement = () => {
25+
mockedUseGetRunnerEnabled.mockReturnValue({
26+
isRunnerEnabled: true,
27+
isLoading: false,
28+
error: undefined
29+
});
30+
1831
return render(
1932
<SWRConfig value={{ provider: () => new Map() }}>
2033
<MemoryRouter>

0 commit comments

Comments
 (0)