Skip to content

Commit

Permalink
feat(heureka): integrate GraphQL backend (#779)
Browse files Browse the repository at this point in the history
* chore(heureka): codegen integration

* feat(heureka): list services

* test(heureka): supply mock data instead of mocking component

---------

Co-authored-by: Wowa <[email protected]>
  • Loading branch information
taymoor89 and barsukov authored Feb 18, 2025
1 parent 392055e commit ad03fff
Show file tree
Hide file tree
Showing 21 changed files with 4,755 additions and 175 deletions.
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

0 comments on commit ad03fff

Please sign in to comment.