Skip to content

Commit

Permalink
enhancement: Policies improvements (#1421)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciaszczykm authored Oct 1, 2024
1 parent 583602b commit d7955d3
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 44 deletions.
85 changes: 67 additions & 18 deletions assets/src/components/policies/Policies.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { Breadcrumb, useSetBreadcrumbs } from '@pluralsh/design-system'
import {
Breadcrumb,
Input,
SearchIcon,
SubTab,
TabList,
useSetBreadcrumbs,
} from '@pluralsh/design-system'
import { GqlError } from 'components/utils/Alert'
import LoadingIndicator from 'components/utils/LoadingIndicator'
import { FullHeightTableWrap } from 'components/utils/layout/FullHeightTableWrap'
import { usePolicyConstraintsQuery } from 'generated/graphql'
import { useState } from 'react'
import { useRef, useState } from 'react'
import { POLICIES_REL_PATH } from 'routes/policiesRoutesConsts'
import styled from 'styled-components'

import { useFetchPaginatedData } from 'components/utils/table/useFetchPaginatedData'

import { Overline } from 'components/cd/utils/PermissionsModal'
import { useDebounce } from '@react-hooks-library/core'

import PoliciesFilter from './PoliciesFilter'
import { PoliciesTable } from './PoliciesTable'
Expand All @@ -21,9 +27,28 @@ const breadcrumbs: Breadcrumb[] = [

export const POLL_INTERVAL = 10_000

enum ViolationFilter {
All = 'All',
Passing = 'Passing',
Violated = 'Violated',
}

const violatedParam = (filter: ViolationFilter) => {
switch (filter) {
case ViolationFilter.Violated:
return true
case ViolationFilter.Passing:
return false
case ViolationFilter.All:
default:
return undefined
}
}

export function Policies() {
useSetBreadcrumbs(breadcrumbs)
// const [searchString, setSearchString] = useState('')
const tabStateRef = useRef<any>(null)
const [searchString, setSearchString] = useState('')
const [violationFilter, setViolationFilter] = useState(ViolationFilter.All)
const [selectedKinds, setSelectedKinds] = useState<(string | null)[]>([])
const [selectedNamespaces, setSelectedNamespaces] = useState<
(string | null)[]
Expand All @@ -32,13 +57,14 @@ export function Policies() {
[]
)

// const debouncedSearchString = useDebounce(searchString, 100)
const debouncedSearchString = useDebounce(searchString, 100)

const policyQFilters = {
// ...(debouncedSearchString ? { q: debouncedSearchString } : {}),
...(debouncedSearchString ? { q: debouncedSearchString } : {}),
...(selectedKinds.length ? { kinds: selectedKinds } : {}),
...(selectedNamespaces.length ? { namespaces: selectedNamespaces } : {}),
...(selectedClusters.length ? { clusters: selectedClusters } : {}),
violated: violatedParam(violationFilter),
}

const { data, loading, error, refetch, fetchNextPage, setVirtualSlice } =
Expand All @@ -49,14 +75,14 @@ export function Policies() {
},
policyQFilters
)

const policies = data?.policyConstraints?.edges

if (error) {
return <GqlError error={error} />
}
if (!data) {
return <LoadingIndicator />
}
useSetBreadcrumbs(breadcrumbs)

if (error) return <GqlError error={error} />

if (!data) return <LoadingIndicator />

return (
<PoliciesContainer>
Expand All @@ -71,16 +97,37 @@ export function Policies() {
setSelectedClusters={setSelectedClusters}
/>
</div>
{/* <div className="search">
<div className="search">
<Input
placeholder="Search policies"
startIcon={<SearchIcon />}
value={searchString}
onChange={(e) => {
setSearchString?.(e.currentTarget.value)
}}
flexGrow={0.5}
/>
</div> */}
<TabList
stateRef={tabStateRef}
stateProps={{
orientation: 'horizontal',
selectedKey: violationFilter,
onSelectionChange: (key) => {
setViolationFilter(key as ViolationFilter)
},
}}
>
{Object.values(ViolationFilter)?.map((label) => (
<SubTab
key={label}
textValue={label}
className="statusTab"
>
{label}
</SubTab>
))}
</TabList>
</div>
<div className="violations">
{policies && policies?.length > 0 && (
<PoliciesViolationsGauge filters={policyQFilters} />
Expand Down Expand Up @@ -112,10 +159,10 @@ const PoliciesContainer = styled.div(({ theme }) => ({
overflowY: 'auto',
padding: theme.spacing.large,
gridTemplateColumns: 'auto 250px',
gridTemplateRows: 'auto 1fr',
gridTemplateRows: 'auto auto 1fr',
gap: '16px 16px',
gridTemplateAreas: `
// "search filter"
"search filter"
"violations filter"
"table filter"
`,
Expand All @@ -131,6 +178,8 @@ const PoliciesContainer = styled.div(({ theme }) => ({
gap: theme.spacing.small,
},
'.search': {
display: 'flex',
justifyContent: 'space-between',
gridArea: 'search',
},
'.violations': {
Expand Down
90 changes: 65 additions & 25 deletions assets/src/components/policies/PoliciesFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import {
AccordionItem,
Checkbox,
Flex,
Input,
} from '@pluralsh/design-system'
import {
ConstraintViolationField,
useClustersQuery,
useViolationStatisticsQuery,
} from 'generated/graphql'

import { Dispatch, SetStateAction, useMemo } from 'react'
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { useDebounce } from '@react-hooks-library/core'

import { useProjectId } from '../contexts/ProjectsContext'
import { mapExistingNodes } from '../../utils/graphql'
import { useFetchPaginatedData } from '../utils/table/useFetchPaginatedData'

const FETCH_MARGIN = 30

function PoliciesFilter({
selectedKinds,
Expand All @@ -33,6 +37,9 @@ function PoliciesFilter({
}) {
const theme = useTheme()
const projectId = useProjectId()
const [searchString, setSearchString] = useState('')
const debouncedSearchString = useDebounce(searchString, 100)

const { data: kindsData } = useViolationStatisticsQuery({
variables: {
field: ConstraintViolationField.Kind,
Expand All @@ -43,12 +50,22 @@ function PoliciesFilter({
field: ConstraintViolationField.Namespace,
},
})
const { data: clustersData } = useClustersQuery({
variables: {
first: 100,
projectId,

const {
data: clustersData,
loading,
pageInfo,
fetchNextPage,
} = useFetchPaginatedData(
{
queryHook: useClustersQuery,
keyPath: ['clusters'],
},
})
{
q: debouncedSearchString,
projectId,
}
)

const kinds = kindsData?.violationStatistics
?.map((statistic) => ({
Expand Down Expand Up @@ -87,6 +104,23 @@ function PoliciesFilter({
})
}

const fetchMoreOnBottomReached = useCallback(
(element?: HTMLDivElement | undefined) => {
if (!element) return

const { scrollHeight, scrollTop, clientHeight } = element

if (
scrollHeight - scrollTop - clientHeight < FETCH_MARGIN &&
!loading &&
pageInfo.hasNextPage
) {
fetchNextPage()
}
},
[fetchNextPage, loading, pageInfo]
)

return (
<Accordion
defaultValue={[clusterLabel, kindLabel, namespaceLabel]}
Expand All @@ -97,31 +131,33 @@ function PoliciesFilter({
trigger={clusterLabel}
value={clusterLabel}
>
<Flex flexDirection="column">
<Checkbox
name={clusterLabel}
value={null}
checked={selectedClusters.includes(null)}
onChange={({ target: { checked } }: any) =>
handleCheckboxChange(setSelectedClusters, null, checked)
}
>
No cluster
</Checkbox>
{clusters?.map((node) => (
<Input
placeholder="Filter clusters"
marginBottom={theme.spacing.small}
value={searchString}
onChange={(e) => setSearchString?.(e.currentTarget.value)}
/>
<div
css={{ minHeight: 56, maxHeight: 200, overflowY: 'auto' }}
onScrollCapture={(e) =>
fetchMoreOnBottomReached(e?.target as HTMLDivElement)
}
>
{[{ id: null, name: 'No cluster' }, ...clusters].map((cluster) => (
<Checkbox
key={node.id}
small
key={cluster.id}
name={clusterLabel}
value={node.id}
checked={selectedClusters.includes(node.id)}
value={cluster.id}
checked={selectedClusters.includes(cluster.id)}
onChange={({ target: { checked } }: any) => {
handleCheckboxChange(setSelectedClusters, node.id, checked)
handleCheckboxChange(setSelectedClusters, cluster.id, checked)
}}
>
{node.name}
{cluster.name}
</Checkbox>
))}
</Flex>
</div>
</AccordionItem>
<AccordionItem
trigger={kindLabel}
Expand All @@ -133,6 +169,7 @@ function PoliciesFilter({
>
<Flex flexDirection="column">
<Checkbox
small
name={kindLabel}
value={null}
checked={selectedKinds.includes(null)}
Expand All @@ -145,6 +182,7 @@ function PoliciesFilter({
{kinds?.map((kind) => (
<CheckboxWrapperSC key={kind.id}>
<Checkbox
small
name="kinds"
value={kind.id}
checked={selectedKinds.includes(kind.id)}
Expand All @@ -165,6 +203,7 @@ function PoliciesFilter({
>
<Flex flexDirection="column">
<Checkbox
small
name={namespaceLabel}
value={null}
checked={selectedNamespaces.includes(null)}
Expand All @@ -177,6 +216,7 @@ function PoliciesFilter({
{namespaces?.map((namespace) => (
<CheckboxWrapperSC key={namespace.id}>
<Checkbox
small
name={namespaceLabel}
value={namespace}
checked={selectedNamespaces.includes(namespace.id)}
Expand Down
5 changes: 4 additions & 1 deletion assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10116,6 +10116,7 @@ export type PolicyConstraintsQueryVariables = Exact<{
namespace?: InputMaybe<Scalars['String']['input']>;
namespaces?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>> | InputMaybe<Scalars['String']['input']>>;
q?: InputMaybe<Scalars['String']['input']>;
violated?: InputMaybe<Scalars['Boolean']['input']>;
}>;


Expand Down Expand Up @@ -20150,7 +20151,7 @@ export type DeletePersonaMutationHookResult = ReturnType<typeof useDeletePersona
export type DeletePersonaMutationResult = Apollo.MutationResult<DeletePersonaMutation>;
export type DeletePersonaMutationOptions = Apollo.BaseMutationOptions<DeletePersonaMutation, DeletePersonaMutationVariables>;
export const PolicyConstraintsDocument = gql`
query PolicyConstraints($after: String, $before: String, $clusters: [ID], $first: Int, $kind: String, $kinds: [String], $last: Int, $namespace: String, $namespaces: [String], $q: String) {
query PolicyConstraints($after: String, $before: String, $clusters: [ID], $first: Int, $kind: String, $kinds: [String], $last: Int, $namespace: String, $namespaces: [String], $q: String, $violated: Boolean) {
policyConstraints(
after: $after
before: $before
Expand All @@ -20162,6 +20163,7 @@ export const PolicyConstraintsDocument = gql`
namespace: $namespace
namespaces: $namespaces
q: $q
violated: $violated
) {
pageInfo {
...PageInfo
Expand Down Expand Up @@ -20198,6 +20200,7 @@ ${PolicyConstraintFragmentDoc}`;
* namespace: // value for 'namespace'
* namespaces: // value for 'namespaces'
* q: // value for 'q'
* violated: // value for 'violated'
* },
* });
*/
Expand Down
2 changes: 2 additions & 0 deletions assets/src/graph/policies.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ query PolicyConstraints(
$namespace: String
$namespaces: [String]
$q: String
$violated: Boolean
) {
policyConstraints(
after: $after
Expand All @@ -38,6 +39,7 @@ query PolicyConstraints(
namespace: $namespace
namespaces: $namespaces
q: $q
violated: $violated
) {
pageInfo {
...PageInfo
Expand Down

0 comments on commit d7955d3

Please sign in to comment.