From 354cdb6ad9eb3e02604db8bc482293501d514379 Mon Sep 17 00:00:00 2001 From: Sammy Teillet Date: Sat, 6 May 2023 07:59:30 +0200 Subject: [PATCH] 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 --- front/package.json | 4 + .../FilterDropdownButton.stories.tsx | 8 +- .../src/interfaces/company.interface.test.ts | 2 + front/src/interfaces/company.interface.ts | 3 +- front/src/pages/companies/Companies.tsx | 42 +++++-- .../__stories__/Companies.stories.tsx | 5 +- .../pages/companies/__stories__/mock-data.ts | 18 --- .../companies/__tests__/__data__/mock-data.ts | 64 +++++++++++ .../companies-filter.test.ts.snap | 53 +++++++++ .../__tests__/companies-filter.test.ts | 62 ++++++++++ front/src/pages/companies/companies-table.tsx | 106 ++++++++++++++++-- .../people/__stories__/People.stories.tsx | 4 +- .../__data__/mock-data.ts} | 4 +- .../__snapshots__/people-filter.test.ts.snap | 4 +- .../people/__tests__/people-filter.test.ts | 6 +- front/src/services/companies/select.ts | 16 ++- front/src/services/search/search.ts | 1 + 17 files changed, 350 insertions(+), 52 deletions(-) delete mode 100644 front/src/pages/companies/__stories__/mock-data.ts create mode 100644 front/src/pages/companies/__tests__/__data__/mock-data.ts create mode 100644 front/src/pages/companies/__tests__/__snapshots__/companies-filter.test.ts.snap create mode 100644 front/src/pages/companies/__tests__/companies-filter.test.ts rename front/src/pages/people/{default-data.ts => __tests__/__data__/mock-data.ts} (92%) diff --git a/front/package.json b/front/package.json index 3af479f03..d4193cdb9 100644 --- a/front/package.json +++ b/front/package.json @@ -59,6 +59,10 @@ "apollo.tsx$", "src/index.tsx$" ], + "testMatch": [ + "/**/*.test.ts", + "/**/*.test.tsx" + ], "coverageThreshold": { "global": { "branches": 70, diff --git a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx index ce551c6f3..ee9930c87 100644 --- a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx +++ b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx @@ -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')], }, }, }, diff --git a/front/src/interfaces/company.interface.test.ts b/front/src/interfaces/company.interface.test.ts index 23d7c0c64..7478fa0ee 100644 --- a/front/src/interfaces/company.interface.test.ts +++ b/front/src/interfaces/company.interface.test.ts @@ -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'); diff --git a/front/src/interfaces/company.interface.ts b/front/src/interfaces/company.interface.ts index 2952448eb..6b80908a7 100644 --- a/front/src/interfaces/company.interface.ts +++ b/front/src/interfaces/company.interface.ts @@ -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 = { diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx index 8b33e9620..9d0b24b1d 100644 --- a/front/src/pages/companies/Companies.tsx +++ b/front/src/pages/companies/Companies.tsx @@ -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); - const [orderBy, setOrderBy] = useState(defaultOrderBy); + const [orderBy, setOrderBy] = useState(defaultOrderBy); + const [where, setWhere] = useState({}); + + const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch(); const updateSorts = useCallback((sorts: Array) => { - setSorts(sorts); setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); }, []); - const { data } = useCompaniesQuery(orderBy); + const updateFilters = useCallback( + (filters: Array>) => { + setWhere(reduceFiltersToWhere(filters)); + }, + [], + ); + + const { data } = useCompaniesQuery(orderBy, where); return ( }> @@ -36,8 +57,15 @@ function Companies() { columns={companiesColumns} viewName="All Companies" viewIcon={} + availableSorts={availableSorts} + availableFilters={availableFilters} + filterSearchResults={filterSearchResults} onSortsUpdate={updateSorts} - availableSorts={sortsAvailable} + onFiltersUpdate={updateFilters} + onFilterSearch={(filter, searchValue) => { + setSearhInput(searchValue); + setFilterSearch(filter); + }} /> diff --git a/front/src/pages/companies/__stories__/Companies.stories.tsx b/front/src/pages/companies/__stories__/Companies.stories.tsx index a98150f38..2dcd9ec11 100644 --- a/front/src/pages/companies/__stories__/Companies.stories.tsx +++ b/front/src/pages/companies/__stories__/Companies.stories.tsx @@ -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, }, }, }, diff --git a/front/src/pages/companies/__stories__/mock-data.ts b/front/src/pages/companies/__stories__/mock-data.ts deleted file mode 100644 index 6da623491..000000000 --- a/front/src/pages/companies/__stories__/mock-data.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { GraphqlQueryCompany } from '../../../interfaces/company.interface'; - -export const mockCompanyData: Array = [ - { - 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(), - }, -]; diff --git a/front/src/pages/companies/__tests__/__data__/mock-data.ts b/front/src/pages/companies/__tests__/__data__/mock-data.ts new file mode 100644 index 000000000..9202fef85 --- /dev/null +++ b/front/src/pages/companies/__tests__/__data__/mock-data.ts @@ -0,0 +1,64 @@ +import { GraphqlQueryCompany } from '../../../../interfaces/company.interface'; + +export const mockData: Array = [ + { + 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', + }, +]; diff --git a/front/src/pages/companies/__tests__/__snapshots__/companies-filter.test.ts.snap b/front/src/pages/companies/__tests__/__snapshots__/companies-filter.test.ts.snap new file mode 100644 index 000000000..75edaba29 --- /dev/null +++ b/front/src/pages/companies/__tests__/__snapshots__/companies-filter.test.ts.snap @@ -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%", + }, +} +`; diff --git a/front/src/pages/companies/__tests__/companies-filter.test.ts b/front/src/pages/companies/__tests__/companies-filter.test.ts new file mode 100644 index 000000000..e92ce7490 --- /dev/null +++ b/front/src/pages/companies/__tests__/companies-filter.test.ts @@ -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( + filter: FilterType, +): filter is FilterType & { + searchResultMapper: (data: GraphqlQueryCompany) => { + displayValue: string; + value: FilterValue; + }; +} { + return filter.searchQuery === SEARCH_COMPANY_QUERY; +} + +function assertFilterUsePeopleSearch( + filter: FilterType, +): filter is FilterType & { + 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(); + }); + } +}); diff --git a/front/src/pages/companies/companies-table.tsx b/front/src/pages/companies/companies-table.tsx index 7691316c8..2b2771d75 100644 --- a/front/src/pages/companies/companies-table.tsx +++ b/front/src/pages/companies/companies-table.tsx @@ -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: , + _type: 'default_sort', + }, + { + key: 'employees', + label: 'Employees', + icon: , _type: 'default_sort', }, { key: 'domain_name', - label: 'Domain', - icon: undefined, + label: 'Url', + icon: , + _type: 'default_sort', + }, + { + key: 'address', + label: 'Address', + icon: , + _type: 'default_sort', + }, + { + key: 'created_at', + label: 'Creation', + icon: , _type: 'default_sort', }, ] satisfies Array>; +export const availableFilters = [ + { + key: 'company_name', + label: 'Company', + icon: , + 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: , + 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>; + const columnHelper = createColumnHelper(); export const companiesColumns = [ columnHelper.accessor('id', { diff --git a/front/src/pages/people/__stories__/People.stories.tsx b/front/src/pages/people/__stories__/People.stories.tsx index f33525e3c..b39ae841a 100644 --- a/front/src/pages/people/__stories__/People.stories.tsx +++ b/front/src/pages/people/__stories__/People.stories.tsx @@ -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, }, }, }, diff --git a/front/src/pages/people/default-data.ts b/front/src/pages/people/__tests__/__data__/mock-data.ts similarity index 92% rename from front/src/pages/people/default-data.ts rename to front/src/pages/people/__tests__/__data__/mock-data.ts index de446cae6..fa9308cfb 100644 --- a/front/src/pages/people/default-data.ts +++ b/front/src/pages/people/__tests__/__data__/mock-data.ts @@ -1,6 +1,6 @@ -import { GraphqlQueryPerson } from '../../interfaces/person.interface'; +import { GraphqlQueryPerson } from '../../../../interfaces/person.interface'; -export const defaultData: Array = [ +export const mockData: Array = [ { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', __typename: 'Person', diff --git a/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap b/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap index cd9e9e36f..8905fb197 100644 --- a/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap +++ b/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap @@ -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", }, }, }, diff --git a/front/src/pages/people/__tests__/people-filter.test.ts b/front/src/pages/people/__tests__/people-filter.test.ts index ffc96b8d4..fed069e07 100644 --- a/front/src/pages/people/__tests__/people-filter.test.ts +++ b/front/src/pages/people/__tests__/people-filter.test.ts @@ -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( @@ -32,7 +32,7 @@ function assertFilterUsePeopleSearch( return filter.searchQuery === SEARCH_PEOPLE_QUERY; } -const JohnDoeUser = defaultData.find( +const JohnDoeUser = mockPeopleData.find( (user) => user.email === 'john@linkedin.com', ) as GraphqlQueryPerson; diff --git a/front/src/services/companies/select.ts b/front/src/services/companies/select.ts index 3dfc29af9..dca409e7a 100644 --- a/front/src/services/companies/select.ts +++ b/front/src/services/companies/select.ts @@ -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; 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 }, }); } diff --git a/front/src/services/search/search.ts b/front/src/services/search/search.ts index bf0f7e18c..4086df5d2 100644 --- a/front/src/services/search/search.ts +++ b/front/src/services/search/search.ts @@ -29,6 +29,7 @@ export const SEARCH_COMPANY_QUERY = gql` searchResults: companies(where: $where, limit: $limit) { id name + domain_name } } `;