Skip to content

Commit ad03fff

Browse files
taymoor89barsukov
andauthored
feat(heureka): integrate GraphQL backend (#779)
* chore(heureka): codegen integration * feat(heureka): list services * test(heureka): supply mock data instead of mocking component --------- Co-authored-by: Wowa <[email protected]>
1 parent 392055e commit ad03fff

File tree

21 files changed

+4755
-175
lines changed

21 files changed

+4755
-175
lines changed

.changeset/many-jars-obey.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"heureka-next": minor
3+
---
4+
5+
The app is now able to communicate with the GraphQL backend.

.github/workflows/deploy-pr-preview.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ jobs:
155155
env:
156156
PACKAGE_PATH: apps/heureka-next
157157
TARGET_FOLDER: heureka_next
158-
APP_PROPS_BASE64: ${{ secrets.HEUREKA_NEXT_APP_PROPS_BASE64 }}
158+
APP_PROPS_BASE64: ${{ secrets.HEUREKA_APP_PROPS_BASE64 }}
159159
continue-on-error: true
160160

161161
- name: Generate index.html for Deployed Apps

apps/heureka-next/.env.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#
2+
# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
6+
API_ENDPOINT=SOME_URL

apps/heureka-next/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,52 @@
44

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

7+
## How to run locally?
8+
9+
1. Go to app directory:
10+
```bash
11+
cd apps/heureka-next
12+
```
13+
2. If you are running the app for the first time, follow these steps. Otherwise, skip to step 3:
14+
1. Create `appProps.json` and change the configurations accordingly:
15+
```bash
16+
cp appProps.template.json appProps.json
17+
```
18+
2. Create `.env` file and change configurations accordingly:
19+
```bash
20+
cp .env.template .env
21+
```
22+
3. Install dependencies:
23+
```bash
24+
npm i
25+
```
26+
4. Run the development server:
27+
```bash
28+
npm run dev
29+
```
30+
31+
## How to update GraphQL types?
32+
33+
1. Go to app directory:
34+
```bash
35+
cd apps/heureka-next
36+
```
37+
2. Update GraphQL types from the latest GraphQL schema:
38+
```bash
39+
npm run generate:types
40+
```
41+
42+
## How to run tests?
43+
44+
1. Go to app directory:
45+
```bash
46+
cd apps/heureka-next
47+
```
48+
2. Run tests:
49+
```bash
50+
npm run test
51+
```
52+
753
## Contributing
854

955
We welcome contributions from the community. Please follow our [contribution guidelines] to contribute to this project.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"apiEndpoint": "YOUR_GRAPHQL_API_ENDPOINT",
3+
"embedded": false,
4+
"theme": "light"
5+
}

apps/heureka-next/codegen.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { CodegenConfig } from "@graphql-codegen/cli"
2+
import * as dotenv from "dotenv"
3+
4+
// Load environment variables from .env file
5+
dotenv.config()
6+
7+
const config: CodegenConfig = {
8+
schema: process.env.API_ENDPOINT,
9+
documents: "src/**/*.graphql",
10+
11+
generates: {
12+
"src/generated/graphql.tsx": {
13+
plugins: ["typescript", "typescript-operations", "typescript-react-apollo"],
14+
config: {
15+
withHooks: true,
16+
withHOC: false,
17+
withComponent: false,
18+
},
19+
},
20+
},
21+
}
22+
23+
export default config

apps/heureka-next/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
<title>Heureka Next</title>
88
</head>
99
<body>
10+
<script type="module">
11+
// appProps are excluded from standalone build and should be generated from outside
12+
fetch("./appProps.json")
13+
.then((res) => res.json())
14+
.catch((error) => {
15+
console.warn("No appProps found, using default props", error.message)
16+
})
17+
.then((props) => {
18+
import("./src/index").then((app) => {
19+
app.mount(document.getElementById("root"), props)
20+
})
21+
})
22+
</script>
1023
<div id="root"></div>
1124
<script type="module" src="/src/index.tsx"></script>
1225
</body>

