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

feat: implemented filtering users by coding language #1048

Merged
merged 10 commits into from
Dec 8, 2023
5 changes: 5 additions & 0 deletions frontend/app/src/people/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,11 @@ export interface BountyHeaderProps {
checkboxIdToSelectedMapLanguage: any;
}

export interface PeopleHeaderProps {
onChangeLanguage: (number) => void;
checkboxIdToSelectedMapLanguage: any;
}

export interface DeleteTicketModalProps {
closeModal: () => void;
confirmDelete: () => void;
Expand Down
29 changes: 27 additions & 2 deletions frontend/app/src/people/main/Body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router';
import styled from 'styled-components';
import { EuiLoadingSpinner, EuiGlobalToastList } from '@elastic/eui';
import PeopleHeader from 'people/widgetViews/PeopleHeader';
import { Person as PersonType } from 'store/main';
import filterByCodingLanguage from 'people/utils/filterPeople';
import { SearchTextInput } from '../../components/common';
import { colors } from '../../config/colors';
import { useFuse, useIsMobile, usePageScroll, useScreenWidth } from '../../hooks';
Expand All @@ -24,6 +27,7 @@ const Body = styled.div<{ isMobile: boolean }>`
& > .header {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 10px 0;
}
& > .content {
Expand All @@ -50,6 +54,8 @@ function BodyComponent() {
const [loading, setLoading] = useState(true);
const screenWidth = useScreenWidth();
const [openStartUpModel, setOpenStartUpModel] = useState<boolean>(false);
const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({});
const [filterResult, setFilterResult] = useState<PersonType[]>(main.people);
const closeModal = () => setOpenStartUpModel(false);
const { peoplePageNumber } = ui;
const history = useHistory();
Expand All @@ -71,6 +77,16 @@ function BodyComponent() {
const loadBackwardFunc = () => loadMore(-1);
const { loadingBottom, handleScroll } = usePageScroll(loadForwardFunc, loadBackwardFunc);

const onChangeLanguage = (optionId: any) => {
const newCheckboxIdToSelectedMapLanguage = {
...checkboxIdToSelectedMapLanguage,
...{
[optionId]: !checkboxIdToSelectedMapLanguage[optionId]
}
};
setCheckboxIdToSelectedMapLanguage(newCheckboxIdToSelectedMapLanguage);
};

const toastsEl = (
<EuiGlobalToastList
toasts={ui.toasts}
Expand All @@ -85,6 +101,10 @@ function BodyComponent() {
}
}, [main, ui.meInfo]);

useEffect(() => {
setFilterResult(filterByCodingLanguage(main.people, checkboxIdToSelectedMapLanguage));
}, [checkboxIdToSelectedMapLanguage]);

// update search
useEffect(() => {
(async () => {
Expand Down Expand Up @@ -116,6 +136,11 @@ function BodyComponent() {
}}
>
<div className="header">
<PeopleHeader
onChangeLanguage={onChangeLanguage}
checkboxIdToSelectedMapLanguage={checkboxIdToSelectedMapLanguage}
/>

<SearchTextInput
small
name="search"
Expand All @@ -134,7 +159,7 @@ function BodyComponent() {
/>
</div>
<div className="content">
{(people ?? []).map((t: any) => (
{(ui.searchText ? people : filterResult).map((t: any) => (
<Person
{...t}
key={t.owner_pubkey}
Expand All @@ -144,7 +169,7 @@ function BodyComponent() {
select={selectPerson}
/>
))}
{!people.length && <NoResults />}
{!(ui.searchText ? people : filterResult)?.length && <NoResults />}
<PageLoadSpinner noAnimate show={loadingBottom} />
</div>

Expand Down
75 changes: 75 additions & 0 deletions frontend/app/src/people/utils/__test__/__mockData__/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Person } from 'store/main';

export const users: Person[] = [
{
id: 1,
pubkey: 'test_pub_key',
alias: '',
contact_key: 'test_owner_contact_key',
owner_route_hint: 'test_owner_route_hint',
unique_name: 'test 1',
tags: [],
photo_url: '',
route_hint: 'test_hint:1099567661057',
price_to_meet: 0,
url: 'https://mockApi.com',
description: 'description',
verification_signature: 'test_verification_signature',
extras: {
email: [{ value: '[email protected]' }],
liquid: [{ value: 'none' }],
wanted: [],
coding_languages: [{ label: 'Typescript', value: 'Typescript' }]
},
owner_alias: 'test 1',
owner_pubkey: 'test_pub_key',
img: '/static/avatarPlaceholders/placeholder_34.jpg'
},
{
id: 2,
pubkey: 'test_pub_key',
alias: 'test 2',
contact_key: 'test_owner_contact_key',
owner_route_hint: 'test_owner_route_hint',
unique_name: 'test 2',
tags: [],
photo_url: '',
route_hint: 'test_hint:1099567661057',
price_to_meet: 0,
url: 'https://mockApi.com',
description: 'description',
verification_signature: 'test_verification_signature',
extras: {
email: [{ value: '[email protected]' }],
liquid: [{ value: 'none' }],
wanted: [],
coding_languages: [{ label: 'Java', value: 'Java' }]
},
owner_alias: 'test 2',
owner_pubkey: 'test_pub_key',
img: '/static/avatarPlaceholders/placeholder_34.jpg'
},
{
id: 3,
pubkey: 'test_pub_key',
alias: 'test 3',
contact_key: 'test_owner_contact_key',
owner_route_hint: 'test_owner_route_hint',
unique_name: 'test 3',
tags: [],
photo_url: '',
route_hint: 'test_hint:1099567661057',
price_to_meet: 0,
url: 'https://mockApi.com',
description: 'description',
verification_signature: 'test_verification_signature',
extras: {
email: [{ value: '[email protected]' }],
liquid: [{ value: 'none' }],
wanted: []
},
owner_alias: 'test 3',
owner_pubkey: 'test_pub_key',
img: '/static/avatarPlaceholders/placeholder_34.jpg'
}
];
Original file line number Diff line number Diff line change
@@ -1,50 +1,69 @@
import { bountyHeaderFilter, bountyHeaderLanguageFilter } from '../filterValidation';

describe('testing filters', () => {
describe('bountyHeaderFilter', () => {
test('o/t/t', () => {
expect(bountyHeaderFilter({ Open: true }, true, true)).toEqual(false);
});
test('a/t/t', () => {
expect(bountyHeaderFilter({ Assigned: true }, true, true)).toEqual(false);
});
test('p/t/t', () => {
expect(bountyHeaderFilter({ Paid: true }, true, true)).toEqual(true);
});
test('/t/t', () => {
expect(bountyHeaderFilter({}, true, true)).toEqual(true);
});
test('o/f/t', () => {
expect(bountyHeaderFilter({ Open: true }, false, true)).toEqual(false);
});
test('a/f/t', () => {
expect(bountyHeaderFilter({ Assigned: true }, false, true)).toEqual(true);
});
test('p/f/t', () => {
expect(bountyHeaderFilter({ Paid: true }, false, true)).toEqual(false);
});
});
describe('bountyHeaderLanguageFilter', () => {
test('match', () => {
expect(bountyHeaderLanguageFilter(['Javascript', 'Python'], { Javascript: true })).toEqual(
true
);
});
test('no-match', () => {
expect(
bountyHeaderLanguageFilter(['Javascript'], { Python: true, Javascript: false })
).toEqual(false);
});
test('no filters', () => {
expect(bountyHeaderLanguageFilter(['Javascript'], {})).toEqual(true);
});
test('no languages', () => {
expect(bountyHeaderLanguageFilter([], { Javascript: true })).toEqual(false);
});
test('false filters', () => {
expect(
bountyHeaderLanguageFilter(['Javascript'], { Javascript: false, Python: false })
).toEqual(true);
});
});
});
import { bountyHeaderFilter, bountyHeaderLanguageFilter } from '../filterValidation';
import filterByCodingLanguage from '../filterPeople';
import { users } from '../__test__/__mockData__/users';

describe('testing filters', () => {
describe('bountyHeaderFilter', () => {
test('o/t/t', () => {
expect(bountyHeaderFilter({ Open: true }, true, true)).toEqual(false);
});
test('a/t/t', () => {
expect(bountyHeaderFilter({ Assigned: true }, true, true)).toEqual(false);
});
test('p/t/t', () => {
expect(bountyHeaderFilter({ Paid: true }, true, true)).toEqual(true);
});
test('/t/t', () => {
expect(bountyHeaderFilter({}, true, true)).toEqual(true);
});
test('o/f/t', () => {
expect(bountyHeaderFilter({ Open: true }, false, true)).toEqual(false);
});
test('a/f/t', () => {
expect(bountyHeaderFilter({ Assigned: true }, false, true)).toEqual(true);
});
test('p/f/t', () => {
expect(bountyHeaderFilter({ Paid: true }, false, true)).toEqual(false);
});
});
describe('bountyHeaderLanguageFilter', () => {
test('match', () => {
expect(bountyHeaderLanguageFilter(['Javascript', 'Python'], { Javascript: true })).toEqual(
true
);
});
test('no-match', () => {
expect(
bountyHeaderLanguageFilter(['Javascript'], { Python: true, Javascript: false })
).toEqual(false);
});
test('no filters', () => {
expect(bountyHeaderLanguageFilter(['Javascript'], {})).toEqual(true);
});
test('no languages', () => {
expect(bountyHeaderLanguageFilter([], { Javascript: true })).toEqual(false);
});
test('false filters', () => {
expect(
bountyHeaderLanguageFilter(['Javascript'], { Javascript: false, Python: false })
).toEqual(true);
});
});
describe('peopleHeaderCodingLanguageFilters', () => {
test('match', () => {
expect(filterByCodingLanguage(users, { Typescript: true })).toStrictEqual([users[0]]);
});
test('no_match', () => {
expect(filterByCodingLanguage(users, { Rust: true })).toStrictEqual([]);
});
test('no filters', () => {
expect(filterByCodingLanguage(users, {})).toEqual(users);
});
test('false filters', () => {
expect(filterByCodingLanguage(users, { PHP: false, MySQL: false })).toStrictEqual(users);
});
test('no users', () => {
expect(filterByCodingLanguage([], { Typescript: true })).toStrictEqual([]);
});
});
});
22 changes: 22 additions & 0 deletions frontend/app/src/people/utils/filterPeople.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Person } from 'store/main';

interface CodingLanguage {
[language: string]: boolean;
}

const filterByCodingLanguage = (users: Person[], codingLanguages: CodingLanguage) => {
const requiredLanguages = Object.keys(codingLanguages).filter(
(key: string) => codingLanguages[key]
);

return users.filter((user: Person) => {
const userCodingLanguages = (user.extras.coding_languages ?? []).map(
(t: { [key: string]: string }) => t.value
);
return requiredLanguages?.every((requiredLanguage: string) =>
userCodingLanguages.includes(requiredLanguage)
);
});
};

export default filterByCodingLanguage;
Loading
Loading