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

TableView enhancements #4809

Merged
merged 1 commit into from
Oct 21, 2024
Merged

TableView enhancements #4809

merged 1 commit into from
Oct 21, 2024

Conversation

imanjra
Copy link
Contributor

@imanjra imanjra commented Sep 17, 2024

What changes are proposed in this pull request?

  • Add support for highlighting cells, rows, and columns
  • Add support for handling click even on cells, rows, and columns
  • Add support for row actions
  • Add support for rounding numeric data across the entire table
  • Add support for less-redundant data format for table

How is this patch tested? If it is not, please explain why.

Using an example python panel

class IMSnippet(foo.Panel):
    @property
    def config(self):
        return foo.PanelConfig(name="im_snippet", label="IM: Snippet")

    def on_load(self, ctx):
        table_data_new = {
            "rows": [
                ["Jon", "Snow", 1.0453],
                ["Arya", "Stark", 1.0453],
                ["Daenerys", "Targaryen", 1.0453],
            ],
            "columns": ["first_name", "last_name", "time"],
            "selected_cells": [[0, 0]],
            "selected_rows": [2],
            "selected_columns": [1],
        }
        ctx.panel.set_data("table", table_data_new)

    def on_click_row(self, ctx):
        print(f"on_click_row - Row: {ctx.params.get('row')}")

    def on_click_cell(self, ctx):
        print(f"on_click_cell - Row: {ctx.params.get('row')}, Column: {ctx.params.get('column')}")

    def on_click_column(self, ctx):
        print(f"on_click_column - Column: {ctx.params.get('column')}")

    def on_delete_row(self, ctx):
        print(f"on_delete_row - Delete row: {ctx.params.get('row')}")

    def on_edit_row(self, ctx):
        print(f"on_edit_row - Edit row: {ctx.params.get('row')}")

    def render(self, ctx):
        panel = types.Object()
        table_view = types.TableView(
            on_click_cell=self.on_click_cell,
            round=2,
            on_click_row=self.on_click_row,
            on_click_column=self.on_click_column,
        )
        table_view.add_column("first_name", label="First Name")
        table_view.add_column("last_name", label="Last Name")
        table_view.add_column("time", label="Time")
        table_view.add_row_action(
            "delete", self.on_delete_row, label="Delete", icon="delete"
        )
        table_view.add_row_action("edit", self.on_edit_row, label="Edit", icon="edit")
        panel.list("table", types.Object(), view=table_view)
        return types.Property(
            panel,
            view=types.GridView(
                height=100,
                width=100,
                align_x="center",
                align_y="center",
            ),
        )

Release Notes

Is this a user-facing change that should be mentioned in the release notes?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release
    notes for FiftyOne users.

(Details in 1-2 sentences. You can just refer to another PR with a description
if this PR is part of a larger change.)

What areas of FiftyOne does this PR affect?

  • App: FiftyOne application changes
  • Build: Build and test infrastructure changes
  • Core: Core fiftyone Python library changes
  • Documentation: FiftyOne documentation changes
  • Other

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced an ActionsMenu component for better action management, allowing inline display or dropdown for additional actions.
    • Enhanced TableView component to support row actions, improving interactivity with selection states and new action capabilities.
  • Improvements

    • Updated TableView to handle structured props and provide visual feedback for selected rows and cells.
    • Added functionality for formatting cell values based on schema specifications.
    • Improved data handling in TableView with new utility functions for better modularity.

Copy link
Contributor

coderabbitai bot commented Sep 17, 2024

Walkthrough

The changes introduce a new ActionsMenu component for managing user actions flexibly, alongside significant enhancements to the TableView component to improve interactivity and data handling. A new Action class is added to facilitate row actions in TableView, allowing actions to be associated with table rows. The modifications include updated props, new utility functions, and improved event handling to enhance the overall functionality of the components.

Changes

Files Change Summary
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx Added ActionsMenu, ActionsOverflowMenu, and Action components; defined action types.
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx Enhanced TableView with structured props, selection states, and integrated ActionsMenu.
fiftyone/operators/types.py Introduced Action class for row actions; modified TableView to support and manage row actions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ActionsMenu
    participant TableView
    participant Action

    User->>TableView: Interacts with table
    TableView->>ActionsMenu: Displays actions
    User->>ActionsMenu: Selects action
    ActionsMenu->>Action: Executes action
    Action-->>TableView: Updates table state
Loading

🐇 In the meadow, actions bloom,
A menu here, dispelling gloom.
Rows now dance with clicks and cheer,
With every action, joy draws near!
Hops of code, a playful tune,
Enhancing views beneath the moon. 🌙

Possibly related PRs

  • visual tweaks for menu and views in menu #4562: This PR includes visual tweaks for menu components, which may relate to the ActionsMenu component introduced in the main PR, as both involve user interface elements for displaying actions.
  • enhancement for grid layout #4567: This PR enhances the GridView component, which is relevant because the TableView component in the main PR now incorporates the ActionsMenu for row actions, indicating a connection in how actions are managed and displayed within grid layouts.

Suggested reviewers

  • ritch

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@imanjra imanjra marked this pull request as ready for review September 18, 2024 04:51
@imanjra imanjra requested review from ritch and a team September 18, 2024 04:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 2173d35 and 638f20b.

Files selected for processing (3)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1 hunks)
  • app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (5 hunks)
  • fiftyone/operators/types.py (2 hunks)
Additional context used
Path-based instructions (2)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Biome
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx

[error] 195-195: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

Additional comments not posted (11)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (5)

1-14: Imports look good!

The imports are relevant, and there are no unused imports. The naming is clear and follows conventions.


17-31: The ActionsMenu component looks good!

  • The component handles the rendering logic based on the number of actions effectively.
  • The use of a constant for the default maxInline value is a good practice.
  • The component is exported correctly as the default export.
  • The rendering logic is clear and concise.

33-70: The ActionsOverflowMenu component looks good!

  • The component manages the menu state effectively using the open state variable and anchorRef.
  • The handleClose function is memoized using useCallback to avoid unnecessary re-creation.
  • The onClick handler for each action is wrapped to close the menu after the action is triggered, which is a good user experience.
  • The rendering of the menu items is done by mapping over the actions prop, which is a clean and readable approach.

72-94: The Action component looks good!

  • The component handles the rendering of an action based on the mode prop effectively.
  • The Icon component is conditionally rendered based on the icon prop, which is a good practice.
  • The handleClick function is memoized using useCallback to avoid unnecessary re-creation.
  • The onClick handler is called with the props object and the event object, providing flexibility.
  • The rendering logic is clean and readable.

96-108: The type definitions look good!

  • The ActionsPropsType and ActionPropsType are defined correctly and cover all the necessary props.
  • The onClick prop in ActionPropsType is defined as a function that accepts the action object and the event object, providing flexibility.
  • The mode prop in ActionPropsType is defined as a union type of "inline" and "menu", ensuring type safety.
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (4)

Line range hint 22-146: LGTM!

The TableView component follows several good practices:

  • It extracts the required props and handles the case when no data is provided.
  • It uses the getTableData function to extract the table data in a structured manner, which improves the clarity and maintainability of the code.
  • It renders the table header and body based on the extracted data.
  • It handles click events using the usePanelEvent hook, which ensures that the events are tied to the correct panel context.
  • It uses the ActionsMenu component to render row actions, which keeps the component modular.
  • It uses the getComponentProps function to extract the component-specific props, which keeps the component props structured.
  • It uses the scrollable class to make the table scrollable, which handles large tables.
  • It uses the formatCellValue function to format the cell values based on the schema's rounding specifications, which keeps the formatting logic separate.

Overall, the component is well-structured and follows the best practices for React and TypeScript.


148-165: LGTM!

The getTableData function follows several good practices:

  • It extracts the table data from the props correctly.
  • It handles both standard and advanced data formats, which improves the flexibility of the component.
  • It uses the isAdvancedData and parseAdvancedData functions to handle the advanced data format, which improves the clarity and maintainability of the code.

Overall, the function is well-structured and follows the best practices for TypeScript.


167-173: LGTM!

The isAdvancedData function follows several good practices:

  • It correctly checks if the provided data is in the advanced format.
  • It uses the isPlainObject function from the lodash library to check if the data is an object, which ensures that the data is in the expected format.
  • It checks if the rows and columns properties of the data are arrays, which ensures that the data is in the expected format.

Overall, the function is well-structured and follows the best practices for TypeScript.


175-190: LGTM!

The parseAdvancedData function follows several good practices:

  • It correctly parses the advanced data format.
  • It converts the rows array into an array of objects, where each object represents a row with the column names as keys, which makes the data more accessible.
  • It extracts the selectedCells, selectedRows, and selectedColumns properties from the data and converts them into sets for faster lookup, which improves the performance of the component.
  • It handles the case when the selectedCells, selectedRows, and selectedColumns properties are not provided, which improves the flexibility of the component.

Overall, the function is well-structured and follows the best practices for TypeScript.

fiftyone/operators/types.py (2)

1621-1640: LGTM!

The Action class is well-designed with clear attributes and methods for cloning and JSON serialization. The implementation looks good.


1647-1647: Excellent enhancement!

