Skip to content

Commit cd56254

Browse files
Merge branch 'main' into fix/e2e-tests-523
2 parents 61281cc + 7866c47 commit cd56254

18 files changed

Lines changed: 634 additions & 275 deletions

File tree

.env.example

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# ------------------------------------------------------------------------------
2+
# OpenCRE Environment Configuration
3+
# ------------------------------------------------------------------------------
4+
# Copy this file to `.env` and update the values for your environment.
5+
# Never commit your real `.env` file to version control.
6+
# ------------------------------------------------------------------------------
7+
# Database
8+
9+
DEV_DATABASE_URL=postgresql://user:password@localhost:5432/opencre
10+
11+
# Neo4j
12+
13+
NEO4J_URL=neo4j://neo4j:password@localhost:7687
14+
15+
# Redis
16+
17+
REDIS_HOST=localhost
18+
REDIS_PORT=6379
19+
REDIS_URL=redis://localhost:6379
20+
REDIS_NO_SSL=false
21+
22+
# Flask
23+
24+
FLASK_CONFIG=development
25+
INSECURE_REQUESTS=false
26+
27+
# Embeddings
28+
29+
NO_GEN_EMBEDDINGS=false
30+
31+
# Google Auth
32+
33+
GOOGLE_CLIENT_ID=your-client-id
34+
GOOGLE_CLIENT_SECRET=your-client-secret
35+
GOOGLE_SECRET_JSON=path/to/secret.json
36+
LOGIN_ALLOWED_DOMAINS=example.com
37+
38+
# GCP
39+
40+
GCP_NATIVE=false
41+
42+
# Spreadsheet Auth
43+
44+
OpenCRE_gspread_Auth=path/to/credentials.json

README.md

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -216,23 +216,28 @@ make docker-dev
216216

217217
The environment variables used by OpenCRE are:
218218

219+
## Environment Configuration
220+
221+
Copy the example configuration file:
222+
223+
```bash
224+
cp .env.example .env
219225
```
220-
- NEO4J_URL
221-
- NO_GEN_EMBEDDINGS
222-
- FLASK_CONFIG
223-
- DEV_DATABASE_URL
224-
- INSECURE_REQUESTS
225-
- REDIS_HOST
226-
- REDIS_PORT
227-
- REDIS_NO_SSL
228-
- REDIS_URL
229-
- GCP_NATIVE
230-
- GOOGLE_SECRET_JSON
231-
- GOOGLE_CLIENT_ID
232-
- GOOGLE_CLIENT_SECRET
233-
- LOGIN_ALLOWED_DOMAINS
234-
- OpenCRE_gspread_Auth
235-
```
226+
227+
Then edit `.env` and provide values appropriate for your environment.
228+
229+
### Variables
230+
231+
* Database: `DEV_DATABASE_URL`
232+
* Neo4j: `NEO4J_URL`
233+
* Redis: `REDIS_HOST`, `REDIS_PORT`, `REDIS_URL`, `REDIS_NO_SSL`
234+
* Flask: `FLASK_CONFIG`, `INSECURE_REQUESTS`
235+
* Embeddings: `NO_GEN_EMBEDDINGS`
236+
* Google Auth: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_SECRET_JSON`, `LOGIN_ALLOWED_DOMAINS`
237+
* GCP: `GCP_NATIVE`
238+
* Spreadsheet Auth: `OpenCRE_gspread_Auth`
239+
240+
See `.env.example` for full list and defaults.
236241

237242
You can run the containers with:
238243

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { useEnvironment } from './useEnvironment';
22
export { useLocationFromOutsideRoute } from './useLocationFromOutsideRoute';
3+
export { useCapabilities } from './useCapabilities';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { useEnvironment } from './useEnvironment';
4+
5+
export type Capabilities = {
6+
myopencre: boolean;
7+
};
8+
9+
export const useCapabilities = () => {
10+
const { apiUrl } = useEnvironment();
11+
const [capabilities, setCapabilities] = useState<Capabilities | null>(null);
12+
const [loading, setLoading] = useState(true);
13+
14+
useEffect(() => {
15+
const baseUrl = apiUrl.replace('/rest/v1', '');
16+
17+
fetch(`${baseUrl}/api/capabilities`)
18+
.then((res) => res.json())
19+
.then(setCapabilities)
20+
.catch(() => setCapabilities({ myopencre: false }))
21+
.finally(() => setLoading(false));
22+
}, [apiUrl]);
23+
24+
return { capabilities, loading };
25+
};

