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

Add filter to Policies and Roles tables, refactor filter #3690

Merged
merged 1 commit into from
Aug 24, 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
27 changes: 10 additions & 17 deletions catalog/app/containers/Admin/Buckets/Buckets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { useTracker } from 'utils/tracking'
import * as Types from 'utils/types'
import * as validators from 'utils/validators'

import Filter from '../Filter'
import * as Form from '../Form'
import * as Table from '../Table'

Expand Down Expand Up @@ -1186,7 +1185,7 @@ function CustomBucketIcon({ src }: CustomBucketIconProps) {
return <BucketIcon alt="" classes={classes} src={src} title="Default icon" />
}

const columns = [
const columns: Table.Column<BucketConfig>[] = [
{
id: 'name',
label: 'Name (relevance)',
Expand Down Expand Up @@ -1272,17 +1271,14 @@ interface CRUDProps {

function CRUD({ bucketName }: CRUDProps) {
const { bucketConfigs: rows } = GQL.useQueryS(BUCKET_CONFIGS_QUERY)
const [filter, setFilter] = React.useState('')
const filtered = React.useMemo(
() =>
filter
? rows.filter(({ name, title }) =>
(name + title).toLowerCase().includes(filter.toLowerCase()),
)
: rows,
[filter, rows],
)
const ordering = Table.useOrdering({ rows: filtered, column: columns[0] })
const filtering = Table.useFiltering({
rows,
filterBy: ({ name, title }) => name + title,
})
const ordering = Table.useOrdering({
rows: filtering.filtered,
column: columns[0],
})
const pagination = Pagination.use(ordering.ordered, {
// @ts-expect-error
getItemId: R.prop('name'),
Expand Down Expand Up @@ -1346,8 +1342,7 @@ function CRUD({ bucketName }: CRUDProps) {
</M.Dialog>

<Table.Toolbar heading="Buckets" actions={toolbarActions}>
{/* @ts-expect-error */}
<Filter value={filter} onChange={setFilter} />
<Table.Filter {...filtering} />
</Table.Toolbar>
<Table.Wrapper>
<M.Table size="small">
Expand All @@ -1361,9 +1356,7 @@ function CRUD({ bucketName }: CRUDProps) {
style={{ cursor: 'pointer' }}
>
{columns.map((col) => (
// @ts-expect-error
<M.TableCell key={col.id} align={col.align} {...col.props}>
{/* @ts-expect-error */}
{(col.getDisplay || R.identity)(col.getValue(i), i)}
</M.TableCell>
))}
Expand Down
27 changes: 24 additions & 3 deletions catalog/app/containers/Admin/RolesAndPolicies/AssociatedRoles.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as React from 'react'
import * as R from 'ramda'
import * as RF from 'react-final-form'
import * as M from '@material-ui/core'

import * as GQL from 'utils/GraphQL'

import * as Table from '../Table'
import Filter from './Filter'
import { MAX_POLICIES_PER_ROLE } from './shared'

import ROLES_QUERY from './gql/Roles.generated'
Expand Down Expand Up @@ -45,12 +48,26 @@ function RoleSelectionDialog({
[setSelected],
)

const filtering = Table.useFiltering({
rows: roles,
filterBy: ({ name }: ManagedRole) => name,
})
const ordered = React.useMemo(
() => R.sortBy(({ name }: ManagedRole) => name, filtering.filtered),
[filtering.filtered],
)

return (
<M.Dialog maxWidth="xs" open={open} onClose={onClose} onExited={handleExited}>
<M.DialogTitle>Attach policy to roles</M.DialogTitle>
{roles.length && (
<M.Box ml={3} mr={1.5}>
<Filter {...filtering} />
</M.Box>
)}
<M.DialogContent dividers>
{roles.length ? (
roles.map((role) => (
{ordered.length ? (
ordered.map((role) => (
<M.FormControlLabel
key={role.id}
style={{ display: 'flex', marginRight: 0 }}
Expand All @@ -73,7 +90,11 @@ function RoleSelectionDialog({
/>
))
) : (
<M.Typography>No more roles to attach this policy to</M.Typography>
<M.Typography>
{filtering.value
? 'No roles found, try resetting filter'
: 'No more roles to attach this policy to'}
</M.Typography>
)}
</M.DialogContent>
<M.DialogActions>
Expand Down
71 changes: 46 additions & 25 deletions catalog/app/containers/Admin/RolesAndPolicies/AttachedPolicies.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as React from 'react'
import * as R from 'ramda'
import * as RF from 'react-final-form'
import * as M from '@material-ui/core'

import * as GQL from 'utils/GraphQL'
import StyledLink from 'utils/StyledLink'

import * as Table from '../Table'
import Filter from './Filter'
import { MAX_POLICIES_PER_ROLE } from './shared'

import POLICIES_QUERY from './gql/Policies.generated'
Expand Down Expand Up @@ -38,34 +41,52 @@ function PolicySelectionDialog({
[setSelected, onClose],
)

const filtering = Table.useFiltering({
rows: policies,
filterBy: ({ title }: Policy) => title,
})
const ordered = React.useMemo(
() => R.sortBy(({ title }: Policy) => title, filtering.filtered),
[filtering.filtered],
)

return (
<M.Dialog maxWidth="xs" open={open} onClose={onClose} onExited={handleExited}>
<M.DialogTitle>Attach a policy</M.DialogTitle>
<M.List dense>
{policies.length ? (
policies.map((policy) => (
<M.ListItem button key={policy.id} onClick={() => select(policy)}>
<M.ListItemText>
{policy.title}
<M.Box component="span" color="text.secondary">
{' '}
(
{policy.managed ? (
<>{policy.permissions.length} buckets</>
) : (
<>unmanaged</>
)}
)
</M.Box>
</M.ListItemText>
</M.ListItem>
))
) : (
<M.DialogContent dividers>
<M.Typography>No more policies to attach</M.Typography>
</M.DialogContent>
)}
</M.List>
{!!policies.length && (
<M.Box ml={3} mr={1.5}>
<Filter {...filtering} />
</M.Box>
)}
<M.DialogContent dividers>
<M.List dense>
{ordered.length ? (
ordered.map((policy) => (
<M.ListItem button key={policy.id} onClick={() => select(policy)}>
<M.ListItemText>
{policy.title}
<M.Box component="span" color="text.secondary">
{' '}
(
{policy.managed ? (
<>{policy.permissions.length} buckets</>
) : (
<>unmanaged</>
)}
)
</M.Box>
</M.ListItemText>
</M.ListItem>
))
) : (
<M.Typography>
{filtering.value
? 'No policies found, try resetting filter'
: 'No more policies to attach'}
</M.Typography>
)}
</M.List>
</M.DialogContent>
<M.DialogActions>
<M.Button autoFocus onClick={onClose} color="primary">
Cancel
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react'
import * as R from 'ramda'
import * as RF from 'react-final-form'
import * as M from '@material-ui/core'

Expand All @@ -7,6 +8,9 @@ import * as Model from 'model'
import * as GQL from 'utils/GraphQL'
import StyledLink from 'utils/StyledLink'

import * as Table from '../Table'
import Filter from './Filter'

import BUCKETS_QUERY from './gql/Buckets.generated'
import { BucketPermissionSelectionFragment as BucketPermission } from './gql/BucketPermissionSelection.generated'

Expand Down Expand Up @@ -39,33 +43,51 @@ function BucketAddDialog({ open, onClose, buckets, addBucket }: BucketAddDialogP
[onClose, select],
)

const filtering = Table.useFiltering({
rows: buckets,
filterBy: ({ name, title }: Bucket) => name + title,
})
const ordered = React.useMemo(
() => R.sortBy(({ name }: Bucket) => name, filtering.filtered),
[filtering.filtered],
)

return (
<M.Dialog maxWidth="xs" open={open} onClose={onClose} onExited={handleExited}>
<M.DialogTitle>Add a bucket</M.DialogTitle>
{buckets.length ? (
<M.List>
{buckets.map((bucket) => (
<M.ListItem key={bucket.name} button onClick={() => handleAdd(bucket)}>
<M.ListItemAvatar style={{ minWidth: 44 }}>
<M.Avatar
style={{ width: 32, height: 32 }}
src={bucket.iconUrl || defaultBucketIcon}
/>
</M.ListItemAvatar>
<M.ListItemText>
s3://{bucket.name}{' '}
<M.Box component="span" color="text.secondary" ml={0.5}>
{bucket.title}
</M.Box>
</M.ListItemText>
</M.ListItem>
))}
</M.List>
) : (
<M.DialogContent>
<M.Typography>No more buckets to add</M.Typography>
</M.DialogContent>
{!!buckets.length && (
<M.Box ml={3} mr={1.5}>
<Filter {...filtering} />
</M.Box>
)}
<M.DialogContent dividers>
{ordered.length ? (
<M.List>
{ordered.map((bucket) => (
<M.ListItem key={bucket.name} button onClick={() => handleAdd(bucket)}>
<M.ListItemAvatar style={{ minWidth: 44 }}>
<M.Avatar
style={{ width: 32, height: 32 }}
src={bucket.iconUrl || defaultBucketIcon}
/>
</M.ListItemAvatar>
<M.ListItemText>
s3://{bucket.name}{' '}
<M.Box component="span" color="text.secondary" ml={0.5}>
{bucket.title}
</M.Box>
</M.ListItemText>
</M.ListItem>
))}
</M.List>
) : (
<M.Typography>
{filtering.value
? 'No buckets found, try resetting filter'
: 'No more buckets to add'}
</M.Typography>
)}
</M.DialogContent>
<M.DialogActions>
<M.Button autoFocus onClick={onClose} color="primary">
Cancel
Expand Down
26 changes: 26 additions & 0 deletions catalog/app/containers/Admin/RolesAndPolicies/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react'
import * as M from '@material-ui/core'

interface FilterProps {
value: string
onChange: (v: string) => void
}

export default function Filter({ value, onChange }: FilterProps) {
return (
<M.InputBase
endAdornment={
value && (
<M.IconButton onClick={() => onChange('')}>
<M.Icon fontSize="small">clear</M.Icon>
</M.IconButton>
)
}
fullWidth
onChange={(event) => onChange(event.target.value)}
placeholder="Filter"
startAdornment={<M.Icon fontSize="small">search</M.Icon>}
value={value}
/>
)
}
21 changes: 14 additions & 7 deletions catalog/app/containers/Admin/RolesAndPolicies/Policies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const validateNonEmptyString: FF.FieldValidator<any> = validate(
validators.matches(/\S/),
)

const columns = [
const columns: Table.Column<Policy>[] = [
{
id: 'title',
label: 'Title',
Expand Down Expand Up @@ -649,7 +649,14 @@ interface DialogsOpenProps {
export default function Policies() {
const { policies: rows } = GQL.useQueryS(POLICIES_QUERY)

const ordering = Table.useOrdering({ rows, column: columns[0] })
const filtering = Table.useFiltering({
rows,
filterBy: ({ title }) => title,
})
const ordering = Table.useOrdering({
rows: filtering.filtered,
column: columns[0],
})
const dialogs = Dialogs.use()

const toolbarActions = [
Expand All @@ -664,11 +671,11 @@ export default function Policies() {

const inlineActions = (policy: Policy) => [
policy.arn
? {
? ({
title: 'Open AWS Console',
icon: <M.Icon>launch</M.Icon>,
href: getArnLink(policy.arn),
}
} as Table.Action)
: null,
{
title: 'Edit',
Expand Down Expand Up @@ -697,22 +704,22 @@ export default function Policies() {
>
<M.Paper>
{dialogs.render({ fullWidth: true, maxWidth: 'sm' })}
<Table.Toolbar heading="Policies" actions={toolbarActions} />
<Table.Toolbar heading="Policies" actions={toolbarActions}>
<Table.Filter {...filtering} />
</Table.Toolbar>
<Table.Wrapper>
<M.Table>
<Table.Head columns={columns} ordering={ordering} withInlineActions />
<M.TableBody>
{ordering.ordered.map((i: Policy) => (
<M.TableRow hover key={i.id}>
{columns.map((col) => (
// @ts-expect-error
<M.TableCell key={col.id} {...col.props}>
{(col.getDisplay || R.identity)(col.getValue(i), i)}
</M.TableCell>
))}
<M.TableCell align="right" padding="none">
<Table.InlineActions actions={inlineActions(i)}>
{/* @ts-expect-error */}
<SettingsMenu policy={i} openDialog={dialogs.open} />
</Table.InlineActions>
</M.TableCell>
Expand Down
Loading