Skip to content

Commit

Permalink
feat: filter by notification type (#1860)
Browse files Browse the repository at this point in the history
* feat: filter by notification type

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* feat: filter by notification type

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* feat: filter by notification type

Signed-off-by: Adam Setch <adam.setch@outlook.com>

---------

Signed-off-by: Adam Setch <adam.setch@outlook.com>
  • Loading branch information
setchy authored Feb 26, 2025
1 parent adc30a5 commit a2ffd6c
Show file tree
Hide file tree
Showing 14 changed files with 1,861 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/renderer/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const mockFilters: FilterSettingsState = {
filterUserTypes: [],
filterIncludeHandles: [],
filterExcludeHandles: [],
filterSubjectTypes: [],
filterStates: [],
filterReasons: [],
};
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/filters/StateFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type FC, useContext } from 'react';

import { BellIcon } from '@primer/octicons-react';
import { IssueOpenedIcon } from '@primer/octicons-react';
import { Stack, Text } from '@primer/react';

import { AppContext } from '../../context/App';
Expand All @@ -21,7 +21,7 @@ export const StateFilter: FC = () => {
return (
<fieldset id="filter-state" className="mb-3">
<Stack direction="horizontal" gap="condensed" align="baseline">
<Title icon={BellIcon}>State</Title>
<Title icon={IssueOpenedIcon}>State</Title>
<Tooltip
name="tooltip-filter-state"
tooltip={
Expand Down
96 changes: 96 additions & 0 deletions src/renderer/components/filters/SubjectTypeFilter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { mockAccountNotifications } from '../../__mocks__/notifications-mocks';
import { mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import type { SettingsState } from '../../types';
import { SubjectTypeFilter } from './SubjectTypeFilter';

describe('renderer/components/filters/SubjectTypeFilter.tsx', () => {
const updateFilter = jest.fn();

it('should render itself & its children', () => {
const tree = render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
} as SettingsState,
notifications: mockAccountNotifications,
}}
>
<MemoryRouter>
<SubjectTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);

expect(tree).toMatchSnapshot();
});

it('should be able to toggle subject type - none already set', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
filterSubjectTypes: [],
},
notifications: [],
updateFilter,
}}
>
<MemoryRouter>
<SubjectTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);
});

fireEvent.click(screen.getByLabelText('Issue'));

expect(updateFilter).toHaveBeenCalledWith(
'filterSubjectTypes',
'Issue',
true,
);

expect(
screen.getByLabelText('Issue').parentNode.parentNode,
).toMatchSnapshot();
});

it('should be able to toggle subject type - some filters already set', async () => {
await act(async () => {
render(
<AppContext.Provider
value={{
settings: {
...mockSettings,
filterSubjectTypes: ['Issue'],
},
notifications: [],
updateFilter,
}}
>
<MemoryRouter>
<SubjectTypeFilter />
</MemoryRouter>
</AppContext.Provider>,
);
});

fireEvent.click(screen.getByLabelText('Pull Request'));

expect(updateFilter).toHaveBeenCalledWith(
'filterSubjectTypes',
'PullRequest',
true,
);

expect(
screen.getByLabelText('Pull Request').parentNode.parentNode,
).toMatchSnapshot();
});
});
75 changes: 75 additions & 0 deletions src/renderer/components/filters/SubjectTypeFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { type FC, useContext } from 'react';

import { BellIcon } from '@primer/octicons-react';
import { Stack, Text } from '@primer/react';

import { AppContext } from '../../context/App';
import type { SubjectType } from '../../typesGitHub';
import {
FILTERS_SUBJECT_TYPES,
getSubjectTypeDetails,
getSubjectTypeFilterCount,
isSubjectTypeFilterSet,
} from '../../utils/notifications/filters/subjectType';
import { Checkbox } from '../fields/Checkbox';
import { Tooltip } from '../fields/Tooltip';
import { Title } from '../primitives/Title';

export const SubjectTypeFilter: FC = () => {
const { updateFilter, settings, notifications } = useContext(AppContext);

return (
<fieldset id="filter-subject-type" className="mb-3">
<Stack direction="horizontal" gap="condensed" align="baseline">
<Title icon={BellIcon}>Type</Title>
<Tooltip
name="tooltip-filter-subject-type"
tooltip={
<Stack direction="vertical" gap="condensed">
<Text>Filter notifications by type.</Text>
</Stack>
}
/>
</Stack>

<Stack direction="vertical" gap="condensed">
{Object.keys(FILTERS_SUBJECT_TYPES).map((subjectType: SubjectType) => {
const subjectTypeDetails = getSubjectTypeDetails(subjectType);
const subjectTypeTitle = subjectTypeDetails.title;
const subjectTypeDescription = subjectTypeDetails.description;
const isSubjectTypeChecked = isSubjectTypeFilterSet(
settings,
subjectType,
);
const subjectTypeCount = getSubjectTypeFilterCount(
notifications,
subjectType,
);

return (
<Checkbox
key={subjectType}
name={subjectTypeTitle}
label={subjectTypeTitle}
checked={isSubjectTypeChecked}
onChange={(evt) =>
updateFilter(
'filterSubjectTypes',
subjectType,
evt.target.checked,
)
}
tooltip={
subjectTypeDescription ? (
<Text>{subjectTypeDescription}</Text>
) : null
}
disabled={!settings.detailedNotifications}
counter={subjectTypeCount}
/>
);
})}
</Stack>
</fieldset>
);
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a2ffd6c

Please sign in to comment.