Skip to content

Conversation

@juanjofgliferay
Copy link
Collaborator

Followup #5175

Story / Task

LPD-9465 / LPD-59387

Warning

In the FDS Sample Custom Views, Config in URL and Active View Settings may clash and return unexpected results.
If you want to test Custom/User Views in the FDS Sample it is better to start with a fresh DB or remove the corresponding ActiveViewSettingsJSON rows from the PortalPreferenceValue table.

Tip

To test this feature we need to update a given Data Set and set customViewsEnabled to true. This can be done throug API Explorer using the Data Sets API /by-external-reference-code/{externalReferenceCode} PATCH with {"snapshotsEnabled": true} body.
To ease testing, it is possible to enable custom views via UI with LPD-10683 feature flag turned on.

Goal

Allows end-users to create, edit and delete customized versions of admin views.
Aspects that can be saved as part of the custom view:

  • Visualization Mode
  • To be done in next Story:
  • Pagination delta
  • Filters applied
  • Current sorting
  • Visualization Mode
  • Columns shown

UI

Screencast.From.2025-11-20.13-01-15.mp4

Tests

FDS web

fds-web-test

FDS admin web

fds-admin-web-test

@liferay-continuous-integration
Copy link
Collaborator

CI is automatically triggering the following test suites:

  •     ci:test:sf

@juanjofgliferay juanjofgliferay requested review from dsanz and removed request for liferay-frontend December 5, 2025 12:43
@liferay-continuous-integration
Copy link
Collaborator

Test suite sf has been triggered on http://test-1-37

@gemini-code-assist
Copy link

Summary of Changes

Hello @juanjofgliferay, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience by introducing 'Snapshots' for data sets, enabling users to save and manage their personalized view configurations. This includes persisting various display and interaction settings such as visualization modes, pagination, filters, sorting, and column visibility. The implementation involved a comprehensive overhaul of both backend and frontend components, migrating from a preference-based storage system to a more robust Object Entry API for custom views, and integrating a new snapshotsEnabled property. Frontend components were updated to reflect these changes, providing a seamless UI for snapshot management and ensuring data integrity through immutable state updates.

Highlights

  • Custom Views (Snapshots) Feature: Introduced the ability for end-users to create, edit, and delete customized versions of data set views, now referred to as 'Snapshots'. This allows users to save their preferred configurations, including visualization mode, pagination delta, applied filters, current sorting, and visible columns.
  • Backend Implementation: Added a new DataSetSnapshot object definition to store custom view configurations and a snapshotsEnabled boolean property to data sets. The backend now uses the Object Entry API for managing these snapshots, deprecating older PortalPreferences-based custom view logic.
  • Frontend UI Updates: Implemented the user interface for managing snapshots, including dropdowns and actions for saving, renaming, and deleting views. The customViewsEnabled property and related frontend logic have been replaced with snapshotsEnabled.
  • Code Refactoring and Utility: Refactored frontend state management to use immutable updates with a new deepClone utility function, ensuring that state changes are handled cleanly and predictably across the application.
  • Feature Flag Integration: The new 'User Views' functionality is controlled by the LPD-10683 feature flag, allowing for staged rollout and testing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-executed refactoring of the "Custom Views" feature, now termed "Snapshots". The core change is migrating the storage mechanism from Portal Preferences to a more robust Liferay Object-based solution, which is a great architectural improvement. The changes are comprehensive, spanning the backend, frontend, and tests. Key highlights include the new DataSetSnapshot object, new Vulcan REST endpoints, and a refactored frontend with improved state management using a new deepClone utility for immutability. The PR also includes valuable bug fixes for client extension loading and filter state management, and is accompanied by thorough new Playwright tests. The overall quality of the implementation is high. I have one minor suggestion to improve the serialization of snapshot data.

).put(
"erc", objectEntry.getExternalReferenceCode()
).put(
"label", String.valueOf(properties.get("label"))

Choose a reason for hiding this comment

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

medium

Using String.valueOf() on a potentially null object will result in the string literal "null". This is likely not what the frontend expects. It's better to pass the raw object from properties.get("label") to JSONUtil.put(), which will correctly serialize a null value to JSON null.

Suggested change
"label", String.valueOf(properties.get("label"))
"label", properties.get("label")

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

🤔 Not sure. I'm using the same pattern used in other FDS places

@liferay-continuous-integration
Copy link
Collaborator

✔️ ci:test:sf - 1 out of 1 jobs passed in 5 minutes

Ran com.liferay.source.formatter at released version 1.0.1554.
*The latest version has not been released.

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 098e3f6b6ac8fe2118aea5b168764437f132a628

Sender Branch:

Branch Name: LPD-9465.v2
Branch GIT ID: 753715bd36bd337ed05bb76e1e7ed68a3554e4dc

1 out of 1 jobs PASSED
1 Successful Jobs:
For more details click here.

@liferay-continuous-integration
Copy link
Collaborator

@juanjofgliferay
Copy link
Collaborator Author

ci:test:frontend-data-set

@liferay-continuous-integration
Copy link
Collaborator

Test suite frontend-data-set has been triggered on http://test-1-40

Copy link
Collaborator

@dsanz dsanz left a comment

Choose a reason for hiding this comment

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

LGTM ;) added some inline comments for our future selves though

