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

Refactoring getNavigationTree to include folders #105

Merged
merged 31 commits into from
Jun 16, 2023

Conversation

prestonbourne
Copy link
Contributor

@prestonbourne prestonbourne commented Jun 14, 2023

Asana Task 🔗

What

  • Restructures exported member, categories from swingset/meta
  • Updates rendering logic in hashicorp-theme to match new NavigationTree data structure.

Why

  • Added functionality for Folder this way themes can easier integrate multifaceted components or sub components.
  • Allows for easier long term goal of sorting and searching functionality.

How

  • Rewrote getNavigationTree
  • Added new Node types within tree

@prestonbourne prestonbourne marked this pull request as ready for review June 15, 2023 16:58
import { resolveComponents } from './resolvers/component.js'
import { stringifyEntity } from './resolvers/stringify-entity.js'
import { getNavigationTree } from './get-nav-tree.js'
import { getNavigationTree } from './get-nav-tree.js'
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
import { getNavigationTree } from './get-nav-tree.js'
import { getNavigationTree } from './get-nav-tree.js'

Comment on lines 44 to 54
/*
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage.
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients
*/
export type ComponentNode = Omit<
Pick<
ComponentEntity,
'__type' | 'title' | 'slug' | 'componentPath' | 'children'
>,
'__type'
> & { type: ComponentEntity['__type'] }
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove the Omit here?

Suggested change
/*
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage.
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients
*/
export type ComponentNode = Omit<
Pick<
ComponentEntity,
'__type' | 'title' | 'slug' | 'componentPath' | 'children'
>,
'__type'
> & { type: ComponentEntity['__type'] }
/*
Utility type to extract just necessary properties before going to theme, also renames '__type' key to type for external usage.
Unwanted Behavior?? The children property comes through as a ComponentEntity, which exposes the entities to the theme/clients
*/
export type ComponentNode = Pick<
ComponentEntity,
"title" | "slug" | "componentPath" | "children"
> & { type: ComponentEntity["__type"] };




if (!!storedCategory) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would potentially split out the category creation (if not exists) from the folder handling. I think the flow would look like (in plain english):

check if category is stored

if category is not stored, create the category object

then, handle folder logic

This dedupes some of the folder logic between these two branches.

(optional) I also wonder if it'd be easier to build up the category map and folder map separately, and then after we're done iterating over all of the entities we can make sure each folder gets added to its proper category. that way we don't need to check each of a category's children for the correct folder when we're trying to add an element to it.

'__type' | 'title' | 'slug' | 'componentPath' | 'children'
>,
'__type'
> & { type: ComponentEntity['__type'] }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Removes __ from type so themes know to use this as a discriminator.
  2. Extracting the children property without converting the type from ComponentEntity[] to ComponentNode[] is leading to issues with type inference. Currently using as unknown type cast. Will address in separate issue and PR. ComponentEntity should be ComponentNode in categories #107

//Swap this out for already existing heading && Enquire about semantics, https://helios.hashicorp.design/components/application-state uses <div>
function CategoryHeading({ children }: { children: string }) {
return (
<div className="ss-uppercase ss-text-xs ss-font-semibold ss-leading-6 ss-text-foreground-faint ss-border-b ss-border-faint ss-pb-2">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently using a div for a heading because it's the pattern Helios uses.

<LinkItem to={item.slug} title={item.title} />
<ComponentList
isNested
items={item.children as unknown[] as ComponentNode[]}
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 as unknown[] as ComponentNode[] typecast is a result of typescript expecting ComponentEntities instead of ComponentNodes. Which is unwanted behavior (see changes to types.ts)

Should be a fairly quick fix, see #107

Copy link
Contributor

Choose a reason for hiding this comment

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

Want to add a // TODO here linking to the issue so we know to clean this up as part of addressing that issue?

@@ -0,0 +1,3 @@
export function Checkbox(props) {
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 new components under the [Form] folder to better demonstrate sorting and collapsing.

)
}

const hasChildren = (item.children?.length as number) > 0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to determine whether children can be undefined or an empty []. Currently the type is NavigationNode[] | undefined hence the typecast, but all the values resolve to []

* https://github.com/hashicorp/swingset/pull/105
* @type {NavigationTree}
* Leaving ts ignore to ensure build step succeeds
* @ts-ignore */}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Issue #106


export type ComponentNode = Pick<
ComponentEntity,
'__type' | 'title' | 'slug' | 'componentPath' | 'children'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

