Skip to content

Commit 814383c

Browse files
React FrontEnd (#12)
* Initial commit of code - not tested/functional * Sync all changes from github repo spring petclinic sample app, to our sample app. * Remove lombok and mapstruct / simplify This builds and runs now. * Updates and clean up * Clean up resources; prepare for react usage * Rename containing folder * Super major hackery * Cleanup * No longer needed * Consolidate tasks * More clean up * Task cleanup * Hardcode NR version * Clean up * Clean up * Clean upo * Sync port and version changes to Docker scripts * Gather NEW_RELIC_* environment variables and inject them into front-end during build * Rename project / entity name * Move react app to root and legacy app entrypoint to /welcome * Add build-time args for fulfilling the browser "agent" requirements * Add a build action for PRs * Actually don't need the Kotlin libs * Rename the step * Move react app to /react and legacy app entrypoint back to / --------- Co-authored-by: dsellarsnr <[email protected]>
1 parent 78f2f9e commit 814383c

File tree

117 files changed

+18847
-2999
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+18847
-2999
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
**/node_modules
2+
**/dist

.github/workflows/build_image.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Build the Docker Image
2+
3+
on:
4+
pull_request:
5+
types: [ opened, reopened, synchronize ]
6+
branches:
7+
- main
8+
9+
env:
10+
REGISTRY: ghcr.io
11+
IMAGE_NAME: ${{ github.repository }}
12+
13+
jobs:
14+
build-image:
15+
runs-on: ubuntu-latest
16+
17+
permissions: write-all
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
23+
- name: Build Docker image
24+
id: push
25+
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
26+
with:
27+
context: .
28+
push: false
29+
build-args: |
30+
BROWSER_LICENSE_KEY=${{ secrets.BROWSER_LICENSE_KEY }},
31+
BROWSER_ACCOUNT_ID=${{ secrets.BROWSER_ACCOUNT_ID }},
32+
BROWSER_TRUST_KEY=${{ secrets.BROWSER_TRUST_KEY }},
33+
BROWSER_AGENT_ID=${{ secrets.BROWSER_AGENT_ID }},
34+
BROWSER_APPLICATION_ID=${{ secrets.BROWSER_APPLICATION_ID }}

.github/workflows/publish_image.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ jobs:
4141
tags: ${{ steps.meta.outputs.tags }}
4242
labels: ${{ steps.meta.outputs.labels }}
4343
github-token: ${{ secrets.GITHUB_TOKEN }}
44+
build-args: |
45+
BROWSER_LICENSE_KEY=${{ secrets.BROWSER_LICENSE_KEY }},
46+
BROWSER_ACCOUNT_ID=${{ secrets.BROWSER_ACCOUNT_ID }},
47+
BROWSER_TRUST_KEY=${{ secrets.BROWSER_TRUST_KEY }},
48+
BROWSER_AGENT_ID=${{ secrets.BROWSER_AGENT_ID }},
49+
BROWSER_APPLICATION_ID=${{ secrets.BROWSER_APPLICATION_ID }}
4450
4551
- name: Generate artifact attestation
4652
uses: actions/attest-build-provenance@v1

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ _site/
1414
*.css
1515
!petclinic.css
1616
logs/
17+
/client/dist
18+
/client/node_modules
19+
.idea/
20+
21+
src/main/resources/static/react
22+
23+
.kotlin/*
24+
25+
newrelic/extension.xsd
26+
newrelic/*.jar

Dockerfile

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,43 @@ FROM gradle:jdk17 AS base
33
RUN apt-get update && apt-get install -y curl
44

55
WORKDIR /app
6-
EXPOSE 8080
6+
EXPOSE 8081
77

88
FROM gradle:jdk17 AS build
99
WORKDIR /src
10+
11+
ENV NODE_VERSION=20.15.1
12+
RUN apt-get update && apt-get install -y curl
13+
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/refs/tags/v0.40.1/install.sh | bash
14+
ENV NVM_DIR=/root/.nvm
15+
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
16+
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
17+
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
18+
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
19+
1020
COPY src ./src
21+
COPY client ./client
1122
COPY gradle ./gradle
12-
COPY build.gradle settings.gradle ./
23+
COPY build.gradle settings.gradle browserMonitoringTemplate.js ./
1324
COPY --chmod=0755 gradlew ./
25+
26+
ARG BROWSER_LICENSE_KEY
27+
ARG BROWSER_ACCOUNT_ID
28+
ARG BROWSER_TRUST_KEY
29+
ARG BROWSER_AGENT_ID
30+
ARG BROWSER_APPLICATION_ID
31+
32+
ENV BROWSER_LICENSE_KEY=$BROWSER_LICENSE_KEY
33+
ENV BROWSER_ACCOUNT_ID=$BROWSER_ACCOUNT_ID
34+
ENV BROWSER_TRUST_KEY=$BROWSER_TRUST_KEY
35+
ENV BROWSER_AGENT_ID=$BROWSER_AGENT_ID
36+
ENV BROWSER_APPLICATION_ID=$BROWSER_APPLICATION_ID
37+
1438
RUN --mount=type=cache,target=/root/.gradle ./gradlew build --console=plain --info --no-daemon --no-watch-fs
1539

1640
FROM base AS final
1741
WORKDIR /app
18-
COPY --from=build /src/build/libs/java-spring-demogorgon-1.0.0.jar .
42+
COPY --from=build /src/build/libs/petclinic-backend-1.0.0.jar .
1943
COPY ["newrelic/", "./newrelic"]
2044

2145
COPY --chmod=0755 entrypoint.sh /

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ NEW_RELIC_LICENSE_KEY=12345 ./gradlew bootRun
2121
```
2222
Substitute 12345 with your New Relic license key.
2323

24-
You can then access petclinic here: http://localhost:8080/
24+
You can then access petclinic here:
25+
- react: http://localhost:8081/react
26+
- legacy app: http://localhost:8081/
2527

2628
The main page will have some links that exercise auto and manual instrumentation in different modes.
2729

browserMonitoringTemplate.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle

Lines changed: 135 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,165 @@
1-
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
1+
import org.apache.tools.ant.taskdefs.condition.Os
22

33
plugins {
4-
id 'org.springframework.boot' version '2.7.18'
54
id 'java'
6-
id 'org.jetbrains.kotlin.jvm' version '1.7.21'
7-
id "com.github.ben-manes.versions" version "0.51.0"
5+
id 'org.springframework.boot' version '3.3.2'
6+
id 'io.spring.dependency-management' version '1.1.6'
87
}
98

10-
apply plugin: 'io.spring.dependency-management'
9+
apply plugin: 'java'
10+
11+
ext {
12+
newrelic_version = '8.15.0'
13+
}
1114

1215
group = 'org.springframework.samples'
1316
version = '1.0.0'
14-
sourceCompatibility = JavaVersion.VERSION_17
15-
targetCompatibility = JavaVersion.VERSION_17
16-
17-
tasks.withType(KotlinCompile) {
18-
kotlinOptions.jvmTarget = JavaVersion.VERSION_17
19-
}
2017

2118
repositories {
2219
mavenCentral()
2320
}
2421

25-
ext.webjarsFontawesomeVersion = "4.7.0"
26-
ext.webjarsBootstrapVersion = "5.1.3"
27-
2822
dependencies {
23+
implementation 'io.micrometer:micrometer-core'
2924
implementation 'org.springframework.boot:spring-boot-starter-cache'
3025
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3126
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
3227
implementation 'org.springframework.boot:spring-boot-starter-web'
3328
implementation 'org.springframework.boot:spring-boot-starter-validation'
34-
implementation 'org.springframework.boot:spring-boot-starter-aop'
3529
implementation 'javax.cache:cache-api'
30+
implementation 'jakarta.xml.bind:jakarta.xml.bind-api'
31+
implementation 'jakarta.annotation:jakarta.annotation-api'
32+
implementation "com.newrelic.agent.java:newrelic-api:$newrelic_version"
33+
3634
runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'
37-
runtimeOnly 'org.webjars:webjars-locator-core'
38-
runtimeOnly "org.webjars.npm:bootstrap:${webjarsBootstrapVersion}"
39-
runtimeOnly "org.webjars.npm:font-awesome:${webjarsFontawesomeVersion}"
40-
runtimeOnly 'org.ehcache:ehcache'
35+
runtimeOnly 'com.github.ben-manes.caffeine:caffeine'
4136
runtimeOnly 'com.h2database:h2'
42-
runtimeOnly 'mysql:mysql-connector-java:8.0.28'
43-
runtimeOnly 'org.postgresql:postgresql'
44-
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
45-
implementation 'com.newrelic.agent.java:newrelic-api:8.14.0'
46-
implementation 'io.micrometer:micrometer-core:1.10.3'
4737

48-
// developmentOnly 'com.newrelic.agent.java:newrelic-java:7.8.0'
4938
developmentOnly 'org.springframework.boot:spring-boot-devtools'
50-
testImplementation 'org.springframework.boot:spring-boot-starter-test'
5139
}
5240

53-
tasks.named('test') {
54-
useJUnitPlatform()
41+
build {
42+
dependsOn 'buildFrontEnd'
43+
mustRunAfter 'buildFrontEnd'
5544
}
5645

5746
bootRun {
58-
jvmArgs = ["-javaagent:${projectDir}/newrelic/newrelic.jar"]
47+
dependsOn 'downloadNewRelicAgent', 'buildFrontEnd', 'build'
48+
mustRunAfter 'downloadNewRelicAgent', 'buildFrontEnd', 'build'
49+
50+
jvmArgs = ["-javaagent:${projectDir}/newrelic/newrelic-v${newrelic_version}.jar"]
51+
}
52+
53+
tasks.register('buildFrontEnd', DefaultTask) {
54+
group 'New Relic'
55+
doFirst {
56+
exec {
57+
String cmd = 'npm'
58+
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
59+
cmd = "npm.cmd"
60+
}
61+
62+
workingDir "$rootDir/client"
63+
commandLine cmd, 'run', 'build'
64+
}
65+
66+
delete 'src/main/resources/static/react'
67+
68+
copy {
69+
from "client/dist"
70+
into "src/main/resources/static/react"
71+
}
72+
}
73+
74+
doLast {
75+
def newRelicLicenseKey = System.getenv('BROWSER_LICENSE_KEY')
76+
def newRelicAccountId = System.getenv('BROWSER_ACCOUNT_ID')
77+
def newRelicTrustKey = System.getenv('BROWSER_TRUST_KEY')
78+
def newRelicAgentId = System.getenv('BROWSER_AGENT_ID')
79+
def newRelicApplicationId = System.getenv('BROWSER_APPLICATION_ID')
80+
81+
if (newRelicTrustKey == null && newRelicAccountId != null) {
82+
newRelicTrustKey = newRelicAccountId
83+
}
84+
85+
if (newRelicAgentId == null && newRelicApplicationId != null) {
86+
newRelicAgentId = newRelicApplicationId
87+
}
88+
89+
if (newRelicLicenseKey == null || newRelicAccountId == null || newRelicTrustKey == null || newRelicAgentId == null || newRelicApplicationId == null) {
90+
throw new GradleException('BROWSER_* ENVIRONMENT VARIABLES NOT SET')
91+
}
92+
93+
def templateJavaScript = file("$rootDir/browserMonitoringTemplate.js")
94+
def indexFile = file("$rootDir/src/main/resources/static/react/index.html")
95+
96+
def indexFileContents = indexFile.getText()
97+
def templateContents = templateJavaScript.getText()
98+
99+
templateContents = templateContents.replace("{{NEW_RELIC_APPLICATION_ID}}", newRelicApplicationId)
100+
templateContents = templateContents.replace("{{NEW_RELIC_AGENT_ID}}", newRelicAgentId)
101+
templateContents = templateContents.replace("{{NEW_RELIC_ACCOUNT_ID}}", newRelicAccountId)
102+
templateContents = templateContents.replace("{{NEW_RELIC_TRUST_KEY}}", newRelicTrustKey)
103+
templateContents = templateContents.replace("{{NEW_RELIC_LICENSE_KEY}}", newRelicLicenseKey)
104+
105+
templateContents = "<script type=\"text/javascript\">\n" + templateContents + "\n</script>"
106+
def newIndexFileContent = indexFileContents.replace("<script id=\"new-relic-template\"><!-- FILLED OUT DURING BUILD --></script>", templateContents)
107+
108+
indexFile.write(newIndexFileContent)
109+
}
110+
111+
outputs.file('src/main/resources/static/react/index.html')
112+
outputs.dir('src/main/resources/static/react/assets')
113+
}
114+
115+
tasks.register('downloadNewRelicAgent', DefaultTask) {
116+
group 'New Relic'
117+
118+
onlyIf {
119+
def agentExists = file("newrelic/newrelic-v${newrelic_version}.jar").exists()
120+
def agentDoesNotExist = !agentExists
121+
122+
if (agentDoesNotExist) {
123+
println "Downloading New Relic Java Agent v${newrelic_version}..."
124+
} else {
125+
println "Found Local Java Agent v${newrelic_version}; Skipping Task (delete " +
126+
"newrelic-v${newrelic_version}.jar to force the download again)"
127+
}
128+
129+
return agentDoesNotExist
130+
}
131+
132+
doFirst {
133+
mkdir 'newrelic'
134+
delete(fileTree("newrelic") {
135+
include("**/newrelic-v*.jar")
136+
})
137+
138+
def fileUrl = "https://download.newrelic.com/newrelic/java-agent/newrelic-agent/current/newrelic-java-${newrelic_version}.zip"
139+
def destinationFile = file("$rootDir/newrelic/newrelic.zip")
140+
141+
new URL(fileUrl).withInputStream { inputStream ->
142+
destinationFile.withOutputStream { outputStream ->
143+
outputStream << inputStream
144+
}
145+
}
146+
}
147+
148+
doLast {
149+
copy {
150+
from zipTree(file('newrelic/newrelic.zip'))
151+
into rootDir
152+
exclude("newrelic/newrelic.yml")
153+
exclude("newrelic/LICENSE")
154+
exclude("newrelic/THIRD_PARTY_NOTICES.md")
155+
exclude("newrelic/extension-example.xml")
156+
exclude("newrelic/newrelic-api.jar")
157+
exclude("newrelic/newrelic-api-javadoc.jar")
158+
exclude("newrelic/newrelic-api-sources.jar")
159+
}
160+
161+
file("newrelic/newrelic.jar").renameTo("newrelic/newrelic-v${newrelic_version}.jar")
162+
163+
delete("newrelic/newrelic.zip")
164+
}
59165
}

client/.eslintrc.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module.exports = {
2+
root: true,
3+
env: { browser: true, es2020: true },
4+
extends: [
5+
'eslint:recommended',
6+
'plugin:@typescript-eslint/recommended',
7+
'plugin:react-hooks/recommended',
8+
],
9+
ignorePatterns: ['dist', '.eslintrc.cjs'],
10+
parser: '@typescript-eslint/parser',
11+
plugins: ['react-refresh'],
12+
rules: {
13+
'react-refresh/only-export-components': [
14+
'warn',
15+
{ allowConstantExport: true },
16+
],
17+
},
18+
}

client/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>PetClinic</title>
7+
<script id="new-relic-template"><!-- FILLED OUT DURING BUILD --></script>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)