The changes to the TableView class to support row actions are well-implemented. The new row_actions attribute, add_row_action method, and the updates to clone and to_json methods all look good. This will provide a convenient way to incorporate interactive actions for each row in the table.

Also applies to: 1653-1653, 1663-1669, 1673-1673, 1680-1680

{columns.map(({ key }, columnIndex) => {
const coordinate = [rowIndex, columnIndex].join(",");
const isSelected =
selectedCells.has(coordinate) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

I have been running into some of these errors when a tableview does not have selectedRows/Columns/Cells in the initial state.

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Outside diff range and nitpick comments (4)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (4)

38-50: Well-implemented row actions handling.

The getRowActions callback is well-structured:

  • Proper use of useCallback for performance optimization.
  • Correct implementation of onClick handler using handleClick with panel context.

Consider memoizing row_actions.map result if row_actions is expected to be static:

const memoizedRowActions = useMemo(() => row_actions.map(/* ... */), [row_actions]);

const getRowActions = useCallback((row) => {
  return memoizedRowActions.map(action => ({
    ...action,
    onClick: (e) => {
      handleClick(panelId, {
        operator: action.on_click,
        params: { path, event: action.name, row },
      });
    },
  }));
}, [handleClick, panelId, path, memoizedRowActions]);

This optimization could be beneficial if getRowActions is called frequently with different rows.


Line range hint 51-143: Enhanced table rendering with improved interactivity.

The updated table rendering logic significantly improves the component's functionality:

  • Proper incorporation of selection states for cells, rows, and columns.
  • Well-implemented row actions integration.
  • Correct use of MUI's sx prop for styling selected cells.

Consider extracting the click handler logic into a separate function to improve readability:

const handleCellClick = useCallback((rowIndex, columnIndex) => {
  if (on_click_cell) {
    handleClick(panelId, {
      operator: on_click_cell,
      params: { row: rowIndex, column: columnIndex, path, event: "on_click_cell" },
    });
  }
  if (on_click_row) {
    handleClick(panelId, {
      operator: on_click_row,
      params: { row: rowIndex, path, event: "on_click_row" },
    });
  }
  if (on_click_column) {
    handleClick(panelId, {
      operator: on_click_column,
      params: { column: columnIndex, path, event: "on_click_column" },
    });
  }
}, [on_click_cell, on_click_row, on_click_column, handleClick, panelId, path]);

Then use it in the onClick prop:

onClick={() => handleCellClick(rowIndex, columnIndex)}

This refactoring would improve code readability and maintainability.


148-190: Well-implemented data handling utility functions.

The new utility functions effectively handle different data formats:

  • getTableData provides a flexible approach to handle both simple and advanced data structures.
  • isAdvancedData correctly identifies the advanced data format.
  • parseAdvancedData efficiently transforms the data into a consistent structure.

The use of Set for selected elements is a good choice for performance.

Consider adding basic error handling to parseAdvancedData:

function parseAdvancedData(data) {
  if (!isAdvancedData(data)) {
    console.warn('Invalid data format passed to parseAdvancedData');
    return { rows: [], selectedCells: new Set(), selectedRows: new Set(), selectedColumns: new Set() };
  }
  
  // ... rest of the function
}

This addition would make the function more robust against potential misuse.


192-199: Well-implemented cell value formatting function.

The formatCellValue function effectively handles numeric rounding:

  • Correct use of Number.isNaN for number checking.
  • Proper implementation of rounding based on the schema.

Consider adding type assertions or checks to improve type safety:

function formatCellValue(value: unknown, props: ViewPropsType): string {
  const round = props?.schema?.view?.round;
  if (typeof value !== 'string' && typeof value !== 'number') {
    return String(value);
  }
  const valueAsFloat = parseFloat(String(value));
  if (!Number.isNaN(valueAsFloat) && typeof round === "number") {
    return valueAsFloat.toFixed(round);
  }
  return String(value);
}

This change would make the function more robust against different input types.

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 638f20b and beed93a.

Files selected for processing (3)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1 hunks)
  • app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (5 hunks)
  • fiftyone/operators/types.py (2 hunks)
Files skipped from review as they are similar to previous changes (2)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx
  • fiftyone/operators/types.py
Additional context used
Path-based instructions (1)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

Additional comments not posted (1)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

1-3: Improved imports and component structure.

The changes to the imports and component declaration are well-structured:

  • New hooks (usePanelEvent, usePanelId) have been added, improving the component's integration with the panel system.
  • The ActionsMenu import suggests enhanced interactivity.
  • Using ViewPropsType for props improves type safety.

These changes align with React best practices and enhance the component's functionality.

Also applies to: 14-15, 18-20, 22-37

@minhtuev
Copy link
Contributor

Also I think this new tableview might be backward incompatible; previously this is how we set the data state to the Table:

summary_table = types.TableView()
summary_table.add_column("Field Name", label="Field Name")

panel.list(
    "summary_field_table",
    types.Object(),
    view=summary_table,
    label=f"Summary fields for dataset: {ctx.dataset.name}",
)

And then we set the data state in another section:

summary_fields = self._get_summary_fields(ctx)
summary_table = []
for name in sorted(summary_fields):
    summary_table.append(
        {
            "Field Name": name,
        }
    )
ctx.panel.state.summary_field_table = summary_table

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (4)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (3)

15-15: Consider adding a comment to explain the purpose of DEFAULT_MAX_INLINE.

While the constant is appropriately named, its purpose and significance in the component's logic might not be immediately clear to other developers. A brief comment explaining its role would enhance code readability.


33-70: LGTM: ActionsOverflowMenu component is well-implemented, with a minor suggestion.

The component effectively manages its state and correctly passes props to child components. The use of useCallback for handleClose is good for performance.

Suggestion for improvement:
Consider memoizing the onClick handler for each Action to prevent unnecessary re-renders:

const createActionClickHandler = useCallback(
  (action: ActionPropsType) => (e: React.MouseEvent) => {
    handleClose();
    action.onClick?.(action, e);
  },
  [handleClose]
);

Then use it in the Action component like this:

onClick={createActionClickHandler(action)}

This change would ensure that the onClick handler for each Action is only recreated when handleClose changes, potentially improving performance for menus with many actions.


72-94: LGTM: Action component is well-implemented, with a suggestion for improved type safety.

The component is flexible and efficiently handles both inline and menu modes. The use of useCallback for the onClick handler is good for performance.

Suggestion for improved type safety:
Consider using a discriminated union type for the mode prop to ensure type safety and enable better autocomplete:

type ActionPropsType = {
  name: string;
  label: string;
  onClick: (action: ActionPropsType, e: React.MouseEvent) => void;
  icon?: string;
  variant?: string;
} & (
  | { mode: "inline"; variant: string }
  | { mode: "menu" }
);

This change would ensure that the variant prop is required when mode is "inline", and not allowed when mode is "menu". It would also make the icon and variant props optional, which seems to align with their usage in the component.

app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

148-199: Add TypeScript type annotations to helper functions

The helper functions lack explicit type annotations, which can reduce code readability and type safety. Adding type annotations to the functions enhances maintainability.

Apply this diff to add type annotations:

-function getTableData(props) {
+function getTableData(props: ViewPropsType): {
+  rows: any[];
+  selectedCells: Set<string>;
+  selectedRows: Set<number>;
+  selectedColumns: Set<number>;
+} {
   // function body
 }

-function isAdvancedData(data) {
+function isAdvancedData(data: any): boolean {
   // function body
 }

-function parseAdvancedData(data) {
+function parseAdvancedData(data: any): {
+  rows: any[];
+  selectedCells: Set<string>;
+  selectedRows: Set<number>;
+  selectedColumns: Set<number>;
+} {
   // function body
 }

-function formatCellValue(value: string, props: ViewPropsType) {
+function formatCellValue(value: string, props: ViewPropsType): string {
   // function body
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between beed93a and 27057a5.

📒 Files selected for processing (3)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1 hunks)
  • app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (5 hunks)
  • fiftyone/operators/types.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

🔇 Additional comments (6)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (2)

1-13: LGTM: Import statements are well-organized and follow best practices.

The import statements are appropriately structured, using named imports where possible. The use of Material-UI components and icons is consistent with the project's UI framework.


17-31: LGTM: ActionsMenu component is well-implemented.

The component's logic is clear and concise. It efficiently handles both inline and overflow scenarios based on the number of actions. The use of a default value for maxInline is a good practice. The component is pure, which is beneficial for performance and testing.

fiftyone/operators/types.py (4)

1647-1647: Docstring updated to include row_actions

The docstring for the TableView class now includes the row_actions parameter, enhancing the documentation and informing users about the new functionality.


1653-1653: Proper initialization of row_actions in TableView.__init__

The row_actions attribute is correctly initialized in the __init__ method using kwargs.get("row_actions", []), ensuring that it defaults to an empty list if not provided.


1673-1673: Cloning row_actions in TableView.clone()

The clone() method of TableView correctly clones the row_actions list, preventing unintended side effects from shared references between instances.


1680-1680: Including row_actions in TableView.to_json() output

The to_json() method has been appropriately updated to include the serialized row_actions, ensuring that all action information is available in the JSON representation.

Comment on lines 38 to 56
const getRowActions = useCallback((row) => {
return row_actions.map((action) => {
return {
...action,
onClick: (action, e) => {
handleClick(panelId, {
operator: action.on_click,
params: { path, event: action.name, row },
});
},
};
});
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Include dependencies in the useCallback hook

The useCallback hook at line 38 depends on row_actions, handleClick, panelId, and path, but the dependencies array is empty. This can lead to stale closures or unexpected behavior. Include these dependencies in the array to ensure the callback updates when these values change.

Apply this diff to fix the dependencies array:

-  }, []);
+  }, [row_actions, handleClick, panelId, path]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getRowActions = useCallback((row) => {
return row_actions.map((action) => {
return {
...action,
onClick: (action, e) => {
handleClick(panelId, {
operator: action.on_click,
params: { path, event: action.name, row },
});
},
};
});
}, []);
const getRowActions = useCallback((row) => {
return row_actions.map((action) => {
return {
...action,
onClick: (action, e) => {
handleClick(panelId, {
operator: action.on_click,
params: { path, event: action.name, row },
});
},
};
});
}, [row_actions, handleClick, panelId, path]);

Comment on lines 42 to 45
onClick: (action, e) => {
handleClick(panelId, {
operator: action.on_click,
params: { path, event: action.name, row },
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid variable shadowing in the onClick handler

In the onClick function, the parameter action shadows the variable action from the outer scope, which can cause confusion or errors. Rename the parameter to avoid shadowing.

Apply this diff to fix the issue:

-        onClick: (action, e) => {
+        onClick: (event, e) => {
           handleClick(panelId, {
-            operator: action.on_click,
-            params: { path, event: action.name, row },
+            operator: action.on_click,
+            params: { path, event: action.name, row },
           });
         },

Committable suggestion was skipped due to low confidence.

Comment on lines +1663 to +1669
def add_row_action(self, name, on_click, label=None, icon=None, **kwargs):
row_action = Action(
name=name, on_click=on_click, label=label, icon=icon, **kwargs
)
self.row_actions.append(row_action)
return row_action

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure consistency between Action initialization and usage

In the add_row_action method of the TableView class, you are creating an Action instance with named parameters name, on_click, label, and icon. However, the Action class's __init__ method does not currently accept these parameters explicitly, which will lead to a TypeError.

After updating the Action.__init__ method to accept and store these parameters as suggested earlier, this inconsistency will be resolved.

Comment on lines +1638 to +1640
def to_json(self):
return {**super().to_json()}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Action.to_json() omits action-specific attributes

The to_json() method of the Action class currently only returns the JSON from the superclass, which excludes the Action class's own attributes. As a result, the serialized output will not include the action's name, label, icon, or on_click, which are crucial for rendering and executing the action.

Update the to_json() method to include the action-specific properties:

     def to_json(self):
-        return {**super().to_json()}
+        return {
+            **super().to_json(),
+            "name": self.name,
+            "label": self.label,
+            "icon": self.icon,
+            "on_click": self.on_click,
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def to_json(self):
return {**super().to_json()}
def to_json(self):
return {
**super().to_json(),
"name": self.name,
"label": self.label,
"icon": self.icon,
"on_click": self.on_click,
}

Comment on lines +1631 to +1633
def __init__(self, **kwargs):
super().__init__(**kwargs)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Action.__init__ does not store action properties

The __init__ method of the Action class accepts keyword arguments but does not store the action-specific properties (name, label, icon, on_click) as instance variables. This means that instances of Action will not retain these properties, which are essential for the action's functionality.

To fix this, modify the __init__ method to accept these parameters explicitly and assign them to instance variables:

-    def __init__(self, **kwargs):
+    def __init__(self, name, on_click, label=None, icon=None, **kwargs):
         super().__init__(**kwargs)
+        self.name = name
+        self.on_click = on_click
+        self.label = label
+        self.icon = icon
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __init__(self, name, on_click, label=None, icon=None, **kwargs):
super().__init__(**kwargs)
self.name = name
self.on_click = on_click
self.label = label
self.icon = icon

Copy link
Contributor

@lanzhenw lanzhenw left a comment

Choose a reason for hiding this comment

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

Can we add virtualization to the table (https://mui.com/material-ui/react-table/#virtualized-table)?

@imanjra imanjra force-pushed the feat/tableviewv2 branch 2 times, most recently from 3867d81 to efac7b6 Compare October 17, 2024 19:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (3)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (3)

15-31: LGTM: ActionsMenu component is well-structured.

The component logic is clear and follows React best practices. The use of a default value for maxInline and conditional rendering based on the number of actions provides good flexibility.

Consider adding prop types validation using PropTypes or TypeScript interfaces for better type safety and documentation. For example:

interface ActionsMenuProps {
  actions: ActionPropsType[];
  maxInline?: number;
}

export default function ActionsMenu({ actions, maxInline = DEFAULT_MAX_INLINE }: ActionsMenuProps) {
  // ... existing code ...
}

33-70: LGTM: ActionsOverflowMenu component is well-implemented.

The component makes good use of React hooks for state management and performance optimization. The handling of menu opening/closing and action mapping is efficient.

Consider extracting the IconButton into a separate component for better reusability and testability. For example:

const OverflowMenuButton = React.forwardRef<HTMLButtonElement, { onClick: () => void }>(
  ({ onClick }, ref) => (
    <IconButton onClick={onClick} ref={ref}>
      <MoreVert />
    </IconButton>
  )
);

// Then in ActionsOverflowMenu:
<OverflowMenuButton
  onClick={() => setOpen(!open)}
  ref={anchorRef}
/>

72-94: LGTM: Action component is well-designed and flexible.

The component efficiently handles both inline and menu modes, and the use of useCallback for the click handler is good for performance optimization.

Consider using TypeScript's discriminated unions to improve type safety for the mode prop:

type BaseActionProps = {
  name: string;
  label: string;
  onClick: (action: ActionPropsType, e: React.MouseEvent) => void;
  icon?: string;
};

type InlineActionProps = BaseActionProps & {
  mode: "inline";
  variant: string;
};

type MenuActionProps = BaseActionProps & {
  mode: "menu";
};

type ActionPropsType = InlineActionProps | MenuActionProps;

function Action(props: ActionPropsType) {
  // ... existing code ...
}

This approach ensures that variant is only required when mode is "inline".

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 27057a5 and efac7b6.

📒 Files selected for processing (3)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1 hunks)
  • app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (5 hunks)
  • fiftyone/operators/types.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

🔇 Additional comments (5)
app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1)

1-13: LGTM: Imports are well-organized and appropriate.

The imports are correctly structured, using named imports where possible, which is good for tree-shaking. The use of Material-UI components and icons is consistent with the project's UI framework.

app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

112-144: Clarify the event handling logic in the onClick handler

When a cell is clicked, the onClick handler triggers multiple events: on_click_cell, on_click_row, and on_click_column if they are defined. This could result in multiple handlers being called simultaneously for a single click, which might not be the intended behavior. Consider reviewing the logic to ensure that only the intended event(s) are triggered based on the specific user interaction to prevent unexpected behavior.

fiftyone/operators/types.py (3)

1631-1633: Ensure Action class stores action properties

Currently, the Action class __init__ method accepts keyword arguments but does not explicitly store the action-specific properties (name, label, icon, on_click) as instance variables. This means instances of Action will not retain these properties, which are essential for the action's functionality.


1638-1640: Action.to_json() omits action-specific attributes

The to_json() method of the Action class currently only returns the JSON from the superclass, which excludes the Action class's own attributes. As a result, the serialized output will not include the action's name, label, icon, or on_click, which are crucial for rendering and executing the action.


1663-1669: Inconsistency between Action initialization and usage

In the add_row_action method of the TableView class, you are creating an Action instance with named parameters name, on_click, label, and icon. However, the Action class's __init__ method does not currently accept these parameters explicitly, which will lead to a TypeError.

Comment on lines +96 to +108
type ActionsPropsType = {
actions: Array<ActionPropsType>;
maxInline?: number;
};

type ActionPropsType = {
name: string;
label: string;
onClick: (action: ActionPropsType, e: React.MouseEvent) => void;
icon: string;
variant: string;
mode: "inline" | "menu";
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type definitions for better type safety and flexibility.

The current type definitions are good, but can be enhanced:

  1. For ActionsPropsType, consider using a more specific type for maxInline:
type ActionsPropsType = {
  actions: Array<ActionPropsType>;
  maxInline?: 1 | 2 | 3 | 4 | 5; // or whatever reasonable maximum you want to allow
};
  1. For ActionPropsType, make some properties optional and use a union type for mode:
type BaseActionProps = {
  name: string;
  label: string;
  onClick: (action: ActionPropsType, e: React.MouseEvent) => void;
  icon?: string;
};

type InlineActionProps = BaseActionProps & {
  mode: "inline";
  variant: string;
};

type MenuActionProps = BaseActionProps & {
  mode: "menu";
};

type ActionPropsType = InlineActionProps | MenuActionProps;

These changes would improve type safety and make the types more accurately reflect how they're used in the components.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (3)

1-3: LGTM! Consider grouping related imports.

The new imports and component signature changes improve the overall structure and readability of the component. However, consider grouping related imports together for better organization.

You could group the imports like this:

import { scrollable } from "@fiftyone/components";
import { usePanelEvent } from "@fiftyone/operators";
import { usePanelId } from "@fiftyone/spaces";
import { ViewPropsType } from "../utils/types";

import { Box, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import { isPlainObject } from "lodash";
import React, { useCallback } from "react";

import { HeaderView } from ".";
import { getComponentProps } from "../utils";
import ActionsMenu from "./ActionsMenu";
import EmptyState from "./EmptyState";

Also applies to: 14-15, 18-19, 22-32


33-56: LGTM! Consider memoizing getRowActions dependencies.

The new data processing logic and row actions handling improve the component's functionality. The use of getTableData for data processing and the callback for getRowActions are good practices.

Consider memoizing the dependencies of getRowActions to optimize performance:

const getRowActions = useCallback((row) => {
  // ... existing code ...
}, [row_actions, handleClick, panelId, path]);

This ensures that the function is only recreated when its dependencies change.


Line range hint 82-163: LGTM! Consider extracting table header and body into separate components.

The updated table rendering logic successfully implements new features like highlighting selected cells and rendering row actions. The use of getComponentProps for styling is consistent and maintainable.

To improve readability and maintainability, consider extracting the table header and body into separate components:

const TableHeader = ({ columns, hasRowActions, handleCellClick, actionsLabel, ...props }) => (
  <TableHead {...getComponentProps(props, "tableHead")}>
    {/* ... header rendering logic ... */}
  </TableHead>
);

const TableBodyContent = ({ rows, columns, hasRowActions, handleCellClick, getRowActions, ...props }) => (
  <TableBody {...getComponentProps(props, "tableBody")}>
    {/* ... body rendering logic ... */}
  </TableBody>
);

// Use these components in the main render function
<Table sx={{ minWidth: 650 }} {...getComponentProps(props, "table")}>
  <TableHeader {...headerProps} />
  <TableBodyContent {...bodyProps} />
</Table>

This separation would make the main component more concise and easier to understand.

fiftyone/operators/types.py (1)

1663-1669: Minor inconsistency in add_row_action method

The add_row_action method creates an Action instance with named parameters, but the Action class's __init__ method doesn't currently accept these parameters explicitly. To ensure consistency, you should update the Action class's __init__ method as suggested in the previous comment.

Once the Action class is updated, this implementation will work correctly. If you prefer to keep the current Action class implementation, you should modify this method to use **kwargs instead:

     def add_row_action(self, name, on_click, label=None, icon=None, **kwargs):
         row_action = Action(
-            name=name, on_click=on_click, label=label, icon=icon, **kwargs
+            **kwargs
         )
         self.row_actions.append(row_action)
         return row_action

However, updating the Action class as suggested earlier is the preferred solution to maintain consistency and type safety.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between efac7b6 and 033976c.

📒 Files selected for processing (3)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx (1 hunks)
  • app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (4 hunks)
  • fiftyone/operators/types.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/packages/core/src/plugins/SchemaIO/components/ActionsMenu.tsx
🧰 Additional context used
📓 Path-based instructions (1)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (1)

Pattern **/*.{ts,tsx}: Review the Typescript and React code for conformity with best practices in React, Recoil, Graphql, and Typescript. Highlight any deviations.

🔇 Additional comments (4)
app/packages/core/src/plugins/SchemaIO/components/TableView.tsx (3)

58-80: LGTM! Well-implemented cell click handling.

The handleCellClick function is well-implemented, handling clicks for cells, rows, and columns efficiently. The use of usePanelEvent and the correct implementation of the useCallback hook's dependencies array are commendable.


185-208: LGTM! Well-implemented helper functions for advanced data format.

The isAdvancedData and parseAdvancedData functions are well-implemented and effectively handle the new advanced data format. The parsing logic in parseAdvancedData correctly processes the data and selection states, improving the overall flexibility of the component.


210-217: LGTM! Well-implemented cell value formatting.

The formatCellValue function is well-implemented, handling the rounding of numeric values based on the schema. The use of Number.isNaN instead of isNaN is a good practice, avoiding potential type coercion issues. The function correctly falls back to the original value if rounding is not applicable, ensuring robust behavior.

fiftyone/operators/types.py (1)

Line range hint 1621-1680: Summary of changes and recommendations

The changes introduce a new Action class and update the TableView class to support row actions. While the concept is sound, there are some implementation issues that need to be addressed:

  1. The Action class needs to be updated to properly handle its attributes (name, label, icon, on_click) in the __init__, clone, and to_json methods.
  2. Once the Action class is fixed, the add_row_action method in TableView will work correctly.

After addressing these issues, the new row actions feature for TableView will be fully functional and consistent with the rest of the codebase. These changes will enhance the interactivity of tables in the FiftyOne application.

Comment on lines +166 to +183
function getTableData(props) {
const { schema, data } = props;
const defaultValue = schema?.default;

if (isAdvancedData(data)) {
return parseAdvancedData(data);
}
if (isAdvancedData(defaultValue)) {
return parseAdvancedData(defaultValue);
}
return {
rows: Array.isArray(data)
? data
: Array.isArray(defaultValue)
? defaultValue
: [],
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM! Initialize selection sets for non-advanced data format.

The getTableData function effectively handles different data formats, including the new advanced data format. The use of helper functions isAdvancedData and parseAdvancedData improves code readability.

However, when the data is not in advanced format, selectedCells, selectedRows, and selectedColumns are not initialized, which could lead to errors. Apply this diff to fix the issue:

 return {
   rows: Array.isArray(data)
     ? data
     : Array.isArray(defaultValue)
     ? defaultValue
     : [],
+  selectedCells: new Set(),
+  selectedRows: new Set(),
+  selectedColumns: new Set(),
 };

This ensures that these sets are always initialized, preventing potential runtime errors.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getTableData(props) {
const { schema, data } = props;
const defaultValue = schema?.default;
if (isAdvancedData(data)) {
return parseAdvancedData(data);
}
if (isAdvancedData(defaultValue)) {
return parseAdvancedData(defaultValue);
}
return {
rows: Array.isArray(data)
? data
: Array.isArray(defaultValue)
? defaultValue
: [],
};
}
function getTableData(props) {
const { schema, data } = props;
const defaultValue = schema?.default;
if (isAdvancedData(data)) {
return parseAdvancedData(data);
}
if (isAdvancedData(defaultValue)) {
return parseAdvancedData(defaultValue);
}
return {
rows: Array.isArray(data)
? data
: Array.isArray(defaultValue)
? defaultValue
: [],
selectedCells: new Set(),
selectedRows: new Set(),
selectedColumns: new Set(),
};
}

Comment on lines +1621 to +1640
class Action(View):
"""An action (currently supported only in a :class:`TableView`).

Args:
name: the name of the action
label (None): the label of the action
icon (None): the icon of the action
on_click: the operator to execute when the action is clicked
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)

def clone(self):
clone = Action(**self._kwargs)
return clone

def to_json(self):
return {**super().to_json()}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

The Action class implementation is incomplete and inconsistent with its docstring.

The current implementation of the Action class has several issues:

  1. The __init__ method doesn't handle the parameters mentioned in the docstring (name, label, icon, on_click).
  2. The to_json method doesn't include any action-specific attributes.
  3. The clone method creates a new instance but doesn't ensure all attributes are copied.

To address these issues, consider the following changes:

 class Action(View):
     """An action (currently supported only in a :class:`TableView`).
 
     Args:
         name: the name of the action
         label (None): the label of the action
         icon (None): the icon of the action
         on_click: the operator to execute when the action is clicked
     """
 
-    def __init__(self, **kwargs):
+    def __init__(self, name, on_click, label=None, icon=None, **kwargs):
         super().__init__(**kwargs)
+        self.name = name
+        self.on_click = on_click
+        self.label = label
+        self.icon = icon
 
     def clone(self):
-        clone = Action(**self._kwargs)
+        clone = Action(
+            name=self.name,
+            on_click=self.on_click,
+            label=self.label,
+            icon=self.icon,
+            **self._kwargs
+        )
         return clone
 
     def to_json(self):
-        return {**super().to_json()}
+        return {
+            **super().to_json(),
+            "name": self.name,
+            "on_click": self.on_click,
+            "label": self.label,
+            "icon": self.icon,
+        }

These changes ensure that the Action class properly initializes its attributes, clones itself correctly, and includes all relevant information in its JSON representation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Action(View):
"""An action (currently supported only in a :class:`TableView`).
Args:
name: the name of the action
label (None): the label of the action
icon (None): the icon of the action
on_click: the operator to execute when the action is clicked
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
def clone(self):
clone = Action(**self._kwargs)
return clone
def to_json(self):
return {**super().to_json()}
class Action(View):
"""An action (currently supported only in a :class:`TableView`).
Args:
name: the name of the action
label (None): the label of the action
icon (None): the icon of the action
on_click: the operator to execute when the action is clicked
"""
def __init__(self, name, on_click, label=None, icon=None, **kwargs):
super().__init__(**kwargs)
self.name = name
self.on_click = on_click
self.label = label
self.icon = icon
def clone(self):
clone = Action(
name=self.name,
on_click=self.on_click,
label=self.label,
icon=self.icon,
**self._kwargs
)
return clone
def to_json(self):
return {
**super().to_json(),
"name": self.name,
"on_click": self.on_click,
"label": self.label,
"icon": self.icon,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants