Skip to content
2 changes: 1 addition & 1 deletion cmd/ui/src/views/GroupManagement/GroupManagement.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('GroupManagement', () => {
it('displays default text for domain selector when globalDomain is null', async () => {
const { screen } = await setup();

expect(screen.getByTestId('data-selector')).toBeInTheDocument();
expect(screen.getByTestId('data-quality_context-selector')).toBeInTheDocument();
});

it('renders an edit form for the selected asset group when a user has graph write permissions', async () => {
Expand Down
15 changes: 8 additions & 7 deletions cmd/ui/src/views/QA/QA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import makeStyles from '@mui/styles/makeStyles';
import {
ActiveDirectoryPlatformInfo,
AzurePlatformInfo,
DataSelector,
DomainInfo,
LoadingOverlay,
PageWithTitle,
SelectedEnvironment,
SimpleEnvironmentSelector,
TenantInfo,
useInitialEnvironment,
} from 'bh-shared-ui';
Expand Down Expand Up @@ -103,13 +103,14 @@ const QualityAssurance: React.FC = () => {
data-testid='data-quality'
pageDescription={<QualityAssuranceDescription />}>
<Box display='flex' justifyContent='flex-end' alignItems='center' minHeight='24px' mb={2}>
<DataSelector
value={{
<SimpleEnvironmentSelector
selected={{
type: environment?.type ?? null,
id: environment?.id ?? null,
}}
errorMessage={environmentErrorMessage}
onChange={(selection) => setSelectedEnvironment(selection)}
buttonPrimary={false}
onSelect={(selection) => setSelectedEnvironment(selection)}
/>
</Box>
<Alert severity='info'>
Expand All @@ -127,10 +128,10 @@ const QualityAssurance: React.FC = () => {
data-testid='data-quality'
pageDescription={<QualityAssuranceDescription />}>
<Box display='flex' justifyContent='flex-end' alignItems='center' minHeight='24px' mb={2}>
<DataSelector
value={selectedEnvironment || initialEnvironment || { type: null, id: null }}
<SimpleEnvironmentSelector
selected={selectedEnvironment || initialEnvironment || { type: null, id: null }}
errorMessage={environmentErrorMessage}
onChange={(selection) => setSelectedEnvironment({ ...selection })}
onSelect={(selection) => setSelectedEnvironment({ ...selection })}
/>
</Box>
{dataError && (
Expand Down
70 changes: 59 additions & 11 deletions cmd/ui/src/views/ZoneManagement/InfoHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,75 @@
//
// SPDX-License-Identifier: Apache-2.0
import { Button } from '@bloodhoundenterprise/doodleui';
import { AppLink, getTagUrlValue, useHighestPrivilegeTagId } from 'bh-shared-ui';
import { FC } from 'react';
import {
AD_PLATFORM,
AZ_PLATFORM,
AppLink,
EnvironmentAggregation,
SelectedEnvironment,
SelectorValueTypes,
SimpleEnvironmentSelector,
getTagUrlValue,
useEnvironmentParams,
useHighestPrivilegeTagId,
useInitialEnvironment,
} from 'bh-shared-ui';
import { FC, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

const aggregationFromType = (type: SelectorValueTypes | null): EnvironmentAggregation | null => {
switch (type) {
case AD_PLATFORM:
return 'active-directory';
case AZ_PLATFORM:
return 'azure';
default:
return null;
}
};

const InfoHeader: FC = () => {
const { tagId: topTagId } = useHighestPrivilegeTagId();
const { tierId = topTagId?.toString(), labelId } = useParams();
const tagId = labelId === undefined ? tierId : labelId;

const { data: initialEnvironment } = useInitialEnvironment({ orderBy: 'name' });

const [selectedEnvironment, setSelectedEnvironment] = useState<SelectedEnvironment | undefined>(initialEnvironment);

const { setEnvironmentParams } = useEnvironmentParams();

const handleSelect = (environment: SelectedEnvironment) => {
const { id, type } = environment;

const aggregation = aggregationFromType(type);

setEnvironmentParams({ environmentId: id, environmentAggregation: aggregation });

setSelectedEnvironment(environment);
};

useEffect(() => {
initialEnvironment && setSelectedEnvironment(initialEnvironment);
}, [initialEnvironment]);

return (
<div className='flex justify-around basis-2/3'>
<div className='flex justify-start gap-4 items-center basis-2/3'>
<div className='flex items-center align-middle'>
<Button variant='primary' disabled={!tagId} asChild>
<AppLink
data-testid='zone-management_create-selector-link'
to={`/zone-management/save/${getTagUrlValue(labelId)}/${tagId}/selector`}>
Create Selector
</AppLink>
</Button>
</div>
<SimpleEnvironmentSelector
selected={{
type: selectedEnvironment?.type ?? null,
id: selectedEnvironment?.id ?? null,
}}
onSelect={handleSelect}
/>
<Button variant='primary' disabled={!tagId} asChild>
<AppLink
data-testid='zone-management_create-selector-link'
to={`/zone-management/save/${getTagUrlValue(labelId)}/${tagId}/selector`}>
Create Selector
</AppLink>
</Button>
</div>
<div className='flex justify-start basis-1/3'>
<input type='text' placeholder='search' className='hidden' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { Button } from '@bloodhoundenterprise/doodleui';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Box, MenuItem, Popover, Tooltip, Typography } from '@mui/material';
import { FC, useState } from 'react';
import { cn } from '../../utils/theme';
import { AppIcon } from '../AppIcon';
import { DropdownOption } from './types';

const DropdownSelector: FC<{
options: DropdownOption[];
selectedText: string;
fullWidth?: boolean;
onChange: (selection: DropdownOption) => void;
}> = ({ options, selectedText, onChange, fullWidth }) => {
}> = ({ options, selectedText, onChange }) => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);

Expand All @@ -39,18 +40,13 @@ const DropdownSelector: FC<{

return (
<Box p={1}>
<Button
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'block',
width: fullWidth ? '100%' : '',
textTransform: 'uppercase',
}}
onClick={handleClick}
data-testid='dropdown_context-selector'>
{selectedText}
<Button className='w-full truncate uppercase' onClick={handleClick} data-testid='dropdown_context-selector'>
<span className='inline-flex justify-between gap-4 items-center w-full'>
<span>{selectedText}</span>
<span className={cn({ 'rotate-180 transition-transform': open })}>
<AppIcon.CaretDown size={12} />
</span>
</span>
</Button>
<Popover
open={open}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ import { Box, Grid, Paper, Typography, useTheme } from '@mui/material';
import { AssetGroup, AssetGroupMember, AssetGroupMemberParams } from 'js-client-library';
import { FC, ReactNode, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { apiClient } from '../../utils';
import { DataSelector } from '../../views/DataQuality/DataSelector';
import { apiClient } from '../../utils/api';
import { SelectedEnvironment, SimpleEnvironmentSelector } from '../../views/DataQuality/SimpleEnvironmentSelector';
import AssetGroupEdit from '../AssetGroupEdit';
import AssetGroupFilters from '../AssetGroupFilters';
import { FILTERABLE_PARAMS } from '../AssetGroupFilters/AssetGroupFilters';
import AssetGroupMemberList from '../AssetGroupMemberList';
import DropdownSelector, { DropdownOption } from '../DropdownSelector';
import { SelectedEnvironment } from './types';

interface GroupManagementContentProps {
globalEnvironment: SelectedEnvironment | null;
Expand Down Expand Up @@ -119,6 +118,8 @@ const GroupManagementContent: FC<GroupManagementContentProps> = ({
setFilterParams((prev) => ({ ...prev, [key]: value.toString() }));
};

const handleSelect = (selection: SelectedEnvironment) => setSelectedEnvironment({ ...selection });

// Start building a filter query for members that gets passed down to AssetGroupMemberList to make the request
useEffect(() => {
const filterDomain = selectedEnvironment || globalEnvironment;
Expand Down Expand Up @@ -149,20 +150,17 @@ const GroupManagementContent: FC<GroupManagementContentProps> = ({
options={listAssetGroups.data ? mapAssetGroups(listAssetGroups.data) : []}
selectedText={getAssetGroupSelectorLabel()}
onChange={handleAssetGroupSelectorChange}
fullWidth
/>
</Grid>
<Grid item xs={4} sx={selectorLabelStyles} alignItems={'center'} paddingLeft={3}>
<Typography variant='button'>Environment:</Typography>
</Grid>
<Grid item xs={12} xl={8}>
<DataSelector
value={selectedEnvironment || globalEnvironment || { type: null, id: null }}
<Grid item xs={12} xl={8} padding={theme.spacing()}>
<SimpleEnvironmentSelector
selected={selectedEnvironment || globalEnvironment || { type: null, id: null }}
errorMessage={domainSelectorErrorMessage}
onChange={(selection: SelectedEnvironment) =>
setSelectedEnvironment({ ...selection })
}
fullWidth={true}
buttonPrimary
onSelect={handleSelect}
/>
</Grid>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@
import GroupManagementContent from './GroupManagementContent';

export default GroupManagementContent;
export * from './types';

This file was deleted.

2 changes: 2 additions & 0 deletions packages/javascript/bh-shared-ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export * from './useConfiguration';

export * from './useDataQualityStats';

export * from './useEnvironmentIdList';

export * from './useFeatureFlags';

export * from './useFetchEntityProperties';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ describe('the useAssetGroupTags utilities', () => {
});
});

test('tag members refetches on environment change', async () => {
const tagMembersSpy = vi.spyOn(apiClient, 'getAssetGroupTagMembers');

const { rerender } = renderHook((environments: string[]) =>
agtHook.useTagMembersInfiniteQuery(1, 'asc', environments)
);

await waitFor(() => {
expect(tagMembersSpy).toHaveBeenCalledTimes(1);
});

rerender(['1']);

await waitFor(() => {
expect(tagMembersSpy).toHaveBeenCalledTimes(2);
});
});

test('selector members refetches on sort change', async () => {
const selectorMembersSpy = vi.spyOn(apiClient, 'getAssetGroupTagSelectorMembers');

Expand All @@ -182,4 +200,22 @@ describe('the useAssetGroupTags utilities', () => {
expect(selectorMembersSpy).toHaveBeenCalledTimes(2);
});
});

test('selector members refetches on envrionment change', async () => {
const selectorMembersSpy = vi.spyOn(apiClient, 'getAssetGroupTagSelectorMembers');

const { rerender } = renderHook((environments: string[]) =>
agtHook.useSelectorMembersInfiniteQuery(1, 1, 'asc', environments)
);

await waitFor(() => {
expect(selectorMembersSpy).toHaveBeenCalledTimes(1);
});

rerender(['1']);

await waitFor(() => {
expect(selectorMembersSpy).toHaveBeenCalledTimes(2);
});
});
});
Loading
Loading