From e38c65e6d5e2a672dea39d6a516494b63fc96587 Mon Sep 17 00:00:00 2001 From: Craig Atkinson Date: Wed, 3 Apr 2024 06:44:07 -0500 Subject: [PATCH] Adding test run badge to main page (#1219) --- .../src/main/kotlin/projektor/AppModule.kt | 2 +- .../projektor/badge/TestRunBadgeService.kt | 13 +++++ .../projektor/route/BadgeTestsRoutes.kt | 9 ++++ .../badge/TestRunTestsBadgeApplicationTest.kt | 50 +++++++++++++++++++ ui/src/Badge/tests/TestRunTestsBadge.tsx | 39 +++++++++++++++ ui/src/Coverage/CoverageSummary.tsx | 46 +++++++---------- ui/src/Dashboard/DashboardSummary.tsx | 21 +++++++- ui/src/TestCase/FailedTestCases.tsx | 11 +++- ui/src/TestRun/TestRunAllTests.tsx | 11 +++- ui/src/service/TestRunService.ts | 5 ++ 10 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 server/server-app/src/test/kotlin/projektor/badge/TestRunTestsBadgeApplicationTest.kt create mode 100644 ui/src/Badge/tests/TestRunTestsBadge.tsx diff --git a/server/server-app/src/main/kotlin/projektor/AppModule.kt b/server/server-app/src/main/kotlin/projektor/AppModule.kt index d99a32868..27ba41b6d 100644 --- a/server/server-app/src/main/kotlin/projektor/AppModule.kt +++ b/server/server-app/src/main/kotlin/projektor/AppModule.kt @@ -145,6 +145,6 @@ fun createAppModule( single { CoverageBadgeService(get(), get(), get()) } single { SvgCoverageBadgeCreator() } - single { TestRunBadgeService(get(), get()) } + single { TestRunBadgeService(get(), get(), get()) } single { SvgTestRunBadgeCreator() } } diff --git a/server/server-app/src/main/kotlin/projektor/badge/TestRunBadgeService.kt b/server/server-app/src/main/kotlin/projektor/badge/TestRunBadgeService.kt index e23436d85..62da10731 100644 --- a/server/server-app/src/main/kotlin/projektor/badge/TestRunBadgeService.kt +++ b/server/server-app/src/main/kotlin/projektor/badge/TestRunBadgeService.kt @@ -2,13 +2,26 @@ package projektor.badge import projektor.compare.PreviousTestRunService import projektor.notification.badge.SvgTestRunBadgeCreator +import projektor.server.api.PublicId import projektor.server.api.repository.BranchSearch import projektor.server.api.repository.BranchType +import projektor.testrun.TestRunService class TestRunBadgeService( private val previousTestRunService: PreviousTestRunService, private val testRunBadgeCreator: SvgTestRunBadgeCreator, + private val testRunService: TestRunService, ) { + suspend fun createTestsBadge(publicId: PublicId): String? { + val testRun = testRunService.fetchTestRun(publicId) + + return if (testRun != null) { + testRunBadgeCreator.createBadge(testRun.summary.passed) + } else { + null + } + } + suspend fun createTestsBadge(repoName: String, projectName: String?): String? { val previousTestRun = previousTestRunService.findMostRecentRun( repoName, diff --git a/server/server-app/src/main/kotlin/projektor/route/BadgeTestsRoutes.kt b/server/server-app/src/main/kotlin/projektor/route/BadgeTestsRoutes.kt index 46b9d8f51..85ae94140 100644 --- a/server/server-app/src/main/kotlin/projektor/route/BadgeTestsRoutes.kt +++ b/server/server-app/src/main/kotlin/projektor/route/BadgeTestsRoutes.kt @@ -4,8 +4,17 @@ import io.ktor.server.application.* import io.ktor.server.routing.* import io.ktor.server.util.* import projektor.badge.TestRunBadgeService +import projektor.server.api.PublicId fun Route.badgeTests(testRunBadgeService: TestRunBadgeService) { + get("/run/{publicId}/badge/tests") { + val publicId = call.parameters.getOrFail("publicId") + + val svgBadge = testRunBadgeService.createTestsBadge(PublicId(publicId)) + + respondWithSvg(svgBadge, call) + } + get("/repo/{orgPart}/{repoPart}/badge/tests") { val orgPart = call.parameters.getOrFail("orgPart") val repoPart = call.parameters.getOrFail("repoPart") diff --git a/server/server-app/src/test/kotlin/projektor/badge/TestRunTestsBadgeApplicationTest.kt b/server/server-app/src/test/kotlin/projektor/badge/TestRunTestsBadgeApplicationTest.kt new file mode 100644 index 000000000..121152780 --- /dev/null +++ b/server/server-app/src/test/kotlin/projektor/badge/TestRunTestsBadgeApplicationTest.kt @@ -0,0 +1,50 @@ +package projektor.badge + +import io.ktor.http.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Test +import projektor.ApplicationTestCase +import projektor.incomingresults.randomPublicId +import projektor.util.randomOrgAndRepo +import strikt.api.expectThat +import strikt.assertions.contains +import strikt.assertions.isEqualTo +import strikt.assertions.isNotNull + +class TestRunTestsBadgeApplicationTest : ApplicationTestCase() { + @Test + fun `when test run passed should tests create badge`() { + val repoName = randomOrgAndRepo() + + val publicId = randomPublicId() + + withTestApplication(::createTestApplication) { + handleRequest(HttpMethod.Get, "/run/$publicId/badge/tests") { + testRunDBGenerator.createSimpleTestRunInRepo(publicId, repoName, true, null) + }.apply { + expectThat(response.status()).isEqualTo(HttpStatusCode.OK) + expectThat(response.contentType().toString()).contains(ContentType.Image.SVG.toString()) + + expectThat(response.content).isNotNull().contains("passing") + } + } + } + + @Test + fun `when test run failed should tests create badge`() { + val repoName = randomOrgAndRepo() + + val publicId = randomPublicId() + + withTestApplication(::createTestApplication) { + handleRequest(HttpMethod.Get, "/run/$publicId/badge/tests") { + testRunDBGenerator.createSimpleFailingTestRunInRepo(publicId, repoName, true, null) + }.apply { + expectThat(response.status()).isEqualTo(HttpStatusCode.OK) + expectThat(response.contentType().toString()).contains(ContentType.Image.SVG.toString()) + + expectThat(response.content).isNotNull().contains("failing") + } + } + } +} diff --git a/ui/src/Badge/tests/TestRunTestsBadge.tsx b/ui/src/Badge/tests/TestRunTestsBadge.tsx new file mode 100644 index 000000000..e1f5d92db --- /dev/null +++ b/ui/src/Badge/tests/TestRunTestsBadge.tsx @@ -0,0 +1,39 @@ +import * as React from "react"; +import TestsBadge from "./TestsBadge"; +import { fetchTestsBadge } from "../../service/TestRunService"; + +interface TestRunTestsBadgeProps { + publicId: string; + repoName: string; + projectName?: string; +} + +const TestRunTestsBadge = ({ + publicId, + repoName, + projectName, +}: TestRunTestsBadgeProps) => { + const [badgeSvg, setBadgeSvg] = React.useState(null); + + React.useEffect(() => { + fetchTestsBadge(publicId) + .then((response) => { + setBadgeSvg(response.data); + }) + .catch((_) => {}); + }, [setBadgeSvg]); + + if (badgeSvg) { + return ( + + ); + } else { + return null; + } +}; + +export default TestRunTestsBadge; diff --git a/ui/src/Coverage/CoverageSummary.tsx b/ui/src/Coverage/CoverageSummary.tsx index dcb342707..d33517df2 100644 --- a/ui/src/Coverage/CoverageSummary.tsx +++ b/ui/src/Coverage/CoverageSummary.tsx @@ -7,7 +7,6 @@ import OverallCoverageGraphs from "./OverallCoverageGraphs"; import CleanLink from "../Link/CleanLink"; import { makeStyles } from "@material-ui/styles"; import TestRunCoverageBadge from "../Badge/coverage/TestRunCoverageBadge"; -import { Grid, Hidden } from "@material-ui/core"; interface CoverageSummaryProps { publicId: string; @@ -15,9 +14,11 @@ interface CoverageSummaryProps { } const useStyles = makeStyles(() => ({ + mainSection: { + marginTop: "20px", + }, coverageBadgeSection: { - marginTop: "15px", - marginLeft: "30px", + marginLeft: "15px", }, })); @@ -38,34 +39,25 @@ const CoverageSummary = ({ publicId, gitMetadata }: CoverageSummaryProps) => { if (coverage) { return ( -
- - - - - - - {gitMetadata && ( - - - - - - )} - +
+ + + + + + {gitMetadata && ( +
+ +
+ )}
); } else { diff --git a/ui/src/Dashboard/DashboardSummary.tsx b/ui/src/Dashboard/DashboardSummary.tsx index 9104f3073..636bd59b5 100644 --- a/ui/src/Dashboard/DashboardSummary.tsx +++ b/ui/src/Dashboard/DashboardSummary.tsx @@ -9,9 +9,10 @@ import TestRunCleanupDate from "./TestRunCleanupDate"; import TestRunMessages from "../TestRunMessages/TestRunMessages"; import GitRepoListItem from "./GitRepoListItem"; import DashboardSummaryItem from "./DashboardSummaryItem"; -import CleanLink from "../Link/CleanLink"; import { createGitHubUrl } from "../VersionControl/VersionControlHelpers"; import CleanLinkText from "../Link/CleanLinkText"; +import { makeStyles } from "@material-ui/styles"; +import TestRunTestsBadge from "../Badge/tests/TestRunTestsBadge"; interface DashboardSummaryProps { publicId: string; @@ -19,11 +20,19 @@ interface DashboardSummaryProps { gitMetadata?: TestRunGitMetadata; } +const useStyles = makeStyles(() => ({ + testsBadgeSection: { + marginLeft: "15px", + }, +})); + const DashboardSummary = ({ publicId, testRunSummary, gitMetadata, }: DashboardSummaryProps) => { + const classes = useStyles({}); + const { totalPassingCount, totalFailureCount, @@ -50,6 +59,7 @@ const DashboardSummary = ({ title={hasTests ? "Tests" : "Summary"} testid="dashboard-summary-title" /> + {hasTests ? ( @@ -66,6 +76,15 @@ const DashboardSummary = ({ totalCount={totalTestCount} horizontal={false} /> + {hasTests && gitMetadata && ( +
+ +
+ )}
) : null} {totalTestCount > 0 ? ( diff --git a/ui/src/TestCase/FailedTestCases.tsx b/ui/src/TestCase/FailedTestCases.tsx index 99d17397f..1acda3302 100644 --- a/ui/src/TestCase/FailedTestCases.tsx +++ b/ui/src/TestCase/FailedTestCases.tsx @@ -5,12 +5,21 @@ import { fetchFailedTestCases } from "../service/TestRunService"; import LoadingState from "../Loading/LoadingState"; import { RouteComponentProps } from "@reach/router"; import PageTitle from "../PageTitle"; +import { makeStyles } from "@material-ui/styles"; interface FailedTestCasesProps extends RouteComponentProps { publicId: string; } +const useStyles = makeStyles(() => ({ + mainSection: { + marginTop: "20px", + }, +})); + const FailedTestCases = ({ publicId }: FailedTestCasesProps) => { + const classes = useStyles({}); + const [failedTestCases, setFailedTestCases] = React.useState([]); const [loadingState, setLoadingState] = React.useState(LoadingState.Loading); @@ -22,7 +31,7 @@ const FailedTestCases = ({ publicId }: FailedTestCasesProps) => { }, [setFailedTestCases, setLoadingState]); return ( -
+
({ + mainSection: { + marginTop: "20px", + }, +})); + const TestRunAllTests = ({ publicId }: TestRunAllTestsProps) => { + const classes = useStyles({}); + const [testRun, setTestRun] = React.useState(null); const [testRunLoadingState, setTestRunLoadingState] = React.useState( LoadingState.Loading, @@ -27,7 +36,7 @@ const TestRunAllTests = ({ publicId }: TestRunAllTestsProps) => { }, [setTestRun, setTestRunLoadingState]); return ( -
+
> => // @ts-ignore axiosInstance.get(`/run/${publicId}/badge/coverage`); +const fetchTestsBadge = (publicId: string): Promise> => + // @ts-ignore + axiosInstance.get(`/run/${publicId}/badge/tests`); + const fetchPerformanceResults = ( publicId: string, ): Promise> => @@ -193,6 +197,7 @@ export { fetchCoverageExists, fetchCoverageGroupFiles, fetchCoverageBadge, + fetchTestsBadge, fetchPerformanceResults, fetchOverallCoverageStats, fetchSlowTestCases,