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

refactor(ui): WorkflowsToolbar component from class to functional #12046

Merged
merged 3 commits into from
Nov 2, 2023

Conversation

juijeong8324
Copy link
Contributor

Partial Fixes for #9810

Motivation

Use newer, modern functional components compatible with hooks instead of legacy class-based components

Modifications

  1. class fo functional
  • this.props -> arguments
  • methods -> inner functions

  1. Delete loadWorkflows() from WorkflowsToolBar props
  • Beacause ListWatch() update workflows when workflows was performed action

  1. getNumberSelected() -> WorkflowsToolbar's local variable numberSelected
  • This function get the number of selected workflows from calling props. However it gets called multiple times each rendering.
  • So, when they are updated, we can get the number of selected workflows from numberSelected once without multiple function call.

  1. using popup and notifications with useContext

  1. delete renderActions
  • move rendering Action buttons element toWorkflowsToolBars rendering
  • local variable
    • actionButtons -> delete
    • actions -> local variable in useMemo
    • disabled -> local variable in useMemo
    • groupActions -> variable groupAction in WorkflowsToolbar
      • using useMemo with dependency props.selectedWorkflows

  1. modify name in WorkflowsGroupAction and variable
  • interface WorkflowsGroupAction -> WorkflowsOperation
    • Beacuse I think it is ambiguous what 'group' means 'workflows' or 'Action'
    • WorkflowsOperation is similar with WorkflowOperation
  • groupIsDisabled -> isDisabled
    • I think 'disabled' is more related with "operation" not "group",
    • Also, isDiabled -> actionIsDiabled in WorkflowsToolBarProps
  • className -> delete
    • It is same action.title
  • groupAction -> workflowsAction

  1. modify function performActionOnSelectedWorfklows
  • async/await -> delete
    • await keyword use in popup that check action
    • popup move to workflowsAction
  • This function is called only when action are performed in the selected workflow.

Verification

Pure refactor, no real semantic changes
Tested in Local environment

  • Screenshot below, Action of delete workflows
    image
    image

  • Screenshot below, Action of resubmit workflows
    image
    image

  • Screenshot below, action button when workflows running
    image

Signed-off-by: juijeong8324 <juijeong8324@gmail.com>
Signed-off-by: juijeong8324 <juijeong8324@gmail.com>
Copy link
Contributor

@agilgur5 agilgur5 left a comment

Choose a reason for hiding this comment

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

Thanks for diving into this and your detailed explanations!

The renames and re-ordering took a bit for me to wrap my head around, so I waited a bit to finish the review until I was fully recovered 😅 . It definitely made more sense when I was feeling better! And the namings were definitely confusing before your changes too

I have a few suggestions in-line below:

