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: record board component state refactor #8779

Merged
merged 6 commits into from
Nov 28, 2024

Conversation

magrinj
Copy link
Member

@magrinj magrinj commented Nov 27, 2024

Fix #8758

This PR is migrating the recoil component state from v1 to v2 for board.
It also now share some states and logics between board and table, further can be done later.
Lastly this PR fix an issue since the PR #8613 that was treating no-value as a normal record-group.

@magrinj magrinj force-pushed the feat/record-board-component-state-refactor branch from 940d4b4 to 14ebef5 Compare November 27, 2024 15:04
@magrinj magrinj marked this pull request as ready for review November 27, 2024 15:19
@charlesBochet charlesBochet self-assigned this Nov 27, 2024
recordBoardId,
);

const recordBoardSelectedRecordIdsSelector =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also extract selection at recordIndex level too (but that's another story!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think too, I didn't make it here because the PR was big enough 😅

import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isDefined } from '~/utils/isDefined';

export const useSetRecordIdsForColumn = (recordBoardId?: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks similar to useSetRecordBoardRecordIds?

naming is not consistent: useSetRecordIdsForColumn + do we need to do it twice?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's similar but not exactly doing the same, I didn't take time to figure out why we need both of them, but they're used

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, could you add a TODO?

);

const recordIds = useRecoilValue(
recordIdsByColumnIdFamilyState(recordBoardColumnId),
const recordRowIdsByGroup = useRecoilComponentFamilyValueV2(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be recordCardIdsByGroup here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for me to double check: what is rowIds vs recordIds? is it actually recordIds?

to me rowIds are only useful to navigate between records using the keyboard, I'm a bit surprise we have this on Kanban

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's record ids (uuids), I'm a bit confused too with the naming as before states was names with rowIds, but I think we should use recordIds everywhere ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I think we should rename them all to recordIds, it would be way clearer

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

This PR implements a significant refactoring of the record board component state management, moving from hook-based to component-scoped Recoil state management.

  • Replaced useRecordBoardStates and related hooks with direct component-scoped Recoil states using useRecoilComponentValueV2 and useRecoilComponentFamilyStateV2
  • Migrated from table-specific states to more generic record index states (e.g., tableAllRowIdsComponentState -> recordIndexAllRowIdsComponentState)
  • Introduced RecordBoardComponentInstanceContext for better state isolation and management
  • Added new utility functions like recordGroupDefinitionToViewGroup and sortedInsert to improve code organization
  • Improved handling of 'No Value' groups in view group mapping with proper UUID generation and positioning

💡 (4/5) You can add custom instructions or style guidelines for the bot here!

95 file(s) reviewed, 41 comment(s)
Edit PR Review Bot Settings | Greptile

@@ -113,7 +114,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
onClick={() => onContentChange('recordGroups')}
LeftIcon={IconLayoutList}
text="Group by"
contextualText={viewGroupFieldMetadataItem?.label}
contextualText={recordGroupFieldMetadata?.label}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: optional chaining here could result in 'undefined' being displayed - consider adding a default value

Comment on lines 36 to +43
useEffect(() => {
if (
currentContentId === 'hiddenRecordGroups' &&
hiddenRecordGroups.length === 0
hiddenRecordGroupIds.length === 0
) {
onContentChange('recordGroups');
}
}, [hiddenRecordGroups, currentContentId, onContentChange]);
}, [hiddenRecordGroupIds, currentContentId, onContentChange]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: this useEffect could potentially be moved to a shared hook since the same pattern appears in multiple dropdown content components

Comment on lines 190 to 191
onColumnsChange={() => {}}
onFieldsChange={() => {}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Empty callback functions should be implemented or removed if not needed

<RecordBoardColumnHeaderWrapper columnId={columnId} key={columnId} />
{visibleRecordGroupIds.map((recordGroupId) => (
<RecordBoardColumnHeaderWrapper
columnId={recordGroupId}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: columnId prop is being passed a recordGroupId value - consider renaming the prop to match the new data structure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This file managed critical state for record board functionality including filters, sorts, field definitions, and selection states. Ensure all this functionality is preserved in the new implementation.

Comment on lines +18 to +22
if (!componentInstanceContext) {
throw new Error(
`Instance context for key "${componentState.key}" is not defined`,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: error message should include more context about how to properly initialize the context

Comment on lines +42 to +45
const existingField = currentViewGroups.find(
(currentViewGroup) =>
currentViewGroup.fieldValue === viewGroupToSave.fieldValue,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: variable name 'existingField' is inconsistent with type 'ViewGroup', should be 'existingViewGroup' for clarity

Comment on lines +66 to +69
await updateViewGroupRecords([
{ ...viewGroupToSave, id: existingField.id },
]);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: saveViewGroup doesn't handle creation of new view groups, unlike saveViewGroups. This inconsistency could lead to bugs

Comment on lines +73 to +76
viewGroup?.position ??
recordGroupDefinitionsFromViewGroups
.map((option) => option.position)
.reduce((a, b) => Math.max(a, b), 0) + 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: potential error if recordGroupDefinitionsFromViewGroups is empty - reduce will throw

fieldMetadataId: recordGroup.fieldMetadataId,
position: recordGroup.position,
isVisible: recordGroup.isVisible ?? true,
fieldValue: recordGroup.value ?? '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: fieldValue should be nullable to match potential null values from the backend


export const useCurrentRecordGroupDefinition = (recordTableId?: string) => {
export const useCurrentRecordGroupDefinition = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why "Current here"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we're getting the RecordGroup based on the current recordGroupId present in the context, but we can change the naming if you want

import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { atomFamily } from 'recoil';

export const recordGroupDefinitionFamilyState = atomFamily<
Copy link
Member

@charlesBochet charlesBochet Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the recordGroupDefinition used for? I feel the "Definition" pattern is not useful anymore and could be replaced by utils

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecordGroupDefinition is almost a mapping of view-groups, at first it was added because of the NoValue group that is not stored in the backend side. But as we now wan't it to act as a normal RecordGroup this one is stored in the backend too.
But I think we should keep it now for consistency in the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's do a follow up

>({
key: 'recordGroupFieldMetadataComponentState',
defaultValue: undefined,
componentInstanceContext: ViewComponentInstanceContext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not use ViewComponentInstanceContext in RecordGroups

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context war haha, actually mixing context between different modules can be tricky because some selectors or are mixing some states from different modules.
It's a bit due to the refactor, but also due to the structure of record-group, maybe we should move it inside record-index.
Mixing states in selectors can actually work as the instanceId between context is often the same, but it's not a good option.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, ok, let's do that in a folllow up

string[]
>({
key: 'hiddenRecordGroupIdsComponentSelector',
componentInstanceContext: ViewComponentInstanceContext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same everywhere :)

import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { isDefined } from '~/utils/isDefined';

export const recordGroupDefinitionsComponentSelector =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

@@ -0,0 +1,20 @@
export const sortedInsert = <T>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this function which is doing two things: sorting + insert. Not sure we need a combined logic here.
why not just sorting where needed and inserting when needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I make this util to avoid looping too many times on the recordGroupIds for performance purpose.
By using it it's reduced to 2 loop, and I think that can help with big tables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's give it a local naming then: recordGroupSortIndex so people don't start using it

>({
key: 'recordIndexAllRowIdsComponentState',
defaultValue: [],
componentInstanceContext: ViewComponentInstanceContext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@@ -20,6 +20,56 @@ export const useSaveCurrentViewGroups = (viewBarComponentId?: string) => {
viewBarComponentId,
);

const saveViewGroup = useRecoilCallback(
({ snapshot }) =>
async (viewGroupToSave: ViewGroup) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

views hooks should expose RecordGroups in its API and internally convert it to view Group, this way everybody is taking dependency on RecordGroup (types) and ViewGroups is only in ViewBar

Copy link
Member Author

@magrinj magrinj Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a look on that, maybe in another PR ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, please discuss with Lucas too on this

Copy link
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bravo!

@charlesBochet charlesBochet merged commit 812ed6e into main Nov 28, 2024
13 of 16 checks passed
@charlesBochet charlesBochet deleted the feat/record-board-component-state-refactor branch November 28, 2024 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor board states
2 participants