From 3defce7589ecb018f337b9c757ee538685de3574 Mon Sep 17 00:00:00 2001 From: Elijah Brown Date: Thu, 12 Dec 2024 15:19:16 -0700 Subject: [PATCH 1/4] Added the 'countLabel' field and added a field to the data returned by the endpoint, which EntityInfoDataTable can look at for help getting the correct count for top level Vault Readers. --- .../bh-shared-ui/src/utils/content.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/javascript/bh-shared-ui/src/utils/content.ts b/packages/javascript/bh-shared-ui/src/utils/content.ts index e17c21937a..9630a33ce4 100644 --- a/packages/javascript/bh-shared-ui/src/utils/content.ts +++ b/packages/javascript/bh-shared-ui/src/utils/content.ts @@ -29,6 +29,7 @@ export interface EntityInfoDataTableProps { id: string; label: string; endpoint?: ({ counts, skip, limit, type }: EntitySectionEndpointParams) => Promise; + countLabel?: string; sections?: EntityInfoDataTableProps[]; } @@ -286,6 +287,7 @@ export const allSections: Partial EntityInfo { id, label: 'Vault Readers', + countLabel: 'All Readers', sections: [ { id, @@ -295,7 +297,11 @@ export const allSections: Partial EntityInfo .getAZEntityInfoV2('key-vaults', id, 'key-readers', counts, skip, limit, type, { signal: controller.signal, }) - .then((res) => res.data), + .then((res) => { + if (type !== 'graph') res.data.countLabel = 'Key Readers'; + return res.data; + }), + }, { id, @@ -305,7 +311,10 @@ export const allSections: Partial EntityInfo .getAZEntityInfoV2('key-vaults', id, 'certificate-readers', counts, skip, limit, type, { signal: controller.signal, }) - .then((res) => res.data), + .then((res) => { + if (type !== 'graph') res.data.countLabel = 'Certificate Readers'; + return res.data; + }), }, { id, @@ -315,7 +324,10 @@ export const allSections: Partial EntityInfo .getAZEntityInfoV2('key-vaults', id, 'secret-readers', counts, skip, limit, type, { signal: controller.signal, }) - .then((res) => res.data), + .then((res) => { + if (type !== 'graph') res.data.countLabel = 'Secret Readers'; + return res.data; + }), }, { id, @@ -325,7 +337,10 @@ export const allSections: Partial EntityInfo .getAZEntityInfoV2('key-vaults', id, 'all-readers', counts, skip, limit, type, { signal: controller.signal, }) - .then((res) => res.data), + .then((res) => { + if (type !== 'graph') res.data.countLabel = 'All Readers'; + return res.data; + }), }, ], }, From f38f8695a64fa5b7f70792fa5d1b043d23f1d0e9 Mon Sep 17 00:00:00 2001 From: Elijah Brown Date: Fri, 13 Dec 2024 12:25:01 -0700 Subject: [PATCH 2/4] Add logic to correctly count the AZKeyVaults. --- .../Explore/EntityInfo/EntityInfoDataTable.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.tsx b/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.tsx index bb83111aa1..2f127c3c16 100644 --- a/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.tsx +++ b/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.tsx @@ -28,7 +28,7 @@ import { addSnackbar } from 'src/ducks/global/actions'; import { transformFlatGraphResponse } from 'src/utils'; import EntityInfoCollapsibleSection from './EntityInfoCollapsibleSection'; -const EntityInfoDataTable: React.FC = ({ id, label, endpoint, sections }) => { +const EntityInfoDataTable: React.FC = ({ id, label, endpoint, countLabel, sections }) => { const dispatch = useDispatch(); const countQuery = useQuery( @@ -83,12 +83,18 @@ const EntityInfoDataTable: React.FC = ({ id, label, en let count: number | undefined; if (Array.isArray(countQuery.data)) { - count = countQuery.data.reduce((acc, val) => { - const count = val.count ?? 0; - return acc + count; - }, 0); + if (countLabel !== undefined) { + countQuery.data.forEach((sectionData: any) => { + if (sectionData.countLabel === countLabel) count = sectionData.count; + }); + } else { + count = countQuery.data.reduce((acc, val) => { + const count = val?.count ?? 0; + return acc + count; + }, 0); + } } else if (countQuery.data) { - count = countQuery.data.count ?? 0; + count = countQuery.data?.count ?? 0; } return ( From 069ea19f58384262fd3691f196b126ed087f4f7b Mon Sep 17 00:00:00 2001 From: Elijah Brown Date: Fri, 13 Dec 2024 12:59:57 -0700 Subject: [PATCH 3/4] Added a test for BED-4868. --- .../EntityInfo/EntityInfoDataTable.test.tsx | 111 ++++++++++++++++-- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.test.tsx b/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.test.tsx index 302a51a490..4a63c75ae9 100644 --- a/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.test.tsx +++ b/cmd/ui/src/views/Explore/EntityInfo/EntityInfoDataTable.test.tsx @@ -18,12 +18,13 @@ import { render, screen } from 'src/test-utils'; import userEvent from '@testing-library/user-event'; import { rest, RequestHandler } from 'msw'; import { setupServer } from 'msw/node'; -import { ActiveDirectoryNodeKind, allSections } from 'bh-shared-ui'; +import { ActiveDirectoryNodeKind, allSections, AzureNodeKind } from 'bh-shared-ui'; import EntityInfoDataTable from './EntityInfoDataTable'; import { EntityInfoPanelContextProvider } from './EntityInfoPanelContextProvider'; const objectId = 'fake-object-id'; -const sections = allSections[ActiveDirectoryNodeKind.GPO]!(objectId); +const adGpoSections = allSections[ActiveDirectoryNodeKind.GPO]!(objectId); +const azKeyVaultSections = allSections[AzureNodeKind.KeyVault]!(objectId); const queryCount = { controllers: { @@ -58,25 +59,70 @@ const queryCount = { }, } as const; +const keyVaultTest = { + KeyReaders: { + count: 0, + limit: 128, + skip: 0, + data: [], + }, + CertificateReaders: { + count: 8, + limit: 128, + skip: 0, + data: [], + }, + SecretReaders: { + count: 3003, + limit: 128, + skip: 0, + data: [], + }, + AllReaders: { + count: 1998, + limit: 128, + skip: 0, + data: [], + }, +} as const; + const handlers: Array = [ rest.get(`api/v2/gpos/${objectId}/:asset`, (req, res, ctx) => { const asset = req.params.asset as keyof typeof queryCount; return res(ctx.json(queryCount[asset])); }), + + rest.get(`api/v2/azure/key-vaults*`, (req, res, ctx) => { + if (req.url.searchParams.get('related_entity_type') === 'all-readers') { + return res(ctx.json(keyVaultTest.AllReaders)); + } + + if (req.url.searchParams.get('related_entity_type') === 'key-readers') { + return res(ctx.json(keyVaultTest.KeyReaders)); + } + + if (req.url.searchParams.get('related_entity_type') === 'secret-readers') { + return res(ctx.json(keyVaultTest.SecretReaders)); + } + + if (req.url.searchParams.get('related_entity_type') === 'certificate-readers') { + return res(ctx.json(keyVaultTest.CertificateReaders)); + } + }), ]; const server = setupServer(...handlers); -describe('EntityInfoDataTable', () => { - describe('Node count', () => { - beforeAll(() => server.listen()); - afterEach(() => server.resetHandlers()); - afterAll(() => server.close()); +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); +describe('EntityInfoDataTable', () => { + describe('Node count for nested table that counts all sections', () => { it('sums nested section node counts', async () => { render( - + ); const sum = await screen.findByText('5,064'); @@ -93,7 +139,7 @@ describe('EntityInfoDataTable', () => { render( - + ); @@ -113,7 +159,7 @@ describe('EntityInfoDataTable', () => { const user = userEvent.setup(); render( - + ); @@ -127,4 +173,49 @@ describe('EntityInfoDataTable', () => { expect(zero.textContent).toBe('0'); }); }); + + describe('Node count for Vault Readers nested table', () => { + it('Verify Vault Reader count is the count returned by All Readers', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + // wait for the total count to be available - this holds off all following tests until the counts have been returned + const sum = await screen.findByText('1,998'); + expect(sum).not.toBeNull(); + + // verify the vault reader count is as expected + const vaultReadersHeader = await screen.findByText('Vault Readers'); + const vaultReadersCount = vaultReadersHeader.nextElementSibling; + expect(vaultReadersCount).toHaveTextContent('1,998'); + + // expand the Vault Readers accordian + const button = await screen.findByRole('button'); + await user.click(button); + + // verify the key readers count is as expected + const keyReadersHeader = await screen.findByText('Key Readers'); + const keyReadersCount = keyReadersHeader.nextElementSibling; + expect(keyReadersCount).toHaveTextContent('0'); + + // verify the certificate readers count is as expected + const certReadersHeader = await screen.findByText('Certificate Readers'); + const certReadersCount = certReadersHeader.nextElementSibling; + expect(certReadersCount).toHaveTextContent('8'); + + // verify the secret readers count is as expected + const secretReadersHeader = await screen.findByText('Secret Readers'); + const secretReadersCount = secretReadersHeader.nextElementSibling; + expect(secretReadersCount).toHaveTextContent('3,003'); + + // verify the all readers count is as expected + const allReadersHeader = await screen.findByText('All Readers'); + const allReadersCount = allReadersHeader.nextElementSibling; + expect(allReadersCount).toHaveTextContent('1,998'); + }); + }); }); From 7fadfb50d0a7f9a6aa36ab3c5dcb2b1ba61f235a Mon Sep 17 00:00:00 2001 From: Elijah Brown Date: Mon, 16 Dec 2024 12:30:13 -0700 Subject: [PATCH 4/4] Check in whitespace and header fixes from just prepare-for-codereview --- .../harnesses/enrollonbehalfof-1.svg | 19 ++++++++++++++++++- .../harnesses/enrollonbehalfof-2.svg | 19 ++++++++++++++++++- .../harnesses/enrollonbehalfof-3.svg | 17 +++++++++++++++++ .../bh-shared-ui/src/utils/content.ts | 3 +-- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg index 6fcfa6838d..4f22563a8b 100644 --- a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg +++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-1.svg @@ -1 +1,18 @@ -NTAuthStoreForTrustedForNTAuthPublishedToPublishedToPublishedToRootCAForEnterpriseCAForEnrollOnBehalfOfEnrollOnBehalfOfEnrollOnBehalfOfDomain1CertTemplate1-1schemaversion:2effectiveekus:["2.5.29.37.0"]NTAuthStore1EnterpriseCA1CertTemplate1-2schemaversion:1effectiveekus:["2.5.29.37.0"]CertTemplate1-3schemaversion:2effectiveekus:["2.5.29.37.0"]RootCA1 \ No newline at end of file + +NTAuthStoreForTrustedForNTAuthPublishedToPublishedToPublishedToRootCAForEnterpriseCAForEnrollOnBehalfOfEnrollOnBehalfOfEnrollOnBehalfOfDomain1CertTemplate1-1schemaversion:2effectiveekus:["2.5.29.37.0"]NTAuthStore1EnterpriseCA1CertTemplate1-2schemaversion:1effectiveekus:["2.5.29.37.0"]CertTemplate1-3schemaversion:2effectiveekus:["2.5.29.37.0"]RootCA1 diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg index d8d8ec0fcb..96181905f2 100644 --- a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg +++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-2.svg @@ -1 +1,18 @@ -NTAuthStoreForTrustedForNTAuthRootCAForEnterpriseCAForPublishedToPublishedToPublishedToPublishedToEnrollOnBehalfOfDomain2NTAuthStore2EnterpriseCA2RootCA2CertTemplate2-1effectiveekus:["1.3.6.1.4.1.311.20.2.1"]schemaversion:2CertTemplate2-2effectiveekus:["1.3.6.1.4.1.311.20.2.1", "2.5.29.37.0"]schemaversion:2CertTemplate2-3effectiveekus:[]schemaversion:2authorizedsignatures:1applicationpolicies:["1.3.6.1.4.1.311.20.2.1"]CertTemplate2-4effectiveekus:[]schemaversion:2authorizedsignatures:1applicationpolicies:[] \ No newline at end of file + +NTAuthStoreForTrustedForNTAuthRootCAForEnterpriseCAForPublishedToPublishedToPublishedToPublishedToEnrollOnBehalfOfDomain2NTAuthStore2EnterpriseCA2RootCA2CertTemplate2-1effectiveekus:["1.3.6.1.4.1.311.20.2.1"]schemaversion:2CertTemplate2-2effectiveekus:["1.3.6.1.4.1.311.20.2.1", "2.5.29.37.0"]schemaversion:2CertTemplate2-3effectiveekus:[]schemaversion:2authorizedsignatures:1applicationpolicies:["1.3.6.1.4.1.311.20.2.1"]CertTemplate2-4effectiveekus:[]schemaversion:2authorizedsignatures:1applicationpolicies:[] diff --git a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-3.svg b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-3.svg index e93ece036f..42dced49a9 100644 --- a/cmd/api/src/test/integration/harnesses/enrollonbehalfof-3.svg +++ b/cmd/api/src/test/integration/harnesses/enrollonbehalfof-3.svg @@ -1 +1,18 @@ + NTAuthStoreForTrustedForNTAuthPublishedToPublishedToRootCAForEnterpriseCAForEnrollOnBehalfOfPublishedToEnterpriseCAForEnrollOnBehalfOfDomain1CertTemplate1-1schemaversion:2effectiveekus:["2.5.29.37.0"]NTAuthStore1EnterpriseCA1CertTemplate1-2schemaversion:1effectiveekus:["2.5.29.37.0"]CertTemplate1-3schemaversion:2effectiveekus:["2.5.29.37.0"]RootCA1EnterpriseCA2 diff --git a/packages/javascript/bh-shared-ui/src/utils/content.ts b/packages/javascript/bh-shared-ui/src/utils/content.ts index 9630a33ce4..621bf0aecc 100644 --- a/packages/javascript/bh-shared-ui/src/utils/content.ts +++ b/packages/javascript/bh-shared-ui/src/utils/content.ts @@ -287,7 +287,7 @@ export const allSections: Partial EntityInfo { id, label: 'Vault Readers', - countLabel: 'All Readers', + countLabel: 'All Readers', sections: [ { id, @@ -301,7 +301,6 @@ export const allSections: Partial EntityInfo if (type !== 'graph') res.data.countLabel = 'Key Readers'; return res.data; }), - }, { id,