Comment on lines 120 to 122
onClick={() => {
action.workflowsAction().catch();
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
onClick={() => {
action.workflowsAction().catch();
}}
onClick={action.workflowsAction}

this can be simplified and optimized a tiny bit. the previous .catch() is redundant since it doesn't do anything

}
export function WorkflowsToolbar(props: WorkflowsToolbarProps) {
const {popup, notifications} = useContext(Context);
const numberSelected: number = props.selectedWorkflows.size;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const numberSelected: number = props.selectedWorkflows.size;
const numberSelected = props.selectedWorkflows.size;

I believe this should be inferred properly, no?

// check for delete from archived workflows
let deleteArchived = false;
if (action.title === 'DELETE') {
// check for delete workflows from archived workflows
Copy link
Contributor

@agilgur5 agilgur5 Oct 27, 2023

Choose a reason for hiding this comment

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

Suggested change
// check for delete workflows from archived workflows
// check if there are archived workflows to delete

be a bit more specific and less redundant

if (!confirmed) {
return Promise.resolve(false);
}
// check for delete from archived workflows
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// check for delete from archived workflows

this is duplicative of the below comment

isDisabled: disabled[actionName],
action,
workflowsAction: async () => {
// check for action
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// check for action

this seems redundant to me since the word "confirm" is already used multiple times in the code and in the message

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it seems redundant and unnecessary, too!

content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
this.props.loadWorkflows();
Copy link
Contributor

Choose a reason for hiding this comment

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

so I actually don't think we should remove loadWorkflows at this point if we plan to replace it with an optimistic update per #12008. this specific call would be replaced with another call that takes the action as an argument

it would make the future change to an optimistic update more straightforward if we left this in

@@ -10,66 +11,71 @@ require('./workflows-toolbar.scss');

interface WorkflowsToolbarProps {
selectedWorkflows: Map<string, Workflow>;
loadWorkflows: () => void;
isDisabled: Actions.OperationDisabled;
actionsIsDisabled: Actions.OperationDisabled;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
actionsIsDisabled: Actions.OperationDisabled;
disabledActions: Actions.OperationDisabled;

that probably makes a bit more sense since it's a mapping

return {
title: action.title,
iconClassName: action.iconClassName,
isDisabled: disabled[actionName],
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
isDisabled: disabled[actionName],
isDisabled: props.disabledActions[actionName],

we can just in-line this as it's only used here. no renames etc needed then

groupAction: () => Promise<any>;
interface WorkflowsOperation extends WorkflowOperation {
isDisabled: boolean;
workflowsAction: () => Promise<any>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
workflowsAction: () => Promise<any>;
action: () => Promise<any>;

I think we can just overwrite action, since it's not used otherwise. the interface is already plural Workflows, so I don't think we need to repeat it again inside of it

Copy link
Contributor Author

@juijeong8324 juijeong8324 Oct 31, 2023

Choose a reason for hiding this comment

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

Umm Then, Should we rename the name ofaction in below the code? Like this!

  • Before
        <div className={`workflows-toolbar ${numberSelected === 0 ? 'hidden' : ''}`}>
            <div className='workflows-toolbar__count'>
                {numberSelected === 0 ? 'No' : numberSelected}
                &nbsp;workflow{numberSelected === 1 ? '' : 's'} selected
            </div>
            <div className='workflows-toolbar__actions'>
                {operations.map(action => {
                    return (
                        <button
                            key={action.title}
                            onClick={() => {
                                action.workflowsAction();
                            }}
                            className={`workflows-toolbar__actions--${action.title} workflows-toolbar__actions--action`}
                            disabled={numberSelected === 0 || action.isDisabled}>
                            <i className={action.iconClassName} />
                            &nbsp;{action.title}
                        </button>
                    );
                })}
            </div>
        </div>
  • After
<div className={`workflows-toolbar ${numberSelected === 0 ? 'hidden' : ''}`}>
            <div className='workflows-toolbar__count'>
                {numberSelected === 0 ? 'No' : numberSelected}
                &nbsp;workflow{numberSelected === 1 ? '' : 's'} selected
            </div>
            <div className='workflows-toolbar__actions'>
                {operations.map(operation => {
                    return (
                        <button
                            key={operation.title}
                            onClick={() => {
                                operation.action();
                            }}
                            className={`workflows-toolbar__actions--${operation.title} workflows-toolbar__actions--action`}
                            disabled={numberSelected === 0 || operation.isDisabled}>
                            <i className={operation.iconClassName} />
                            &nbsp;{operation.title}
                        </button>
                    );
                })}
            </div>
        </div>

Copy link
Contributor

@agilgur5 agilgur5 Oct 31, 2023

Choose a reason for hiding this comment

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

Yup, that would be required

Note that with the onClick optimization in my above comment, those lines become onClick={operation.action}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that with the onClick optimization in my above comment, those lines become onClick={operation.action}

Oh I missed it, thank you!

</Consumer>
);
}
const groupAction = useMemo<WorkflowsOperation[]>(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const groupAction = useMemo<WorkflowsOperation[]>(() => {
const groupActions = useMemo<WorkflowsOperation[]>(() => {

this should be plural since there are multiple actions in here

Copy link
Contributor

Choose a reason for hiding this comment

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

or maybe better, we could rename it to operations to match the type names

Suggested change
const groupAction = useMemo<WorkflowsOperation[]>(() => {
const operations = useMemo<WorkflowsOperation[]>(() => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's better to change the name to 'operations' to match the type name. Actually, I was confused that why the type was Operation but the name was groupAction.

Signed-off-by: juijeong8324 <juijeong8324@gmail.com>
@juijeong8324
Copy link
Contributor Author

The renames and re-ordering took a bit for me to wrap my head around, so I waited a bit to finish the review until I was fully recovered 😅 . It definitely made more sense when I was feeling better! And the namings were definitely confusing before your changes too

Thanks for detailed advice!!
The most difficult in refactoring is obviously naming value and parameter... 😂

@agilgur5
Copy link
Contributor

agilgur5 commented Nov 2, 2023

The most difficult in refactoring is obviously naming value and parameter... 😂

"There are only two hard things in Computer Science: cache invalidation and naming things." -- Phil Karlton

naming is indeed a hard thing, no joke 😉

Copy link
Contributor

@agilgur5 agilgur5 left a comment

Choose a reason for hiding this comment

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

LGTM 🚀 Thanks for iterating on this, getting the refactor done, and improving naming while you were at it!

@terrytangyuan terrytangyuan merged commit 4d062d1 into argoproj:master Nov 2, 2023
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants