Skip to content

feat: implement CRUD UI for IDP organization sync settings #15503

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

Merged
merged 71 commits into from
Dec 4, 2024

Conversation

jaaydenh
Copy link
Contributor

@jaaydenh jaaydenh commented Nov 13, 2024

resolves coder/internal#205

The goal is to create a new page located in deployment settings to allow users to create and update organization IDP sync settings.

  • Use shadcn button for export policy button
  • Disable save button if form is not dirty
  • Disable "Add IdP organization" button if idp org name or coder orgs are empty
  • Add footnote label below organization sync field input
  • Add button to Delete rows in mapping table
  • Create Multi-select combox box component to select coder org to map to idp org
  • Storybook tests
  • Tooltip for assign default org switch
  • Display success/error toast on form submission
Screenshot 2024-12-02 at 20 33 07

@jaaydenh jaaydenh self-assigned this Nov 13, 2024
@jaaydenh jaaydenh force-pushed the jaaydenh/org-sync-settings-ui branch from 42deaba to b632f87 Compare November 17, 2024 02:48
@jaaydenh jaaydenh changed the title feat: initial commit for idp org sync settings feat: CRUD UI for IDP organization sync settings Nov 17, 2024
@jaaydenh jaaydenh changed the title feat: CRUD UI for IDP organization sync settings feat: implement CRUD UI for IDP organization sync settings Nov 17, 2024
@jaaydenh jaaydenh requested a review from Parkreiner November 21, 2024 05:19
@jaaydenh jaaydenh marked this pull request as ready for review November 21, 2024 16:43
@Kira-Pilot Kira-Pilot requested a review from aslilac November 21, 2024 17:41
Copy link
Member

@Parkreiner Parkreiner left a comment

Choose a reason for hiding this comment

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

I left a ton of comments, but I'm willing to budge on a lot of these.

I do have concerns about how some of the code in this PR reads. It doesn't really read like any of the code I've seen from you before, and there are definitely some logic bugs that seem almost like they were written by someone else. I don't know what parts are from Shad, and which aren't, but I am a little worried if this is the quality we're getting from Shad going forward

extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: one thing we've been doing recently is bringing in the FC type to give better feedback about accidentally returning wrong values (especially since React 18 fixed the previous issues with the type)

Copy link
Member

Choose a reason for hiding this comment

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

yeah, I think we should still use const and FC and arrow functions. it's a small bit of work to clean up shadcn things as we add them, but there are reasons we've consolidated on writing components the way that we do, and we should definitely stick with it.

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 agree, seems like a good idea. This means I should probably create a doc to list all the extra steps needed when importing a shadcn component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aslilac it would be useful to know the various reasons that components are currently written as they are.

);
}

export { Badge, badgeVariants };
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure what we've been doing in core lately, so just making sure I understand: are we going to be exporting styles like this going forward? At first blush, it's not super clear to me if this will serve a benefit

Copy link
Member

Choose a reason for hiding this comment

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

this is definitely another shadcn-ism we should avoid. just put export on the declaration if we need to. (I'm also not sure what value exporting a constant like this provides.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My original thought was to keep the shadcn components as close as possible to how they are imported but it makes sense to change this since the current codebase convention is not to group all exports at the bottom of the file. I think this pattern is generally used so its easy to look up all exports in one place.

@Parkreiner Since I have seen the export at the bottom pattern more often in other codebases, I am curious if you know why the current pattern exists?

Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I haven't been in enough "big" codebases to notice any "industry-leading" patterns, but Coder was just like this when I started working for them. Though I am a bigger fan of just exporting any relevant values directly

I know that .d.ts files often have all their exports at the bottom, but I don't know if that's a meaningful reference, since those are auto-generated anyway

Copy link
Member

Choose a reason for hiding this comment

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

On a similar note to the one about the style exports, are we changing how we're writing out code now? I few things immediately jump out to me:

  • Using a wildcard import for React (which shouldn't matter, since React isn't easy to tree-shake anyway)
  • Consolidated exports at the end of the file
  • Random library-specific HTML attribute (cmdk-input-wrapper)
  • Extensive use of Tailwind's arbitrary variants feature to inject styles for the Cmdk component

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Everything in src/components/ui comes directly from shadcn except for multiple-selector. I don't want to assume any existing patterns in the codebase are what we should definitely use going forward. if there is disagreement with how things are done with shadcn, lets bring them up all so I can make a list of everything that needs to be changed when importing a shadcn component.

return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
Copy link
Member

Choose a reason for hiding this comment

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

I feel like I'm missing something, but how are we formatting the Tailwind class names?

Copy link
Member

Choose a reason for hiding this comment

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

uhh, this is a particularly nasty string of class names. it wraps across 4 lines on my enormous screen and immediately triggers my "this is incomprehensible and I should run away" reflex. how could we refactor this to be clearer? maybe for things with lots of complicated selectors like this we should stick with emotion actually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All this is really doing is styling the underlying cmdk component using the tailwind arbitrary variants feature. The purpose of doing this is because of the assumption that shadcn components are battle tested and are saving us alot of time compared to writing this from scratch. I don't think it would matter much if emotion was used here as the complexity exists because of consuming the cmdk component.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, to be clear – I think this is an area where the Tailwind abstractions break down – definitely not the fault of anyone here. I understand why the code is the way it is:

  • The CMDK component expects styles to be provided via a specific set of exposed class names
  • We also can't dynamically process the class names in a function or something, because then Tailwind can't create CSS classes at compile time
  • TW isn't great at that, so we have to bust out their arbitrary variants, and make sure that we're satisfying the TW compiler's needs, and the component's styling needs. It's just that the syntax is awkward and ugly

Copy link
Member

Choose a reason for hiding this comment

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

I'm not a fan of how it looks, but if we can shove all the nastiness into a corner and put a nice bow on it, I think it's fine enough

Getting back to my original question, though: what tools do we have to sort the class names automatically? The syntax is bad enough, but if we can't even order the styles by the order that they're applying in, this will be a nightmare to debug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doing the class sorting is a solved problem when using prettier. Its a work in progress with biome as discussed here. biomejs/biome#1274

<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't the logic, obviously, but the fact that it's here has me worried our formatting logic isn't working properly: there's a double-space right after the bg-black/80

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now biome doesn't help with this. Once this PR is merged I was planning on looking into the issue with Tailwind class sorting using Biome, biomejs/biome#1274

Comment on lines 501 to 505
onKeyDown={(e) => {
if (e.key === "Enter") {
handleUnselect(option);
}
}}
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be necessary. The HTML spec makes sure that an onClick handler is also automatically wired up to the onKeyDown event for Enter and the onKeyUp event for Space

The whole thing can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I test this by removing the onKeyDown handler by tabbing to an item in the multi-select and pressing enter, I am no longer able to unselect/remove the item from the multi-select component.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, in that case, they might be doing some things that break from the spec. I'm okay with keeping it – I would just like a comment explaining that it is necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Parkreiner Can you refer to where in the HTML spec you found this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

btw if I remove onKeyDown, the key that actually works in this case is space instead of enter

Comment on lines 602 to 603
{/* biome-ignore lint/complexity/noUselessFragments: A parent element is
needed for multiple dropdown items */}
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain this comment a bit more? Is this a library need? And just making sure: is it okay that the element used is never rendered on screen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Parkreiner I am only seeing line numbers for this comment and the line numbers have changed since the original comment. Which comment is this referring to?

Copy link
Member

@Parkreiner Parkreiner left a comment

Choose a reason for hiding this comment

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

Did a quick second QA pass, and highlighted more concerns. Didn't have a ton of time, because I'm helping out with registry, but if you want to pair next week, just let me know

Copy link
Member

@aslilac aslilac left a comment

Choose a reason for hiding this comment

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

I guess one thing we should be clearer on: my vision for using shadcn was that we would perform heavy alterations to each component we bring in as necessary to make it feel cohesive with the rest of our codebase. Stuff like..

  • file names
  • code structure
  • component definitions
  • folder structure

etc. etc.

I'm also already really worried about the complexity of our tailwind class lists from this. I knew going in that using tailwind just kind of looks like this, but some of these are way gnarlier than I think should be acceptable for us.

extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
Copy link
Member

Choose a reason for hiding this comment

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

yeah, I think we should still use const and FC and arrow functions. it's a small bit of work to clean up shadcn things as we add them, but there are reasons we've consolidated on writing components the way that we do, and we should definitely stick with it.

);
}

export { Badge, badgeVariants };
Copy link
Member

Choose a reason for hiding this comment

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

this is definitely another shadcn-ism we should avoid. just put export on the declaration if we need to. (I'm also not sure what value exporting a constant like this provides.)

return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
Copy link
Member

Choose a reason for hiding this comment

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

uhh, this is a particularly nasty string of class names. it wraps across 4 lines on my enormous screen and immediately triggers my "this is incomprehensible and I should run away" reflex. how could we refactor this to be clearer? maybe for things with lots of complicated selectors like this we should stick with emotion actually?

@@ -32,6 +32,7 @@ module.exports = {
primary: "hsl(var(--surface-primary))",
secondary: "hsl(var(--surface-secondary))",
tertiary: "hsl(var(--surface-tertiary))",
quaternary: "hsl(var(--surface-quaternary))",
Copy link
Member

Choose a reason for hiding this comment

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

I was already against primary, secondary and tertiary as being three bad names, but now we're adding quaternary? this is such a horribly meaningless name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At this point, the best place to discuss the naming issues would be in Figma as that is the source of truth. https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=6-2115&m=dev

<Pill
key={organization}
className={cn("border-none w-fit", {
"bg-surface-error": UUID.test(organization),
Copy link
Member

Choose a reason for hiding this comment

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

wait, so it's an error to specify the UUID? why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is the same behavior as the idp sync groups and roles feature that went out with orgs. The idea is that if the uuid does not map to an organization, then the organization has been deleted and this is a problem that should be corrected.

@jaaydenh jaaydenh force-pushed the jaaydenh/org-sync-settings-ui branch from 0ea6dee to ae30db0 Compare December 2, 2024 23:20
@@ -0,0 +1,80 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Trash } from "lucide-react";
Copy link
Member

Choose a reason for hiding this comment

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

does this bring in all of lucide? how does this affect our bundle size?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only the icons that are used, https://lucide.dev/guide/#code-optimization

Copy link
Member

@Parkreiner Parkreiner left a comment

Choose a reason for hiding this comment

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

Still have feedback and things I'd like changed, but nothing to block you.

Comment on lines +49 to +51
if (isLoading) {
return <Loader />;
}
Copy link
Member

@Parkreiner Parkreiner Dec 3, 2024

Choose a reason for hiding this comment

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

The more I think about this, the more I think we should be giving the user a better experience while the page is in a loading state.

Right now, while the entire page is loading, the user only sees a loading spinner, and because updating the title is tied to Helmet, the HTML title stays the same from the previous page, too.

Ideally, I think I'd prefer for the page to be updated so that:

  1. The HTML title, page title, and description are updated immediately, since none of that requires fetching. All the data is available synchronously
  2. Move the loader inside the main content area, and also update the button so that it's more granularly tied to the loading state, too

Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I feel like we're also kind of blurring what should be a page component concern, and what should be a view component concern

All the header content here is part of the UI view, but because it's not part of the view component, any errors or rendering problems will never show up in Storybook

Not sure if you have any thoughts @aslilac

<header className="flex flex-row items-baseline justify-between">
<div className="flex flex-col gap-2">
<h1 className="text-3xl m-0">Organization IdP Sync</h1>
<p className="flex flex-row gap-1 text-sm text-content-secondary font-medium m-0">
Copy link
Member

Choose a reason for hiding this comment

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

Just making sure: this is the only description in the Deployment navigation section that has a medium font weight. Is that something we're moving towards with the design tokens?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in the designs, most fonts are 500 but on this page it only needs to be specified here

Copy link
Member

@Parkreiner Parkreiner Dec 4, 2024

Choose a reason for hiding this comment

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

The reason why I was asking was because all the other pages use font weight 400 (Roman weight). I think it's outside the scope of the PR, but we'll need to make another PR to update those, too
Screenshot 2024-12-04 at 9 40 55 AM

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 is an issue already created to handle global style changes

* @see {@link https://ui.shadcn.com/docs/components/dialog}
*/
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
Copy link
Member

Choose a reason for hiding this comment

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

Nit: one thing I usually do when I import icons is that I rename them to SomethingIcon, where Something is the role I'm intending for the icon to fill. I find this really helpful when I'm using an icon outside its intended purpose, but even here, I think <CloseIcon /> is more readable than <X />

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 there are pros and cons either way. I like to easily tell what the icon visually looks like and the original name works well for that. Using icon in the name could also be a good way to look up all icons. Overall, I am not a fan of naming imported components for their specific purpose unless its really confusing otherwise.

@jaaydenh jaaydenh merged commit 6c9ccca into main Dec 4, 2024
29 checks passed
@jaaydenh jaaydenh deleted the jaaydenh/org-sync-settings-ui branch December 4, 2024 19:23
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.

UI page for IdP Organization Sync Settings
3 participants