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

COMMERCE-5449 | COMMERCE-5450 Inline editing and entity creation via dataset display #700

Closed
wants to merge 6 commits into from

Conversation

fabio-d-m
Copy link

Data Set Display should allow users to create new entities or editing existing ones directly from the component itself.

Requirements for editing:

  1. The Editing mode can be activated via the action dropdown menu
  2. The Editing mode can also be configured to be always on every item at the same time
  3. Any column can be configured to switch the data renderer when the edit mode is active
  4. The update request can have a default body object in case the API that handles the editing requires a richer payload. This default object properties will be appended to every update request.
  5. The new data renderers input should handle: plain text, numbers, dates, booleans (via checkbox)

Requirements for adding: 

  1. When the "Add new entity mode" is active, the table view will always start with a row with empty input boxes in it
  2. The empty boxes should match the ones in the editing mode
  3. As per the Editing mode. the post request can have a default body object in case the API that handles the entity creation requires a richer payload.
  4. As per the Editing mode, the dataset should handle: plain text, numbers, dates, booleans (via checkbox)

Example for item creation and editing (alwayOn true):
image

Example for item editing (alwayOn not true):
image
image

@liferay-continuous-integration
Copy link
Collaborator

CI is automatically triggering the following test suites:

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

@liferay-continuous-integration
Copy link
Collaborator

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

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 9abb5412f865531a30c879edc61e94795e419bce

Sender Branch:

Branch Name: COMMERCE-5450
Branch GIT ID: 372488f14239c91bd335677317ebfa2ad6e58577

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

@liferay-continuous-integration
Copy link
Collaborator

@liferay-continuous-integration
Copy link
Collaborator

✔️ ci:test:stable - 9 out of 9 jobs passed

✔️ ci:test:relevant - 23 out of 23 jobs passed in 2 hours 35 minutes

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 9abb5412f865531a30c879edc61e94795e419bce

Copied in Private Modules Branch:

Branch Name: master-private
Branch GIT ID: 15218ef33680a1fe233ea812e8d097cfcb0feb42

ci:test:stable - 9 out of 9 jobs PASSED
9 Successful Jobs:
ci:test:relevant - 23 out of 23 jobs PASSED
23 Successful Jobs:
For more details click here.

@liferay-continuous-integration
Copy link
Collaborator

@jbalsas
Copy link

jbalsas commented Jan 15, 2021

Thanks a lot, @FabioDiegoMastrorilli, looks nice, but feels like it might take a little bit to review! 😂

Going to assign this to @bryceosterhaus for monday, but @markocikos, @julien, @carloslancha, feel free to take a look too, please! 🙏

@julien
Copy link

julien commented Jan 18, 2021

Going to have a look at the code changes.


window.defaultFetch = fetch;

window.fetch = (resource, {headers, ...init}) => {
Copy link

Choose a reason for hiding this comment

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

Couldn't you use fetch from frontend-js-web and pass those Headers?

Copy link
Author

Choose a reason for hiding this comment

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

Julien on this one I preferred keeping it as it is. This file contains some utilities we place a static env to replace standard liferay ones (like Liferay.Language.get or themeDisplay.getLanguageId) and it doesn't get compiled.

Its only purpose is avoiding to write exceptions (that are aimed to make everything working in static env) in code which will be deployed

('0' + dateInstance.getHours()).slice(-2) +
':' +
('0' + dateInstance.getMinutes()).slice(-2);

Copy link

Choose a reason for hiding this comment

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

I'd also use template literals here instead of concatenation

sidePanelId || 'support-side-panel-' + getRandomId()
);
const [delta, setDelta] = useState(
pagination.initialDelta || pagination.deltas[0].label

Choose a reason for hiding this comment

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

What if pagination is not defined or pagination.deltas is empty?

Copy link
Author

Choose a reason for hiding this comment

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

I set it in DataSetDisplay.defaultProps so it's pretty safe I hope

@@ -75,29 +78,30 @@ function DataSetDisplay({
sorting: sortingProp,
style,
}) {
const {apiURL} = useContext(AppContext);

Copy link
Collaborator

Choose a reason for hiding this comment

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

There are a handful of functions declared here in the "render" of this component. For the sake of performance and to avoid re-creating these functions repeatedly, you might want to go through and try to utilize useCallback and useMemo for some of these.

Copy link

Choose a reason for hiding this comment

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

I'd be very careful about adding useCallback or useMemo just to avoid "re-creating functions repeatedly". Note that in order to make use of either, you are instantiating a new function every time through your render anyway; eg:

Copy link

Choose a reason for hiding this comment

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

Whoops, accidentally focused the "Comment" button and hit "Enter" before I finished entering the comment. Updated comment incoming...

Copy link

Choose a reason for hiding this comment

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

I'd be very careful about adding useCallback or useMemo just to avoid "re-creating functions repeatedly". Note that in order to make use of either, you are instantiating a new function every time through your render anyway; eg: (function chosen at random, but it's just for illustration):

function DataSetDisplay() {
    // stuff

    function updateDataSetItems(dataSetData) {
        setTotal(dataSetData.totalCount);
        updateItems(dataSetData.items);
    }

    // etc
}

vs:

function DataSetDisplay() {
    // stuff

    const updateDataSetItems = useCallback((dataSetData) => {
        setTotal(dataSetData.totalCount);
        updateItems(dataSetData.items);
    }, [...]);

    // etc
}

The first version is re-instantiating a function object on every render, but so is the second one (the arrow function). In addition, it is paying the overhead of calling useCallback on every render, and it is allocating a dependencies array on every render. useCallback itself is going to incur the cost of comparing the items in the dependency array (using Object.is()), as well as the memory overhead of any related book-keeping details that React is maintaining internally.

Now, I'm not saying "never use useCallback or useMemo"; I'm just saying, make sure you have a good reason to use them. A non-exhaustive list of good reasons for using them is:

  • Use useMemo to avoid repeatedly recomputing an intermediate value that is expensive to evaluate (note: in order for this to pay off, the value shouldn't be changing on every render and the cost of determining equality should be cheaper than just redoing the calculation — remember that you're paying the cost of the equality comparison plus the overhead of the function call into React internals plus whatever internal book-keeping React might be doing). In many cases, it's cheaper to just re-do the computation every time.
  • Use useCallback when you want to maintain a stable identity for a callback function that you will be passing down to children; this can be important when the callback ends up being a member of a useEffect dependencies array in the child (or grandchild, or great-grandchild etc). If the callback hasn't really changed, you may find that failure to use useCallback causes children to re-run effects unnecessarily because they think something has changed when it really hasn't. This is another context-dependent thing: sometimes you know everything about your children subtree and you can know deterministically that useCallback is required; at other times you are writing "library" code and you have no idea what your children are going to be doing, so you may want to err on the side of being defensive and using useCallback "just in case".

Another angle on this: https://kentcdodds.com/blog/usememo-and-usecallback/

Performance optimizations ALWAYS come with a cost but do NOT always come with a benefit.

Copy link

Choose a reason for hiding this comment

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

Made this issue for us to put something about these hooks in the guidelines: liferay/liferay-frontend-projects#383

@@ -194,11 +249,27 @@ function ActionsDropdownRenderer({actions, itemData, itemId}) {
}, [])
: [];

