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

upcoming: [M3-7869] - Linode Create Refactor - Part 2 - Images and Distributions #10281

Merged

Conversation

bnussman-akamai
Copy link
Contributor

@bnussman-akamai bnussman-akamai commented Mar 14, 2024

Description πŸ“

Part 2 of the Linode Create refactor effort πŸŽ‰

Changes πŸ”„

  • Introduces Tabs (Distribution, Marketplace , StackScripts, Backups, Clone)
  • Introduces some helpers for managing query params (Linode Create relies somewhat heavily on query params)
  • Completely implements the Distribution and Image Tabs
  • Implements a new ImageSelectv2
    • Yes, this does expand this scope of this project, and yes, it does add a 3rd ImageSelect to the codebase but we'd eventually write this code anyway. The current LinodeSelect literally has a Paper baked into it πŸ™„
  • Adds LandingHeader to match the existing Linode Create
  • Switches use of watch to useWatch to fix other components re-rendering when changing region
  • Configure MUI Autocomplete Group Label theme so that it looks more like our old component

Preview πŸ“·

Putting a before and after so you can how the refactor is progressing 🀩

Before After
Screenshot 2024-03-13 at 8 07 33β€―PM Screenshot 2024-03-13 at 8 07 04β€―PM

How to test πŸ§ͺ

  • Manually test the new functionality
  • Check for unnecessary renders
  • Look and my code and roast it 😳
    • I want this new code to be super clean and maintainable. If I'm doing something questionable, please call me out! πŸ“£

As an Author I have considered πŸ€”

  • πŸ‘€ Doing a self review
  • ❔ Our contribution guidelines
  • 🀏 Splitting feature into small PRs
  • βž• Adding a changeset
  • πŸ§ͺ Providing/Improving test coverage
  • πŸ” Removing all sensitive information from the code and PR description
  • 🚩 Using a feature flag to protect the release
  • πŸ‘£ Providing comprehensive reproduction steps
  • πŸ“‘ Providing or updating our documentation
  • πŸ•› Scheduling a pair reviewing session
  • πŸ“± Providing mobile support
  • β™Ώ Providing accessibility support

const { field, fieldState } = useController<CreateLinodeRequest>({
name: 'type',
});

const { data: regions } = useRegionsQuery();
const { data: types } = useAllTypes();

const regionId = watch('region');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turns out, this is very bad.

It resulted in the entire form re-rendering when the region changed. With this new flow, I'm going to focus on performance and fine-grain rendering.

Changing out this watch call for a useWatch makes it so that only this component subscribes to region changing.

This is what makes react-hook-form great. They build things in a way so that you have to be very intentional with what state updates you subscribe to.

@bnussman-akamai bnussman-akamai changed the title feat: [M3-xxxx] - Linode Create Refactor - Part 2 feat: [M3-7869] - Linode Create Refactor - Part 2 Mar 14, 2024
@bnussman-akamai bnussman-akamai changed the title feat: [M3-7869] - Linode Create Refactor - Part 2 feat: [M3-7869] - Linode Create Refactor - Part 2 - Images and Distributions Mar 14, 2024
Comment on lines +61 to +85
<Tabs
index={currentTabIndex}
onChange={(index) => updateParams({ type: tabs[index] })}
>
<TabList>
<Tab>Distributions</Tab>
<Tab>Marketplace</Tab>
<Tab>StackScripts</Tab>
<Tab>Images</Tab>
<Tab>Backups</Tab>
<Tab>Clone Linode</Tab>
</TabList>
<TabPanels>
<SafeTabPanel index={0}>
<Distributions />
</SafeTabPanel>
<SafeTabPanel index={1}>Marketplace</SafeTabPanel>
<SafeTabPanel index={2}>StackScripts</SafeTabPanel>
<SafeTabPanel index={3}>
<Images />
</SafeTabPanel>
<SafeTabPanel index={4}>Bckups</SafeTabPanel>
<SafeTabPanel index={5}>Clone Linode</SafeTabPanel>
</TabPanels>
</Tabs>
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 debated moving this chunk into another component to keep this root file super clean but I decided keeping it here helps your brain match up the code with the UI.

Curious to hear what others think!

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with it this way, the file remains pretty short and readable.

Copy link
Contributor

Choose a reason for hiding this comment

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

ya, it's fine to keep it here.

@bnussman-akamai bnussman-akamai marked this pull request as ready for review March 14, 2024 00:49
@bnussman-akamai bnussman-akamai requested a review from a team as a code owner March 14, 2024 00:49
@bnussman-akamai bnussman-akamai requested review from jdamore-linode and mjac0bs and removed request for a team March 14, 2024 00:49
Copy link

