Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
59d7386
feat(myopencre): improve UI messaging for no-op CSV imports (#684)
PRAteek-singHWY Feb 28, 2026
13e5fda
MyOpenCRE: add CSV import preview, confirmation step, and improved UX…
PRAteek-singHWY Mar 1, 2026
395f54f
Fix : resolve navbar alignment on desktop and mobile (#772)
ParthAggarwal16 Mar 1, 2026
c3c5ab5
MyOpenCRE: add inline help for CSV preparation (#686)
PRAteek-singHWY Mar 1, 2026
142bb2f
feat(myopencre): gate MyOpenCRE behind deployment capability flag (#700)
PRAteek-singHWY Mar 2, 2026
24d8444
feat(myopencre): implement MyOpenCRE frontend functionality behind ba…
PRAteek-singHWY Mar 4, 2026
35ddacb
docs: add .env.example and document environment configuration (#750)
manshusainishab Mar 4, 2026
0827240
Fix add graph debugging information to explorer (#775)
Mahaboobunnisa123 Mar 4, 2026
f9d6dde
fix: add Redis setup instructions and handle missing Redis connection…
rashmivemuri Mar 4, 2026
9a5d58f
Improve footer UI with glass card layout (Option-2) (#765)
DEMOCODE675 Mar 4, 2026
7866c47
fix(search): hide scroll-to-next arrow when mobile navbar is open (#778)
ParthAggarwal16 Mar 4, 2026
f33510b
feat: Apply general styling, text colors, and input background to the…
manshusainishab Mar 5, 2026
2522535
fix(frontend): prevent overscroll artifacts without changing global p…
PRAteek-singHWY Mar 5, 2026
9cb88a5
Fix - Improve debug panel UX and fix mobile table layout (#792)
Mahaboobunnisa123 Mar 12, 2026
d76e819
Update CONTRIBUTING.md with contribution guidelines (#810)
Pa04rth Mar 16, 2026
5d41013
fix: clean up misc_tools_parser.py (#817)
Deez-Automations Mar 25, 2026
c9f0308
proto-rfc for easier importing (#786)
northdpole Mar 26, 2026
bbe9262
fix: correct tag handling in dbNodeFromCode (#822)
akashrajeev Mar 27, 2026
849b2a4
Collapse linked sources if more than 5 entries (fixes #283) (#828)
rashmivemuri Mar 27, 2026
1b052bd
Fix: Improve Search Bar Icon Sharpness and CSS Cleanup (#650)
DEMOCODE675 Apr 8, 2026
1b788f0
Improve opencre chat prompt to be less talking about the provided con…
robvanderveer Apr 8, 2026
13cce72
test: rename duplicated db and oscal test cases (#873)
PRAteek-singHWY Apr 8, 2026
67650c0
Clean up topic page labels and link hierarchy (#859)
PRAteek-singHWY Apr 8, 2026
9924cfc
Improve RAG prompt to better assess usefulness of knowledge (#875)
robvanderveer Apr 8, 2026
69fa14a
week_1-1: Module C — RFC-aligned Pydantic contracts + config loader
PRAteek-singHWY May 30, 2026
3f5ca86
week_1-1: Module C — schema + config_loader tests
PRAteek-singHWY May 30, 2026
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
44 changes: 44 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ------------------------------------------------------------------------------
# OpenCRE Environment Configuration
# ------------------------------------------------------------------------------
# Copy this file to `.env` and update the values for your environment.
# Never commit your real `.env` file to version control.
# ------------------------------------------------------------------------------
# Database

DEV_DATABASE_URL=postgresql://user:password@localhost:5432/opencre

# Neo4j

NEO4J_URL=neo4j://neo4j:password@localhost:7687

# Redis

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_URL=redis://localhost:6379
REDIS_NO_SSL=false

# Flask

FLASK_CONFIG=development
INSECURE_REQUESTS=false

# Embeddings

NO_GEN_EMBEDDINGS=false

# Google Auth

GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_SECRET_JSON=path/to/secret.json
LOGIN_ALLOWED_DOMAINS=example.com

# GCP

GCP_NATIVE=false

# Spreadsheet Auth

OpenCRE_gspread_Auth=path/to/credentials.json
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,23 +216,28 @@ make docker-dev

The environment variables used by OpenCRE are:

## Environment Configuration

Copy the example configuration file:

```bash
cp .env.example .env
```
- NEO4J_URL
- NO_GEN_EMBEDDINGS
- FLASK_CONFIG
- DEV_DATABASE_URL
- INSECURE_REQUESTS
- REDIS_HOST
- REDIS_PORT
- REDIS_NO_SSL
- REDIS_URL
- GCP_NATIVE
- GOOGLE_SECRET_JSON
- GOOGLE_CLIENT_ID
- GOOGLE_CLIENT_SECRET
- LOGIN_ALLOWED_DOMAINS
- OpenCRE_gspread_Auth
```

Then edit `.env` and provide values appropriate for your environment.

### Variables

* Database: `DEV_DATABASE_URL`
* Neo4j: `NEO4J_URL`
* Redis: `REDIS_HOST`, `REDIS_PORT`, `REDIS_URL`, `REDIS_NO_SSL`
* Flask: `FLASK_CONFIG`, `INSECURE_REQUESTS`
* Embeddings: `NO_GEN_EMBEDDINGS`
* Google Auth: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_SECRET_JSON`, `LOGIN_ALLOWED_DOMAINS`
* GCP: `GCP_NATIVE`
* Spreadsheet Auth: `OpenCRE_gspread_Auth`

See `.env.example` for full list and defaults.

You can run the containers with:

Expand Down
1 change: 1 addition & 0 deletions application/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

basedir = os.path.abspath(os.path.dirname(__file__))
ENABLE_MYOPENCRE = os.getenv("ENABLE_MYOPENCRE", "false").lower() == "true"


class Config:
Expand Down
2 changes: 1 addition & 1 deletion application/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2060,7 +2060,7 @@ def dbNodeFromCode(code: cre_defs.Node) -> Node:
code = cast(cre_defs.Code, code)
tags = ""
if code.tags:
tags = ",".join(tags)
tags = ",".join(code.tags)
return Node(
name=code.name,
ntype=code.doctype.value,
Expand Down
14 changes: 10 additions & 4 deletions application/frontend/src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@ body {
width: 100%;
margin: 0;
padding: 0;
overscroll-behavior-y: none;
}

html {
text-size-adjust: 100%;
background-color: white;
}

body {
background-color: #010817;
background-color: white;
overflow: hidden;
}

#mount {
height: 100vh;
width: 100vw;
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior-y: none;
}

.app {
height: 100%;
min-height: 100%;
}
111 changes: 69 additions & 42 deletions application/frontend/src/components/DocumentNode/DocumentNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ import './documentNode.scss';

import axios from 'axios';
import React, { FunctionComponent, useContext, useEffect, useMemo, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { Icon } from 'semantic-ui-react';

import { TYPE_AUTOLINKED_TO, TYPE_CONTAINS, TYPE_IS_PART_OF, TYPE_RELATED } from '../../const';
import {
CRE,
TYPE_AUTOLINKED_TO,
TYPE_CONTAINS,
TYPE_IS_PART_OF,
TYPE_LINKED_TO,
TYPE_RELATED,
} from '../../const';
import { useEnvironment } from '../../hooks';
import { applyFilters } from '../../hooks/applyFilters';
import { Document } from '../../types';
import { getDocumentDisplayName, groupLinksByType } from '../../utils';
import { getApiEndpoint, getDocumentTypeText, getInternalUrl } from '../../utils/document';
import { groupLinksByType } from '../../utils';
import { getApiEndpoint, getDocumentTypeText, getInternalUrl, getTopicDisplayName } from '../../utils/document';
import { FilterButton } from '../FilterButton/FilterButton';
import { LoadingAndErrorIndicator } from '../LoadingAndErrorIndicator';

const MAX_LENGTH_FOR_AUTO_EXPAND = 5;
export interface DocumentNode {
node: Document;
linkType: string;
Expand All @@ -23,13 +31,15 @@ export interface DocumentNode {
const linkTypesToNest = [TYPE_IS_PART_OF, TYPE_RELATED, TYPE_AUTOLINKED_TO];
const linkTypesExcludedInNesting = [TYPE_CONTAINS];
const linkTypesExcludedWhenNestingRelatedTo = [TYPE_RELATED, TYPE_IS_PART_OF, TYPE_CONTAINS];
const linkDisplayOrder = [TYPE_LINKED_TO, TYPE_AUTOLINKED_TO, TYPE_CONTAINS, TYPE_IS_PART_OF, TYPE_RELATED];

export const DocumentNode: FunctionComponent<DocumentNode> = ({
node,
linkType,
hasLinktypeRelatedParent,
}) => {
const [expanded, setExpanded] = useState<boolean>(false);
const [showAll, setShowAll] = useState<Record<number, boolean>>({});
const isStandard = node.doctype in ['Tool', 'Code', 'Standard'];
const { apiUrl } = useEnvironment();
const [nestedNode, setNestedNode] = useState<Document>();
Expand All @@ -42,6 +52,30 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
const hasExternalLink = Boolean(usedNode.hyperlink);
const linksByType = useMemo(() => groupLinksByType(usedNode), [usedNode]);
let currentUrlParams = new URLSearchParams(window.location.search);
const { pathname } = useLocation();
const isCrePath = useMemo(() => pathname.includes(CRE), [pathname]);

const isNestedInRelated = hasLinktypeRelatedParent || linkType === TYPE_RELATED;

const getTopicsToDisplayOrderdByLinkType = () => {
return linkDisplayOrder
.map((type) => [type, linksByType[type]] as [string, any])
.filter(([_, links]) => Array.isArray(links) && links.length > 0)
.filter(([type, _]) => !linkTypesExcludedInNesting.includes(type))
.filter(([type, _]) =>
isNestedInRelated ? !linkTypesExcludedWhenNestingRelatedTo.includes(type) : true
);
};

const topicsToDisplay = useMemo(() => getTopicsToDisplayOrderdByLinkType(), [linksByType, isNestedInRelated]);

useEffect(() => {
const isAllowedToAutoExpandByLength =
topicsToDisplay.map(([, links]) => links).reduce((prev, cur) => prev.concat(cur), []).length <=
MAX_LENGTH_FOR_AUTO_EXPAND;
const shouldCollapseRelatedByDefault = linkType === TYPE_RELATED;
setExpanded(isCrePath && !shouldCollapseRelatedByDefault ? isAllowedToAutoExpandByLength : false);
}, [topicsToDisplay, isCrePath, linkType]);

useEffect(() => {
if (!isStandard && linkTypesToNest.includes(linkType)) {
Expand All @@ -51,34 +85,22 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
.then(function (response) {
setLoading(false);
setNestedNode(response.data.data);
setExpanded(true);
setExpanded(linkType !== TYPE_RELATED);
setError('');
})
.catch(function (axiosError) {
setLoading(false);
setError(axiosError);
});
}
}, [id]);
}, [id, linkType]);

const fetchedNodeHasLinks = () => {
return usedNode.links && usedNode.links.length > 0;
};

const hasActiveLinks = () => {
return getTopicsToDisplayOrderdByLinkType().length > 0;
};

const isNestedInRelated = (): Boolean => {
return hasLinktypeRelatedParent || linkType === TYPE_RELATED;
};

const getTopicsToDisplayOrderdByLinkType = () => {
return Object.entries(linksByType)
.filter(([type, _]) => !linkTypesExcludedInNesting.includes(type))
.filter(([type, _]) =>
isNestedInRelated() ? !linkTypesExcludedWhenNestingRelatedTo.includes(type) : true
);
return topicsToDisplay.length > 0;
};

const Hyperlink = (hyperlink) => {
Expand All @@ -93,6 +115,9 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
{' '}
{hyperlink.hyperlink}
</a>
<a href={hyperlink.hyperlink} target="_blank" rel="noopener noreferrer" aria-label="Open reference in new tab">
<Icon name="external" />
</a>
</>
);
};
Expand All @@ -114,7 +139,7 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
<div className={`title external-link document-node f2`}>
<Link to={getInternalUrl(usedNode)}>
<i aria-hidden="true" className="circle icon"></i>
{getDocumentDisplayName(usedNode)}
{getTopicDisplayName(usedNode)}
</Link>
<HyperlinkIcon hyperlink={usedNode.hyperlink} />
</div>
Expand All @@ -129,42 +154,44 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
<LoadingAndErrorIndicator loading={loading} error={error} />
<div className={`title ${active} document-node`} onClick={() => setExpanded(!expanded)}>
<i aria-hidden="true" className="dropdown icon"></i>
<Link to={getInternalUrl(usedNode)}>{getDocumentDisplayName(usedNode)}</Link>
<Link to={getInternalUrl(usedNode)}>{getTopicDisplayName(usedNode)}</Link>
</div>
<div className={`content${active} document-node`}>
<Hyperlink hyperlink={usedNode.hyperlink} />
{expanded &&
getTopicsToDisplayOrderdByLinkType().map(([type, links], idx) => {
const sortedResults = links.sort((a, b) =>
getDocumentDisplayName(a.document).localeCompare(getDocumentDisplayName(b.document))
topicsToDisplay.map(([type, links], idx) => {
const sortedResults = [...links].sort((a, b) =>
getTopicDisplayName(a.document).localeCompare(getTopicDisplayName(b.document))
);
let lastDocumentName = sortedResults[0].document.name;
return (
<div className="document-node__link-type-container" key={type}>
<div className="document-node__link-type-container" key={`${type}-${idx}`}>
{idx > 0 && <hr style={{ borderColor: 'transparent', margin: '20px 0' }} />}
<div>
<b>Which {getDocumentTypeText(type, links[0].document.doctype, node.doctype)}</b>:
{/* Risk here of mixed doctype in here causing odd output */}
</div>
<div>
<div className="accordion ui fluid styled f0">
{sortedResults.map((link, i) => {
const temp = (
<div key={Math.random()}>
{lastDocumentName !== link.document.name && <span style={{ margin: '5px' }} />}
<DocumentNode
node={link.document}
linkType={type}
hasLinktypeRelatedParent={isNestedInRelated()}
key={Math.random()}
/>
<FilterButton document={link.document} />
</div>
);
lastDocumentName = link.document.name;
return temp;
})}
{sortedResults.slice(0, showAll[idx] ? sortedResults.length : MAX_LENGTH_FOR_AUTO_EXPAND).map((link, i) => (
<div key={`document-node-container-${type}-${idx}-${i}`} style={{ marginBottom: '4px' }}>
<DocumentNode
node={link.document}
linkType={type}
hasLinktypeRelatedParent={isNestedInRelated as boolean}
key={`document-sub-node-${type}-${idx}-${i}`}
/>
<FilterButton document={link.document} />
</div>
))}
</div>
{sortedResults.length > MAX_LENGTH_FOR_AUTO_EXPAND && (
<button
onClick={() => setShowAll(prev => ({ ...prev, [idx]: !prev[idx] }))}
style={{ marginTop: '8px', cursor: 'pointer' }}
>
{showAll[idx] ? 'Show less ▲' : 'Show more ▼'}
</button>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
&__link-type-container hr {
margin: 15px 0;
}

&__link-type-container {
border-left: 3px solid rgba(33, 133, 208, 0.2);
margin-left: 0.5rem;
padding-left: 0.75rem;
}
}


Expand Down
10 changes: 5 additions & 5 deletions application/frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export const TYPE_AUTOLINKED_TO = 'Automatically linked to';

export const DOCUMENT_TYPE_NAMES = {
[TYPE_AUTOLINKED_TO]: ' has been automatically mapped to',
[TYPE_LINKED_TO]: ' is linked to sources',
[TYPE_IS_PART_OF]: ' is part of CREs',
[TYPE_LINKED_FROM]: 'is linked from CREs',
[TYPE_CONTAINS]: ' contains CREs',
[TYPE_RELATED]: ' is related to CREs',
[TYPE_LINKED_TO]: ' refers to sources',
[TYPE_IS_PART_OF]: ' is child of Requirements',
[TYPE_LINKED_FROM]: ' is referenced by Requirements',
[TYPE_CONTAINS]: ' contains Requirements',
[TYPE_RELATED]: ' is related to Requirements',
};

export const DOCUMENT_TYPES = {
Expand Down
1 change: 1 addition & 0 deletions application/frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { useEnvironment } from './useEnvironment';
export { useLocationFromOutsideRoute } from './useLocationFromOutsideRoute';
export { useCapabilities } from './useCapabilities';
Loading
Loading