if (alwaysOn && (!formattedActions || !formattedActions.length)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as comment above about using a descriptive variable

<ClayIcon
className={classNames(
'sorting-icon',
sortingMatch?.direction === 'asc' && 'active'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, it's probably gonna be a bit more readable to use the object syntax for classNames. Note that you can use both an object and a string. So the first argument would be 'sorting-icon' and the second arg would be an object for toggling 'active'

Copy link
Author

Choose a reason for hiding this comment

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

Only on this one and the other one below I would prefer keeping them as they are. Passing both a string and an object to classnames looks a bit less readable (or maybe I'm just not used to it). active also will not be highlighted as a string due to our linter an it looks a bit strange to me:

className={classNames(
	'sorting-icon',
	{
		active: sortingMatch?.direction === 'asc'
	}
)}

Btw if there are some standards we have to follow in this cases, not problems at all :)

Copy link

Choose a reason for hiding this comment

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

I don't think we have any standards written down, and we probably won't either unless we start to see classNames() usage "spiralling into chaos" 😂.

I don't have an opinion on this strong enough to make me want to comment, but seeing as the thread is already open, the way I usually tend to write these things to avoid the feeling of impedance mismatch you get when mixing string and object is to just use object for everything:

<ClayIcon
    className={{
        active: sortingMatch?.direction === 'asc',
        'sorting-icon': true,
    }}
/>

but like all things, it's a trade-off; like you say @FabioDiegoMastrorilli, you lose the string highlighting of the active classname. We should be able to get it back though because I believe a long-standing Prettier bug has been fixed (see discussion) that would format the above as:

<ClayIcon
    className={{
        'active': sortingMatch?.direction === 'asc',
        'sorting-icon': true,
    }}
/>

Copy link
Collaborator

Choose a reason for hiding this comment

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

@FabioDiegoMastrorilli, thats fine, its not vital that you use the object notation, I just wanted to make sure you were aware of it.

@fabio-d-m
Copy link
Author

@wincent @bryceosterhaus @carloslancha @julien the PR has been updated! Thanks a lot for your code review! :D

cc @jbalsas

@wincent
Copy link

wincent commented Jan 25, 2021

ci:forward

@liferay-continuous-integration
Copy link
Collaborator

CI is automatically triggering the following test suites:

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

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

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

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 99d540dc01007db6419f6103f3baa44404f78d5a

Sender Branch:

Branch Name: COMMERCE-5450
Branch GIT ID: f780c644114a509e98d3df4615e274b55a5c1eb2

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

@liferay-continuous-integration
Copy link
Collaborator

@liferay-continuous-integration

This comment has been minimized.

@liferay-continuous-integration
Copy link
Collaborator

✔️ ci:test:stable - 9 out of 9 jobs passed

✔️ ci:test:relevant - 23 out of 23 jobs passed in 2 hours 58 minutes

Click here for more details.

Base Branch:

Branch Name: master
Branch GIT ID: 99d540dc01007db6419f6103f3baa44404f78d5a

Copied in Private Modules Branch:

Branch Name: master-private
Branch GIT ID: 2b89e6be0ae0f6e12dec390930d3e9525006e687

ci:test:stable - 9 out of 9 jobs PASSED
9 Successful Jobs:
ci:test:relevant - 23 out of 23 jobs PASSED
23 Successful Jobs:
For more details click here.

@liferay-continuous-integration
Copy link
Collaborator

All required test suite(s) passed.
Forwarding pullrequest to brianchandotcom.

@liferay-continuous-integration
Copy link
Collaborator

Pull request has been successfully forwarded to brianchandotcom#98054

@liferay-continuous-integration
Copy link
Collaborator

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.

None yet

7 participants