github-actions bot commented Mar 14, 2024

Coverage Report: βœ…
Base Coverage: 81.45%
Current Coverage: 81.57%

@@ -1,9 +1,8 @@
import CssBaseline from '@mui/material/CssBaseline';
import 'font-logos/assets/font-logos.css';
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 moved this import to DistributionIcon.tsx. Moving this import down the tree makes it so font-logos.css isn't loaded until it is needed. πŸŽ‰

Copy link
Contributor

@hkhalil-akamai hkhalil-akamai left a comment

Choose a reason for hiding this comment

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

Changes look good in testing and code is clean and readable. Left a few minor comments. Great to see this work proceed @bnussman-akamai!

const value = images?.find((i) => i.id === props.value);

return (
<Autocomplete
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to display the selected distro's icon (to match the current functionality)? This might be possible using startAdornment

Screenshot 2024-03-18 at 3 25 15 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I should do that for the 1:1 parity with the old component.

I left it simple for now because I prefer the clean look πŸ˜–

Copy link
Contributor

Choose a reason for hiding this comment

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

Keeping it vs. not keeping it might be worth raising during Cafe this week

Comment on lines -28 to -29
// @todo this is temporary API error handling. We will develop a more
// robust helper that can convert API errors to react-hook-form errors
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason for removing this todo?

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 removed it because this whole entire flow could have "@todo"s everywhere. I don't wanna go crazy with them. I also think this error handling might work as is.

Comment on lines +61 to +85
<Tabs
index={currentTabIndex}
onChange={(index) => updateParams({ type: tabs[index] })}
>
<TabList>
<Tab>Distributions</Tab>
<Tab>Marketplace</Tab>
<Tab>StackScripts</Tab>
<Tab>Images</Tab>
<Tab>Backups</Tab>
<Tab>Clone Linode</Tab>
</TabList>
<TabPanels>
<SafeTabPanel index={0}>
<Distributions />
</SafeTabPanel>
<SafeTabPanel index={1}>Marketplace</SafeTabPanel>
<SafeTabPanel index={2}>StackScripts</SafeTabPanel>
<SafeTabPanel index={3}>
<Images />
</SafeTabPanel>
<SafeTabPanel index={4}>Bckups</SafeTabPanel>
<SafeTabPanel index={5}>Clone Linode</SafeTabPanel>
</TabPanels>
</Tabs>
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm fine with it this way, the file remains pretty short and readable.

Comment on lines +66 to +71
<Tab>Distributions</Tab>
<Tab>Marketplace</Tab>
<Tab>StackScripts</Tab>
<Tab>Images</Tab>
<Tab>Backups</Tab>
<Tab>Clone Linode</Tab>
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this be better expressed as a tabs.map?

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 thought about it, but because this page is likely not going to change much, I think keeping it static is okay.

Copy link
Contributor

Choose a reason for hiding this comment

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

My only concern is with currentTabIndex going out of sync, but as you said with these not changing often its unlikely.

Copy link
Contributor

@abailly-akamai abailly-akamai left a comment

Choose a reason for hiding this comment

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

Nice! super clean - some improvement to be made but the direction is great as far as i can see the scaffolding!

Apart from my comments, the Image Select Option still needs some TLC (alignment w/ icon, padding, hover styles)
Screenshot 2024-03-19 at 09 57 43
Screenshot 2024-03-19 at 09 56 22

<i
className={className}
data-testid="distro-icon"
style={{ fontSize: size ?? '1.8em' }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want the icon size to be relative to the parent? I am not saying we don't, i am frankly wondering cause sometimes it can cause unwanted side effects. Since we can override the size am wondering about using px instead for the default. No big deal either way

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 updated this component in 5981137

It will now just inherit the fontSize and have no default.

import * as React from 'react';
import { OptionProps } from 'react-select';
import { makeStyles } from 'tss-react/mui';
Copy link
Contributor

Choose a reason for hiding this comment

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

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

perfectionist/sort-imports 🀷

@@ -137,6 +137,9 @@ const isMemo = (prevProps: ImageSelectProps, nextProps: ImageSelectProps) => {
);
};

/**
* @deprecated Start using ImageSelectv2 when possible
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice πŸ‘

Copy link
Contributor

Choose a reason for hiding this comment

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

Extending the scope is fine, but let's keep going with our recommanded practices? This one definitely warrants a unit test and to be in Storybook since we do that for our custom selects (LinodeSelect, RegionSelect etc). A bit of of overhead but worth it IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some testing in 1bf9aa5

* for the Images Select.
*
* Please use API filtering (getAPIFilterForImageSelect) when possible!
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the original behavior here (with legacy ImageSelect)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ImageSelect just had intense client side filtering / sorting. This new implementation should be much more simple!

Copy link
Contributor

Choose a reason for hiding this comment

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

right right just trying to understand if anything is changing to the logic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, there are changes to logic. packages/manager/src/components/ImageSelect/ImageSelect.tsx does a lot of crazy stuff and this new component likely doesn't work exactly like the old one.

For example, there is an API bug that is breaking the ordering on this new component. I filed an ARB for it already!

This is something we can iterate on as we need other functionality

Copy link
Contributor

Choose a reason for hiding this comment

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

@bnussman-akamai Does the bug you're referring to cover the reason why, for example, openSUSE images get listed under separate headers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly that! @dwiley-akamai

Comment on lines +61 to +85
<Tabs
index={currentTabIndex}
onChange={(index) => updateParams({ type: tabs[index] })}
>
<TabList>
<Tab>Distributions</Tab>
<Tab>Marketplace</Tab>
<Tab>StackScripts</Tab>
<Tab>Images</Tab>
<Tab>Backups</Tab>
<Tab>Clone Linode</Tab>
</TabList>
<TabPanels>
<SafeTabPanel index={0}>
<Distributions />
</SafeTabPanel>
<SafeTabPanel index={1}>Marketplace</SafeTabPanel>
<SafeTabPanel index={2}>StackScripts</SafeTabPanel>
<SafeTabPanel index={3}>
<Images />
</SafeTabPanel>
<SafeTabPanel index={4}>Bckups</SafeTabPanel>
<SafeTabPanel index={5}>Clone Linode</SafeTabPanel>
</TabPanels>
</Tabs>
Copy link
Contributor

Choose a reason for hiding this comment

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

ya, it's fine to keep it here.

export const useLinodeCreateQueryParams = () => {
const history = useHistory();

const rawParams = getQueryParamsFromQueryString(history.location.search);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason you are not using useParams?

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'm going to 1:1 parity with the existing create flow and it only uses query params. I think useParams is only for router path params


const updateParams = (params: Partial<LinodeCreateQueryParams>) => {
const newParams = new URLSearchParams({ ...rawParams, ...params });
history.replace({ search: newParams.toString() });
Copy link
Contributor

Choose a reason for hiding this comment

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

history.replace will prevent the browser from remember the history, therefore not allowing the browser back and forward buttons. This introduces regressions with the current behavior. history.push is probably what you want here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Thanks! 4a3e081

@bnussman-akamai bnussman-akamai added Add'tl Approval Needed Waiting on another approval! and removed Ready for Review labels Mar 19, 2024
@bnussman-akamai bnussman-akamai changed the title feat: [M3-7869] - Linode Create Refactor - Part 2 - Images and Distributions upcoming: [M3-7869] - Linode Create Refactor - Part 2 - Images and Distributions Mar 19, 2024
@dwiley-akamai dwiley-akamai self-requested a review March 20, 2024 16:50
Copy link
Contributor

@dwiley-akamai dwiley-akamai left a comment

Choose a reason for hiding this comment

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

Will circle back to wrap up my review tomorrow morning!

Copy link
Contributor

Choose a reason for hiding this comment

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

minor: can we do "V2" in the directory and file names instead of "v2"? Just makes it easier to notice when looking quickly

* for the Images Select.
*
* Please use API filtering (getAPIFilterForImageSelect) when possible!
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

@bnussman-akamai Does the bug you're referring to cover the reason why, for example, openSUSE images get listed under separate headers?

@@ -47,7 +48,7 @@ export const Plan = () => {
selectedId={field.value}
selectedRegionID={regionId}
showTransfer
types={types?.map(extendType) ?? []}
types={types?.map(extendType) ?? []} // @todo don't extend type
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a notation like @TODO Linode Create Refactor to make it easy to track these down during this refactoring effort?

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 I might leave it as a basic todo because that change isn't within the scope of this project.

const value = images?.find((i) => i.id === props.value);

return (
<Autocomplete
Copy link
Contributor

Choose a reason for hiding this comment

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

Keeping it vs. not keeping it might be worth raising during Cafe this week

Copy link
Contributor

@abailly-akamai abailly-akamai 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 addressing the changes!

PR is good to go from my end, for future ones I'd still love to see out of scope commits (such as the new ImageSelect) decoupled in a separate PR - especially since it seems like this is prompting the most questions on this PR and diverting the attention from the create flow refactor. My two cents!

@bnussman-akamai bnussman-akamai merged commit 2ca1896 into linode:develop Mar 21, 2024
18 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.

None yet

5 participants