Skip to content

Commit 0723dc1

Browse files
committed
ATLAS-5350: Atlas UI: Enhance Purge Audit Results UI
1 parent 883984a commit 0723dc1

4 files changed

Lines changed: 105 additions & 9 deletions

File tree

dashboard/src/views/Administrator/Audits/AuditResults.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ const AuditResults = ({ componentProps, row }: AuditResultsProps) => {
213213
<CustomModal
214214
open={openPurgeModal}
215215
onClose={handleClosePurgeModal}
216-
title={`Purged Entity Details: ${currentPurgeResultObj}`}
216+
title={operation === "AUTO_PURGE" ? `Auto Purge Entity Details: ${currentPurgeResultObj}` : `Purged Entity Details: ${currentPurgeResultObj}`}
217217
button1Handler={undefined}
218218
button2Handler={undefined}
219219
maxWidth="md"
@@ -492,10 +492,10 @@ const PurgeEntitiesDrawer: React.FC<PurgeEntitiesDrawerProps> = ({
492492
}
493493
}, [drawerPage, drawerSearchText, activePurgeView]);
494494

495-
const rawListForView: (string | Record<string, any>)[] =
495+
const rawListForView: (string | { guid: string; attributes?: { name?: string } })[] =
496496
activePurgeView === PurgeActiveView.REQUESTED ? requestedEntitiesList : purgedApiGuids;
497497