apps/heureka-next/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"dev": "vite",
1818
"build": "vite build",
1919
"build:static": "vite build --mode static",
20+
"generate:types": "graphql-codegen --config codegen.ts",
2021
"serve": "vite preview",
2122
"test": "vitest run",
2223
"lint": "eslint",
@@ -31,6 +32,11 @@
3132
"react-dom": "18.3.1"
3233
},
3334
"devDependencies": {
35+
"@apollo/client": "^3.11.10",
36+
"@graphql-codegen/cli": "^5.0.3",
37+
"@graphql-codegen/typescript": "^4.1.2",
38+
"@graphql-codegen/typescript-operations": "^4.4.0",
39+
"@graphql-codegen/typescript-react-apollo": "^4.3.2",
3440
"@cloudoperators/juno-config": "*",
3541
"@testing-library/jest-dom": "6.6.3",
3642
"@testing-library/react": "16.2.0",

apps/heureka-next/src/App.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ import React from "react"
77
import { AppShellProvider } from "@cloudoperators/juno-ui-components"
88
import { ErrorBoundary } from "./components/ErrorBoundary"
99
import { Shell } from "./components/Shell"
10+
import { ApolloProvider } from "@apollo/client"
11+
import { getClient } from "./apollo-client"
1012

1113
export type AppProps = {
1214
theme?: "theme-dark" | "theme-light"
15+
apiEndpoint?: string
1316
embedded?: boolean
1417
}
1518

16-
export const App = (props: AppProps) => (
17-
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
18-
<ErrorBoundary>
19-
<Shell {...props} />
20-
</ErrorBoundary>
21-
</AppShellProvider>
19+
const App = (props: AppProps) => (
20+
<ApolloProvider client={getClient({ uri: props.apiEndpoint })}>
21+
<AppShellProvider theme={`${props.theme ? props.theme : "theme-dark"}`}>
22+
<ErrorBoundary>
23+
<Shell {...props} />
24+
</ErrorBoundary>
25+
</AppShellProvider>
26+
</ApolloProvider>
2227
)
28+
29+
export default App
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ApolloClient, InMemoryCache } from "@apollo/client"
2+
3+
type ClientOptions = {
4+
uri?: string
5+
}
6+
7+
export const getClient = ({ uri }: ClientOptions) => {
8+
if (typeof uri === "undefined") {
9+
throw new Error("No API endpoint provided.")
10+
}
11+
return new ApolloClient({
12+
uri,
13+
cache: new InMemoryCache(),
14+
})
15+
}

apps/heureka-next/src/components/Services/Services.test.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,42 @@
55

66
import React from "react"
77
import { render, screen } from "@testing-library/react"
8+
import { MockedProvider } from "@apollo/client/testing"
89
import { Services } from "./Services"
10+
import { GetServicesDocument } from "../../generated/graphql"
11+
12+
const mocks = [
13+
{
14+
request: {
15+
query: GetServicesDocument,
16+
},
17+
result: {
18+
data: {
19+
Services: {
20+
edges: [
21+
{
22+
node: {
23+
id: "some-id",
24+
ccrn: "some-ccrn",
25+
__typename: "Service",
26+
},
27+
__typename: "ServiceEdge",
28+
},
29+
],
30+
__typename: "ServiceConnection",
31+
},
32+
},
33+
},
34+
},
35+
]
936

1037
describe("Services", () => {
11-
it("should render correctly", () => {
12-
render(<Services />)
13-
expect(screen.getByText("render services here...")).toBeInTheDocument()
38+
it("should render correctly", async () => {
39+
render(
40+
<MockedProvider mocks={mocks}>
41+
<Services />
42+
</MockedProvider>
43+
)
44+
expect(await screen.findByText("some-ccrn")).toBeInTheDocument()
1445
})
1546
})

apps/heureka-next/src/components/Services/Services.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,12 @@
44
*/
55

66
import React from "react"
7+
import { useGetServicesQuery } from "../../generated/graphql"
78

8-
export const Services = () => <div>render services here...</div>
9+
export const Services = () => {
10+
const { data, loading, error } = useGetServicesQuery()
11+
if (loading) return <p>Loading...</p>
12+
if (error) return <p>Error</p>
13+
14+
return <div>{data?.Services?.edges?.map((service) => <div key={service?.node.id}>{service?.node.ccrn}</div>)}</div>
15+
}

apps/heureka-next/src/components/Shell/Shell.test.tsx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,60 @@
66
import React from "react"
77
import { render, screen } from "@testing-library/react"
88
import userEvent from "@testing-library/user-event"
9+
import { MockedProvider } from "@apollo/client/testing"
910
import { Shell } from "./Shell"
11+
import { GetServicesDocument } from "../../generated/graphql"
12+
13+
const mocks = [
14+
{
15+
request: {
16+
query: GetServicesDocument,
17+
},
18+
result: {
19+
data: {
20+
Services: {
21+
edges: [
22+
{
23+
node: {
24+
id: "some-id",
25+
ccrn: "some-ccrn",
26+
__typename: "Service",
27+
},
28+
__typename: "ServiceEdge",
29+
},
30+
],
31+
__typename: "ServiceConnection",
32+
},
33+
},
34+
},
35+
},
36+
]
1037

1138
const renderShell = () => ({
1239
user: userEvent.setup(),
13-
...render(<Shell />),
40+
...render(
41+
<MockedProvider mocks={mocks}>
42+
<Shell />
43+
</MockedProvider>
44+
),
1445
})
1546

1647
describe("Shell", () => {
17-
it("should render correctly", () => {
48+
it("should render correctly", async () => {
1849
renderShell()
1950
// assert that page header is rendered
2051
expect(screen.getByText("Heureka")).toBeInTheDocument()
2152
// assert that the top navigation is rendered
2253
expect(screen.getByText("Services")).toBeInTheDocument()
2354
expect(screen.getByText("Vulnerabilities")).toBeInTheDocument()
2455
expect(screen.getByText("Images")).toBeInTheDocument()
25-
})
26-
27-
it("should render services view by default", () => {
28-
renderShell()
29-
expect(screen.getByText("render services here...")).toBeInTheDocument()
56+
// assert that the default view 'Services' is rendered
57+
expect(await screen.findByText("some-ccrn")).toBeInTheDocument()
3058
})
3159

3260
it("should allow switching to other view", async () => {
3361
const { user } = renderShell()
3462
await user.click(screen.getByText("Vulnerabilities"))
3563
expect(screen.getByText("render vulnerabilities here...")).toBeInTheDocument()
36-
expect(screen.queryByText("render services here...")).not.toBeInTheDocument()
3764
})
3865
})

apps/heureka-next/src/components/Shell/Shell.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,34 @@
66
import React, { useState, ReactNode } from "react"
77
import { AppShell, Container, PageHeader } from "@cloudoperators/juno-ui-components"
88
import { MessagesProvider, Messages } from "@cloudoperators/juno-messages-provider"
9-
import { Navigation } from "../Navigation"
10-
import { ShellContent } from "./ShellContent"
11-
import { AppProps } from "../../App"
12-
import { SERVICES } from "../../constants"
139
import styles from "../../styles.scss?inline"
10+
import { Navigation } from "../Navigation"
11+
import { IMAGES, SERVICES, VULNERABILITIES } from "../../constants"
12+
import { Services } from "../Services"
13+
import { Vulnerabilities } from "../Vulnerabilities"
14+
import { Images } from "../Images"
15+
16+
const getViewComponent = (selectedView: ReactNode) => {
17+
switch (selectedView) {
18+
case SERVICES:
19+
return Services
20+
case VULNERABILITIES:
21+
return Vulnerabilities
22+
case IMAGES:
23+
return Images
24+
default:
25+
return () => null
26+
}
27+
}
28+
29+
type ShellProps = {
30+
embedded?: boolean
31+
defaultSelectedView?: ReactNode
32+
}
1433

15-
export const Shell = ({ embedded }: AppProps) => {
16-
const [selectedView, setSelectedView] = useState<ReactNode>(SERVICES)
34+
export const Shell = ({ embedded, defaultSelectedView = SERVICES }: ShellProps) => {
35+
const [selectedView, setSelectedView] = useState<ReactNode>(defaultSelectedView)
36+
const SelectedViewComponent = getViewComponent(selectedView)
1737

1838
return (
1939
<AppShell
@@ -28,7 +48,7 @@ export const Shell = ({ embedded }: AppProps) => {
2848
<MessagesProvider>
2949
<Messages />
3050
</MessagesProvider>
31-
<ShellContent selectedView={selectedView} />
51+
<SelectedViewComponent />
3252
</>
3353
</Container>
3454
</AppShell>

apps/heureka-next/src/components/Shell/ShellContent/ShellContent.tsx

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)