reverted back to __type

componentPath: entity.componentPath,
children: entity.children,
})
//if node belongs in a folder, and folder doesnt exist, create folder with node
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reduced nesting, should read like:

  1. If Category doesn't exist yet create it
  2. Check if current entity belongs in folder or not
  3. Add entity to folder if it already exists, if not create folder and add entity
  4. If entity doesn't have folder, add entity

@@ -67,9 +66,7 @@ export async function loader(
const result = `
export const meta = {
${entities
.map(
(entity) => `'${entity.slug}': ${stringifyEntity(entity)}`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

prettier formatting fix


result[category].sort((a, b) => (a.slug > b.slug ? 1 : -1))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorting will be handled separately

Copy link
Contributor

@BRKalow BRKalow left a comment

Choose a reason for hiding this comment

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

Looking good! I think the component breakdown you've come up with is sensible. There seems to be a mismatch between what we're returning from getNavigationTree and the type we're expecting (returning an object instead of CategoryNode[])

<LinkItem to={item.slug} title={item.title} />
<ComponentList
isNested
items={item.children as unknown[] as ComponentNode[]}
Copy link
Contributor

Choose a reason for hiding this comment

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

Want to add a // TODO here linking to the issue so we know to clean this up as part of addressing that issue?

export type NavigationNode = ComponentNode | FolderNode

export type CategoryNode = {
type: 'category'
Copy link
Contributor

Choose a reason for hiding this comment

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

Is __type the name we want to use? (I think yes based on your other comments)

Suggested change
type: 'category'
__type: 'category'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Originally the Omit<...> & {type: '....'} changed it but I saw in the first review you requested to remove the Omit.

I'm impartial on the naming but think type felt more consistent with the other properties.

Copy link
Contributor

Choose a reason for hiding this comment

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

The other two nodes use __type, so I'd say let's pick one or the other and be consistent between each interface 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will stick with what's already there then, since __type is used across a few other files already

Comment on lines 84 to 85
const tree = Object.fromEntries(categories)
return tree
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to return NavigationTree here, right? That's an array of CategoryNode, so we don't want an object here.

This might work (builds an array from the Map's values):

Array.from(categories.values())

Comment on lines 13 to 17
const categoriesJSX = Object.values(categories).map(
(category) => (
<Category title={category.title} key={category.title} items={category.children} />
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think Object.values() will be unnecessary here if we fix the return value of getNavigationTree. In that case, we can use categories.map().

Comment on lines 8 to 9
function SideNavigation(props: SideNavBarProps) {
const { categories } = props
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, we can destructure this:

Suggested change
function SideNavigation(props: SideNavBarProps) {
const { categories } = props
function SideNavigation({ categories }: SideNavBarProps) {

import { Link } from "../link"
import { cx } from "class-variance-authority"

function LinkItem({ title, to }: Record<'title' | 'to', string>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The use of Record is clever here, but I think it'd be clearer if we used a more conventional type definition:

Suggested change
function LinkItem({ title, to }: Record<'title' | 'to', string>) {
type LinkItemProps = {
title: string
to: string
}
function LinkItem({ title, to }: LinkItemProps) {

)}
>
{items.map((item) => {
const isFolder = item.__type === 'folder'
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be a good candidate for using a switch statement on __type. It's stylistic really, but I think it would make the logic of this block easier to understand.

}
)

//TODO: Account for duplicate folder names [category]/[folder]
Copy link
Contributor

Choose a reason for hiding this comment

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

Just noting that this is applicable for folders, not necessarily categories

DocsEntity,
ComponentNode,
CategoryNode,
} from './types.js'

export function getNavigationTree(entities: (ComponentEntity | DocsEntity)[]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Some unit tests for this function could be helpful! It'd make it easier for us to iterate on the logic going forward too. We can do this as a follow-up.

Comment on lines 78 to 81
/**TODO:
* We should be pushing @type {ComponentNode} instead of @type {ComponentEntity}
* https://github.com/hashicorp/swingset/issues/107
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Issue #107

Copy link
Contributor Author

@prestonbourne prestonbourne left a comment

Choose a reason for hiding this comment

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

Made the suggested adjustments. Definitely agree tests are the next step, also want to get Vercel previews working so you + over reviewers can reference it

@prestonbourne prestonbourne merged commit 5898ca6 into main Jun 16, 2023
@prestonbourne prestonbourne deleted the restructure-nav-data branch June 16, 2023 20:58
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.

2 participants