Skip to content

Commit 6945ba6

Browse files
new feature:Added Sync with NRF Content feature
1 parent bb757bc commit 6945ba6

File tree

3 files changed

+140
-19
lines changed

3 files changed

+140
-19
lines changed

src/Main.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const Main = () => {
2222
const [colorSelect, setColorSelect] = useState(null);
2323
const [riskScoreContent, setRiskScoreContent] = useState(null);
2424
const [hideToggleVal, setHideToggleVal] = useState(true);
25+
const [syncContent, setSyncContent] = useState(null);
2526

2627
useEffect(() => {
2728
setViewCustomContent(false);
@@ -58,6 +59,11 @@ export const Main = () => {
5859
const handleAddClick = useCallback((value) => {
5960
setAddContent(value);
6061
}, []);
62+
63+
const handleSyncClick = useCallback((value) => {
64+
console.log('inside handleSyncClick')
65+
setSyncContent(true);
66+
}, []);
6167

6268
const handleColorClick = useCallback((value) => {
6369
setColorSelect(value);
@@ -109,6 +115,11 @@ export const Main = () => {
109115
setViewCustomContent(viewCustomContentMode);
110116
}, []);
111117

118+
const handleSyncCompletion = useCallback(() => {
119+
console.log('inside handleSyncCompletion')
120+
setSyncContent(null);
121+
}, []);
122+
112123
return (
113124
<>
114125
<Header
@@ -129,6 +140,7 @@ export const Main = () => {
129140
onRiskScore={handleRiskScore}
130141
selectedTechnique={selectedValue}
131142
viewCustomMode={viewCustomContent}
143+
onSyncClick={handleSyncClick}
132144
/>
133145

134146
{shouldRenderTechniques && !addContent && !editContent && !hideTechnique && (
@@ -147,6 +159,8 @@ export const Main = () => {
147159
hideToggleStatus={hideToggleVal}
148160
selectedColor={colorSelect}
149161
riskScoreInfo={riskScoreContent}
162+
onSyncCompletion={handleSyncCompletion}
163+
shouldSync={syncContent}
150164
/>
151165
)}
152166

src/components/Header/Header.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
55
import {
66
RiAddCircleLine, RiImportLine, RiArrowLeftLine,
77
RiExportLine, RiDeleteBin5Line, RiEyeLine, RiPaletteLine,
8-
RiFilterFill, RiSettings5Fill, RiBarChartFill
8+
RiFilterFill, RiSettings5Fill, RiBarChartFill, RiRefreshFill
99
} from 'react-icons/ri';
1010
import { handleExport } from '../ContentManager/ManageContentUtils';
1111
import { Alert } from '../Alert/Alert';
@@ -23,7 +23,7 @@ const Header = ({
2323
toggleControl, onAddClick, onEditMode, onImportClick,
2424
onViewCustomContent, editStatus, editContent, onBackClick,
2525
addContent, onHideClick, hideStatus, onHideToggle, hideToggleStatus,
26-
onColorClick, onRiskScore, selectedTechnique, viewCustomMode
26+
onColorClick, onRiskScore, selectedTechnique, viewCustomMode, onSyncClick
2727
}) => {
2828
const [isToggled, setIsToggled] = useState(editStatus);
2929
const [viewCustomContent, setViewCustomContent] = useState(viewCustomMode);
@@ -50,6 +50,8 @@ const Header = ({
5050

5151
const handleAddClick = () => onAddClick('add');
5252

53+
const handleSynclick = () => onSyncClick('sync')
54+
5355
const handleBackClick = () => {
5456
setViewCustomContent(false);
5557
onViewCustomContent(false);
@@ -222,6 +224,12 @@ const Header = ({
222224
<header>
223225
<img width="250" height="50" className="logo" alt="NRF Logo" src={NRFLogo} />
224226
<span>Dev Build</span>
227+
<div className="header-controls">
228+
<button className="header-button" onClick={handleSynclick}>
229+
<RiRefreshFill style={{ fontSize: '35px' }}/>
230+
Sync NRF Content
231+
</button>
232+
</div>
225233

226234
{showFailAlert && <Alert classStyle="alert-fail" heading={alertHeading} value={alertVal} />}
227235
{responseSubmit && <Alert classStyle="alert-success" heading={alertHeading} value={alertVal} />}

src/components/TableContent/TechniquesTable.jsx

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import './Table.css';
1212
import './subtechnique.css';
1313
import './more_details.css';
14+
import { Alert } from '../Alert/Alert'
1415

1516
const EXPAND = 'expand';
1617
const COLLAPSE = 'collapse';
@@ -35,7 +36,9 @@ const TechniquesTable = ({
3536
hideStatus,
3637
hideToggleStatus,
3738
selectedColor,
38-
riskScoreInfo
39+
riskScoreInfo,
40+
shouldSync,
41+
onSyncCompletion
3942
}) => {
4043
const [tableData, setTableData] = useState(() => {
4144
const savedData = localStorage.getItem('technique_table');
@@ -54,12 +57,18 @@ const TechniquesTable = ({
5457
const [hiddenTechniques, setHideTechniques] = useState([]);
5558
const [cellColors, setCellColors] = useState({});
5659

60+
const [alertVal, setAlertVal] = useState('')
61+
const [showFailAlert, setShowFailAlert] = useState(false)
62+
const [alertHeading, setAlertHeading] = useState('')
63+
const [responseSubmit, setResponseSubmit] = useState(false)
64+
5765
const handleEdit = (technique) => {
5866
onEditClick(technique);
5967
};
6068

6169
const fetchTechniques = async () => {
6270
const fetchedTechniques = await fetchAllTechniques(viewCustomMode);
71+
6372
setAllTechniques(fetchedTechniques);
6473
setTableData(fetchedTechniques);
6574
};
@@ -83,6 +92,9 @@ const TechniquesTable = ({
8392
};
8493
}, []);
8594

95+
96+
97+
8698
useEffect(() => {
8799
if (viewCustomMode) {
88100
const local = localStorage.getItem("technique_table");
@@ -101,6 +113,73 @@ const TechniquesTable = ({
101113
}
102114
}, []);
103115

116+
//Sync TechniquesTable
117+
function syncTechniquesTable(array1, array2) {
118+
const result = array2.map(row => ({ ...row }));
119+
120+
array1.forEach(sourceItem => {
121+
const match = result.find(targetItem => {
122+
// Match on stable keys (exclude empty fields from matching)
123+
return Object.keys(targetItem).every(key => {
124+
const targetValue = targetItem[key];
125+
const sourceValue = sourceItem[key];
126+
127+
// If target value is empty, don't use it to match
128+
if (targetValue === '' || targetValue === undefined) return true;
129+
130+
// Otherwise, values must match
131+
return targetValue === sourceValue;
132+
});
133+
});
134+
135+
if (match) {
136+
// Fill only missing/empty fields
137+
Object.entries(sourceItem).forEach(([key, value]) => {
138+
if (
139+
!match.hasOwnProperty(key) ||
140+
match[key] === '' ||
141+
match[key] === undefined
142+
) {
143+
match[key] = value;
144+
}
145+
});
146+
}
147+
});
148+
149+
return result;
150+
}
151+
152+
// Handle Sync Content
153+
useEffect(() => {
154+
if (shouldSync) {
155+
onSyncCompletion('synccompleted')
156+
157+
//Sync TechniqueTable
158+
const NRFTechniqueTable = fetchAllTechniques(false);
159+
const customTechniqueTable = JSON.parse(localStorage.getItem('technique_table')) || [];
160+
const mergedTechniqueTable = syncTechniquesTable(NRFTechniqueTable, customTechniqueTable);
161+
localStorage.setItem('technique_table', JSON.stringify(mergedTechniqueTable));
162+
163+
const NRFTechniques = dataArray
164+
const customTechniques = JSON.parse(localStorage.getItem('techniques')) || [];
165+
166+
//Sync Techniques
167+
const techniqueNamesList = new Set(customTechniques.map(item => item.name));
168+
const syncedArray = [
169+
...customTechniques,
170+
...NRFTechniques.filter(item => !techniqueNamesList.has(item.name)),
171+
];
172+
localStorage.setItem('techniques', JSON.stringify(syncedArray));
173+
174+
setResponseSubmit(true)
175+
setAlertVal('Data synced successfully')
176+
setTimeout(() => {
177+
setResponseSubmit(false)
178+
}, 2000)
179+
}
180+
181+
}, [shouldSync]);
182+
104183
// Handle importContent updates
105184
useEffect(() => {
106185
if (importContent?.technique_table) {
@@ -464,12 +543,12 @@ const TechniquesTable = ({
464543
const currentCell = tableRef.current?.querySelector(
465544
`tr:nth-child(${row + 1}) td:nth-child(${col + 2})`
466545
);
467-
546+
468547
if (!currentCell) return;
469-
548+
470549
const subCells = currentCell.querySelectorAll('li');
471550
const isSubcell = subCells.length > 0;
472-
551+
473552
if (isSubcell) {
474553
for (let i = 0; i < subCells.length; i++) {
475554
if (i === index) {
@@ -487,15 +566,15 @@ const TechniquesTable = ({
487566
}}
488567
>
489568
<span style={{ flex: 1, textAlign: 'center' }}>{line}</span>
490-
569+
491570
{searchFilter !== '' && (searchFilterType === MITIGATION || searchFilterType === DETECTION) && line && (
492571
<div>
493572
{fetchImplementationStatus(line)
494573
? CustomCheckbox()
495574
: <RiFolderWarningFill style={{ color: 'orange' }} />}
496575
</div>
497576
)}
498-
577+
499578
{editStatus && (
500579
<div
501580
style={{
@@ -515,8 +594,8 @@ const TechniquesTable = ({
515594
)}
516595
</li>
517596
);
518-
}
519-
597+
}
598+
520599
if (hideToggleStatus) {
521600
return (
522601
<li
@@ -529,12 +608,12 @@ const TechniquesTable = ({
529608
const currentCell = tableRef.current?.querySelector(
530609
`tr:nth-child(${row + 1}) td:nth-child(${col + 2})`
531610
);
532-
611+
533612
if (!currentCell) return;
534-
613+
535614
const subCells = currentCell.querySelectorAll('li');
536615
const isSubcell = subCells.length > 0;
537-
616+
538617
if (isSubcell) {
539618
for (let i = 0; i < subCells.length; i++) {
540619
if (i === index) {
@@ -552,15 +631,15 @@ const TechniquesTable = ({
552631
}}
553632
>
554633
<span style={{ flex: 1, textAlign: 'center' }}>{line}</span>
555-
634+
556635
{searchFilter !== '' && (searchFilterType === MITIGATION || searchFilterType === DETECTION) && line && (
557636
<div>
558637
{fetchImplementationStatus(line)
559638
? CustomCheckbox()
560639
: <RiFolderWarningFill style={{ color: 'orange' }} />}
561640
</div>
562641
)}
563-
642+
564643
{editStatus && (
565644
<div
566645
style={{
@@ -586,10 +665,10 @@ const TechniquesTable = ({
586665
})}
587666
</ul>
588667
);
589-
668+
590669
return stringWithBreaks;
591670
};
592-
671+
593672
const rename_headers = (updatedData, operation, other_columns) => {
594673
let headers = [];
595674

@@ -1179,9 +1258,10 @@ const TechniquesTable = ({
11791258
{sub_techniques &&
11801259
sub_techniques.map((line, index) => (
11811260
<li
1182-
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center',
1261+
style={{
1262+
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
11831263
color: hiddenTechniques?.includes(line) ? 'grey' : 'white',
1184-
}}
1264+
}}
11851265
tabIndex={0}
11861266
key={index}
11871267
onFocus={() => {
@@ -1447,14 +1527,33 @@ const TechniquesTable = ({
14471527
}
14481528
}
14491529
return (
1530+
<>
1531+
<div>
1532+
{showFailAlert && (
1533+
<Alert
1534+
classStyle="alert-fail"
1535+
heading={alertHeading}
1536+
value={alertVal}
1537+
/>
1538+
)}
1539+
{responseSubmit && (
1540+
<Alert
1541+
classStyle="alert-success"
1542+
heading={alertHeading}
1543+
value={alertVal}
1544+
/>
1545+
)}
1546+
</div>
14501547
<nav tabIndex={0} onKeyDown={handleKeyDown} ref={tableRef}>
1548+
14511549
<table className={`table-container ${isPanelOpen ? 'shrink' : ''}`}>
14521550
<thead>
14531551
<tr>{renderHeaders() || <th colSpan={4}>Loading...</th>}</tr>
14541552
</thead>
14551553
<tbody>{renderRows()}</tbody>
14561554
</table>
14571555
</nav>
1556+
</>
14581557
);
14591558
};
14601559

0 commit comments

Comments
 (0)