Skip to content

Commit

Permalink
Workflow history filters (#685)
Browse files Browse the repository at this point in the history
* workflow history filters

* add missing files

* fix lint

* add filter helpers

* fix lint warning

* fix workflow history tests

* update buttons sizes

* fix filter icon size
  • Loading branch information
Assem-Hafez authored Oct 1, 2024
1 parent 0666adb commit a3d6ac4
Show file tree
Hide file tree
Showing 32 changed files with 1,048 additions and 176 deletions.
20 changes: 20 additions & 0 deletions src/components/page-filters/__fixtures__/page-filters.fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { PageQueryParam } from '@/hooks/use-page-query-params/use-page-query-params.types';

import { type PageFilterConfig } from '../page-filters.types';

const defaultParamA = 'valueA1';
const defaultParamB = 'valueB1';

Expand All @@ -21,3 +23,21 @@ export const mockQueryParamsValues = {
paramA: defaultParamA,
paramB: defaultParamB,
};

export const mockFiltersConfig: [
PageFilterConfig<typeof mockPageQueryParamConfig, { paramA: string }>,
PageFilterConfig<typeof mockPageQueryParamConfig, { paramB: string }>,
] = [
{
id: 'filterA',
getValue: (v) => ({ paramA: v.paramA }),
formatValue: (v) => v,
component: () => 'FilterA',
},
{
id: 'filterB',
getValue: (v) => ({ paramB: v.paramB }),
formatValue: (v) => v,
component: () => 'FilterB',
},
];
98 changes: 15 additions & 83 deletions src/components/page-filters/__tests__/page-filters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,25 @@ import { type PageQueryParamValues } from '@/hooks/use-page-query-params/use-pag
import {
mockQueryParamsValues,
mockPageQueryParamConfig,
mockFiltersConfig,
} from '../__fixtures__/page-filters.fixtures';
import PageFilters from '../page-filters';
import {
type PageFilterComponentProps,
type PageFilterConfig,
} from '../page-filters.types';
import { type Props as PageFiltersToggleProps } from '../page-filters-toggle/page-filters-toggle.types';

const mockSetQueryParams = jest.fn();
jest.mock('../../../hooks/use-page-query-params/use-page-query-params', () =>
jest.fn(() => [mockQueryParamsValues, mockSetQueryParams])
);

const MockFilterA = ({
value,
setValue,
}: PageFilterComponentProps<{ paramA: string }>) => {
return (
<div>
<div>Value 1: {value.paramA}</div>
<div
data-testid="change-filters"
onClick={() => setValue({ paramA: 'valueA2' })}
/>
</div>
);
};

const MockFilterB = ({
value,
setValue,
}: PageFilterComponentProps<{ paramB: string }>) => {
return (
<div>
<div>Value 2: {value.paramB}</div>
<div
data-testid="change-filters"
onClick={() => setValue({ paramB: 'valueB2' })}
/>
</div>
);
};

const MOCK_FILTERS_CONFIG: [
PageFilterConfig<typeof mockPageQueryParamConfig, { paramA: string }>,
PageFilterConfig<typeof mockPageQueryParamConfig, { paramB: string }>,
] = [
{
id: 'filterA',
getValue: (v) => ({ paramA: v.paramA }),
formatValue: (v) => v,
component: MockFilterA,
},
{
id: 'filterB',
getValue: (v) => ({ paramB: v.paramB }),
formatValue: (v) => v,
component: MockFilterB,
},
];
jest.mock('../page-filters-fields/page-filters-fields', () =>
jest.fn(() => <div data-testid="filter-fields">Filter Fields</div>)
);

jest.mock('../page-filters-toggle/page-filters-toggle', () =>
jest.fn((props: PageFiltersToggleProps) => (
<button onClick={props.onClick}>Filters Button</button>
))
);

afterEach(() => {
jest.clearAllMocks();
Expand All @@ -88,43 +48,15 @@ describe('PageFilters', () => {
it('should show filters when Filters button is clicked, and modify additional filters', async () => {
setup({});

const filtersButton = await screen.findByText('Filters');

act(() => {
fireEvent.click(filtersButton);
const filtersButton = await screen.findByRole('button', {
name: 'Filters Button',
});

const filtersButtons = await screen.findAllByTestId('change-filters');
expect(filtersButtons).toHaveLength(2);

act(() => {
fireEvent.click(filtersButtons[0]);
});

expect(mockSetQueryParams).toHaveBeenCalledWith({ paramA: 'valueA2' });
});

it('should reset filters when clear filters button is pressed', async () => {
setup({
valuesOverrides: { paramA: 'valueA2', paramB: 'valueB2' },
});

const filtersButton = await screen.findByText('Filters (2)');

act(() => {
fireEvent.click(filtersButton);
});

const clearFiltersButton = await screen.findByText('Clear filters');

act(() => {
fireEvent.click(clearFiltersButton);
});

expect(mockSetQueryParams).toHaveBeenCalledWith({
paramA: undefined,
paramB: undefined,
});
expect(screen.getByTestId('filter-fields')).toBeInTheDocument();
});
});

Expand All @@ -148,7 +80,7 @@ function setup({
<PageFilters
searchQueryParamKey="search"
searchPlaceholder="placeholder"
pageFiltersConfig={MOCK_FILTERS_CONFIG}
pageFiltersConfig={mockFiltersConfig}
pageQueryParamsConfig={mockPageQueryParamConfig}
/>
);
Expand Down
58 changes: 58 additions & 0 deletions src/components/page-filters/hooks/use-page-filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useMemo, useCallback } from 'react';

import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
import {
type PageQueryParams,
type PageQueryParamKeys,
type PageQueryParamValues,
} from '@/hooks/use-page-query-params/use-page-query-params.types';

import { type PageFilterConfig } from '../page-filters.types';

export default function usePageFilters<P extends PageQueryParams>({
pageFiltersConfig,
pageQueryParamsConfig,
}: {
pageFiltersConfig: Array<PageFilterConfig<P, any>>;
pageQueryParamsConfig: P;
}) {
const [queryParams, setQueryParams] = usePageQueryParams(
pageQueryParamsConfig,
{ pageRerender: false }
);

const activeFiltersCount = useMemo(() => {
const configsByKey = Object.fromEntries(
pageQueryParamsConfig.map((c) => [c.key, c])
);
return pageFiltersConfig.filter((pageFilter) =>
Object.keys(pageFilter.getValue(queryParams)).every(
(queryParamKey: PageQueryParamKeys<P>) =>
queryParams[queryParamKey] &&
queryParams[queryParamKey] !==
configsByKey[queryParamKey].defaultValue
)
).length;
}, [pageFiltersConfig, pageQueryParamsConfig, queryParams]);

const resetAllFilters = useCallback(() => {
const emptyQueryParamsObject = pageQueryParamsConfig.reduce(
(acc, config) => ({
...acc,
[config.key]: undefined,
}),
{}
) as PageQueryParamValues<P>;

setQueryParams(
pageFiltersConfig.reduce((acc, pageFilter) => {
return {
...acc,
...pageFilter.getValue(emptyQueryParamsObject),
};
}, {})
);
}, [pageFiltersConfig, pageQueryParamsConfig, setQueryParams]);

return { resetAllFilters, activeFiltersCount, queryParams, setQueryParams };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';

import { render, screen, act, fireEvent } from '@/test-utils/rtl';

import { type PageQueryParamValues } from '@/hooks/use-page-query-params/use-page-query-params.types';

import {
type mockPageQueryParamConfig,
mockQueryParamsValues,
} from '../../__fixtures__/page-filters.fixtures';
import {
type PageFilterComponentProps,
type PageFilterConfig,
} from '../../page-filters.types';
import PageFiltersFields from '../page-filters-fields';

const MockFilterA = ({
value,
setValue,
}: PageFilterComponentProps<{ paramA: string }>) => {
return (
<div>
<div>Value 1: {value.paramA}</div>
<div
data-testid="change-filters"
onClick={() => setValue({ paramA: 'valueA2' })}
/>
</div>
);
};

const MockFilterB = ({
value,
setValue,
}: PageFilterComponentProps<{ paramB: string }>) => {
return (
<div>
<div>Value 2: {value.paramB}</div>
<div
data-testid="change-filters"
onClick={() => setValue({ paramB: 'valueB2' })}
/>
</div>
);
};

export const mockFiltersConfig: [
PageFilterConfig<typeof mockPageQueryParamConfig, { paramA: string }>,
PageFilterConfig<typeof mockPageQueryParamConfig, { paramB: string }>,
] = [
{
id: 'filterA',
getValue: (v) => ({ paramA: v.paramA }),
formatValue: (v) => v,
component: MockFilterA,
},
{
id: 'filterB',
getValue: (v) => ({ paramB: v.paramB }),
formatValue: (v) => v,
component: MockFilterB,
},
];

describe('PageFiltersFields', () => {
it('should reset filters when clear filters button is pressed', async () => {
const { mockedResetAllFilters } = setup({
valuesOverrides: { paramA: 'valueA2', paramB: 'valueB2' },
});

const clearFiltersButton = await screen.findByText('Clear filters');

act(() => {
fireEvent.click(clearFiltersButton);
});

expect(mockedResetAllFilters).toHaveBeenCalled();
});

it('should call setQueryParams when filter setValue is called', async () => {
const { mockedSetQueryParams } = setup({});

const filtersButtons = await screen.findAllByTestId('change-filters');
expect(filtersButtons).toHaveLength(2);

act(() => {
fireEvent.click(filtersButtons[0]);
});

expect(mockedSetQueryParams).toHaveBeenCalledWith({ paramA: 'valueA2' });
});
});

function setup({
valuesOverrides,
}: {
valuesOverrides?: Partial<
PageQueryParamValues<typeof mockPageQueryParamConfig>
>;
}) {
const mockedResetAllFilters = jest.fn();
const mockedSetQueryParams = jest.fn();

render(
<PageFiltersFields
pageFiltersConfig={mockFiltersConfig}
resetAllFilters={mockedResetAllFilters}
setQueryParams={mockedSetQueryParams}
queryParams={{ ...mockQueryParamsValues, ...valuesOverrides }}
/>
);
return { mockedResetAllFilters, mockedSetQueryParams };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { styled as createStyled, type Theme } from 'baseui';
import type { ButtonOverrides } from 'baseui/button';
import { type StyleObject } from 'styletron-react';

export const styled = {
SearchFiltersContainer: createStyled(
'div',
({ $theme }: { $theme: Theme }) => ({
display: 'flex',
flexDirection: 'column',
flexWrap: 'wrap',
gap: $theme.sizing.scale500,
marginBottom: $theme.sizing.scale700,
[$theme.mediaQuery.medium]: {
flexDirection: 'row',
},
})
),
SearchFilterContainer: createStyled('div', {
flexGrow: 2,
flexBasis: 0,
}),
};

export const overrides = {
clearFiltersButton: {
Root: {
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
whiteSpace: 'nowrap',
flexGrow: 2,
height: $theme.sizing.scale950,
[$theme.mediaQuery.medium]: {
flexGrow: 0,
alignSelf: 'flex-end',
marginTop: $theme.sizing.scale700,
},
}),
},
} satisfies ButtonOverrides,
};
Loading

0 comments on commit a3d6ac4

Please sign in to comment.