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

enhancement: Policies improvements #1421

Merged
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
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
Loading