-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[Issue-5772] Add sort feature on settings tables #5787
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
- Introduced
useTableSort
custom hook for table sorting - Added test cases for
useTableSort
- Integrated sorting in
SettingsObjectFieldItemTableRow
andSettingsObjectItemTableRow
- Enabled clickable headers for sorting in
TableHeader
- Updated
SettingsObjectDetail
andSettingsObjects
pages to use sorting functionality
type SortConfig<T> = { | ||
sortByColumnKey: keyof T; | ||
sortOrder: sortOrders; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider renaming sortOrders
to SortOrders
to follow TypeScript enum naming conventions.
return order === sortOrders.ascending | ||
? (a[columnKey] as string).localeCompare(b[columnKey] as string) | ||
: (b[columnKey] as string).localeCompare(a[columnKey] as string); | ||
} else if (typeof a[columnKey] === 'number') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle cases where a[columnKey]
or b[columnKey]
might be undefined
to prevent runtime errors.
@@ -7,6 +7,7 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe | |||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; | |||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure fieldType
is correctly populated in all instances of fieldMetadataItem
.
@@ -41,10 +43,8 @@ const StyledIconTableCell = styled(TableCell)` | |||
export const SettingsObjectFieldItemTableRow = ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a default value or error handling for updateDataTypes
to avoid potential runtime errors.
import { useTheme } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import { useIcons } from 'twenty-ui'; | ||
|
||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; | ||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; | ||
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/SettingsDataModelObjectTypeTag'; | ||
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; | ||
import { | ||
getObjectTypeLabel, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure useEffect
dependency array is correct to avoid unnecessary re-renders.
@@ -1,7 +1,13 @@ | |||
import { useEffect } from 'react'; | |||
import { useCallback, useEffect, useState } from 'react'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider importing useCallback
and useState
from React in a single line for better readability.
@@ -38,13 +64,74 @@ const StyledH1Title = styled(H1Title)` | |||
margin-bottom: 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure that instanceCountObj
is correctly populated before using it in getRowData
to avoid potential undefined values.
Hi Charles,
Thanks a lot for your feedback. I will look in this and update the PR. Some
immediate work has come up and I hope to fix this on the weekend.
…On Mon, 10 Jun 2024, 5:11 pm Charles Bochet, ***@***.***> wrote:
@Anand-Krishnan-M-J <https://github.com/Anand-Krishnan-M-J> Thanks for
your PR :) Honnestly, I think we should restrict the scope to supporting
Links filtering. Supporting sort in this PR (for Thursday) looks very
ambitious. Let's treat that in a separated PR and keep this one for Links
filtering
1. Please run npx nx run twenty-front:lint:ci, you should see some
errors. (I believe they are tied to the changes you have made for sorting)
2. The work on Links looks good but I cannot test it as the front does
not build at the moment
3. we'll have few lines to add in the backend to remove secondaryLinks
from the FilterInput
image.png (view on web)
<https://github.com/twentyhq/twenty/assets/12035771/9b6f0909-8d3a-42d9-8f5d-58d696180fa6>
—
Reply to this email directly, view it on GitHub
<#5787 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AU4NDQEI4NO5BFCQ5D2SX6LZGWGG3AVCNFSM6AAAAABJBAMXOSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNJYGEYTKNRSGE>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for contributing.
It's conceptually ok but some patterns feels odd, due to props drilling.
We use Recoil extensively in Twenty codebase and this feature particularly fits Recoil.
Could you please rework on a solution where :
- Sorts are stored in a Recoil family state (you'll find many examples in our codebase) I suggest you make a family state with a key like { tableId: string, tableObjectName: string, tableFieldName: string } => value: OrderBy | null
- The logic around a column header cell is abstracted in that header cell component and just modifies the related sort in the recoil family state
- Then each list should just use a hook like useSortedArray that takes (arrayToSort: ObjectRecord[], tableId: string, tableObjectName: string and fieldName: string (or fieldName array string[]) and it applies the sort and just derives the state.
It will be much more simpler to read and maintain (and review ;) )
@@ -0,0 +1,62 @@ | |||
import { useState } from 'react'; | |||
|
|||
enum sortOrders { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have OrderBy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the type to OrderBy. thanks for pointing it out.
: sortOrders.ascending; | ||
}; | ||
|
||
const handleSortClick = (columnKey: keyof T) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handlers should be the scope of a component, not of a hook
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved the logic inside component
sortConfig.sortOrder, | ||
); | ||
|
||
const toggleSortOrder = (sortOrder: sortOrders) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function isn't toggling, it's just returning the opposite of the passed sortOrder, so it should either be renamed or either really toggling the state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the logic is moved to component, we no longer have toggleSortOrder()
@@ -17,11 +18,12 @@ import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType'; | |||
|
|||
type SettingsObjectFieldItemTableRowProps = { | |||
ActionIcon: ReactNode; | |||
fieldMetadataItem: FieldMetadataItem; | |||
fieldMetadataItem: FieldMetadataItem & { fieldType: string | boolean }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fieldType is redundant, we already have a type property on fieldMetadataItem which should be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the redundant field
@@ -60,6 +60,18 @@ export const SettingsObjectFieldItemTableRow = ({ | |||
|
|||
const fieldType = fieldMetadataItem.type; | |||
const isFieldTypeSupported = isFieldTypeSupportedInSettings(fieldType); | |||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is not necessary because we already have a fieldMetadataItem array higher up in the tree, the row shouldn't be related to this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the unwanted logic and used the actual value in higher level component instead
!!identifierType && | ||
(identifierType === 'label' ? 'Record text' : 'Record image')} | ||
</TableCell> | ||
<TableCell>{fieldMetadataItem.fieldType}</TableCell> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be keep the derived state here, not adding a new field on fieldMetadataItem, if you need to share it with another place just create a util like getFieldMetadataItemTypeLabel(fieldMetadataItem)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with the derived state
Hi @lucasbordeau Can we keep the family state key simpler, like this?
By using a key as an object comprising tableId and initialFieldName (since each sort is related to a table and has an initial default sort), the state value would be:
Please let me know your thoughts on this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
(updates since last review)
- Introduced
useSortedArray
hook for sorting table content (/packages/twenty-front/src/hooks/useSortedArray.ts
) - Removed
useTableSort
hook and its test cases (/packages/twenty-front/src/hooks/useTableSort.ts
,/packages/twenty-front/src/hooks/__tests__/useTableSort.test.ts
) - Added Recoil state for managing table sort states (
/packages/twenty-front/src/modules/activities/states/tabelSortFamilyState.ts
) - Integrated sorting functionality into
SettingsObjectFieldItemTableRow
andSettingsObjectItemTableRow
components (/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx
,/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectItemTableRow.tsx
) - Updated
SettingsObjectDetail
andSettingsObjects
pages to use new sorting feature (/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetail.tsx
,/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx
)
9 file(s) reviewed, 4 comment(s)
Edit PR Review Bot Settings
packages/twenty-front/src/hooks/__tests__/useSortedArray.test.tsx
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/modules/activities/states/tabelSortFamilyState.ts
Outdated
Show resolved
Hide resolved
TableSortFamilyKey | ||
>({ | ||
key: 'tableSortFamilyState', | ||
defaultValue: getDefaultTableSortState as unknown as TableSortState, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🪶 style: Casting getDefaultTableSortState
as unknown
and then TableSortState
may hide potential type issues. Consider refining the type definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels a bit odd, I'm not sure what you're trying to do here? Assigning a function as default value?
Hey @lucasbordeau I am still getting used to the Recoil pattern. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
(updates since last review)
- Added
useSortedArray
hook for sorting table content (/packages/twenty-front/src/hooks/useSortedArray.ts
) - Introduced test cases for
useSortedArray
hook (/packages/twenty-front/src/hooks/__tests__/useSortedArray.test.tsx
) - Corrected type casting in
tableSortFamilyState.ts
(/packages/twenty-front/src/modules/activities/states/tableSortFamilyState.ts
) - Integrated sorting functionality into
SettingsObjectDetail.tsx
(/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetail.tsx
) - Added sorting feature to
SettingsObjects.tsx
(/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx
)
5 file(s) reviewed, 1 comment(s)
Edit PR Review Bot Settings
}: SettingsObjectItemTableRowProps) => { | ||
const theme = useTheme(); | ||
|
||
const { totalCount } = useFindManyRecords({ | ||
objectNameSingular: objectItem.nameSingular, | ||
}); | ||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you try getting rid of useEffect? https://react.dev/learn/you-might-not-need-an-effect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me find a way to do that. Probably I will have to create a global state to do that, as we have to store the data somewhere to actually use it higher up the React tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a recoil family here also
const SortIcon = ({ sortKey }: { sortKey: SortKeys }) => { | ||
if (sortKey !== sortConfig.fieldName) return null; | ||
return sortConfig.orderBy === 'AscNullsLast' ? ( | ||
<IconArrowUp size="14" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tried to avoid hardcoding size values like that, you can probably find examples in the code, getting the size from the theme directly. Also it feels like the arrow isn't perfectly positionned, it's a bit too closed to the name and not perfectly vertically aligned (a bit too high)
@@ -38,13 +66,92 @@ const StyledH1Title = styled(H1Title)` | |||
margin-bottom: 0; | |||
`; | |||
|
|||
const tableHeadings: TableHeading = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something feels inconsistent here, it should be tableHeadings: TableHeading[] or tableHeading: TableHeading ; I think the singular version is best because heading is a row
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like it should be TableFieldSortDefinition[] or something like that
Otherwise really great PR! Thanks a lot for your work @Anand-Krishnan-M-J - sorry we took so long to review |
instancesCount = 'instancesCount', | ||
} | ||
type InstanceCountStateType = { [key: string]: number }; | ||
type TableHeading = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The developer experience you're creating for future sort use-cases isn't great, you have to redeclare types every time you want to add sort to a table. You're already duplicating some code between SettingsObjects and SettingsObjectDetail
order: OrderBy | null, | ||
): T[] => { | ||
return data.sort((a: T, b: T) => { | ||
if (typeof a[columnKey] === 'string') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't try to guess the type from JS since it can be unreliable. Instead we should give this hook metadata about our table so that it knows how to behave for each field
packages/twenty-front/src/modules/activities/states/tableSortFamilyState.ts
Outdated
Show resolved
Hide resolved
sortKey: SortKeys.dataType, | ||
}, | ||
]; | ||
const SortIcon = ({ sortKey }: { sortKey: SortKeys }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be move in the component that handles the column heading, it should be abstracted down.
@@ -29,7 +39,25 @@ import { Table } from '@/ui/layout/table/components/Table'; | |||
import { TableHeader } from '@/ui/layout/table/components/TableHeader'; | |||
import { TableSection } from '@/ui/layout/table/components/TableSection'; | |||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; | |||
|
|||
import { useSortedArray } from '~/hooks/useSortedArray'; | |||
export enum SortKeys { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SortKeys should be more self-explanatory, I cannot understand at first glance what it talks about.
Is it AvailableSortFieldName ?
@@ -38,13 +66,92 @@ const StyledH1Title = styled(H1Title)` | |||
margin-bottom: 0; | |||
`; | |||
|
|||
const tableHeadings: TableHeading = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like it should be TableFieldSortDefinition[] or something like that
export const SettingsObjects = () => { | ||
const [instanceCountObj, setInstanceCount] = useState<InstanceCountStateType>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming contains abbreviation and is not consistent
const theme = useTheme(); | ||
|
||
const { activeObjectMetadataItems, inactiveObjectMetadataItems } = | ||
useFilteredObjectMetadataItems(); | ||
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem(); | ||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem(); | ||
const getRowData = (objectMetaDataItems: ObjectMetadataItem[]) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getRowData is not self-explanatory enough
@Anand-Krishnan-M-J Keep up the good work, I did a extensive code review so that you can be more in line with our standards and the way we code at Twenty. |
@lucasbordeau |
@Anand-Krishnan-M-J Please take your time, otherwise if it's too much work we'll handle it and update you on the changes for reference so that you can be up-to-date and ready for the next one ! Anyway it's already great to have made the core changes. |
@lucasbordeau |
…eneric table sort
Thanks @Anand-Krishnan-M-J for your contribution! |
Proposed Changes
Related Issue
#5772
Evidence
2024-06-09.21-35-41.mp4
Evidence after addressing review comments
2024-07-04.21-33-12.mp4
Further comments
Apologies for the large PR. Looking forward for the review