498-
const filteredList = rawListForView.filter((item: string | Record<string, any>) => {
498+
const filteredList = rawListForView.filter((item: string | { guid: string; attributes?: { name?: string } }) => {
499499
if (!drawerSearchText) return true;
500500
const guidStr = typeof item === 'object' && item !== null ? item.guid : item;
501501
const nameStr = typeof item === 'object' && item !== null ? item.attributes?.name : '';
@@ -633,7 +633,7 @@ const PurgeEntitiesDrawer: React.FC<PurgeEntitiesDrawerProps> = ({
633633
return (
634634
<>
635635
{paddingTop > 0 && <div style={{ height: paddingTop }} />}
636-
{visibleItems.map((item: string | Record<string, any>, localIndex: number) => {
636+
{visibleItems.map((item: string | { guid: string; attributes?: { name?: string } }, localIndex: number) => {
637637
const index = startIndex + localIndex;
638638
const globalIndex = (drawerPage - 1) * drawerPageSize + index + 1;
639639
const isObj = typeof item === 'object' && item !== null;

dashboard/src/views/Administrator/Audits/__tests__/AuditResults.test.tsx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ describe('AuditResults Component', () => {
468468
expect(screen.getByTestId('custom-modal')).toBeInTheDocument();
469469
});
470470

471-
expect(screen.getByTestId('modal-title')).toHaveTextContent('Purged Entity Details: guid-4');
471+
expect(screen.getByTestId('modal-title')).toHaveTextContent('Auto Purge Entity Details: guid-4');
472472
});
473473

474474
it('should close purge modal when close button is clicked', async () => {
@@ -1304,6 +1304,25 @@ describe('AuditResults Component', () => {
13041304
});
13051305
});
13061306

1307+
it('should show empty-drawer fallback message when PURGED count > 0 but GUID list is empty', async () => {
1308+
const auditDataForSummary = [{
1309+
guid: 'audit-empty-sum',
1310+
operation: 'PURGE',
1311+
params: JSON.stringify(['req-1']),
1312+
result: JSON.stringify({
1313+
requestedCount: 1, purgedCount: 5, purgedDependenciesCount: 0,
1314+
failedCount: 0, skippedCount: 0, executionFailed: false, runId: 'test-empty'
1315+
})
1316+
}];
1317+
render(<AuditResults componentProps={{ auditData: auditDataForSummary }} row={{ original: { guid: 'audit-empty-sum' } }} />);
1318+
1319+
fireEvent.click(screen.getByText('PURGED'));
1320+
1321+
await waitFor(() => {
1322+
expect(screen.getByText('Entity list not available for summary audits — see purgefailure.log')).toBeInTheDocument();
1323+
});
1324+
});
1325+
13071326
it('should NOT trigger action when Failed card is clicked (display only)', () => {
13081327
render(<AuditResults componentProps={{ auditData: summaryAuditData }} row={{ original: { guid: 'audit-sum' } }} />);
13091328

@@ -1476,6 +1495,73 @@ describe('AuditResults Component', () => {
14761495
});
14771496

14781497

1498+
describe('Summary fetch logic (useEffect)', () => {
1499+
afterEach(() => {
1500+
jest.clearAllMocks();
1501+
});
1502+
1503+
it('(1) successful summary fetch populates cards', async () => {
1504+
(fetchApi as jest.Mock).mockResolvedValueOnce({
1505+
data: [{
1506+
action: 'PURGE',
1507+
details: '{"requestedCount": 10, "purgedCount": 8, "purgedDependenciesCount": 2, "failedCount": 0, "skippedCount": 0, "executionFailed": false, "runId": "run-fetch-1"}'
1508+
}]
1509+
});
1510+
1511+
const auditData = [{ guid: 'fetch-test-1', operation: 'PURGE', params: '[]', result: 'run-fetch-1' }];
1512+
render(<AuditResults componentProps={{ auditData }} row={{ original: { guid: 'fetch-test-1' } }} />);
1513+
1514+
await waitFor(() => {
1515+
expect(screen.getAllByText('10').length).toBeGreaterThan(0);
1516+
});
1517+
});
1518+
1519+
it('(2) fetch failure falls back to parsed result', async () => {
1520+
(fetchApi as jest.Mock).mockRejectedValueOnce(new Error('Fetch failed'));
1521+
1522+
const fallbackResult = JSON.stringify({
1523+
requestedCount: 5, purgedCount: 5, purgedDependenciesCount: 0,
1524+
failedCount: 0, skippedCount: 0, executionFailed: false, runId: 'run-fallback'
1525+
});
1526+
const auditData = [{ guid: 'fetch-test-2', operation: 'PURGE', params: '[]', result: fallbackResult }];
1527+
render(<AuditResults componentProps={{ auditData }} row={{ original: { guid: 'fetch-test-2' } }} />);
1528+
1529+
await waitFor(() => {
1530+
expect(screen.getAllByText('5').length).toBeGreaterThan(0);
1531+
});
1532+
});
1533+
1534+
it('(3) loading skeleton shows while fetching', async () => {
1535+
let resolvePromise: any;
1536+
const promise = new Promise((resolve) => {
1537+
resolvePromise = resolve;
1538+
});
1539+
(fetchApi as jest.Mock).mockReturnValueOnce(promise);
1540+
1541+
const auditData = [{ guid: 'fetch-test-3', operation: 'PURGE', params: '[]', result: 'run-fetch-3' }];
1542+
render(<AuditResults componentProps={{ auditData }} row={{ original: { guid: 'fetch-test-3' } }} />);
1543+
1544+
expect(document.querySelector('.MuiSkeleton-root')).toBeInTheDocument();
1545+
1546+
resolvePromise({ data: [] });
1547+
await waitFor(() => {
1548+
expect(document.querySelector('.MuiSkeleton-root')).not.toBeInTheDocument();
1549+
});
1550+
});
1551+
1552+
it('(4) AbortController cancels on unmount', () => {
1553+
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
1554+
1555+
const auditData = [{ guid: 'fetch-test-4', operation: 'PURGE', params: '[]', result: 'run-fetch-4' }];
1556+
const { unmount } = render(<AuditResults componentProps={{ auditData }} row={{ original: { guid: 'fetch-test-4' } }} />);
1557+
1558+
unmount();
1559+
1560+
expect(abortSpy).toHaveBeenCalled();
1561+
abortSpy.mockRestore();
1562+
});
1563+
});
1564+
14791565
describe('Copy Run ID', () => {
14801566
it('should copy Run ID to clipboard and show Copied tooltip', async () => {
14811567
const auditData = [{ guid: 'audit-1', operation: 'PURGE', params: '["a"]', result: '["a"]', runId: 'test-run-1' }];

dashboardv2/public/css/scss/drawer.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,6 @@
384384

385385
body.drawer-open-lock { overflow: hidden \!important; }
386386

387-
body.drawer-open-lock { overflow: hidden \!important; }
388-
389387
.drawer-list-item {
390388
height: 24px;
391389
box-sizing: border-box;

dashboardv2/public/js/views/audit/AdminAuditTableLayoutView.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ define(['require',
182182
checkRunId(auditFilters);
183183

184184
if (hasRunId) {
185+
// Remove any existing auditRowKind to prevent duplicates/conflicts
186+
var removeAuditRowKind = function (node) {
187+
if (node.criterion && Array.isArray(node.criterion)) {
188+
node.criterion = node.criterion.filter(function (c) {
189+
return c.attributeName !== 'auditRowKind';
190+
});
191+
node.criterion.forEach(removeAuditRowKind);
192+
}
193+
};
194+
removeAuditRowKind(auditFilters);
185195
auditFilters = {
186196
"condition": "AND",
187197
"criterion": [
@@ -440,11 +450,13 @@ define(['require',
440450
html += '<div class="card-label">PURGED</div><div class="card-value">' + totalPurgedCount + '</div></div>';
441451

442452
// Failed
443-
html += '<div class="purge-summary-card card-red ' + (totalFailedCount > 0 ? 'has-count' : '') + '" title="Some entities failed to purge. Please check purgefailure.log for details.">';
453+
var failedTitle = (totalFailedCount > 0 || summaryData.executionFailed) ? 'Some entities failed to purge. Please check purgefailure.log for details.' : 'No failed entities during this purge operation.';
454+
html += '<div class="purge-summary-card card-red ' + (totalFailedCount > 0 ? 'has-count' : '') + '" title="' + failedTitle + '">';
444455
html += '<div class="card-label">FAILED</div><div class="card-value">' + totalFailedCount + '</div></div>';
445456

446457
// Skipped
447-
html += '<div class="purge-summary-card card-amber ' + (skippedCount > 0 ? 'has-count' : '') + '" title="Some entities were skipped during purge. Please check purgefailure.log for details.">';
458+
var skippedTitle = (skippedCount > 0 || summaryData.executionFailed) ? 'Some entities were skipped during purge. Please check purgefailure.log for details.' : 'No skipped entities during this purge operation.';
459+
html += '<div class="purge-summary-card card-amber ' + (skippedCount > 0 ? 'has-count' : '') + '" title="' + skippedTitle + '">';
448460
html += '<div class="card-label">SKIPPED</div><div class="card-value">' + skippedCount + '</div></div>';
449461

450462
html += '</div></div></div></div>';

0 commit comments

Comments
 (0)