Sammy/t 195 aau i see filters and sort on company (#104)

* feature: add company filter by name

* feature: add fitler on URL

* feature: set icons for sorts

* feature: add creation date and address sorts

* Add tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Sammy Teillet
2023-05-06 07:59:30 +02:00
committed by GitHub
parent 406e1dc02e
commit 354cdb6ad9
17 changed files with 350 additions and 52 deletions

View File

@ -59,6 +59,10 @@
"apollo.tsx$",
"src/index.tsx$"
],
"testMatch": [
"<rootDir>/**/*.test.ts",
"<rootDir>/**/*.test.tsx"
],
"coverageThreshold": {
"global": {
"branches": 70,

View File

@ -11,7 +11,7 @@ import {
useSearch,
} from '../../../../services/search/search';
import { MockedProvider } from '@apollo/client/testing';
import { defaultData } from '../../../../pages/people/default-data';
import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data';
const component = {
title: 'FilterDropdownButton',
@ -34,7 +34,7 @@ const mocks = [
},
result: {
data: {
searchResults: defaultData,
searchResults: mockData,
},
},
},
@ -53,7 +53,7 @@ const mocks = [
},
result: {
data: {
searchResults: defaultData,
searchResults: mockData,
},
},
},
@ -72,7 +72,7 @@ const mocks = [
},
result: {
data: {
searchResults: [defaultData.find((p) => p.firstname === 'Jane')],
searchResults: [mockData.find((p) => p.firstname === 'Jane')],
},
},
},

View File

@ -17,6 +17,7 @@ describe('mapCompany', () => {
},
employees: 10,
address: '1 Infinite Loop, 95014 Cupertino, California, USA',
__typename: 'Company',
});
expect(company.id).toBe('7dfbc3f7-6e5e-4128-957e-8d86808cdf6b');
expect(company.name).toBe('ACME');
@ -43,6 +44,7 @@ describe('mapCompany', () => {
created_at: now.toUTCString(),
employees: 10,
address: '1 Infinite Loop, 95014 Cupertino, California, USA',
__typename: 'Company',
});
expect(company.id).toBe('7dfbc3f7-6e5e-4128-957e-8d86808cdf6b');
expect(company.name).toBe('ACME');

View File

@ -21,10 +21,11 @@ export type GraphqlQueryCompany = {
id: string;
name: string;
domain_name: string;
account_owner?: GraphqlQueryUser;
account_owner?: GraphqlQueryUser | null;
employees: number;
address: string;
created_at: string;
__typename: string;
};
export type GraphqlMutationCompany = {

View File

@ -9,8 +9,21 @@ import {
} from '../../services/companies';
import Table from '../../components/table/Table';
import { mapCompany } from '../../interfaces/company.interface';
import { companiesColumns, sortsAvailable } from './companies-table';
import { reduceSortsToOrderBy } from '../../components/table/table-header/helpers';
import {
companiesColumns,
availableFilters,
availableSorts,
} from './companies-table';
import {
reduceFiltersToWhere,
reduceSortsToOrderBy,
} from '../../components/table/table-header/helpers';
import {
Companies_Bool_Exp,
Companies_Order_By,
} from '../../generated/graphql';
import { SelectedFilterType } from '../../components/table/table-header/interface';
import { useSearch } from '../../services/search/search';
const StyledCompaniesContainer = styled.div`
display: flex;
@ -18,15 +31,23 @@ const StyledCompaniesContainer = styled.div`
`;
function Companies() {
const [, setSorts] = useState([] as Array<CompaniesSelectedSortType>);
const [orderBy, setOrderBy] = useState(defaultOrderBy);
const [orderBy, setOrderBy] = useState<Companies_Order_By[]>(defaultOrderBy);
const [where, setWhere] = useState<Companies_Bool_Exp>({});
const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch();
const updateSorts = useCallback((sorts: Array<CompaniesSelectedSortType>) => {
setSorts(sorts);
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
}, []);
const { data } = useCompaniesQuery(orderBy);
const updateFilters = useCallback(
(filters: Array<SelectedFilterType<Companies_Bool_Exp>>) => {
setWhere(reduceFiltersToWhere(filters));
},
[],
);
const { data } = useCompaniesQuery(orderBy, where);
return (
<WithTopBarContainer title="Companies" icon={<FaRegBuilding />}>
@ -36,8 +57,15 @@ function Companies() {
columns={companiesColumns}
viewName="All Companies"
viewIcon={<FaList />}
availableSorts={availableSorts}
availableFilters={availableFilters}
filterSearchResults={filterSearchResults}
onSortsUpdate={updateSorts}
availableSorts={sortsAvailable}
onFiltersUpdate={updateFilters}
onFilterSearch={(filter, searchValue) => {
setSearhInput(searchValue);
setFilterSearch(filter);
}}
/>
</StyledCompaniesContainer>
</WithTopBarContainer>

View File

@ -3,7 +3,7 @@ import Companies from '../Companies';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes';
import { GET_COMPANIES } from '../../../services/companies';
import { mockCompanyData } from './mock-data';
import { mockData } from '../__tests__/__data__/mock-data';
import { MockedProvider } from '@apollo/client/testing';
const component = {
@ -19,11 +19,12 @@ const mocks = [
query: GET_COMPANIES,
variables: {
orderBy: [{ name: 'asc' }],
where: {},
},
},
result: {
data: {
companies: mockCompanyData,
companies: mockData,
},
},
},

View File

@ -1,18 +0,0 @@
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
export const mockCompanyData: Array<GraphqlQueryCompany> = [
{
id: 'f121ab32-fac4-4b8c-9a3d-150c877319c2',
name: 'ACME',
domain_name: 'example.com',
account_owner: {
id: '91510aa5-ede6-451f-8029-a7fa69e4bad6',
email: 'john@example.com',
displayName: 'John Doe',
__typename: 'User',
},
employees: 10,
address: '1 Infinity Loop, 95014 Cupertino, California',
created_at: new Date().toUTCString(),
},
];

View File

@ -0,0 +1,64 @@
import { GraphqlQueryCompany } from '../../../../interfaces/company.interface';
export const mockData: Array<GraphqlQueryCompany> = [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
domain_name: 'airbnb.com',
name: 'Airbnb',
created_at: '2023-04-26T10:08:54.724515+00:00',
address: '17 rue de clignancourt',
employees: 12,
account_owner: null,
__typename: 'companies',
},
{
id: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae',
domain_name: 'aircall.io',
name: 'Aircall',
created_at: '2023-04-26T10:12:42.33625+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
domain_name: 'algolia.com',
name: 'Algolia',
created_at: '2023-04-26T10:10:32.530184+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3',
domain_name: 'apple.com',
name: 'Apple',
created_at: '2023-03-21T06:30:25.39474+00:00',
address: null,
employees: 10,
account_owner: null,
__typename: 'companies',
},
{
id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb',
domain_name: 'bereal.com',
name: 'BeReal',
created_at: '2023-04-26T10:13:29.712485+00:00',
address: '10 rue de la Paix',
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: '9d162de6-cfbf-4156-a790-e39854dcd4eb',
domain_name: 'claap.com',
name: 'Claap',
created_at: '2023-04-26T10:09:25.656555+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
];

View File

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CompaniesFilter should render the filter company_name 1`] = `
Object {
"name": Object {
"_eq": "Airbnb",
},
}
`;
exports[`CompaniesFilter should render the filter company_name 2`] = `
Object {
"_not": Object {
"name": Object {
"_eq": "Airbnb",
},
},
}
`;
exports[`CompaniesFilter should render the filter domainName 1`] = `
Object {
"domain_name": Object {
"_eq": "airbnb.com",
},
}
`;
exports[`CompaniesFilter should render the filter domainName 2`] = `
Object {
"_not": Object {
"domain_name": Object {
"_eq": "airbnb.com",
},
},
}
`;
exports[`CompaniesFilter should render the serch company_name with the searchValue 1`] = `
Object {
"name": Object {
"_ilike": "%Search value%",
},
}
`;
exports[`CompaniesFilter should render the serch domainName with the searchValue 1`] = `
Object {
"domain_name": Object {
"_ilike": "%Search value%",
},
}
`;

View File

