Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React FrontEnd #12

Merged
merged 25 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0c1c093
Initial commit of code - not tested/functional
CalvinAllen Oct 4, 2024
e77bb99
Sync all changes from github repo spring petclinic sample app, to our…
CalvinAllen Oct 8, 2024
edbc577
Remove lombok and mapstruct / simplify
CalvinAllen Oct 9, 2024
bd768f8
Updates and clean up
CalvinAllen Oct 9, 2024
20ab133
Clean up resources; prepare for react usage
CalvinAllen Oct 9, 2024
3abab98
Rename containing folder
CalvinAllen Oct 9, 2024
0221cda
Super major hackery
CalvinAllen Oct 9, 2024
d05f21a
Cleanup
CalvinAllen Oct 9, 2024
cf58bb1
No longer needed
CalvinAllen Oct 9, 2024
1772ea1
Consolidate tasks
CalvinAllen Oct 9, 2024
6305064
More clean up
CalvinAllen Oct 9, 2024
54e4fcc
Task cleanup
CalvinAllen Oct 9, 2024
fd0b844
Hardcode NR version
CalvinAllen Oct 9, 2024
e068719
Clean up
CalvinAllen Oct 9, 2024
be0e131
Clean up
CalvinAllen Oct 9, 2024
193036c
Clean upo
CalvinAllen Oct 9, 2024
8c54ca7
Sync port and version changes to Docker scripts
CalvinAllen Oct 10, 2024
170cfe4
Gather NEW_RELIC_* environment variables and inject them into front-e…
CalvinAllen Oct 11, 2024
a5386a8
Rename project / entity name
CalvinAllen Oct 11, 2024
93d2386
Move react app to root and legacy app entrypoint to /welcome
dsellarsnr Oct 11, 2024
4844cf4
Add build-time args for fulfilling the browser "agent" requirements
CalvinAllen Oct 14, 2024
f037225
Add a build action for PRs
CalvinAllen Oct 14, 2024
ebcd40a
Actually don't need the Kotlin libs
CalvinAllen Oct 14, 2024
31564ed
Rename the step
CalvinAllen Oct 14, 2024
2e442c7
Move react app to /react and legacy app entrypoint back to /
dsellarsnr Oct 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/node_modules
**/dist
34 changes: 34 additions & 0 deletions .github/workflows/build_image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build the Docker Image

on:
pull_request:
types: [ opened, reopened, synchronize ]
branches:
- main

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-image:
runs-on: ubuntu-latest

permissions: write-all

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build Docker image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: false
build-args: |
BROWSER_LICENSE_KEY=${{ secrets.BROWSER_LICENSE_KEY }},
BROWSER_ACCOUNT_ID=${{ secrets.BROWSER_ACCOUNT_ID }},
BROWSER_TRUST_KEY=${{ secrets.BROWSER_TRUST_KEY }},
BROWSER_AGENT_ID=${{ secrets.BROWSER_AGENT_ID }},
BROWSER_APPLICATION_ID=${{ secrets.BROWSER_APPLICATION_ID }}
6 changes: 6 additions & 0 deletions .github/workflows/publish_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
github-token: ${{ secrets.GITHUB_TOKEN }}
build-args: |
BROWSER_LICENSE_KEY=${{ secrets.BROWSER_LICENSE_KEY }},
BROWSER_ACCOUNT_ID=${{ secrets.BROWSER_ACCOUNT_ID }},
BROWSER_TRUST_KEY=${{ secrets.BROWSER_TRUST_KEY }},
BROWSER_AGENT_ID=${{ secrets.BROWSER_AGENT_ID }},
BROWSER_APPLICATION_ID=${{ secrets.BROWSER_APPLICATION_ID }}

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ _site/
*.css
!petclinic.css
logs/
/client/dist
/client/node_modules
.idea/

src/main/resources/static/react

