@@ -313,6 +313,42 @@ const ManageDriftPage = () => {
313313 return null ;
314314 } ;
315315
316+ // Helper function to compare JSON objects and find differences
317+ const compareJsonObjects = ( expected , current ) => {
318+ if ( ! expected || ! current ) return null ;
319+
320+ try {
321+ const expectedObj = typeof expected === "string" ? JSON . parse ( expected ) : expected ;
322+ const currentObj = typeof current === "string" ? JSON . parse ( current ) : current ;
323+
324+ // Deep comparison - if they're equal, return null (no diff)
325+ if ( JSON . stringify ( expectedObj ) === JSON . stringify ( currentObj ) ) {
326+ return null ; // No differences
327+ }
328+
329+ // Find differences
330+ const differences = { } ;
331+ const allKeys = new Set ( [ ...Object . keys ( expectedObj ) , ...Object . keys ( currentObj ) ] ) ;
332+
333+ allKeys . forEach ( ( key ) => {
334+ const expectedVal = expectedObj [ key ] ;
335+ const currentVal = currentObj [ key ] ;
336+
337+ if ( JSON . stringify ( expectedVal ) !== JSON . stringify ( currentVal ) ) {
338+ differences [ key ] = {
339+ expected : expectedVal ,
340+ current : currentVal ,
341+ } ;
342+ }
343+ } ) ;
344+
345+ return Object . keys ( differences ) . length > 0 ? differences : null ;
346+ } catch ( e ) {
347+ console . error ( "Error comparing JSON objects:" , e ) ;
348+ return null ;
349+ }
350+ } ;
351+
316352 // Helper function to format policy objects for display
317353 const formatPolicyValue = ( value ) => {
318354 if ( ! value ) return "N/A" ;
@@ -340,6 +376,21 @@ const ManageDriftPage = () => {
340376 // Helper function to create deviation items
341377 const createDeviationItems = ( deviations , statusOverride = null ) => {
342378 return ( deviations || [ ] ) . map ( ( deviation , index ) => {
379+ // Check if this should be skipped due to missing license
380+ const isLicenseSkipped = deviation . LicenseAvailable === false ;
381+
382+ // Check if we have both ExpectedValue and CurrentValue for comparison
383+ let isActuallyCompliant = false ;
384+ let jsonDifferences = null ;
385+
386+ if ( deviation . ExpectedValue && deviation . CurrentValue ) {
387+ jsonDifferences = compareJsonObjects ( deviation . ExpectedValue , deviation . CurrentValue ) ;
388+ // If there are no differences, this is actually compliant
389+ if ( jsonDifferences === null ) {
390+ isActuallyCompliant = true ;
391+ }
392+ }
393+
343394 // Prioritize standardDisplayName from drift data (which has user-friendly names for templates)
344395 // then fallback to standards.json lookup, then raw name
345396 const prettyName =
@@ -354,21 +405,46 @@ const ManageDriftPage = () => {
354405 deviation . standardDescription ||
355406 "No description available" ;
356407
408+ // Determine the actual status
409+ // If actually compliant (values match), mark as aligned regardless of input status
410+ // If license is skipped, mark as skipped
411+ // Otherwise use the provided status
412+ const actualStatus = isActuallyCompliant
413+ ? "aligned"
414+ : isLicenseSkipped
415+ ? "skipped"
416+ : statusOverride || deviation . Status || deviation . state ;
417+ const actualStatusText = isActuallyCompliant
418+ ? "Compliant"
419+ : isLicenseSkipped
420+ ? "Skipped - No License Available"
421+ : getDeviationStatusText ( actualStatus ) ;
422+
423+ // For skipped items, show different expected/received values
424+ let displayExpectedValue = deviation . ExpectedValue || deviation . expectedValue ;
425+ let displayReceivedValue = deviation . CurrentValue || deviation . receivedValue ;
426+
427+ // If we have JSON differences, show only the differences
428+ if ( jsonDifferences && ! isLicenseSkipped && ! isActuallyCompliant ) {
429+ displayExpectedValue = JSON . stringify ( jsonDifferences , null , 2 ) ;
430+ displayReceivedValue = "See differences in Expected column" ;
431+ }
432+
357433 return {
358434 id : statusOverride ? `${ statusOverride } -${ index + 1 } ` : `current-${ index + 1 } ` ,
359435 cardLabelBox : {
360- cardLabelBoxHeader : getDeviationIcon (
361- statusOverride || deviation . Status || deviation . state
362- ) ,
436+ cardLabelBoxHeader : getDeviationIcon ( actualStatus ) ,
363437 } ,
364438 text : prettyName ,
365439 subtext : description ,
366- statusColor : getDeviationColor ( statusOverride || deviation . Status || deviation . state ) ,
367- statusText : getDeviationStatusText ( statusOverride || deviation . Status || deviation . state ) ,
440+ statusColor : isLicenseSkipped ? "text.secondary" : getDeviationColor ( actualStatus ) ,
441+ statusText : actualStatusText ,
368442 standardName : deviation . standardName , // Store the original standardName for action handlers
369443 receivedValue : deviation . receivedValue , // Store the original receivedValue for action handlers
370444 expectedValue : deviation . expectedValue , // Store the original expectedValue for action handlers
371445 originalDeviation : deviation , // Store the complete original deviation object for reference
446+ isLicenseSkipped : isLicenseSkipped , // Flag for filtering and disabling actions
447+ isActuallyCompliant : isActuallyCompliant , // Flag to move to compliant section
372448 children : (
373449 < Stack spacing = { 2 } sx = { { p : 2 } } >
374450 { description && description !== "No description available" && (
@@ -377,49 +453,65 @@ const ManageDriftPage = () => {
377453 </ Typography >
378454 ) }
379455
380- { ( deviation . expectedValue && deviation . expectedValue !== "Compliant with template" ) ||
381- deviation . receivedValue ? (
456+ { isLicenseSkipped && (
457+ < Box
458+ sx = { {
459+ p : 1.5 ,
460+ bgcolor : "warning.lighter" ,
461+ borderRadius : 1 ,
462+ border : "1px solid" ,
463+ borderColor : "warning.main" ,
464+ } }
465+ >
466+ < Typography variant = "body2" color = "warning.dark" sx = { { fontWeight : 600 } } >
467+ ⚠️ This standard was skipped because the required license is not available for
468+ this tenant.
469+ </ Typography >
470+ </ Box >
471+ ) }
472+
473+ { ( displayExpectedValue && displayExpectedValue !== "Compliant with template" ) ||
474+ displayReceivedValue ? (
382475 < Box sx = { { display : "flex" , gap : 2 , flexDirection : { xs : "column" , md : "row" } } } >
383- { deviation . expectedValue &&
384- deviation . expectedValue !== "Compliant with template" && (
385- < Box sx = { { flex : 1 , minWidth : 0 } } >
476+ { displayExpectedValue && displayExpectedValue !== "Compliant with template" && (
477+ < Box sx = { { flex : 1 , minWidth : 0 } } >
478+ < Typography
479+ variant = "caption"
480+ sx = { {
481+ fontWeight : 600 ,
482+ color : "text.secondary" ,
483+ textTransform : "uppercase" ,
484+ letterSpacing : 0.5 ,
485+ } }
486+ >
487+ { jsonDifferences ? "Differences" : "Expected" }
488+ </ Typography >
489+ < Box
490+ sx = { {
491+ mt : 0.5 ,
492+ p : 1.5 ,
493+ bgcolor : isActuallyCompliant ? "success.lighter" : "action.hover" ,
494+ borderRadius : 1 ,
495+ border : "1px solid" ,
496+ borderColor : isActuallyCompliant ? "success.main" : "divider" ,
497+ } }
498+ >
386499 < Typography
387- variant = "caption "
500+ variant = "body2 "
388501 sx = { {
389- fontWeight : 600 ,
390- color : "text.secondary " ,
391- textTransform : "uppercase " ,
392- letterSpacing : 0.5 ,
502+ fontFamily : "monospace" ,
503+ fontSize : "0.8125rem " ,
504+ whiteSpace : "pre-wrap " ,
505+ wordBreak : "break-word" ,
393506 } }
394507 >
395- Expected
508+ { displayExpectedValue }
396509 </ Typography >
397- < Box
398- sx = { {
399- mt : 0.5 ,
400- p : 1.5 ,
401- bgcolor : "action.hover" ,
402- borderRadius : 1 ,
403- border : "1px solid" ,
404- borderColor : "divider" ,
405- } }
406- >
407- < Typography
408- variant = "body2"
409- sx = { {
410- fontFamily : "monospace" ,
411- fontSize : "0.8125rem" ,
412- whiteSpace : "pre-wrap" ,
413- wordBreak : "break-word" ,
414- } }
415- >
416- { deviation . expectedValue }
417- </ Typography >
418- </ Box >
419510 </ Box >
420- ) }
511+ </ Box >
512+ ) }
421513
422- { deviation . receivedValue && (
514+ { displayReceivedValue && ! jsonDifferences && (
423515 < Box sx = { { flex : 1 , minWidth : 0 } } >
424516 < Typography
425517 variant = "caption"
@@ -436,10 +528,10 @@ const ManageDriftPage = () => {
436528 sx = { {
437529 mt : 0.5 ,
438530 p : 1.5 ,
439- bgcolor : "action.hover" ,
531+ bgcolor : isActuallyCompliant ? "success.lighter" : "action.hover" ,
440532 borderRadius : 1 ,
441533 border : "1px solid" ,
442- borderColor : "divider" ,
534+ borderColor : isActuallyCompliant ? "success.main" : "divider" ,
443535 } }
444536 >
445537 < Typography
@@ -451,7 +543,7 @@ const ManageDriftPage = () => {
451543 wordBreak : "break-word" ,
452544 } }
453545 >
454- { formatPolicyValue ( deviation . receivedValue ) }
546+ { displayReceivedValue }
455547 </ Typography >
456548 </ Box >
457549 </ Box >
@@ -524,6 +616,16 @@ const ManageDriftPage = () => {
524616 ) ;
525617 const alignedStandardItems = createDeviationItems ( processedDriftData . alignedStandards , "aligned" ) ;
526618
619+ // Separate items by their actual status
620+ const licenseSkippedItems = deviationItems . filter ( ( item ) => item . isLicenseSkipped ) ;
621+ const compliantFromDeviations = deviationItems . filter ( ( item ) => item . isActuallyCompliant ) ;
622+ const actualDeviationItems = deviationItems . filter (
623+ ( item ) => ! item . isLicenseSkipped && ! item . isActuallyCompliant
624+ ) ;
625+
626+ // Combine compliant items from both sources
627+ const allAlignedItems = [ ...alignedStandardItems , ...compliantFromDeviations ] ;
628+
527629 const handleMenuClick = ( event , itemId ) => {
528630 setAnchorEl ( ( prev ) => ( { ...prev , [ itemId ] : event . currentTarget } ) ) ;
529631 } ;
@@ -776,7 +878,7 @@ const ManageDriftPage = () => {
776878 } , [ templateId ] ) ;
777879
778880 // Add action buttons to each deviation item
779- const deviationItemsWithActions = deviationItems . map ( ( item ) => {
881+ const deviationItemsWithActions = actualDeviationItems . map ( ( item ) => {
780882 // Check if this is a template that supports delete action
781883 const supportsDelete =
782884 ( item . standardName ?. includes ( "ConditionalAccessTemplate" ) ||
@@ -1011,7 +1113,8 @@ const ManageDriftPage = () => {
10111113 const filteredAcceptedItems = applyFilters ( acceptedDeviationItemsWithActions ) ;
10121114 const filteredCustomerSpecificItems = applyFilters ( customerSpecificDeviationItemsWithActions ) ;
10131115 const filteredDeniedItems = applyFilters ( deniedDeviationItemsWithActions ) ;
1014- const filteredAlignedItems = applyFilters ( alignedStandardItems ) ;
1116+ const filteredAlignedItems = applyFilters ( allAlignedItems ) ;
1117+ const filteredLicenseSkippedItems = applyFilters ( licenseSkippedItems ) ;
10151118
10161119 // Helper function to render items grouped by category when category sort is active
10171120 const renderItemsByCategory = ( items ) => {
@@ -1255,6 +1358,12 @@ const ManageDriftPage = () => {
12551358 variant = "outlined"
12561359 />
12571360 </ Box >
1361+ < Box display = "flex" justifyContent = "space-between" alignItems = "center" >
1362+ < Typography variant = "body2" color = "text.secondary" >
1363+ Skipped (No License)
1364+ </ Typography >
1365+ < Chip label = { licenseSkippedItems . length } size = "small" variant = "outlined" />
1366+ </ Box >
12581367 < Divider />
12591368 < Box display = "flex" justifyContent = "space-between" alignItems = "center" >
12601369 < Typography variant = "body2" fontWeight = { 600 } >
@@ -1458,6 +1567,25 @@ const ManageDriftPage = () => {
14581567 />
14591568 </ Box >
14601569 ) }
1570+
1571+ { /* License Skipped Section - Always at the end */ }
1572+ { filteredLicenseSkippedItems . length > 0 && (
1573+ < Box >
1574+ < Typography variant = "h6" sx = { { mb : 2 } } >
1575+ Skipped - No License Available
1576+ </ Typography >
1577+ < Typography variant = "body2" color = "text.secondary" sx = { { mb : 2 } } >
1578+ These standards were skipped because the required licenses are not available
1579+ for this tenant.
1580+ </ Typography >
1581+ < CippBannerListCard
1582+ items = { filteredLicenseSkippedItems }
1583+ isCollapsible = { true }
1584+ layout = { "single" }
1585+ isFetching = { driftApi . isFetching }
1586+ />
1587+ </ Box >
1588+ ) }
14611589 </ Stack >
14621590 </ Grid >
14631591 </ Grid >
0 commit comments