@ -0,0 +1,62 @@
import { FilterType } from '../../../components/table/table-header/interface';
import { Companies_Bool_Exp } from '../../../generated/graphql';
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
import { GraphqlQueryPerson } from '../../../interfaces/person.interface';
import {
SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY,
} from '../../../services/search/search';
import { mockData } from './__data__/mock-data';
import { availableFilters } from '../companies-table';
function assertFilterUseCompanySearch<FilterValue>(
filter: FilterType<Companies_Bool_Exp>,
): filter is FilterType<Companies_Bool_Exp> & {
searchResultMapper: (data: GraphqlQueryCompany) => {
displayValue: string;
value: FilterValue;
};
} {
return filter.searchQuery === SEARCH_COMPANY_QUERY;
}
function assertFilterUsePeopleSearch<FilterValue>(
filter: FilterType<Companies_Bool_Exp>,
): filter is FilterType<Companies_Bool_Exp> & {
searchResultMapper: (data: GraphqlQueryPerson) => {
displayValue: string;
value: FilterValue;
};
} {
return filter.searchQuery === SEARCH_PEOPLE_QUERY;
}
const AirbnbCompany = mockData.find(
(user) => user.name === 'Airbnb',
) as GraphqlQueryCompany;
describe('CompaniesFilter', () => {
for (const filter of availableFilters) {
it(`should render the filter ${filter.key}`, () => {
if (assertFilterUseCompanySearch(filter)) {
const filterSelectedValue = filter.searchResultMapper(mockData[0]);
for (const operand of filter.operands) {
expect(
filter.whereTemplate(operand, filterSelectedValue.value),
).toMatchSnapshot();
}
}
if (assertFilterUsePeopleSearch(filter)) {
const filterSelectedValue = filter.searchResultMapper(AirbnbCompany);
for (const operand of filter.operands) {
expect(
filter.whereTemplate(operand, filterSelectedValue.value),
).toMatchSnapshot();
}
}
});
it(`should render the serch ${filter.key} with the searchValue`, () => {
expect(filter.searchTemplate('Search value')).toMatchSnapshot();
});
}
});

View File

