Skip to content

Commit 91ea6a3

Browse files
authored
feat(dash): add route to service relationship views in dashboard (#833)
1 parent cc1f938 commit 91ea6a3

File tree

12 files changed

+293
-71
lines changed

12 files changed

+293
-71
lines changed

pkg/dashboard/frontend/cypress/e2e/api-explorer.cy.ts

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
const expectedEndpoints = [
2+
'first-api-/all-methods-DELETE',
3+
'first-api-/all-methods-GET',
4+
'first-api-/all-methods-OPTIONS',
5+
'first-api-/all-methods-PATCH',
6+
'first-api-/all-methods-POST',
7+
'first-api-/all-methods-PUT',
8+
'first-api-/header-test-GET',
9+
'first-api-/json-test-POST',
10+
'first-api-/path-test/{name}-GET',
11+
'first-api-/query-test-GET',
12+
'first-api-/schedule-count-GET',
13+
'first-api-/topic-count-GET',
14+
'second-api-/content-type-binary-GET',
15+
'second-api-/content-type-css-GET',
16+
'second-api-/content-type-html-GET',
17+
'second-api-/content-type-image-GET',
18+
'second-api-/content-type-xml-GET',
19+
'second-api-/image-from-bucket-DELETE',
20+
'second-api-/image-from-bucket-GET',
21+
'second-api-/image-from-bucket-PUT',
22+
'second-api-/very-nested-files-PUT',
23+
'my-db-api-/get-GET',
24+
'my-secret-api-/get-GET',
25+
'my-secret-api-/set-POST',
26+
'my-secret-api-/set-binary-POST',
27+
]
28+
129
describe('APIs spec', () => {
230
beforeEach(() => {
331
cy.viewport('macbook-16')
@@ -6,38 +34,40 @@ describe('APIs spec', () => {
634
})
735

836
it('should retrieve correct apis and endpoints', () => {
37+
// open api routes for testing
938
cy.get('[data-rct-item-id="second-api"]').click()
10-
11-
const expectedEndpoints = [
12-
'first-api',
13-
'first-api-/all-methods-DELETE',
14-
'first-api-/all-methods-GET',
15-
'first-api-/all-methods-OPTIONS',
16-
'first-api-/all-methods-PATCH',
17-
'first-api-/all-methods-POST',
18-
'first-api-/all-methods-PUT',
19-
'first-api-/header-test-GET',
20-
'first-api-/json-test-POST',
21-
'first-api-/path-test/{name}-GET',
22-
'first-api-/query-test-GET',
23-
'first-api-/schedule-count-GET',
24-
'first-api-/topic-count-GET',
25-
'second-api-/content-type-binary-GET',
26-
'second-api-/content-type-css-GET',
27-
'second-api-/content-type-html-GET',
28-
'second-api-/content-type-image-GET',
29-
'second-api-/content-type-xml-GET',
30-
'second-api-/image-from-bucket-DELETE',
31-
'second-api-/image-from-bucket-GET',
32-
'second-api-/image-from-bucket-PUT',
33-
'second-api-/very-nested-files-PUT',
34-
]
39+
cy.get('[data-rct-item-id="my-db-api"]').click()
40+
cy.get('[data-rct-item-id="my-secret-api"]').click()
3541

3642
expectedEndpoints.forEach((id) => {
3743
cy.get(`[data-rct-item-id="${id}"]`).should('exist')
3844
})
3945
})
4046

47+
it('should have correct service reference', () => {
48+
// open api routes for testing
49+
cy.get('[data-rct-item-id="second-api"]').click()
50+
cy.get('[data-rct-item-id="my-db-api"]').click()
51+
cy.get('[data-rct-item-id="my-secret-api"]').click()
52+
53+
expectedEndpoints.forEach((id) => {
54+
cy.get(`[data-rct-item-id="${id}"]`).click()
55+
56+
let expectedServiceFile = 'my-test-service.ts'
57+
58+
if (id.includes('my-db-api')) {
59+
expectedServiceFile = 'my-test-db.ts'
60+
} else if (id.includes('my-secret-api')) {
61+
expectedServiceFile = 'my-test-secret.ts'
62+
}
63+
64+
cy.getTestEl('requesting-service').should(
65+
'contain.text',
66+
expectedServiceFile,
67+
)
68+
})
69+
})
70+
4171
it('should allow query params', () => {
4272
cy.intercept('/api/call/**').as('apiCall')
4373

pkg/dashboard/frontend/cypress/e2e/architecture.cy.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,30 @@ describe('Architecture Spec', () => {
3535
expect(cy.contains('.react-flow__node', content)).to.exist
3636
})
3737
})
38+
39+
it('should have correct routes drawer content', () => {
40+
const expected = [
41+
[
42+
'edge-label-e-first-api-services/my-test-service.ts',
43+
'DELETE/all-methodsGET/all-methodsOPTIONS/all-methodsPATCH/all-methodsPOST/all-methodsPUT/all-methodsGET/header-testPOST/json-testGET/path-test/{name}GET/query-testGET/schedule-countGET/topic-count',
44+
],
45+
[
46+
'edge-label-e-second-api-services/my-test-service.ts',
47+
'GET/content-type-binaryGET/content-type-cssGET/content-type-htmlGET/content-type-imageGET/content-type-xmlDELETE/image-from-bucketGET/image-from-bucketPUT/image-from-bucketPUT/very-nested-files',
48+
],
49+
[
50+
'edge-label-e-my-secret-api-services/my-test-secret.ts',
51+
'GET/getPOST/setPOST/set-binary',
52+
],
53+
['edge-label-e-my-db-api-services/my-test-db.ts', 'GET/get'],
54+
]
55+
56+
expected.forEach(([edge, routes]) => {
57+
cy.getTestEl(edge).click({
58+
force: true,
59+
})
60+
61+
cy.getTestEl('api-routes-list').should('have.text', routes)
62+
})
63+
})
3864
})

pkg/dashboard/frontend/src/components/apis/APIExplorer.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,27 @@ const APIExplorer = () => {
505505
</Button>
506506
</div>
507507
</div>
508+
{selectedApiEndpoint.requestingService ? (
509+
<div className={'flex items-center gap-1'}>
510+
<span className="font-semibold">Referenced by:</span>
511+
<div className={'flex items-start gap-1'}>
512+
<Tooltip>
513+
<TooltipTrigger asChild>
514+
<a
515+
data-testid="requesting-service"
516+
className="text-md h-auto p-0 hover:underline"
517+
href={`vscode://file/${data?.services.find((svc) => svc.name === selectedApiEndpoint.requestingService)?.filePath}`}
518+
>
519+
{selectedApiEndpoint.requestingService}
520+
</a>
521+
</TooltipTrigger>
522+
<TooltipContent>
523+
<p>Open in VSCode</p>
524+
</TooltipContent>
525+
</Tooltip>
526+
</div>
527+
</div>
528+
) : null}
508529
{selectedDoesNotExist && (
509530
<NotFoundAlert>
510531
Endpoint not found. It might have been updated or removed.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react'
2+
import { APIMethodBadge } from './APIMethodBadge'
3+
import type { Endpoint } from '@/types'
4+
5+
interface APIRoutesListProps {
6+
endpoints: Endpoint[]
7+
apiAddress: string
8+
}
9+
10+
const APIRoutesList: React.FC<APIRoutesListProps> = ({
11+
endpoints,
12+
apiAddress,
13+
}) => {
14+
return (
15+
<div className="flex flex-col gap-y-2" data-testid="api-routes-list">
16+
{endpoints.map((endpoint) => (
17+
<div key={endpoint.id} className="grid w-full grid-cols-12 gap-4">
18+
<div className="col-span-3 flex">
19+
<APIMethodBadge method={endpoint.method} />
20+
</div>
21+
<div className="col-span-9 flex justify-start">
22+
<a
23+
target="_blank noreferrer noopener"
24+
className="truncate hover:underline"
25+
href={`${apiAddress}${endpoint.path}`}
26+
rel="noreferrer"
27+
>
28+
{endpoint.path}
29+
</a>
30+
</div>
31+
</div>
32+
))}
33+
</div>
34+
)
35+
}
36+
37+
export default APIRoutesList

pkg/dashboard/frontend/src/components/architecture/Architecture.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ import ReactFlow, {
1111
ReactFlowProvider,
1212
Position,
1313
Panel,
14-
useOnSelectionChange,
15-
getConnectedEdges,
16-
applyEdgeChanges,
17-
type EdgeSelectionChange,
1814
} from 'reactflow'
1915
import Dagre from '@dagrejs/dagre'
2016
import 'reactflow/dist/style.css'
@@ -111,29 +107,6 @@ function ReactFlowLayout() {
111107
[setEdges],
112108
)
113109

114-
useOnSelectionChange({
115-
onChange: ({ nodes: nodesChanged }) => {
116-
const connectedEdges = getConnectedEdges(nodesChanged, edges)
117-
118-
// select all connected edges if node is selected
119-
if (connectedEdges.length) {
120-
setEdges(
121-
applyEdgeChanges(
122-
connectedEdges.map(
123-
(edge) =>
124-
({
125-
id: edge.id,
126-
type: 'select',
127-
selected: true,
128-
}) as EdgeSelectionChange,
129-
),
130-
edges,
131-
),
132-
)
133-
}
134-
},
135-
})
136-
137110
useEffect(() => {
138111
if (!data) return
139112

pkg/dashboard/frontend/src/components/architecture/DetailsDrawer.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import {
88
} from '../ui/drawer'
99
import { Button } from '../ui/button'
1010
import { useCallback, type PropsWithChildren } from 'react'
11-
import { applyNodeChanges, useNodes, useNodeId, useReactFlow } from 'reactflow'
11+
import {
12+
applyNodeChanges,
13+
useNodes,
14+
useNodeId,
15+
useReactFlow,
16+
applyEdgeChanges,
17+
useEdges,
18+
} from 'reactflow'
1219
import type { NodeBaseData } from './nodes/NodeBase'
1320
import type { nodeTypes } from '@/lib/utils/generate-architecture-data'
1421
export interface DetailsDrawerProps extends PropsWithChildren {
@@ -17,27 +24,35 @@ export interface DetailsDrawerProps extends PropsWithChildren {
1724
open: boolean
1825
testHref?: string
1926
footerChildren?: React.ReactNode
27+
// children that are rendered after the services reference
28+
trailingChildren?: React.ReactNode
2029
nodeType: keyof typeof nodeTypes
2130
icon: NodeBaseData['icon']
2231
address?: string
2332
services?: string[]
33+
type?: 'node' | 'edge'
34+
edgeId?: string
2435
}
2536

2637
export const DetailsDrawer = ({
2738
title,
2839
description,
2940
children,
3041
footerChildren,
42+
trailingChildren,
3143
open,
3244
testHref,
3345
icon: Icon,
3446
nodeType,
3547
address,
3648
services,
49+
type = 'node',
50+
edgeId,
3751
}: DetailsDrawerProps) => {
3852
const nodeId = useNodeId()
39-
const { setNodes } = useReactFlow()
53+
const { setNodes, setEdges } = useReactFlow()
4054
const nodes = useNodes()
55+
const edges = useEdges()
4156

4257
const selectServiceNode = useCallback(
4358
(serviceNodeId: string) => {
@@ -63,6 +78,23 @@ export const DetailsDrawer = ({
6378
)
6479

6580
const close = () => {
81+
if (type === 'edge') {
82+
setEdges(
83+
applyEdgeChanges(
84+
[
85+
{
86+
id: edgeId || '',
87+
type: 'select',
88+
selected: false,
89+
},
90+
],
91+
edges,
92+
),
93+
)
94+
95+
return
96+
}
97+
6698
setNodes(
6799
applyNodeChanges(
68100
[
@@ -130,6 +162,7 @@ export const DetailsDrawer = ({
130162
</div>
131163
</div>
132164
) : null}
165+
{trailingChildren}
133166
</div>
134167
<DrawerFooter className="px-0">
135168
{footerChildren}

0 commit comments

Comments
 (0)