Skip to content

Commit 256fbb5

Browse files
Merge branch 'main' into feat/explorer-force-graph-polish
2 parents 4143d43 + 849b2a4 commit 256fbb5

10 files changed

Lines changed: 708 additions & 70 deletions

File tree

application/database/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2060,7 +2060,7 @@ def dbNodeFromCode(code: cre_defs.Node) -> Node:
20602060
code = cast(cre_defs.Code, code)
20612061
tags = ""
20622062
if code.tags:
2063-
tags = ",".join(tags)
2063+
tags = ",".join(code.tags)
20642064
return Node(
20652065
name=code.name,
20662066
ntype=code.doctype.value,

application/frontend/src/components/DocumentNode/DocumentNode.tsx

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import './documentNode.scss';
22

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

8-
import { TYPE_AUTOLINKED_TO, TYPE_CONTAINS, TYPE_IS_PART_OF, TYPE_RELATED } from '../../const';
8+
import { CRE, TYPE_AUTOLINKED_TO, TYPE_CONTAINS, TYPE_IS_PART_OF, TYPE_RELATED } from '../../const';
99
import { useEnvironment } from '../../hooks';
1010
import { applyFilters } from '../../hooks/applyFilters';
1111
import { Document } from '../../types';
@@ -14,6 +14,7 @@ import { getApiEndpoint, getDocumentTypeText, getInternalUrl } from '../../utils
1414
import { FilterButton } from '../FilterButton/FilterButton';
1515
import { LoadingAndErrorIndicator } from '../LoadingAndErrorIndicator';
1616

17+
const MAX_LENGTH_FOR_AUTO_EXPAND = 5;
1718
export interface DocumentNode {
1819
node: Document;
1920
linkType: string;
@@ -30,6 +31,7 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
3031
hasLinktypeRelatedParent,
3132
}) => {
3233
const [expanded, setExpanded] = useState<boolean>(false);
34+
const [showAll, setShowAll] = useState<Record<number, boolean>>({});
3335
const isStandard = node.doctype in ['Tool', 'Code', 'Standard'];
3436
const { apiUrl } = useEnvironment();
3537
const [nestedNode, setNestedNode] = useState<Document>();
@@ -42,6 +44,27 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
4244
const hasExternalLink = Boolean(usedNode.hyperlink);
4345
const linksByType = useMemo(() => groupLinksByType(usedNode), [usedNode]);
4446
let currentUrlParams = new URLSearchParams(window.location.search);
47+
const { pathname } = useLocation();
48+
const isCrePath = useMemo(() => pathname.includes(CRE), [pathname]);
49+
50+
const isNestedInRelated = hasLinktypeRelatedParent || linkType === TYPE_RELATED;
51+
52+
const getTopicsToDisplayOrderdByLinkType = () => {
53+
return Object.entries(linksByType)
54+
.filter(([type, _]) => !linkTypesExcludedInNesting.includes(type))
55+
.filter(([type, _]) =>
56+
isNestedInRelated ? !linkTypesExcludedWhenNestingRelatedTo.includes(type) : true
57+
);
58+
};
59+
60+
const topicsToDisplay = useMemo(() => getTopicsToDisplayOrderdByLinkType(), [linksByType, isNestedInRelated]);
61+
62+
useEffect(() => {
63+
const isAllowedToAutoExpandByLength =
64+
topicsToDisplay.map(([, links]) => links).reduce((prev, cur) => prev.concat(cur), []).length <=
65+
MAX_LENGTH_FOR_AUTO_EXPAND;
66+
setExpanded(isCrePath ? isAllowedToAutoExpandByLength : false);
67+
}, [topicsToDisplay, isCrePath]);
4568

4669
useEffect(() => {
4770
if (!isStandard && linkTypesToNest.includes(linkType)) {
@@ -66,19 +89,7 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
6689
};
6790

6891
const hasActiveLinks = () => {
69-
return getTopicsToDisplayOrderdByLinkType().length > 0;
70-
};
71-
72-
const isNestedInRelated = (): Boolean => {
73-
return hasLinktypeRelatedParent || linkType === TYPE_RELATED;
74-
};
75-
76-
const getTopicsToDisplayOrderdByLinkType = () => {
77-
return Object.entries(linksByType)
78-
.filter(([type, _]) => !linkTypesExcludedInNesting.includes(type))
79-
.filter(([type, _]) =>
80-
isNestedInRelated() ? !linkTypesExcludedWhenNestingRelatedTo.includes(type) : true
81-
);
92+
return topicsToDisplay.length > 0;
8293
};
8394

8495
const Hyperlink = (hyperlink) => {
@@ -134,37 +145,39 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
134145
<div className={`content${active} document-node`}>
135146
<Hyperlink hyperlink={usedNode.hyperlink} />
136147
{expanded &&
137-
getTopicsToDisplayOrderdByLinkType().map(([type, links], idx) => {
138-
const sortedResults = links.sort((a, b) =>
148+
topicsToDisplay.map(([type, links], idx) => {
149+
const sortedResults = [...links].sort((a, b) =>
139150
getDocumentDisplayName(a.document).localeCompare(getDocumentDisplayName(b.document))
140151
);
141-
let lastDocumentName = sortedResults[0].document.name;
142152
return (
143-
<div className="document-node__link-type-container" key={type}>
153+
<div className="document-node__link-type-container" key={`${type}-${idx}`}>
144154
{idx > 0 && <hr style={{ borderColor: 'transparent', margin: '20px 0' }} />}
145155
<div>
146156
<b>Which {getDocumentTypeText(type, links[0].document.doctype, node.doctype)}</b>:
147157
{/* Risk here of mixed doctype in here causing odd output */}
148158
</div>
149159
<div>
150160
<div className="accordion ui fluid styled f0">
151-
{sortedResults.map((link, i) => {
152-
const temp = (
153-
<div key={Math.random()}>
154-
{lastDocumentName !== link.document.name && <span style={{ margin: '5px' }} />}
155-
<DocumentNode
156-
node={link.document}
157-
linkType={type}
158-
hasLinktypeRelatedParent={isNestedInRelated()}
159-
key={Math.random()}
160-
/>
161-
<FilterButton document={link.document} />
162-
</div>
163-
);
164-
lastDocumentName = link.document.name;
165-
return temp;
166-
})}
161+
{sortedResults.slice(0, showAll[idx] ? sortedResults.length : MAX_LENGTH_FOR_AUTO_EXPAND).map((link, i) => (
162+
<div key={`document-node-container-${type}-${idx}-${i}`} style={{ marginBottom: '4px' }}>
163+
<DocumentNode
164+
node={link.document}
165+
linkType={type}
166+
hasLinktypeRelatedParent={isNestedInRelated as boolean}
167+
key={`document-sub-node-${type}-${idx}-${i}`}
168+
/>
169+
<FilterButton document={link.document} />
170+
</div>
171+
))}
167172
</div>
173+
{sortedResults.length > MAX_LENGTH_FOR_AUTO_EXPAND && (
174+
<button
175+
onClick={() => setShowAll(prev => ({ ...prev, [idx]: !prev[idx] }))}
176+
style={{ marginTop: '8px', cursor: 'pointer' }}
177+
>
178+
{showAll[idx] ? 'Show less ▲' : 'Show more ▼'}
179+
</button>
180+
)}
168181
</div>
169182
</div>
170183
);

application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,24 @@ import { Document } from '../../types';
1313
import { groupLinksByType } from '../../utils';
1414
import { getDocumentDisplayName, getDocumentTypeText, orderLinksByType } from '../../utils/document';
1515

16+
const MAX_LENGTH_FOR_AUTO_EXPAND = 5;
17+
1618
export const CommonRequirementEnumeration = () => {
1719
const { id } = useParams<{ id: string }>();
1820
const { search } = useLocation();
1921
const { apiUrl } = useEnvironment();
2022
const [loading, setLoading] = useState<boolean>(false);
2123
const [error, setError] = useState<string | Object | null>(null);
2224
const [data, setData] = useState<Document | null>();
25+
const [showAll, setShowAll] = useState<Record<string, boolean>>({});
2326
const source = useMemo(() => {
2427
const sourceParam = new URLSearchParams(search).get('source');
2528
return sourceParam && sourceParam.trim().length > 0 ? sourceParam : null;
2629
}, [search]);
2730

2831
useEffect(() => {
2932
setLoading(true);
33+
setShowAll({});
3034
window.scrollTo(0, 0);
3135

3236
const params = source ? { params: { source } } : undefined;
@@ -93,24 +97,26 @@ export const CommonRequirementEnumeration = () => {
9397
const sortedResults = links.sort((a, b) =>
9498
getDocumentDisplayName(a.document).localeCompare(getDocumentDisplayName(b.document))
9599
);
96-
let lastDocumentName = sortedResults[0].document.name;
97100
return (
98101
<div className="cre-page__links" key={type}>
99102
<div className="cre-page__links-eader">
100103
<b>Which {getDocumentTypeText(type, links[0].document.doctype)}</b>:
101104
{/* Risk of mixed doctype in here causing odd output */}
102105
</div>
103-
{sortedResults.map((link, i) => {
104-
const temp = (
105-
<div key={i} className="accordion ui fluid styled cre-page__links-container">
106-
{lastDocumentName !== link.document.name && <span style={{ margin: '5px' }} />}
107-
<DocumentNode node={link.document} linkType={type} />
108-
<FilterButton document={link.document} />
109-
</div>
110-
);
111-
lastDocumentName = link.document.name;
112-
return temp;
113-
})}
106+
{sortedResults.slice(0, showAll[type] ? sortedResults.length : MAX_LENGTH_FOR_AUTO_EXPAND).map((link, i) => (
107+
<div key={i} className="accordion ui fluid styled cre-page__links-container" style={{ marginBottom: '4px' }}>
108+
<DocumentNode node={link.document} linkType={type} />
109+
<FilterButton document={link.document} />
110+
</div>
111+
))}
112+
{sortedResults.length > MAX_LENGTH_FOR_AUTO_EXPAND && (
113+
<button
114+
onClick={() => setShowAll(prev => ({ ...prev, [type]: !prev[type] }))}
115+
style={{ marginTop: '8px', cursor: 'pointer' }}
116+
>
117+
{showAll[type] ? 'Show less ▲' : 'Show more ▼'}
118+
</button>
119+
)}
114120
</div>
115121
);
116122
})}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
gap: 8px;
1212
margin-bottom: 8px;
1313
font-size: 14px;
14+
flex-wrap: wrap;
1415
}
1516

1617
&__summary {
@@ -26,13 +27,20 @@
2627
}
2728

2829
&__table-wrap {
30+
width: 100%;
2931
overflow-x: auto;
30-
max-height: 400px;
3132
overflow-y: auto;
33+
max-height: 400px;
34+
-webkit-overflow-scrolling: touch;
35+
}
36+
37+
&__table {
38+
min-width: 700px;
3239
}
3340

3441
&__node-name {
3542
font-family: monospace;
3643
font-size: 12px;
44+
white-space: nowrap;
3745
}
3846
}

application/frontend/src/pages/Explorer/GraphDebugPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const GraphDebugPanel = ({ dataStore }: GraphDebugPanelProps) => {
7777
</div>
7878

7979
<div className="graph-debug-panel__table-wrap">
80-
<Table compact size="small" celled>
80+
<Table compact size="small" celled unstackable className="graph-debug-panel__table">
8181
<Table.Header>
8282
<Table.Row>
8383
<Table.HeaderCell>Node</Table.HeaderCell>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ main#explorer-content {
1414
p,
1515
label,
1616
.menu-title {
17-
color: #f7fafc;
17+
color: #1a202c;
1818
}
1919

2020
a {

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import './explorer.scss';
22

33
import React, { useEffect, useState } from 'react';
44
import { Link } from 'react-router-dom';
5-
import { Checkbox, List } from 'semantic-ui-react';
5+
import { Checkbox, List, Popup } from 'semantic-ui-react';
66

77
import { LoadingAndErrorIndicator } from '../../components/LoadingAndErrorIndicator';
88
import { TYPE_CONTAINS, TYPE_LINKED_TO } from '../../const';
@@ -160,13 +160,21 @@ export const Explorer = () => {
160160
</li>
161161
</ul>
162162
</div>
163-
<div id="debug-toggle">
163+
<div id="debug-toggle" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
164164
<Checkbox
165165
toggle
166166
label="Debug mode"
167167
checked={debugMode}
168168
onChange={() => setDebugMode(!debugMode)}
169169
/>
170+
<Popup
171+
content="Debug mode shows graph connectivity stats and link type details for each CRE node."
172+
trigger={
173+
<span style={{ cursor: 'help', color: '#666', fontSize: '14px', border: '1px solid #666', borderRadius: '50%', padding: '0 4px', fontWeight: 'bold' }}>
174+
?
175+
</span>
176+
}
177+
/>
170178
</div>
171179
</div>
172180

application/utils/external_project_parsers/parsers/misc_tools_parser.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import os
44
import re
55
import urllib
6-
from typing import List, NamedTuple
7-
from xmlrpc.client import boolean
6+
from typing import List
87

98
from application.database import db
109
from application.defs import cre_defs as defs
@@ -22,7 +21,7 @@
2221

2322

2423
class MiscTools(ParserInterface):
25-
name = "miscelaneous tools"
24+
name = "miscellaneous tools"
2625
tool_urls = [
2726
"https://github.com/commjoen/wrongsecrets.git",
2827
]
@@ -33,23 +32,30 @@ def parse(self, cache: db.Node_collection, ph: prompt_client.PromptHandler):
3332
tools = {}
3433
for url in self.tool_urls:
3534
tool_entries = self.parse_tool(cache=cache, tool_repo=url)
36-
tools[tool_entries[0].name] = tool_entries
35+
if tool_entries:
36+
tools[tool_entries[0].name] = tool_entries
3737
return ParseResult(results=tools)
3838

3939
def parse_tool(
40-
self, tool_repo: str, cache: db.Node_collection, dry_run: boolean = False
41-
):
42-
if not dry_run:
43-
repo = git.clone(tool_repo)
40+
self, tool_repo: str, cache: db.Node_collection, dry_run: bool = False
41+
) -> List[defs.Tool]:
42+
if dry_run:
43+
logger.info("dry run, skipping clone and parsing for %s", tool_repo)
44+
return []
45+
repo = git.clone(tool_repo)
4446
readme = os.path.join(repo.working_dir, "README.md")
4547
title_regexp = r"# (?P<title>(\w+ ?)+)"
4648
cre_link = r".*\[.*\]\((?P<url>(https\:\/\/www\.)?opencre\.org\/cre\/(?P<cre>\d+-\d+).*)"
47-
tool_entries = []
49+
tool_entries: List[defs.Tool] = []
4850
with open(readme) as rdf:
4951
mdtext = rdf.read()
5052

5153
if "opencre.org" not in mdtext:
52-
logging.error("didn't find a link, bye")
54+
logger.error(
55+
"no opencre.org link found in %s for repo %s, skipping",
56+
readme,
57+
tool_repo,
58+
)
5359
return []
5460
title = re.search(title_regexp, mdtext)
5561
cre = re.search(cre_link, mdtext, flags=re.IGNORECASE)
@@ -86,7 +92,7 @@ def parse_tool(
8692
document=dbcre,
8793
)
8894
)
89-
print(
95+
logger.info(
9096
f"Registered new Document of type:Tool, toolType: {tool_type}, name:{tool_name} and hyperlink:{hyperlink},"
9197
f"linked to cre:{dbcre.id}"
9298
)

0 commit comments

Comments
 (0)