@ -1,5 +1,8 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Company } from '../../interfaces/company.interface';
import {
Company,
GraphqlQueryCompany,
} from '../../interfaces/company.interface';
import { updateCompany } from '../../services/companies';
import ColumnHead from '../../components/table/ColumnHead';
import Checkbox from '../../components/form/Checkbox';
@ -14,28 +17,117 @@ import {
FaStream,
FaRegUser,
FaUsers,
FaBuilding,
} from 'react-icons/fa';
import ClickableCell from '../../components/table/ClickableCell';
import PersonChip from '../../components/chips/PersonChip';
import EditableChip from '../../components/table/editable-cell/EditableChip';
import { SortType } from '../../components/table/table-header/interface';
import { Companies_Order_By } from '../../generated/graphql';
import {
FilterType,
SortType,
} from '../../components/table/table-header/interface';
import {
Companies_Bool_Exp,
Companies_Order_By,
} from '../../generated/graphql';
import { SEARCH_COMPANY_QUERY } from '../../services/search/search';
export const sortsAvailable = [
export const availableSorts = [
{
key: 'name',
label: 'Name',
icon: undefined,
icon: <FaBuilding />,
_type: 'default_sort',
},
{
key: 'employees',
label: 'Employees',
icon: <FaUsers />,
_type: 'default_sort',
},
{
key: 'domain_name',
label: 'Domain',
icon: undefined,
label: 'Url',
icon: <FaLink />,
_type: 'default_sort',
},
{
key: 'address',
label: 'Address',
icon: <FaMapPin />,
_type: 'default_sort',
},
{
key: 'created_at',
label: 'Creation',
icon: <FaCalendar />,
_type: 'default_sort',
},
] satisfies Array<SortType<Companies_Order_By>>;
export const availableFilters = [
{
key: 'company_name',
label: 'Company',
icon: <FaBuilding />,
whereTemplate: (operand, { companyName }) => {
if (operand.keyWord === 'equal') {
return {
name: { _eq: companyName },
};
}
if (operand.keyWord === 'not_equal') {
return {
_not: { name: { _eq: companyName } },
};
}
},
searchQuery: SEARCH_COMPANY_QUERY,
searchTemplate: (searchInput: string) => ({
name: { _ilike: `%${searchInput}%` },
}),
searchResultMapper: (company: GraphqlQueryCompany) => ({
displayValue: company.name,
value: { companyName: company.name },
}),
operands: [
{ label: 'Equal', id: 'equal', keyWord: 'equal' },
{ label: 'Not equal', id: 'not-equal', keyWord: 'not_equal' },
],
},
{
key: 'domainName',
label: 'Url',
icon: <FaLink />,
whereTemplate: (operand, { domainName }) => {
if (operand.keyWord === 'equal') {
return {
domain_name: { _eq: domainName },
};
}
if (operand.keyWord === 'not_equal') {
return {
_not: { domain_name: { _eq: domainName } },
};
}
},
searchQuery: SEARCH_COMPANY_QUERY,
searchTemplate: (searchInput: string) => ({
domain_name: { _ilike: `%${searchInput}%` },
}),
searchResultMapper: (company: GraphqlQueryCompany) => ({
displayValue: company.domain_name,
value: { domainName: company.domain_name },
}),
operands: [
{ label: 'Equal', id: 'equal', keyWord: 'equal' },
{ label: 'Not equal', id: 'not-equal', keyWord: 'not_equal' },
],
},
] satisfies Array<FilterType<Companies_Bool_Exp>>;
const columnHelper = createColumnHelper<Company>();
export const companiesColumns = [
columnHelper.accessor('id', {

View File

@ -3,7 +3,7 @@ import People from '../People';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes';
import { MockedProvider } from '@apollo/client/testing';
import { defaultData } from '../default-data';
import { mockData } from '../__tests__/__data__/mock-data';
import { GET_PEOPLE } from '../../../services/people';
import { SEARCH_PEOPLE_QUERY } from '../../../services/search/search';
@ -25,7 +25,7 @@ const mocks = [
},
result: {
data: {
people: defaultData,
people: mockData,
},
},
},

View File

@ -1,6 +1,6 @@
import { GraphqlQueryPerson } from '../../interfaces/person.interface';
import { GraphqlQueryPerson } from '../../../../interfaces/person.interface';
export const defaultData: Array<GraphqlQueryPerson> = [
export const mockData: Array<GraphqlQueryPerson> = [
{
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'Person',

View File

@ -22,7 +22,7 @@ exports[`PeopleFilter should render the filter company_name 1`] = `
Object {
"company": Object {
"name": Object {
"_eq": "ACME",
"_eq": "Airbnb",
},
},
}
@ -33,7 +33,7 @@ Object {
"_not": Object {
"company": Object {
"name": Object {
"_eq": "ACME",
"_eq": "Airbnb",
},
},
},

View File

@ -6,8 +6,8 @@ import {
SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY,
} from '../../../services/search/search';
import { mockCompanyData } from '../../companies/__stories__/mock-data';
import { defaultData } from '../default-data';
import { mockData as mockCompanyData } from '../../companies/__tests__/__data__/mock-data';
import { mockData as mockPeopleData } from './__data__/mock-data';
import { availableFilters } from '../people-table';
function assertFilterUseCompanySearch<FilterValue>(
@ -32,7 +32,7 @@ function assertFilterUsePeopleSearch<FilterValue>(
return filter.searchQuery === SEARCH_PEOPLE_QUERY;
}
const JohnDoeUser = defaultData.find(
const JohnDoeUser = mockPeopleData.find(
(user) => user.email === 'john@linkedin.com',
) as GraphqlQueryPerson;

View File

@ -1,13 +1,20 @@
import { QueryResult, gql, useQuery } from '@apollo/client';
import { Order_By, Companies_Order_By } from '../../generated/graphql';
import {
Order_By,
Companies_Order_By,
Companies_Bool_Exp,
} from '../../generated/graphql';
import { GraphqlQueryCompany } from '../../interfaces/company.interface';
import { SelectedSortType } from '../../components/table/table-header/interface';
export type CompaniesSelectedSortType = SelectedSortType<Companies_Order_By>;
export const GET_COMPANIES = gql`
query GetCompanies($orderBy: [companies_order_by!]) {
companies(order_by: $orderBy) {
query GetCompanies(
$orderBy: [companies_order_by!]
$where: companies_bool_exp
) {
companies(order_by: $orderBy, where: $where) {
id
domain_name
name
@ -25,9 +32,10 @@ export const GET_COMPANIES = gql`
export function useCompaniesQuery(
orderBy: Companies_Order_By[],
where: Companies_Bool_Exp,
): QueryResult<{ companies: GraphqlQueryCompany[] }> {
return useQuery<{ companies: GraphqlQueryCompany[] }>(GET_COMPANIES, {
variables: { orderBy },
variables: { orderBy, where },
});
}

View File

@ -29,6 +29,7 @@ export const SEARCH_COMPANY_QUERY = gql`
searchResults: companies(where: $where, limit: $limit) {
id
name
domain_name
}
}
`;