.kotlin/*

newrelic/extension.xsd
newrelic/*.jar
30 changes: 27 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,43 @@ FROM gradle:jdk17 AS base
RUN apt-get update && apt-get install -y curl

WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM gradle:jdk17 AS build
WORKDIR /src

ENV NODE_VERSION=20.15.1
RUN apt-get update && apt-get install -y curl
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/refs/tags/v0.40.1/install.sh | bash
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"

COPY src ./src
COPY client ./client
COPY gradle ./gradle
COPY build.gradle settings.gradle ./
COPY build.gradle settings.gradle browserMonitoringTemplate.js ./
COPY --chmod=0755 gradlew ./

ARG BROWSER_LICENSE_KEY
ARG BROWSER_ACCOUNT_ID
ARG BROWSER_TRUST_KEY
ARG BROWSER_AGENT_ID
ARG BROWSER_APPLICATION_ID

ENV BROWSER_LICENSE_KEY=$BROWSER_LICENSE_KEY
ENV BROWSER_ACCOUNT_ID=$BROWSER_ACCOUNT_ID
ENV BROWSER_TRUST_KEY=$BROWSER_TRUST_KEY
ENV BROWSER_AGENT_ID=$BROWSER_AGENT_ID
ENV BROWSER_APPLICATION_ID=$BROWSER_APPLICATION_ID

RUN --mount=type=cache,target=/root/.gradle ./gradlew build --console=plain --info --no-daemon --no-watch-fs

FROM base AS final
WORKDIR /app
COPY --from=build /src/build/libs/java-spring-demogorgon-1.0.0.jar .
COPY --from=build /src/build/libs/petclinic-backend-1.0.0.jar .
COPY ["newrelic/", "./newrelic"]

COPY --chmod=0755 entrypoint.sh /
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ NEW_RELIC_LICENSE_KEY=12345 ./gradlew bootRun
```
Substitute 12345 with your New Relic license key.

You can then access petclinic here: http://localhost:8080/
You can then access petclinic here:
- react: http://localhost:8081/react
- legacy app: http://localhost:8081/

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

Expand Down
4 changes: 4 additions & 0 deletions browserMonitoringTemplate.js

Large diffs are not rendered by default.

164 changes: 135 additions & 29 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,59 +1,165 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.apache.tools.ant.taskdefs.condition.Os

plugins {
id 'org.springframework.boot' version '2.7.18'
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.7.21'
id "com.github.ben-manes.versions" version "0.51.0"
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
}

apply plugin: 'io.spring.dependency-management'
apply plugin: 'java'

ext {
newrelic_version = '8.15.0'
}

group = 'org.springframework.samples'
version = '1.0.0'
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17

tasks.withType(KotlinCompile) {
kotlinOptions.jvmTarget = JavaVersion.VERSION_17
}

repositories {
mavenCentral()
}

ext.webjarsFontawesomeVersion = "4.7.0"
ext.webjarsBootstrapVersion = "5.1.3"

dependencies {
implementation 'io.micrometer:micrometer-core'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'javax.cache:cache-api'
implementation 'jakarta.xml.bind:jakarta.xml.bind-api'
implementation 'jakarta.annotation:jakarta.annotation-api'
implementation "com.newrelic.agent.java:newrelic-api:$newrelic_version"

runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'org.webjars:webjars-locator-core'
runtimeOnly "org.webjars.npm:bootstrap:${webjarsBootstrapVersion}"
runtimeOnly "org.webjars.npm:font-awesome:${webjarsFontawesomeVersion}"
runtimeOnly 'org.ehcache:ehcache'
runtimeOnly 'com.github.ben-manes.caffeine:caffeine'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java:8.0.28'
runtimeOnly 'org.postgresql:postgresql'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'com.newrelic.agent.java:newrelic-api:8.14.0'
implementation 'io.micrometer:micrometer-core:1.10.3'

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

tasks.named('test') {
useJUnitPlatform()
build {
dependsOn 'buildFrontEnd'
mustRunAfter 'buildFrontEnd'
}

bootRun {
jvmArgs = ["-javaagent:${projectDir}/newrelic/newrelic.jar"]
dependsOn 'downloadNewRelicAgent', 'buildFrontEnd', 'build'
mustRunAfter 'downloadNewRelicAgent', 'buildFrontEnd', 'build'

jvmArgs = ["-javaagent:${projectDir}/newrelic/newrelic-v${newrelic_version}.jar"]
}

tasks.register('buildFrontEnd', DefaultTask) {
group 'New Relic'
doFirst {
exec {
String cmd = 'npm'
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
cmd = "npm.cmd"
}

workingDir "$rootDir/client"
commandLine cmd, 'run', 'build'
}

delete 'src/main/resources/static/react'

copy {
from "client/dist"
into "src/main/resources/static/react"
}
}

doLast {
def newRelicLicenseKey = System.getenv('BROWSER_LICENSE_KEY')
def newRelicAccountId = System.getenv('BROWSER_ACCOUNT_ID')
def newRelicTrustKey = System.getenv('BROWSER_TRUST_KEY')
def newRelicAgentId = System.getenv('BROWSER_AGENT_ID')
def newRelicApplicationId = System.getenv('BROWSER_APPLICATION_ID')

if (newRelicTrustKey == null && newRelicAccountId != null) {
newRelicTrustKey = newRelicAccountId
}

if (newRelicAgentId == null && newRelicApplicationId != null) {
newRelicAgentId = newRelicApplicationId
}

if (newRelicLicenseKey == null || newRelicAccountId == null || newRelicTrustKey == null || newRelicAgentId == null || newRelicApplicationId == null) {
throw new GradleException('BROWSER_* ENVIRONMENT VARIABLES NOT SET')
}

def templateJavaScript = file("$rootDir/browserMonitoringTemplate.js")
def indexFile = file("$rootDir/src/main/resources/static/react/index.html")

def indexFileContents = indexFile.getText()
def templateContents = templateJavaScript.getText()

templateContents = templateContents.replace("{{NEW_RELIC_APPLICATION_ID}}", newRelicApplicationId)
templateContents = templateContents.replace("{{NEW_RELIC_AGENT_ID}}", newRelicAgentId)
templateContents = templateContents.replace("{{NEW_RELIC_ACCOUNT_ID}}", newRelicAccountId)
templateContents = templateContents.replace("{{NEW_RELIC_TRUST_KEY}}", newRelicTrustKey)
templateContents = templateContents.replace("{{NEW_RELIC_LICENSE_KEY}}", newRelicLicenseKey)

templateContents = "<script type=\"text/javascript\">\n" + templateContents + "\n</script>"
def newIndexFileContent = indexFileContents.replace("<script id=\"new-relic-template\"><!-- FILLED OUT DURING BUILD --></script>", templateContents)

indexFile.write(newIndexFileContent)
}

outputs.file('src/main/resources/static/react/index.html')
outputs.dir('src/main/resources/static/react/assets')
}

tasks.register('downloadNewRelicAgent', DefaultTask) {
group 'New Relic'

onlyIf {
def agentExists = file("newrelic/newrelic-v${newrelic_version}.jar").exists()
def agentDoesNotExist = !agentExists

if (agentDoesNotExist) {
println "Downloading New Relic Java Agent v${newrelic_version}..."
} else {
println "Found Local Java Agent v${newrelic_version}; Skipping Task (delete " +
"newrelic-v${newrelic_version}.jar to force the download again)"
}

return agentDoesNotExist
}

doFirst {
mkdir 'newrelic'
delete(fileTree("newrelic") {
include("**/newrelic-v*.jar")
})

def fileUrl = "https://download.newrelic.com/newrelic/java-agent/newrelic-agent/current/newrelic-java-${newrelic_version}.zip"
def destinationFile = file("$rootDir/newrelic/newrelic.zip")

new URL(fileUrl).withInputStream { inputStream ->
destinationFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
}

doLast {
copy {
from zipTree(file('newrelic/newrelic.zip'))
into rootDir
exclude("newrelic/newrelic.yml")
exclude("newrelic/LICENSE")
exclude("newrelic/THIRD_PARTY_NOTICES.md")
exclude("newrelic/extension-example.xml")
exclude("newrelic/newrelic-api.jar")
exclude("newrelic/newrelic-api-javadoc.jar")
exclude("newrelic/newrelic-api-sources.jar")
}

file("newrelic/newrelic.jar").renameTo("newrelic/newrelic-v${newrelic_version}.jar")

delete("newrelic/newrelic.zip")
}
}
18 changes: 18 additions & 0 deletions client/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
13 changes: 13 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PetClinic</title>
<script id="new-relic-template"><!-- FILLED OUT DURING BUILD --></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading