Skip to content

Commit

Permalink
Add sorting (#25)
Browse files Browse the repository at this point in the history
* Add sorting

* Fix lint

* Update panel options

---------

Co-authored-by: Mikhail Volkov <[email protected]>
  • Loading branch information
asimonok and mikhail-vl authored Aug 21, 2024
1 parent 5db21d9 commit e5c4c2d
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 34 deletions.
23 changes: 23 additions & 0 deletions src/components/ColumnEditor/ColumnEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,27 @@ describe('ColumnEditor', () => {
expect(onChange).not.toHaveBeenCalled();
});
});

describe('sort', () => {
it('Should allow to enable sorting', () => {
render(
getComponent({
value: createColumnConfig({ sort: { enabled: false } }),
})
);

expect(selectors.fieldSortEnabled()).toBeInTheDocument();
expect(selectors.fieldSortEnabled()).not.toBeChecked();

fireEvent.click(selectors.fieldSortEnabled());

expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
sort: expect.objectContaining({
enabled: true,
}),
})
);
});
});
});
81 changes: 50 additions & 31 deletions src/components/ColumnEditor/ColumnEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DataFrame } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { InlineField, InlineSwitch, Input, Select } from '@grafana/ui';
import { InlineField, InlineFieldRow, InlineSwitch, Input, Select } from '@grafana/ui';
import React, { useMemo } from 'react';

import { TEST_IDS } from '@/constants';
Expand Down Expand Up @@ -166,18 +166,51 @@ export const ColumnEditor: React.FC<Props> = ({ value, onChange, data }) => {
{...TEST_IDS.columnEditor.fieldType.apply()}
/>
</InlineField>
<InlineField label="Group" grow={true}>
<InlineSwitch
value={value.group}
onChange={(event) =>
onChange({
...value,
group: event.currentTarget.checked,
})
}
{...TEST_IDS.columnEditor.fieldGroup.apply()}
/>
</InlineField>
<InlineFieldRow>
<InlineField label="Group" grow={true}>
<InlineSwitch
value={value.group}
onChange={(event) =>
onChange({
...value,
group: event.currentTarget.checked,
})
}
{...TEST_IDS.columnEditor.fieldGroup.apply()}
/>
</InlineField>
<InlineField label="Filter" grow={true}>
<InlineSwitch
value={value.filter.enabled}
onChange={(event) =>
onChange({
...value,
filter: {
...value.filter,
enabled: event.currentTarget.checked,
},
})
}
{...TEST_IDS.columnEditor.fieldFilterEnabled.apply()}
/>
</InlineField>
<InlineField label="Sort" grow={true}>
<InlineSwitch
value={value.sort.enabled}
onChange={(event) =>
onChange({
...value,
sort: {
...value.sort,
enabled: event.currentTarget.checked,
},
})
}
{...TEST_IDS.columnEditor.fieldSortEnabled.apply()}
/>
</InlineField>
</InlineFieldRow>

{!value.group && (
<InlineField label="Aggregation" grow={true}>
<Select
Expand All @@ -193,24 +226,10 @@ export const ColumnEditor: React.FC<Props> = ({ value, onChange, data }) => {
/>
</InlineField>
)}
<InlineField label="Allow Filtering" grow={true}>
<InlineSwitch
value={value.filter.enabled}
onChange={(event) =>
onChange({
...value,
filter: {
...value.filter,
enabled: event.currentTarget.checked,
},
})
}
{...TEST_IDS.columnEditor.fieldFilterEnabled.apply()}
/>
</InlineField>

{value.filter.enabled && (
<>
<InlineField label="Mode">
<InlineFieldRow>
<InlineField label="Filter Mode">
<Select
value={value.filter.mode}
onChange={(event) => {
Expand Down Expand Up @@ -245,7 +264,7 @@ export const ColumnEditor: React.FC<Props> = ({ value, onChange, data }) => {
/>
</InlineField>
)}
</>
</InlineFieldRow>
)}
</>
);
Expand Down
3 changes: 3 additions & 0 deletions src/components/ColumnsEditor/ColumnsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export const ColumnsEditor: React.FC<Props> = ({ items: groups, name, onChange,
mode: ColumnFilterMode.CLIENT,
variable: '',
},
sort: {
enabled: false,
},
},
]);
setNewItem(null);
Expand Down
15 changes: 15 additions & 0 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
getFacetedUniqueValues,
getFilteredRowModel,
getGroupedRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
Expand Down Expand Up @@ -100,6 +102,11 @@ export const Table = <TData,>({
*/
const [columnFilters, setColumnFilters] = useSyncedColumnFilters({ columns, eventBus });

/**
* Sorting
*/
const [sorting, setSorting] = React.useState<SortingState>([]);

/**
* React Table
*/
Expand All @@ -108,6 +115,7 @@ export const Table = <TData,>({
grouping,
expanded,
columnFilters,
sorting,
},

/**
Expand All @@ -134,6 +142,13 @@ export const Table = <TData,>({
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),

/**
* Sorting
*/
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
enableSorting: true,

/**
* Debug
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css } from '@emotion/css';

/**
* Get Styles
*/
export const getStyles = () => {
return {
labelSortable: css`
cursor: pointer;
`,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { cx } from '@emotion/css';
import { Icon, useStyles2 } from '@grafana/ui';
import { flexRender, Header } from '@tanstack/react-table';
import React from 'react';

import { getStyles } from './TableHeaderCell.styles';
import { TableHeaderCellFilter } from './TableHeaderCellFilter';

/**
Expand All @@ -17,9 +20,24 @@ interface Props<TData> {
* Table Header Cell
*/
export const TableHeaderCell = <TData,>({ header }: Props<TData>) => {
/**
* Styles
*/
const styles = useStyles2(getStyles);

const sort = header.column.getIsSorted();

return (
<>
{flexRender(header.column.columnDef.header, header.getContext())}
<span
onClick={header.column.getToggleSortingHandler()}
className={cx({
[styles.labelSortable]: header.column.getCanSort(),
})}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{!!sort && <Icon name={sort === 'asc' ? 'arrow-up' : 'arrow-down'} />}
</span>
{header.column.columnDef.enableColumnFilter && <TableHeaderCellFilter header={header} />}
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const TEST_IDS = {
fieldFilterEnabled: createSelector('data-testid column-editor field-filter-enabled'),
fieldFilterMode: createSelector('data-testid column-editor field-filter-mode'),
fieldFilterVariable: createSelector('data-testid column-editor field-filter-variable'),
fieldSortEnabled: createSelector('data-testid column-editor field-sort-enabled'),
},
defaultCellRenderer: {
root: createSelector('data-testid default-cell-renderer'),
Expand Down
42 changes: 42 additions & 0 deletions src/hooks/useTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,46 @@ describe('useTable', () => {
*/
expect((result.current.columns[0].filterFn as any)()).toBeTruthy();
});

it('Should build column sort', () => {
const deviceColumn = createColumnConfig({
label: 'Device',
field: {
source: refId,
name: 'device',
},
sort: {
enabled: true,
},
});
const valueColumn = createColumnConfig({
field: {
source: refId,
name: 'value',
},
sort: {
enabled: false,
},
});

const { result } = renderHook(() =>
useTable({
data: {
series: [frame],
} as any,
columns: [deviceColumn, valueColumn],
})
);

expect(result.current.columns).toEqual([
expect.objectContaining({
id: deviceColumn.field.name,
enableSorting: true,
}),
expect.objectContaining({
id: valueColumn.field.name,
enableSorting: false,
}),
]);
});
});
1 change: 1 addition & 0 deletions src/hooks/useTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const useTable = ({ data, columns: columnsConfig }: { data: PanelData; co
aggregationFn: column.config.aggregation === CellAggregation.NONE ? () => null : column.config.aggregation,
enableColumnFilter: column.config.filter.enabled && availableFilterTypes.length > 0,
filterFn: column.config.filter.mode === ColumnFilterMode.CLIENT ? columnFilter : () => true,
enableSorting: column.config.sort.enabled,
meta: {
availableFilterTypes,
filterMode: column.config.filter.mode,
Expand Down
20 changes: 18 additions & 2 deletions src/migration.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { PanelModel } from '@grafana/data';

import { ColumnConfig, ColumnFilterConfig, ColumnFilterMode, Group, PanelOptions } from './types';
import { ColumnConfig, ColumnFilterConfig, ColumnFilterMode, ColumnSortConfig, Group, PanelOptions } from './types';

/**
* Outdated Column Config
*/
interface OutdatedColumnConfig extends Omit<ColumnConfig, 'filter'> {
interface OutdatedColumnConfig extends Omit<ColumnConfig, 'filter' | 'sort'> {
/**
* Filter
*
* Introduced in 1.1.0
*/
filter?: ColumnFilterConfig;

/**
* Filter
*
* Introduced in 1.1.0
*/
sort?: ColumnSortConfig;
}

/**
Expand Down Expand Up @@ -56,6 +63,15 @@ export const getMigratedOptions = (panel: PanelModel<OutdatedPanelOptions>): Pan
};
}

/**
* Add sort options
*/
if (!normalized.sort) {
normalized.sort = {
enabled: false,
};
}

return normalized;
}),
};
Expand Down
19 changes: 19 additions & 0 deletions src/types/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ export interface ColumnFilterConfig {
variable: string;
}

/**
* Column Sort Config
*/
export interface ColumnSortConfig {
/**
* Enabled
*
* @type {boolean}
*/
enabled: boolean;
}

/**
* Column Config
*/
Expand Down Expand Up @@ -88,6 +100,13 @@ export interface ColumnConfig {
* @type {ColumnFilterConfig}
*/
filter: ColumnFilterConfig;

/**
* Sort
*
* @type {ColumnSortConfig}
*/
sort: ColumnSortConfig;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/utils/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const createColumnConfig = (item: Partial<ColumnConfig> = {}): ColumnConf
mode: ColumnFilterMode.CLIENT,
variable: '',
},
sort: {
enabled: false,
},
...item,
});

Expand Down

0 comments on commit e5c4c2d

Please sign in to comment.