@@ -11,6 +11,7 @@ import {
1111import './Table.css' ;
1212import './subtechnique.css' ;
1313import './more_details.css' ;
14+ import { Alert } from '../Alert/Alert'
1415
1516const EXPAND = 'expand' ;
1617const 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