From 7ac024fd469b5caa162711940e4633b69ae52691 Mon Sep 17 00:00:00 2001 From: David Mears Date: Tue, 31 Mar 2026 10:42:04 +0100 Subject: [PATCH 1/6] Distinguish core/installed R packages --- .../packit/model/dto/RunnerPackageDto.kt | 1 + .../controllers/RunnerControllerTest.kt | 2 +- .../services/OrderlyRunnerClientTest.kt | 2 +- .../unit/controllers/RunnerControllerTest.kt | 7 +- .../packit/unit/service/RunnerServiceTest.kt | 8 +-- .../contents/runner/PacketRunnerPackages.tsx | 64 ++++++++++++++----- .../runner/PacketRunnerPackages.test.tsx | 23 ++++++- app/src/tests/mocks.ts | 6 +- app/src/types.ts | 3 +- scripts/common | 2 +- 10 files changed, 87 insertions(+), 31 deletions(-) diff --git a/api/app/src/main/kotlin/packit/model/dto/RunnerPackageDto.kt b/api/app/src/main/kotlin/packit/model/dto/RunnerPackageDto.kt index 84492e415..d96fdcfaa 100644 --- a/api/app/src/main/kotlin/packit/model/dto/RunnerPackageDto.kt +++ b/api/app/src/main/kotlin/packit/model/dto/RunnerPackageDto.kt @@ -3,4 +3,5 @@ package packit.model.dto data class RunnerPackageDto( val name: String, val version: String, + val location: String, ) diff --git a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt index 06cbdd3bb..c9e9d82a0 100644 --- a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt +++ b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt @@ -128,7 +128,7 @@ class RunnerControllerTest : IntegrationTest() { HttpMethod.GET, getTokenizedHttpEntity() ) - assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1")), res.body) + assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")), res.body) } @Test diff --git a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt index e47ad7343..c69d8707d 100644 --- a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt +++ b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt @@ -51,7 +51,7 @@ class OrderlyRunnerClientTest( val result = sut.getPackages() assertIs>(result) - assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1")), result) + assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")), result) } @Test diff --git a/api/app/src/test/kotlin/packit/unit/controllers/RunnerControllerTest.kt b/api/app/src/test/kotlin/packit/unit/controllers/RunnerControllerTest.kt index 32bc5a1b3..c8057685f 100644 --- a/api/app/src/test/kotlin/packit/unit/controllers/RunnerControllerTest.kt +++ b/api/app/src/test/kotlin/packit/unit/controllers/RunnerControllerTest.kt @@ -13,8 +13,8 @@ import kotlin.test.assertEquals class RunnerControllerTest { private val runnerService = mock { on { getPackages() } doReturn listOf( - RunnerPackageDto("package1", "1.0.0"), - RunnerPackageDto("package2", "2.0.0"), + RunnerPackageDto("package1", "1.0.0", "/library"), + RunnerPackageDto("package2", "2.0.0", "/usr/lib/R/library"), ) } @@ -33,5 +33,8 @@ class RunnerControllerTest { assertThat( listOf(responseBody?.get(0)?.version, responseBody?.get(1)?.version) ).containsExactly("1.0.0", "2.0.0") + assertThat( + listOf(responseBody?.get(0)?.location, responseBody?.get(1)?.location) + ).containsExactly("/library", "/usr/lib/R/library") } } diff --git a/api/app/src/test/kotlin/packit/unit/service/RunnerServiceTest.kt b/api/app/src/test/kotlin/packit/unit/service/RunnerServiceTest.kt index d8221e1fe..29bd3bc3e 100644 --- a/api/app/src/test/kotlin/packit/unit/service/RunnerServiceTest.kt +++ b/api/app/src/test/kotlin/packit/unit/service/RunnerServiceTest.kt @@ -56,8 +56,8 @@ class RunnerServiceTest { private val client = mock { on { getVersion() } doReturn version on { getPackages() } doReturn listOf( - RunnerPackageDto("package1", "1.0.0"), - RunnerPackageDto("package2", "2.0.0"), + RunnerPackageDto("package1", "1.0.0", "/library"), + RunnerPackageDto("package2", "2.0.0", "/usr/lib/R/library"), ) } private val runInfoRepository = mock { @@ -91,8 +91,8 @@ class RunnerServiceTest { val result = sut.getPackages() assertEquals( listOf( - RunnerPackageDto("package1", "1.0.0"), - RunnerPackageDto("package2", "2.0.0"), + RunnerPackageDto("package1", "1.0.0", "/library"), + RunnerPackageDto("package2", "2.0.0", "/usr/lib/R/library"), ), result ) diff --git a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx index e570794f8..0900baf32 100644 --- a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx +++ b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx @@ -1,9 +1,9 @@ -import { Library } from "lucide-react"; import { useGetPackages } from "./hooks/useGetPackages"; import { Skeleton } from "@components/Base/Skeleton"; import { HttpStatus } from "@lib/types/HttpStatus"; import { Unauthorized } from "../common/Unauthorized"; import { ErrorComponent } from "../common/ErrorComponent"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@components/Base/Accordion"; export const PacketRunnerPackages = () => { const { packages, error } = useGetPackages(); @@ -19,6 +19,9 @@ export const PacketRunnerPackages = () => { return ; } + const sharedLibraryPackages = packages.filter((pkg) => pkg.location === "/library"); + const otherPackages = packages.filter((pkg) => pkg.location !== "/library"); + return ( <>
@@ -26,22 +29,49 @@ export const PacketRunnerPackages = () => {

View installed R packages and versions

-

- This list includes only the additional packages installed in the runner's R library for use by reports, and - excludes some standard packages. -

- - -

Installed packages

-
-
    - {packages.map((pkg, name) => ( -
  • - {pkg.name} - {pkg.version} -
  • - ))} -
+ + + +

Runner packages

+
+ +

+ This list includes only the additional packages installed in the runner’s R library + for use by reports. +

+
    + {sharedLibraryPackages.map((pkg, name) => ( +
  • + {pkg.name} + {pkg.version} +
  • + ))} +
+
+
+ + +

Other packages

+
+

+ This list includes packages that are part of the base R installation, or which are available + in the runner environment without having been specifically installed in the runner’s + R library. They are included here for reference. It is recommended to specifically install + any packages your reports depend on in the runner’s R library to ensure consistent + behavior across different environments. +

+ +
    + {otherPackages.map((pkg, name) => ( +
  • + {pkg.name} + {pkg.version} +
  • + ))} +
+
+
+
); diff --git a/app/src/tests/components/contents/runner/PacketRunnerPackages.test.tsx b/app/src/tests/components/contents/runner/PacketRunnerPackages.test.tsx index dd223489e..6213d5e62 100644 --- a/app/src/tests/components/contents/runner/PacketRunnerPackages.test.tsx +++ b/app/src/tests/components/contents/runner/PacketRunnerPackages.test.tsx @@ -1,4 +1,5 @@ import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { PacketRunnerPackages } from "@components/contents/runner"; import { mockRunnerPackages } from "@/tests/mocks"; import { SWRConfig } from "swr"; @@ -18,17 +19,35 @@ describe("PacketRunnerPackages component", () => { ); - it("should render packages page with a list of installed packages", async () => { + it("should render packages page with accordion sections for library and other packages", async () => { renderComponent(); await waitFor(() => { expect(screen.getByText("Package versions")).toBeVisible(); }); - mockRunnerPackages.forEach((pkg) => { + expect(screen.getByText("Runner packages")).toBeVisible(); + expect(screen.getByText("Other packages")).toBeVisible(); + + const libraryPackages = mockRunnerPackages.filter((pkg) => pkg.location === "/library"); + const otherPackages = mockRunnerPackages.filter((pkg) => pkg.location !== "/library"); + libraryPackages.forEach((pkg) => { expect(screen.getByText(pkg.name)).toBeVisible(); expect(screen.getByText(pkg.version)).toBeVisible(); }); + otherPackages.forEach((pkg) => { + expect(screen.queryByText(pkg.name)).not.toBeInTheDocument(); + expect(screen.queryByText(pkg.version)).not.toBeInTheDocument(); + }); + + await userEvent.click(screen.getByText("Other packages")); + + await waitFor(() => { + otherPackages.forEach((pkg) => { + expect(screen.getByText(pkg.name)).toBeVisible(); + expect(screen.getByText(pkg.version)).toBeVisible(); + }); + }); }); it("should render unauthorized when 401 error fetching packages", async () => { diff --git a/app/src/tests/mocks.ts b/app/src/tests/mocks.ts index 6f2e77baf..aadc4e0bd 100644 --- a/app/src/tests/mocks.ts +++ b/app/src/tests/mocks.ts @@ -842,11 +842,13 @@ export const mockGitBranches: GitBranches = { export const mockRunnerPackages: RunnerPackage[] = [ { name: "package1", - version: "0.1.0" + version: "0.1.0", + location: "/library" }, { name: "package2", - version: "2.0.0" + version: "2.0.0", + location: "/usr/lib/R/library" } ]; diff --git a/app/src/types.ts b/app/src/types.ts index 614d458b2..59cc1026a 100644 --- a/app/src/types.ts +++ b/app/src/types.ts @@ -96,10 +96,11 @@ export interface Custom { }; } -// R packages installed into a shared library for the runner and workers +// R packages installed for the runner and workers export interface RunnerPackage { name: string; version: string; + location: string; } interface Description { diff --git a/scripts/common b/scripts/common index 68297d8d4..59e1d1a2d 100644 --- a/scripts/common +++ b/scripts/common @@ -3,7 +3,7 @@ set -exu # Export env vars needed for running test dependencies export OUTPACK_SERVER_IMAGE=docker.io/mrcide/outpack_server:main -export ORDERLY_RUNNER_IMAGE=ghcr.io/mrc-ide/orderly.runner:main +export ORDERLY_RUNNER_IMAGE=ghcr.io/mrc-ide/orderly.runner:library-list-report-all-packages export RUNNER_CONTAINER_NAMESPACE=orderly.runner export RUNNER_REDIS=$RUNNER_CONTAINER_NAMESPACE-redis From 979b9589cc15c2fa39e2e237cf44b6437db68cc9 Mon Sep 17 00:00:00 2001 From: David Mears Date: Wed, 1 Apr 2026 11:12:30 +0100 Subject: [PATCH 2/6] Update e2e test --- app/e2e/runnerPage.demo.spec.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/e2e/runnerPage.demo.spec.ts b/app/e2e/runnerPage.demo.spec.ts index 3dd78cd98..be90da5c3 100644 --- a/app/e2e/runnerPage.demo.spec.ts +++ b/app/e2e/runnerPage.demo.spec.ts @@ -1,22 +1,16 @@ -import { Locator } from "@playwright/test"; import { test, expect, TAG_DEMO_R_LIBRARY } from "./tagCheckFixture"; import { getContentLocator } from "./utils"; test.describe("Runner page", () => { - let content: Locator; - - test.beforeEach(async ({ page }) => { - await page.goto("./"); - content = await getContentLocator(page); - await page.getByRole("link", { name: "Runner" }).click(); - }); - test.describe("Packages tab", { tag: TAG_DEMO_R_LIBRARY }, () => { // Expect the example R package to be listed as installed; this package will not be present on // demo or prod environments, nor if PACKIT_HOST_R_LIBRARY_PATH was set to anything other than // the `./scripts/runnerDemoLib` path when running dependencies. // See api/README.md for more details. test("can see list of installed packages", async ({ page }) => { + await page.goto("./"); + const content = await getContentLocator(page); + await page.getByRole("link", { name: "Runner" }).click(); await page.getByRole("link", { name: "Package versions" }).click(); await expect(content.getByText(/minimalRPackage.*0\.0\.1/)).toBeVisible(); }); From f63e909f57022c28ed56980de74d379e5efb7c31 Mon Sep 17 00:00:00 2001 From: David Mears Date: Wed, 1 Apr 2026 11:13:59 +0100 Subject: [PATCH 3/6] Format --- .../contents/runner/PacketRunnerPackages.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx index 0900baf32..0587bd779 100644 --- a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx +++ b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx @@ -36,8 +36,8 @@ export const PacketRunnerPackages = () => {

- This list includes only the additional packages installed in the runner’s R library - for use by reports. + This list includes only the additional packages installed in the runner’s R library for use by + reports.

    {sharedLibraryPackages.map((pkg, name) => ( @@ -54,11 +54,10 @@ export const PacketRunnerPackages = () => {

    Other packages

    - This list includes packages that are part of the base R installation, or which are available - in the runner environment without having been specifically installed in the runner’s - R library. They are included here for reference. It is recommended to specifically install - any packages your reports depend on in the runner’s R library to ensure consistent - behavior across different environments. + This list includes packages that are part of the base R installation, or which are available in the runner + environment without having been specifically installed in the runner’s R library. They are included + here for reference. It is recommended to specifically install any packages your reports depend on in the + runner’s R library to ensure consistent behavior across different environments.

      From 0c82b0d19360dc2319b809aca03c418f7dc0d15d Mon Sep 17 00:00:00 2001 From: David Mears Date: Wed, 1 Apr 2026 12:01:14 +0100 Subject: [PATCH 4/6] Move p to correct place in html --- .../contents/runner/PacketRunnerPackages.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx index 0587bd779..0e7e779f8 100644 --- a/app/src/app/components/contents/runner/PacketRunnerPackages.tsx +++ b/app/src/app/components/contents/runner/PacketRunnerPackages.tsx @@ -53,13 +53,13 @@ export const PacketRunnerPackages = () => {

      Other packages

      -

      - This list includes packages that are part of the base R installation, or which are available in the runner - environment without having been specifically installed in the runner’s R library. They are included - here for reference. It is recommended to specifically install any packages your reports depend on in the - runner’s R library to ensure consistent behavior across different environments. -

      +

      + This list includes packages that are part of the base R installation, or which are available in the + runner environment without having been specifically installed in the runner’s R library. They are + included here for reference. It is recommended to specifically install any packages your reports depend + on in the runner’s R library to ensure consistent behavior across different environments. +

        {otherPackages.map((pkg, name) => (
      • From 134b8ad544ad63e2cb4cea12b3889a93811ca438 Mon Sep 17 00:00:00 2001 From: David Mears Date: Wed, 1 Apr 2026 12:17:24 +0100 Subject: [PATCH 5/6] Update unit test --- .../packit/integration/controllers/RunnerControllerTest.kt | 2 +- .../packit/integration/services/OrderlyRunnerClientTest.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt index c9e9d82a0..b5d54cda8 100644 --- a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt +++ b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt @@ -128,7 +128,7 @@ class RunnerControllerTest : IntegrationTest() { HttpMethod.GET, getTokenizedHttpEntity() ) - assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")), res.body) + assertThat(res.body).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) } @Test diff --git a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt index c69d8707d..4258fe637 100644 --- a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt +++ b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt @@ -14,6 +14,7 @@ import packit.model.dto.RunnerSubmitRunInfo import packit.service.OrderlyRunnerClient import kotlin.test.assertEquals import kotlin.test.assertIs +import org.assertj.core.api.Assertions.assertThat class OrderlyRunnerClientTest( @Value("\${orderly.runner.url}") @@ -51,7 +52,7 @@ class OrderlyRunnerClientTest( val result = sut.getPackages() assertIs>(result) - assertEquals(listOf(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")), result) + assertThat(result).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) } @Test From 6377bcef76a836d42018b48e3b2083e9c6be1b50 Mon Sep 17 00:00:00 2001 From: David Mears Date: Wed, 1 Apr 2026 12:52:27 +0100 Subject: [PATCH 6/6] Lint --- .../packit/integration/controllers/RunnerControllerTest.kt | 4 +++- .../packit/integration/services/OrderlyRunnerClientTest.kt | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt index b5d54cda8..220700a4e 100644 --- a/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt +++ b/api/app/src/test/kotlin/packit/integration/controllers/RunnerControllerTest.kt @@ -128,7 +128,9 @@ class RunnerControllerTest : IntegrationTest() { HttpMethod.GET, getTokenizedHttpEntity() ) - assertThat(res.body).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) + assertThat( + res.body + ).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) } @Test diff --git a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt index 4258fe637..eb81d891a 100644 --- a/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt +++ b/api/app/src/test/kotlin/packit/integration/services/OrderlyRunnerClientTest.kt @@ -1,5 +1,6 @@ package packit.integration.services +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Value @@ -14,7 +15,6 @@ import packit.model.dto.RunnerSubmitRunInfo import packit.service.OrderlyRunnerClient import kotlin.test.assertEquals import kotlin.test.assertIs -import org.assertj.core.api.Assertions.assertThat class OrderlyRunnerClientTest( @Value("\${orderly.runner.url}") @@ -52,7 +52,9 @@ class OrderlyRunnerClientTest( val result = sut.getPackages() assertIs>(result) - assertThat(result).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) + assertThat( + result + ).contains(RunnerPackageDto(name = "minimalRPackage", version = "0.0.1", location = "/library")) } @Test