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

fix(column-configurator): convert to using the taxonomic filter #8726

Merged
merged 38 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d121a0f
copy array in selector to trigger change detection
pauldambra Feb 23, 2022
689b2e8
use taxonomic filter for the column configurator
pauldambra Feb 24, 2022
10770fc
fix column configurator tests
pauldambra Feb 24, 2022
de18065
fix weird test failure
pauldambra Feb 25, 2022
16bff88
ensure the taxonomic popup isn't showing row 0 when then column confi…
pauldambra Feb 25, 2022
d135e1a
rename visible columns now there are no hidden columns
pauldambra Feb 25, 2022
df30ad3
rename visible columns now there are no hidden columns
pauldambra Feb 25, 2022
ea8bb34
tidy up column configurator
pauldambra Feb 26, 2022
ebddc4a
which allows removal of a use of the property definitions model
pauldambra Feb 26, 2022
f674c28
Merge branch 'master' into empty-columns
pauldambra Mar 2, 2022
757e7d6
more specific prop to disable popper on taxonomic filter
pauldambra Mar 2, 2022
e389bee
better column name
pauldambra Mar 2, 2022
918f9ab
don't allow duplicate column selections
pauldambra Mar 2, 2022
7e730af
Merge branch 'master' into empty-columns
pauldambra Mar 2, 2022
6ddc641
use autosizer height and width and flex display to have infinite list…
pauldambra Mar 2, 2022
5587ae4
set text overflow for long rows in the infinite list
pauldambra Mar 3, 2022
bab97a7
update column configurator columns when resetting
pauldambra Mar 3, 2022
8cf08c5
Merge branch 'master' into empty-columns
pauldambra Mar 4, 2022
9982f01
select initial row based on props
pauldambra Mar 4, 2022
f58b2af
don't create css if height and width are not provided
pauldambra Mar 4, 2022
0bae446
limit scope of change to propertykeyinfo width
pauldambra Mar 4, 2022
32c17c0
alter column configurator reset behaviour
pauldambra Mar 4, 2022
ac1d347
set popper enabled without breaking text search
pauldambra Mar 4, 2022
016f866
don't need to pass popper enabled two different ways to infinite list
pauldambra Mar 4, 2022
bf26aea
allow taxonomic filter value consumers to configure it not to clear s…
pauldambra Mar 4, 2022
21500ad
clear the taxonomic filter search when the column configurator is saved
pauldambra Mar 4, 2022
e91c407
fix tests
pauldambra Mar 4, 2022
ce40e3a
default column configurator to an empty array of columns
pauldambra Mar 4, 2022
ceb69c8
Revert "default column configurator to an empty array of columns"
pauldambra Mar 4, 2022
2198563
Revert "fix tests"
pauldambra Mar 4, 2022
b040f04
Revert "clear the taxonomic filter search when the column configurato…
pauldambra Mar 4, 2022
1755198
rename popper to popover
pauldambra Mar 4, 2022
ce85c12
move taxonomic filter config onto its props
pauldambra Mar 4, 2022
b0ead05
tidier declaration of style
pauldambra Mar 4, 2022
1ab117b
make selecting the first item explicit
pauldambra Mar 4, 2022
3504c00
always clear taxonomic filter on select but do it so infinite list ca…
pauldambra Mar 4, 2022
a3232d1
fix tests
pauldambra Mar 4, 2022
e0653e6
use the constant for no item selected, since it is available
pauldambra Mar 7, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { expectLogic } from 'kea-test-utils'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { defaultAPIMocks, mockAPI } from 'lib/api.mock'
import { propertyFilterLogic } from 'lib/components/PropertyFilters/propertyFilterLogic'
import { columnConfiguratorLogic } from 'lib/components/ResizableTable/columnConfiguratorLogic'

jest.mock('lib/api')

Expand All @@ -14,6 +15,7 @@ describe('the taxonomic property filter', () => {

beforeEach(() => {
initKeaTests()
columnConfiguratorLogic({ selectedColumns: [] }).mount()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... Will this test fail without it? If so, it's coupled in a strange way. Something to improve?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's too yucky, I'll remove

logic = taxonomicPropertyFilterLogic({
propertyFilterLogic: propertyFilterLogic({
pageKey: 'tests',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/lib/components/ResizableTable/TableConfig.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,11 @@
&.selected {
background-color: $primary_hover;
}

.property-key-info {
flex-grow: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
70 changes: 21 additions & 49 deletions frontend/src/lib/components/ResizableTable/TableConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, Col, Row, Space } from 'antd'
import React from 'react'
import { ControlOutlined, LockOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons'
import { ControlOutlined, LockOutlined, CloseOutlined } from '@ant-design/icons'
import './TableConfig.scss'
import { useActions, useValues } from 'kea'
import { tableConfigLogic } from './tableConfigLogic'
Expand All @@ -11,10 +11,10 @@ import { PropertyKeyInfo } from '../PropertyKeyInfo'
import clsx from 'clsx'
import { Tooltip } from 'lib/components/Tooltip'
import { columnConfiguratorLogic } from 'lib/components/ResizableTable/columnConfiguratorLogic'
import Search from 'antd/lib/input/Search'
import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'

interface TableConfigProps {
availableColumns: string[] //the full set of column titles in the table's data
immutableColumns?: string[] //the titles of the columns that are always displayed
defaultColumns: string[] // the titles of the set of columns to show when there is no user choice
}
Expand All @@ -31,59 +31,38 @@ export function TableConfig(props: TableConfigProps): JSX.Element {
<Button data-attr="events-table-column-selector" onClick={showModal} icon={<ControlOutlined rotate={90} />}>
Customize columns
</Button>
<ColumnConfigurator
immutableColumns={props.immutableColumns}
availableColumns={props.availableColumns}
defaultColumns={props.defaultColumns}
/>
<ColumnConfigurator immutableColumns={props.immutableColumns} defaultColumns={props.defaultColumns} />
</>
)
}

function ColumnConfigurator({ immutableColumns, defaultColumns, availableColumns }: TableConfigProps): JSX.Element {
function ColumnConfigurator({ immutableColumns, defaultColumns }: TableConfigProps): JSX.Element {
// the virtualised list doesn't support gaps between items in the list
// setting the container to be larger than we need
// and adding a container with a smaller height to each row item
// allows the new row item to set a margin around itself
const rowContainerHeight = 36
const rowItemHeight = 32

const { selectedColumns, modalVisible } = useValues(tableConfigLogic)
const { selectedColumns: currentlySelectedColumns, modalVisible } = useValues(tableConfigLogic)
const { hideModal } = useActions(tableConfigLogic)

const logic = columnConfiguratorLogic({
availableColumns,
selectedColumns: selectedColumns === 'DEFAULT' ? defaultColumns : selectedColumns,
selectedColumns: currentlySelectedColumns === 'DEFAULT' ? defaultColumns : currentlySelectedColumns,
})
const { selectColumn, unselectColumn, resetColumns, save, setColumnFilter } = useActions(logic)
const { visibleColumns, hiddenColumns, scrollIndex, columnFilter, filteredVisibleColumns, filteredHiddenColumns } =
useValues(logic)

function AvailableColumn({ index, style, key }: ListRowProps): JSX.Element {
return (
<div style={style} key={key} onClick={() => selectColumn(filteredHiddenColumns[index])}>
<div className="column-display-item" style={{ height: `${rowItemHeight}px` }}>
<PropertyKeyInfo value={filteredHiddenColumns[index]} />
<div className="text-right" style={{ flex: 1 }}>
<Tooltip title="Add">
<PlusOutlined style={{ color: 'var(--success)' }} />
</Tooltip>
</div>
</div>
</div>
)
}
const { selectColumn, unselectColumn, resetColumns, save } = useActions(logic)
const { selectedColumns } = useValues(logic)

function SelectedColumn({ index, style, key }: ListRowProps): JSX.Element {
const disabled = immutableColumns?.includes(filteredVisibleColumns[index])
const disabled = immutableColumns?.includes(selectedColumns[index])

return (
<div style={style} key={key} onClick={() => !disabled && unselectColumn(filteredVisibleColumns[index])}>
<div style={style} key={key} onClick={() => !disabled && unselectColumn(selectedColumns[index])}>
<div
className={clsx(['column-display-item', { selected: !disabled, disabled: disabled }])}
style={{ height: `${rowItemHeight}px` }}
>
<PropertyKeyInfo value={filteredVisibleColumns[index]} />
<PropertyKeyInfo value={selectedColumns[index]} />
<div className="text-right" style={{ flex: 1 }}>
<Tooltip title={disabled ? 'Reserved' : 'Remove'}>
{disabled ? <LockOutlined /> : <CloseOutlined style={{ color: 'var(--danger)' }} />}
Expand Down Expand Up @@ -127,46 +106,39 @@ function ColumnConfigurator({ immutableColumns, defaultColumns, availableColumns
</Row>
}
>
<Search
allowClear
autoFocus
placeholder="Search"
type="search"
value={columnFilter}
onChange={(e) => setColumnFilter(e.target.value)}
/>
<Row gutter={16} className="mt">
<Col xs={24} sm={12}>
<h3 className="l3">Hidden columns ({hiddenColumns.length})</h3>
<h3 className="l3">Available columns</h3>
<div style={{ height: 320 }}>
<AutoSizer>
{({ height, width }: { height: number; width: number }) => {
return (
<VirtualizedList
<TaxonomicFilter
height={height}
rowCount={filteredHiddenColumns.length}
rowRenderer={AvailableColumn}
rowHeight={rowContainerHeight}
width={width}
taxonomicGroupTypes={[TaxonomicFilterGroupType.EventProperties]}
value={undefined}
onChange={(_, value) => value && selectColumn(String(value))}
popperEnabled={false}
clearSearchOnSelection={false}
/>
)
}}
</AutoSizer>
</div>
</Col>
<Col xs={24} sm={12}>
<h3 className="l3">Visible columns ({visibleColumns.length})</h3>
<h3 className="l3">Visible columns ({selectedColumns.length})</h3>
<div style={{ height: 320 }}>
<AutoSizer>
{({ height, width }: { height: number; width: number }) => {
return (
<VirtualizedList
height={height}
rowCount={filteredVisibleColumns.length}
rowCount={selectedColumns.length}
rowRenderer={SelectedColumn}
rowHeight={rowContainerHeight}
width={width}
scrollToIndex={scrollIndex}
/>
)
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,43 @@ describe('the column configurator lets the user change which columns should be v
let logic: ReturnType<typeof columnConfiguratorLogic.build>

const selectedColumns = ['a', 'b', 'ant', 'aardvark']
const availableColumns = [...selectedColumns, 'c', 'd', 'e']

initKeaTestLogic({
logic: columnConfiguratorLogic,
props: { availableColumns, selectedColumns },
props: { selectedColumns },
onLogic: (l) => {
logic = l
},
})

it('has expected defaults', () => {
expectLogic(logic).toMatchValues({
columnFilter: '',
visibleColumns: selectedColumns,
filteredVisibleColumns: selectedColumns,
hiddenColumns: ['c', 'd', 'e'],
filteredHiddenColumns: ['c', 'd', 'e'],
scrollIndex: 4,
selectedColumns: selectedColumns,
})
})

it('selecting a column moves it from hidden to visible and updates the scroll index', () => {
expectLogic(logic, () => logic.actions.selectColumn('d')).toMatchValues({
hiddenColumns: ['c', 'e'],
visibleColumns: ['a', 'b', 'ant', 'aardvark', 'd'],
scrollIndex: 5,
})
})

it('unselecting a column moves it from visible to hidden and updates the scroll index', () => {
expectLogic(logic, () => logic.actions.unselectColumn('a')).toMatchValues({
hiddenColumns: ['c', 'd', 'e', 'a'],
visibleColumns: ['b', 'ant', 'aardvark'],
scrollIndex: 3,
})
})

it('can set the column filter', () => {
expectLogic(logic, () => logic.actions.setColumnFilter('123')).toMatchValues({ columnFilter: '123' })
})

it('setting the column filter, fitlers the visible and hidden columns', () => {
expectLogic(logic, () => logic.actions.setColumnFilter('a')).toMatchValues({
columnFilter: 'a',
filteredHiddenColumns: [],
filteredVisibleColumns: ['a', 'ant', 'aardvark'],
})
})

it('sets selected columns to visible columns on save', async () => {
it('sets selected columns on save', async () => {
await expectLogic(logic, () => {
logic.actions.selectColumn('d')
logic.actions.save()
}).toDispatchActions([tableConfigLogic.actionCreators.setSelectedColumns(['a', 'b', 'ant', 'aardvark', 'd'])])
})

it('sets selected columns to provided on reset', async () => {
it('sets selected columns to those provided on reset', async () => {
const defaultColumns = ['1', '2']
await expectLogic(logic, () => {
logic.actions.resetColumns(defaultColumns)
}).toMatchValues({
selectedColumns: defaultColumns,
})
})

it('can not duplicate columns', async () => {
await expectLogic(logic, () => {
logic.actions.resetColumns(['1', '2'])
}).toDispatchActions([tableConfigLogic.actionCreators.setSelectedColumns(['1', '2'])])
logic.actions.selectColumn('added')
logic.actions.selectColumn('added')
}).toMatchValues({
selectedColumns: ['a', 'b', 'ant', 'aardvark', 'added'],
})
})
})
Original file line number Diff line number Diff line change
@@ -1,72 +1,33 @@
import { columnConfiguratorLogicType } from './columnConfiguratorLogicType'
import { tableConfigLogic } from 'lib/components/ResizableTable/tableConfigLogic'
import { kea } from 'kea'

export interface ColumnConfiguratorLogicProps {
availableColumns: string[] // all of the columns the table could display
selectedColumns: string[] //the columns the table is currently displaying
selectedColumns: string[] // the columns the table is currently displaying
}

import { columnConfiguratorLogicType } from './columnConfiguratorLogicType'
import { tableConfigLogic } from 'lib/components/ResizableTable/tableConfigLogic'
import Fuse from 'fuse.js'

const filterColumns = (columnFilter: string, columns: string[]): string[] =>
columnFilter
? new Fuse(columns, {
threshold: 0.3,
})
.search(columnFilter)
.map(({ item }) => item)
: columns

export const columnConfiguratorLogic = kea<columnConfiguratorLogicType<ColumnConfiguratorLogicProps>>({
path: ['lib', 'components', 'ResizableTable', 'columnConfiguratorLogic'],
props: { availableColumns: [], selectedColumns: [] } as ColumnConfiguratorLogicProps,
props: { selectedColumns: [] } as ColumnConfiguratorLogicProps,
actions: {
selectColumn: (column: string) => ({ column }),
unselectColumn: (column: string) => ({ column }),
resetColumns: (columns: string[]) => ({ columns }),
save: true,
setColumnFilter: (searchTerm: string) => ({ searchTerm }),
},
reducers: ({ props }) => ({
columnFilter: [
'',
{
setColumnFilter: (_, { searchTerm }) => searchTerm,
},
],
visibleColumns: [
selectedColumns: [
props.selectedColumns,
{
selectColumn: (state, { column }) => [...state, column],
selectColumn: (state, { column }) => Array.from(new Set([...state, column])),
unselectColumn: (state, { column }) => state.filter((c) => c !== column),
},
],
hiddenColumns: [
props.availableColumns.filter((c) => !props.selectedColumns.includes(c)),
{
selectColumn: (state, { column }) => state.filter((c) => c !== column),
unselectColumn: (state, { column }) => [...state, column],
resetColumns: (_, { columns }) => columns,
},
],
}),
selectors: {
scrollIndex: [(selectors) => [selectors.visibleColumns], (visibleColumns) => visibleColumns.length],
filteredVisibleColumns: [
(selectors) => [selectors.columnFilter, selectors.visibleColumns],
(columnFilter, visibleColumns) => filterColumns(columnFilter, visibleColumns),
],
filteredHiddenColumns: [
(selectors) => [selectors.columnFilter, selectors.hiddenColumns],
(columnFilter, hiddenColumns) => filterColumns(columnFilter, hiddenColumns),
],
},
listeners: ({ values }) => ({
save: () => {
tableConfigLogic.actions.setSelectedColumns(values.visibleColumns)
},
resetColumns: ({ columns }) => {
tableConfigLogic.actions.setSelectedColumns(columns)
tableConfigLogic.actions.setSelectedColumns(values.selectedColumns)
},
}),
})
13 changes: 11 additions & 2 deletions frontend/src/lib/components/TaxonomicFilter/InfiniteList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@
padding: 4px 12px;
cursor: pointer;
border: none;
white-space: nowrap;
overflow-x: auto;

& > div {
max-width: 100%;

& > span {
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

&.hover {
background: rgba(0, 0, 0, 0.1);
Expand Down
Loading