const tableView = views.find((view) => view.name === 'customizedTable')!;
const tableView = views.find((view) =>
view.name?.toLowerCase().includes('table')
)!;
Copy link
Collaborator

Choose a reason for hiding this comment

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

finally!

<type>com.liferay.frontend.taglib.clay.servlet.taglib.util.CreationMenu</type>
</attribute>
<attribute>
<description>Deprecated as of 7.4.0, replaced by the attribute <![CDATA[<code>snapshotsEnabled</code>]]>.</description>
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need to mark this as a breaking change, because old "custom views" feature has not been announced to the public

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok. I'll change

settings: any;
}) {
if (!appURL) {
if (Liferay.FeatureFlags['LPD-10683'] || !appURL) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

did we end up blocking active view settings reading too?

[activeCustomViewId]: customView,
},
snapshots: [...updatedSnapshots],
viewUpdated: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor nit for a followup: this shall be called snapshotUpdated in line with the new naming,


const fieldAttributes = modifiedFields[name] ?? {};

if (!defaultSnapshot.modifiedFields[name]) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we always count on indexing in defaultSnapshot.modifiedFields ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think so.

let initialVisibleFieldNames = {};

if (activeViewSettings) {
if (!Liferay.FeatureFlags['LPD-10683'] && activeViewSettings) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I answer myself: yes we did!

);

useEffect(() => {
const filterClientExtensionDefinitions = filters
Copy link
Collaborator

Choose a reason for hiding this comment

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

using filters instead of initialFilters makes a huge difference as now we are:

  • chaining the changes one by one, rather than changing initialFilters (or even worse, mutating its contents) in 2 differenc places.
  • each change returns a brand-new, deep copy of the filters array

(!filterClientExtensionDefinitions.length &&
!cellClientExtensionDefinitions.length) ||
cellClientExtensionsLoaded ||
filterClientExtensionsLoaded
Copy link
Collaborator

Choose a reason for hiding this comment

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

cellClientExtensionsLoaded || filterClientExtensionsLoaded is a key piece here too, because we don't want to call loadClientExtensions twice.

This can happen now, because initialFilters is no longer an effect dependency but filters is.

}, [
cellClientExtensionsLoaded,
filterClientExtensionsLoaded,
filters,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here we are executing the efect everytime we change filters, hence the enhanced gate conditions above to ensure loadClientExtensions call (and the change it entails on the array of filters) just happens once

onOpenChange={setInfoPanelOpen}
open={infoPanelOpen}
{filterClientExtensionsLoading ||
cellClientExtensionsLoading ? (
Copy link
Collaborator

Choose a reason for hiding this comment

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

in the end, do we need to gate managementBar component render to this condition as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No need. It works fine like this.

@liferay-continuous-integration
Copy link
Collaborator

❌ ci:test:frontend-data-set - 4 out of 6 jobs passed in 1 hour 17 minutes

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 098e3f6b6ac8fe2118aea5b168764437f132a628

Upstream Comparison:

Branch GIT ID: 0c4b7b90f18c43cbbc2dad3967b7f4953e1d5f21
Jenkins Build URL: EE Development Acceptance (master) - 288 - 2025-11-25[20:40:06]

ci:test:frontend-data-set - 4 out of 6 jobs PASSED
4 Successful Jobs:
    For more details click here.

    Failures unique to this pull:

    For upstream results, click here.

    Test bundle downloads:

    @liferay-continuous-integration
    Copy link
    Collaborator

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:test:sf

    @liferay-continuous-integration
    Copy link
    Collaborator

    Test suite sf has been triggered on http://test-1-40

    @liferay-continuous-integration
    Copy link
    Collaborator

    ✔️ ci:test:sf - 1 out of 1 jobs passed in 5 minutes

    Ran com.liferay.source.formatter at released version 1.0.1554.
    *The latest version has not been released.

    Click here for more details.

    Base Branch:

    Branch Name: master
    Branch GIT ID: 87375166c280a738a4229cb80eeb32eb1a7b8c67

    Sender Branch:

    Branch Name: LPD-9465.v2
    Branch GIT ID: 524dfc1baf885ab52e4a65385f4101313f685d0e

    1 out of 1 jobs PASSED
    1 Successful Jobs:
    For more details click here.

    @liferay-continuous-integration
    Copy link
    Collaborator

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:test:sf

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:forward

    @liferay-continuous-integration
    Copy link
    Collaborator

    Test suite relevant has been triggered on http://test-1-40

    @liferay-continuous-integration
    Copy link
    Collaborator

    CI is automatically triggering the following test suites:

    •     ci:test:relevant
    •     ci:test:sf
    •     ci:test:stable

    The pull request will automatically be forwarded to the user brianchandotcom If the following test suites pass:

    •     ci:test:relevant
    •     ci:test:sf
    •     ci:test:stable

    @liferay-continuous-integration
    Copy link
    Collaborator

    Skipping previously passed test suites:

    • ci:test:sf
    • ci:test:stable

    @liferay-continuous-integration
    Copy link
    Collaborator

    Build started.

    Jenkins is currently running tests in the relevant test suite.

    Base Branch:

    Branch Name: master

    Job Summary:

    Job Link: test-portal-acceptance-pullrequest(master)

    For more details click here.

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:stop

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:forward:force

    @juanjofgliferay
    Copy link
    Collaborator Author

    juanjofgliferay commented Dec 10, 2025

    ci:test:sf

    2 similar comments
    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:test:sf

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:test:sf

    @liferay-continuous-integration
    Copy link
    Collaborator

    Test suite sf has been triggered on http://test-1-31

    @liferay-continuous-integration
    Copy link
    Collaborator

    ✔️ ci:test:sf - 1 out of 1 jobs passed in 6 minutes

    Ran com.liferay.source.formatter at released version 1.0.1554.
    *The latest version has not been released.

    Click here for more details.

    Base Branch:

    Branch Name: master
    Branch GIT ID: 2c503c146b169fd5eb738bced2ed629e7e99e08c

    Sender Branch:

    Branch Name: LPD-9465.v2
    Branch GIT ID: 2275e650b5c5218463bd002855d44c138b8d6199

    1 out of 1 jobs PASSED
    1 Successful Jobs:
    For more details click here.

    @liferay-continuous-integration
    Copy link
    Collaborator

    @juanjofgliferay
    Copy link
    Collaborator Author

    ci:forward:force

    @liferay-continuous-integration
    Copy link
    Collaborator

    Test suite relevant has been triggered on http://test-1-31

    @liferay-continuous-integration
    Copy link
    Collaborator

    CI is automatically triggering the following test suites:

    •     ci:test:relevant
    •     ci:test:sf
    •     ci:test:stable

    The pull request will automatically be forwarded to the user brianchandotcom if the following test suites complete:

    •     ci:test:relevant
    •     ci:test:sf
      AND If the following test suites pass:
    •     ci:test:stable

    @liferay-continuous-integration
    Copy link
    Collaborator

    Skipping previously completed test suites:

    • ci:test:sf

    @liferay-continuous-integration
    Copy link
    Collaborator

    Build started.

    Jenkins is currently running tests in the relevant test suite.

    Base Branch:

    Branch Name: master

    Job Summary:

    Job Link: test-portal-acceptance-pullrequest(master)

    For more details click here.

    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.

    3 participants