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

feat(heureka): integrate GraphQL backend #779

Merged
merged 3 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/many-jars-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"heureka-next": minor
---

The app is now able to communicate with the GraphQL backend.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pr-preview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ jobs:
env:
PACKAGE_PATH: apps/heureka-next
TARGET_FOLDER: heureka_next
APP_PROPS_BASE64: ${{ secrets.HEUREKA_NEXT_APP_PROPS_BASE64 }}
APP_PROPS_BASE64: ${{ secrets.HEUREKA_APP_PROPS_BASE64 }}
continue-on-error: true

- name: Generate index.html for Deployed Apps
Expand Down
6 changes: 6 additions & 0 deletions apps/heureka-next/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#
# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
# SPDX-License-Identifier: Apache-2.0
#

API_ENDPOINT=SOME_URL
46 changes: 46 additions & 0 deletions apps/heureka-next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,52 @@

This app will eventually become a successor to the existing Heureka app.

## How to run locally?

1. Go to app directory:
```bash
cd apps/heureka-next
```
2. If you are running the app for the first time, follow these steps. Otherwise, skip to step 3:
1. Create `appProps.json` and change the configurations accordingly:
```bash
cp appProps.template.json appProps.json
```
2. Create `.env` file and change configurations accordingly:
```bash
cp .env.template .env
```
3. Install dependencies:
```bash
npm i
```
4. Run the development server:
```bash
npm run dev
```

## How to update GraphQL types?

1. Go to app directory:
```bash
cd apps/heureka-next
```
2. Update GraphQL types from the latest GraphQL schema:
```bash
npm run generate:types
```

## How to run tests?

1. Go to app directory:
```bash
cd apps/heureka-next
```
2. Run tests:
```bash
npm run test
```

## Contributing

We welcome contributions from the community. Please follow our [contribution guidelines] to contribute to this project.
Expand Down
5 changes: 5 additions & 0 deletions apps/heureka-next/appProps.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"apiEndpoint": "YOUR_GRAPHQL_API_ENDPOINT",
"embedded": false,
"theme": "light"
}
23 changes: 23 additions & 0 deletions apps/heureka-next/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CodegenConfig } from "@graphql-codegen/cli"
import * as dotenv from "dotenv"

// Load environment variables from .env file
dotenv.config()

const config: CodegenConfig = {
schema: process.env.API_ENDPOINT,
documents: "src/**/*.graphql",

generates: {
"src/generated/graphql.tsx": {
plugins: ["typescript", "typescript-operations", "typescript-react-apollo"],
config: {
withHooks: true,
withHOC: false,
withComponent: false,
},
},
},
}

