Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cloud account id filters #1647

Merged
merged 7 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deepfence_frontend/apps/dashboard/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,5 +359,6 @@ export function getScanResultCompletionApiClient() {
return {
completeVulnerabilityInfo:
scanCompleteionApi.completeVulnerabilityInfo.bind(scanCompleteionApi),
completeHostInfo: scanCompleteionApi.completeHostInfo.bind(scanCompleteionApi),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { SecretsIcon } from '@/components/sideNavigation/icons/Secrets';
import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability';
import { TruncatedText } from '@/components/TruncatedText';
import { NodeDetailsStackedModal } from '@/features/topology/components/NodeDetailsStackedModal';
import { SearchableCloudAccountForHost } from '@/features/topology/data-components/tables/SearchableCloudAccountForHost';
import { queries } from '@/queries';
import {
ComplianceScanNodeTypeEnum,
Expand Down Expand Up @@ -466,6 +467,26 @@ function Filters() {
});
}}
/>
<SearchableCloudAccountForHost
defaultSelectedAccounts={searchParams.getAll('cloudAccounts')}
onChange={(values) => {
setSearchParams((prev) => {
prev.delete('cloudAccounts');
values.forEach((value) => {
prev.append('cloudAccounts', value);
});
prev.delete('page');
return prev;
});
}}
onClearAll={() => {
setSearchParams((prev) => {
prev.delete('cloudAccounts');
prev.delete('page');
return prev;
});
}}
/>
<Combobox
value={searchParams.getAll('agentRunning')}
multiple
Expand Down Expand Up @@ -569,6 +590,7 @@ function useSearchHostsWithPagination() {
agentRunning: searchParams
.getAll('agentRunning')
.map((value) => (value === 'On' ? true : false)),
cloudAccounts: searchParams.getAll('cloudAccounts'),
clusterIds: searchParams.getAll('clusters'),
hosts: searchParams.getAll('hosts'),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { useSuspenseInfiniteQuery } from '@suspensive/react-query';
import { debounce } from 'lodash-es';
import { Suspense, useEffect, useMemo, useState } from 'react';
import { CircleSpinner, Combobox, ComboboxOption } from 'ui-components';

import { queries } from '@/queries';

type SearchableCloudAccountProps = {
onChange?: (value: string[]) => void;
onClearAll?: () => void;
defaultSelectedAccounts?: string[];
triggerVariant?: 'select' | 'button';
helperText?: string;
color?: 'error' | 'default';
};

const PAGE_SIZE = 15;
const SearchableCloudAccountId = ({
onChange,
onClearAll,
defaultSelectedAccounts,
triggerVariant,
helperText,
color,
}: SearchableCloudAccountProps) => {
const [searchText, setSearchText] = useState('');

const [selectedAccounts, setSelectedAccounts] = useState<string[]>(
defaultSelectedAccounts ?? [],
);

const isSelectVariantType = useMemo(() => {
return triggerVariant === 'select';
}, [triggerVariant]);

useEffect(() => {
setSelectedAccounts(defaultSelectedAccounts ?? []);
}, [defaultSelectedAccounts]);

const { data, isFetchingNextPage, hasNextPage, fetchNextPage } =
useSuspenseInfiniteQuery({
...queries.common.searchHostFilters({
size: PAGE_SIZE,
searchText,
fieldName: 'cloud_account_id',
}),
keepPreviousData: true,
getNextPageParam: (lastPage, allPages) => {
return allPages.length * PAGE_SIZE;
},
getPreviousPageParam: (firstPage, allPages) => {
if (!allPages.length) return 0;
return (allPages.length - 1) * PAGE_SIZE;
},
});

const searchId = debounce((query: string) => {
setSearchText(query);
}, 1000);

const onEndReached = () => {
if (hasNextPage) fetchNextPage();
};

return (
<>
<input
type="text"
name="selectedCloudAccountLength"
hidden
readOnly
value={selectedAccounts.length}
/>
<Combobox
startIcon={
isFetchingNextPage ? <CircleSpinner size="sm" className="w-3 h-3" /> : undefined
}
name="cloudAccountFilter"
triggerVariant={triggerVariant || 'button'}
label={isSelectVariantType ? 'Cloud account' : undefined}
getDisplayValue={() =>
isSelectVariantType && selectedAccounts.length > 0
? `${selectedAccounts.length} selected`
: null
}
placeholder="Cloud account"
multiple
value={selectedAccounts}
onChange={(values) => {
setSelectedAccounts(values);
onChange?.(values);
}}
onQueryChange={searchId}
clearAllElement="Clear"
onClearAll={onClearAll}
onEndReached={onEndReached}
helperText={helperText}
color={color}
>
{data?.pages
.flatMap((page) => {
return page.data;
})
.map((item, index) => {
return (
<ComboboxOption key={`${item}-${index}`} value={item}>
{item}
</ComboboxOption>
);
})}
</Combobox>
</>
);
};

export const SearchableCloudAccountForHost = (props: SearchableCloudAccountProps) => {
const { triggerVariant } = props;
const isSelectVariantType = useMemo(() => {
return triggerVariant === 'select';
}, [triggerVariant]);

return (
<Suspense
fallback={
<Combobox
label={isSelectVariantType ? 'Cloud account' : undefined}
triggerVariant={triggerVariant || 'button'}
startIcon={<CircleSpinner size="sm" className="w-3 h-3" />}
placeholder="Cloud account"
multiple
onQueryChange={() => {
// no operation
}}
/>
}
>
<SearchableCloudAccountId {...props} />
</Suspense>
);
};
48 changes: 47 additions & 1 deletion deepfence_frontend/apps/dashboard/src/queries/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import {
getCloudComplianceApiClient,
getComplianceApiClient,
getMalwareApiClient,
getScanResultCompletionApiClient,
getSecretApiClient,
getVulnerabilityApiClient,
} from '@/api/api';
import { ModelNodeIdentifierNodeTypeEnum, ModelScanListReq } from '@/api/generated';
import {
CompletionCompletionNodeFieldReq,
ModelNodeIdentifierNodeTypeEnum,
ModelScanListReq,
} from '@/api/generated';
import { ScanTypeEnum } from '@/types/common';
import { apiWrapper } from '@/utils/api';

Expand Down Expand Up @@ -93,4 +98,45 @@ export const commonQueries = createQueryKeys('common', {
},
};
},
searchHostFilters: (filters: {
fieldName: string;
searchText: string;
size: number;
}) => {
return {
queryKey: [{ filters }],
queryFn: async ({ pageParam = 0 }) => {
const { fieldName, searchText, size } = filters;

const scanResultsReq: CompletionCompletionNodeFieldReq = {
completion: searchText,
field_name: fieldName,
window: {
offset: pageParam,
size,
},
};

const api = apiWrapper({
fn: getScanResultCompletionApiClient().completeHostInfo,
});
const response = await api({
completionCompletionNodeFieldReq: scanResultsReq,
});

if (!response.ok) {
throw response.error;
}

if (response.value === null) {
// TODO: handle this case with 404 status maybe
throw new Error('Error getting host filters');
}

return {
data: response.value.possible_values?.slice(0, size) || [],
};
},
};
},
});
8 changes: 8 additions & 0 deletions deepfence_frontend/apps/dashboard/src/queries/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ export const searchQueries = createQueryKeys('search', {
descending: boolean;
};
agentRunning?: boolean[];
cloudAccounts?: string[];
clusterIds: string[];
hosts: string[];
}) => {
Expand All @@ -629,6 +630,7 @@ export const searchQueries = createQueryKeys('search', {
cloudProvider,
order,
agentRunning,
cloudAccounts,
clusterIds,
hosts,
} = filters;
Expand Down Expand Up @@ -751,6 +753,12 @@ export const searchQueries = createQueryKeys('search', {
cloud_provider: cloudProvider,
};
}
if (cloudAccounts?.length) {
searchSearchNodeReq.node_filter.filters.contains_filter.filter_in = {
...searchSearchNodeReq.node_filter.filters.contains_filter.filter_in,
cloud_account_id: cloudAccounts,
};
}
if (order) {
searchSearchNodeReq.node_filter.filters.order_filter.order_fields?.push({
field_name: order.sortBy,
Expand Down
Loading