application/frontend/src/hooks/useLocationFromOutsideRoute.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
import { matchPath } from 'react-router';
22
import { useLocation } from 'react-router-dom';
33

4-
import { ROUTES } from '../routes';
4+
import { IRoute } from '../routes';
55

66
interface UseLocationFromOutsideRouteReturn {
77
params: Record<string, string>;
88
url: string;
99
showFilter: boolean;
1010
}
1111

12-
export const useLocationFromOutsideRoute = (): UseLocationFromOutsideRouteReturn => {
13-
// The current URL
12+
/**
13+
* Determines route metadata (params, url, showFilter)
14+
* based on the currently active route.
15+
*
16+
* NOTE:
17+
* - This hook no longer imports ROUTES directly
18+
* - Routes must be passed in (already capability-resolved)
19+
*/
20+
export const useLocationFromOutsideRoute = (routes: IRoute[]): UseLocationFromOutsideRouteReturn => {
1421
const { pathname } = useLocation();
15-
// The current ROUTE, from our URL
16-
const currentRoute = ROUTES.map(({ path, showFilter }) => ({
17-
...matchPath(pathname, path),
18-
showFilter,
19-
})).find((matchedPath) => matchedPath?.isExact);
22+
23+
const currentRoute = routes
24+
.map(({ path, showFilter }) => ({
25+
...matchPath(pathname, {
26+
path,
27+
exact: true,
28+
strict: false,
29+
}),
30+
showFilter,
31+
}))
32+
.find((matchedPath) => matchedPath?.isExact);
33+
2034
return {
2135
params: currentRoute?.params || {},
2236
url: currentRoute?.url || '',
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.graph-debug-panel {
2+
margin-top: 16px;
3+
padding: 12px;
4+
border: 1px solid #d4d4d4;
5+
border-radius: 4px;
6+
background: #fafafa;
7+
8+
&__header {
9+
display: flex;
10+
align-items: center;
11+
gap: 8px;
12+
margin-bottom: 8px;
13+
font-size: 14px;
14+
}
15+
16+
&__summary {
17+
color: #666;
18+
font-size: 12px;
19+
margin-left: 4px;
20+
}
21+
22+
&__legend {
23+
font-size: 12px;
24+
color: #555;
25+
margin-bottom: 10px;
26+
}
27+
28+
&__table-wrap {
29+
overflow-x: auto;
30+
max-height: 400px;
31+
overflow-y: auto;
32+
}
33+
34+
&__node-name {
35+
font-family: monospace;
36+
font-size: 12px;
37+
}
38+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import './GraphDebugPanel.scss';
2+
3+
import React from 'react';
4+
import { Icon, Label, List, Table } from 'semantic-ui-react';
5+
6+
import { TreeDocument } from '../../types';
7+
8+
interface NodeStats {
9+
id: string;
10+
displayName: string;
11+
doctype: string;
12+
inDegree: number;
13+
outDegree: number;
14+
isRoot: boolean;
15+
linkTypes: Record<string, number>;
16+
}
17+
18+
interface GraphDebugPanelProps {
19+
dataStore: Record<string, TreeDocument>;
20+
}
21+
22+
const computeStats = (dataStore: Record<string, TreeDocument>): NodeStats[] => {
23+
const inDegreeMap: Record<string, number> = {};
24+
const linkTypeMap: Record<string, Record<string, number>> = {};
25+
26+
Object.values(dataStore).forEach((doc) => {
27+
if (!inDegreeMap[doc.id]) inDegreeMap[doc.id] = 0;
28+
if (!linkTypeMap[doc.id]) linkTypeMap[doc.id] = {};
29+
30+
if (doc.links) {
31+
doc.links.forEach((link) => {
32+
if (!link.document) return;
33+
const targetId = link.document.id;
34+
if (!inDegreeMap[targetId]) inDegreeMap[targetId] = 0;
35+
if (link.ltype === 'Contains') {
36+
inDegreeMap[targetId] = (inDegreeMap[targetId] || 0) + 1;
37+
}
38+
linkTypeMap[doc.id][link.ltype] = (linkTypeMap[doc.id][link.ltype] || 0) + 1;
39+
});
40+
}
41+
});
42+
43+
return Object.values(dataStore)
44+
.filter((doc) => doc.doctype === 'CRE')
45+
.map((doc) => ({
46+
id: doc.id,
47+
displayName: doc.displayName,
48+
doctype: doc.doctype,
49+
inDegree: inDegreeMap[doc.id] || 0,
50+
outDegree: doc.links ? doc.links.length : 0,
51+
isRoot: (inDegreeMap[doc.id] || 0) === 0,
52+
linkTypes: linkTypeMap[doc.id] || {},
53+
}))
54+
.sort((a, b) => (b.isRoot ? 1 : 0) - (a.isRoot ? 1 : 0));
55+
};
56+
57+
export const GraphDebugPanel = ({ dataStore }: GraphDebugPanelProps) => {
58+
const stats = computeStats(dataStore);
59+
const rootCount = stats.filter((s) => s.isRoot).length;
60+
const totalNodes = stats.length;
61+
62+
return (
63+
<div className="graph-debug-panel">
64+
<div className="graph-debug-panel__header">
65+
<Icon name="bug" />
66+
<strong>Graph Debug Info</strong>
67+
<span className="graph-debug-panel__summary">
68+
{totalNodes} CRE nodes — {rootCount} roots
69+
</span>
70+
</div>
71+
72+
<div className="graph-debug-panel__legend">
73+
<Label size="tiny" color="green">
74+
Root
75+
</Label>
76+
<span> = no incoming Contains links</span>
77+
</div>
78+
79+
<div className="graph-debug-panel__table-wrap">
80+
<Table compact size="small" celled>
81+
<Table.Header>
82+
<Table.Row>
83+
<Table.HeaderCell>Node</Table.HeaderCell>
84+
<Table.HeaderCell>Root?</Table.HeaderCell>
85+
<Table.HeaderCell>In</Table.HeaderCell>
86+
<Table.HeaderCell>Out</Table.HeaderCell>
87+
<Table.HeaderCell>Link Types</Table.HeaderCell>
88+
</Table.Row>
89+
</Table.Header>
90+
<Table.Body>
91+
{stats.map((s) => (
92+
<Table.Row key={s.id} positive={s.isRoot}>
93+
<Table.Cell>
94+
<span className="graph-debug-panel__node-name" title={s.displayName}>
95+
{s.id}
96+
</span>
97+
</Table.Cell>
98+
<Table.Cell textAlign="center">
99+
{s.isRoot && (
100+
<Label size="tiny" color="green">
101+
Root
102+
</Label>
103+
)}
104+
</Table.Cell>
105+
<Table.Cell textAlign="center">{s.inDegree}</Table.Cell>
106+
<Table.Cell textAlign="center">{s.outDegree}</Table.Cell>
107+
<Table.Cell>
108+
<List horizontal size="mini">
109+
{Object.entries(s.linkTypes).map(([ltype, count]) => (
110+
<List.Item key={ltype}>
111+
<Label size="mini">
112+
{ltype}
113+
<Label.Detail>{count}</Label.Detail>
114+
</Label>
115+
</List.Item>
116+
))}
117+
</List>
118+
</Table.Cell>
119+
</Table.Row>
120+
))}
121+
</Table.Body>
122+
</Table>
123+
</div>
124+
</div>
125+
);
126+
};

application/frontend/src/pages/Explorer/explorer.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ main#explorer-content {
110110
flex-direction: column;
111111
}
112112
}
113+
#debug-toggle {
114+
margin-top: 10px;
115+
margin-bottom: 4px;
113116
@media (max-width: 768px) {
114117
main#explorer-content {
115118
padding: 1rem; /* Reduce from 30px to prevent content touching edges */

0 commit comments

Comments
 (0)