export default config
13 changes: 13 additions & 0 deletions apps/heureka-next/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@
<title>Heureka Next</title>
</head>
<body>
<script type="module">
// appProps are excluded from standalone build and should be generated from outside
fetch("./appProps.json")
.then((res) => res.json())
.catch((error) => {
console.warn("No appProps found, using default props", error.message)
})
.then((props) => {
import("./src/index").then((app) => {
app.mount(document.getElementById("root"), props)
})
})
</script>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
Expand Down
6 changes: 6 additions & 0 deletions apps/heureka-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dev": "vite",
"build": "vite build",
"build:static": "vite build --mode static",
"generate:types": "graphql-codegen --config codegen.ts",
"serve": "vite preview",
"test": "vitest run",
"lint": "eslint",
Expand All @@ -31,6 +32,11 @@
"react-dom": "18.3.1"
},
"devDependencies": {
"@apollo/client": "^3.11.10",
"@graphql-codegen/cli": "^5.0.3",
"@graphql-codegen/typescript": "^4.1.2",
"@graphql-codegen/typescript-operations": "^4.4.0",
"@graphql-codegen/typescript-react-apollo": "^4.3.2",
"@cloudoperators/juno-config": "*",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
Expand Down
19 changes: 13 additions & 6 deletions apps/heureka-next/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ import React from "react"
import { AppShellProvider } from "@cloudoperators/juno-ui-components"
import { ErrorBoundary } from "./components/ErrorBoundary"
import { Shell } from "./components/Shell"
import { ApolloProvider } from "@apollo/client"
import { getClient } from "./apollo-client"

export type AppProps = {
theme?: "theme-dark" | "theme-light"
apiEndpoint?: string
embedded?: boolean
}

export const App = (props: AppProps) => (
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
<ErrorBoundary>
<Shell {...props} />
</ErrorBoundary>
</AppShellProvider>
const App = (props: AppProps) => (
<ApolloProvider client={getClient({ uri: props.apiEndpoint })}>
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
<ErrorBoundary>
<Shell {...props} />
</ErrorBoundary>
</AppShellProvider>
</ApolloProvider>
)

export default App
15 changes: 15 additions & 0 deletions apps/heureka-next/src/apollo-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApolloClient, InMemoryCache } from "@apollo/client"

type ClientOptions = {
uri?: string
}

export const getClient = ({ uri }: ClientOptions) => {
if (typeof uri === "undefined") {
throw new Error("No API endpoint provided.")
}
return new ApolloClient({
uri,
cache: new InMemoryCache(),
})
}
37 changes: 34 additions & 3 deletions apps/heureka-next/src/components/Services/Services.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,42 @@

import React from "react"
import { render, screen } from "@testing-library/react"
import { MockedProvider } from "@apollo/client/testing"
import { Services } from "./Services"
import { GetServicesDocument } from "../../generated/graphql"

const mocks = [
{
request: {
query: GetServicesDocument,
},
result: {
data: {
Services: {
edges: [
{
node: {
id: "some-id",
ccrn: "some-ccrn",
__typename: "Service",
},
__typename: "ServiceEdge",
},
],
__typename: "ServiceConnection",
},
},
},
},
]

describe("Services", () => {
it("should render correctly", () => {
render(<Services />)
expect(screen.getByText("render services here...")).toBeInTheDocument()
it("should render correctly", async () => {
render(
<MockedProvider mocks={mocks}>
<Services />
</MockedProvider>
)
expect(await screen.findByText("some-ccrn")).toBeInTheDocument()
})
})
9 changes: 8 additions & 1 deletion apps/heureka-next/src/components/Services/Services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@
*/

import React from "react"
import { useGetServicesQuery } from "../../generated/graphql"

export const Services = () => <div>render services here...</div>
export const Services = () => {
const { data, loading, error } = useGetServicesQuery()
if (loading) return <p>Loading...</p>
if (error) return <p>Error</p>

return <div>{data?.Services?.edges?.map((service) => <div key={service?.node.id}>{service?.node.ccrn}</div>)}</div>
}
43 changes: 35 additions & 8 deletions apps/heureka-next/src/components/Shell/Shell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,60 @@
import React from "react"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { MockedProvider } from "@apollo/client/testing"
import { Shell } from "./Shell"
import { GetServicesDocument } from "../../generated/graphql"

const mocks = [
{
request: {
query: GetServicesDocument,
},
result: {
data: {
Services: {
edges: [
{
node: {
id: "some-id",
ccrn: "some-ccrn",
__typename: "Service",
},
__typename: "ServiceEdge",
},
],
__typename: "ServiceConnection",
},
},
},
},
]

const renderShell = () => ({
user: userEvent.setup(),
...render(<Shell />),
...render(
<MockedProvider mocks={mocks}>
<Shell />
</MockedProvider>
),
})

describe("Shell", () => {
it("should render correctly", () => {
it("should render correctly", async () => {
renderShell()
// assert that page header is rendered
expect(screen.getByText("Heureka")).toBeInTheDocument()
// assert that the top navigation is rendered
expect(screen.getByText("Services")).toBeInTheDocument()
expect(screen.getByText("Vulnerabilities")).toBeInTheDocument()
expect(screen.getByText("Images")).toBeInTheDocument()
})

it("should render services view by default", () => {
renderShell()
expect(screen.getByText("render services here...")).toBeInTheDocument()
// assert that the default view 'Services' is rendered
expect(await screen.findByText("some-ccrn")).toBeInTheDocument()
})

it("should allow switching to other view", async () => {
const { user } = renderShell()
await user.click(screen.getByText("Vulnerabilities"))
expect(screen.getByText("render vulnerabilities here...")).toBeInTheDocument()
expect(screen.queryByText("render services here...")).not.toBeInTheDocument()
})
})
34 changes: 27 additions & 7 deletions apps/heureka-next/src/components/Shell/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,34 @@
import React, { useState, ReactNode } from "react"
import { AppShell, Container, PageHeader } from "@cloudoperators/juno-ui-components"
import { MessagesProvider, Messages } from "@cloudoperators/juno-messages-provider"
import { Navigation } from "../Navigation"
import { ShellContent } from "./ShellContent"
import { AppProps } from "../../App"
import { SERVICES } from "../../constants"
import styles from "../../styles.scss?inline"
import { Navigation } from "../Navigation"
import { IMAGES, SERVICES, VULNERABILITIES } from "../../constants"
import { Services } from "../Services"
import { Vulnerabilities } from "../Vulnerabilities"
import { Images } from "../Images"

const getViewComponent = (selectedView: ReactNode) => {
switch (selectedView) {
case SERVICES:
return Services
case VULNERABILITIES:
return Vulnerabilities
case IMAGES:
return Images
default:
return () => null
}
}

type ShellProps = {
embedded?: boolean
defaultSelectedView?: ReactNode
}

export const Shell = ({ embedded }: AppProps) => {
const [selectedView, setSelectedView] = useState<ReactNode>(SERVICES)
export const Shell = ({ embedded, defaultSelectedView = SERVICES }: ShellProps) => {
const [selectedView, setSelectedView] = useState<ReactNode>(defaultSelectedView)
const SelectedViewComponent = getViewComponent(selectedView)

return (
<AppShell
Expand All @@ -28,7 +48,7 @@ export const Shell = ({ embedded }: AppProps) => {
<MessagesProvider>
<Messages />
</MessagesProvider>
<ShellContent selectedView={selectedView} />
<SelectedViewComponent />
</>
</Container>
</AppShell>
Expand Down

This file was deleted.

Loading
Loading