Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

fix(site): tighten interface design for various frontend utility functions#18894

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
Parkreiner wants to merge15 commits intomain
base:main
Choose a base branch
Loading
frommes/batch-update-01

Conversation

Parkreiner
Copy link
Member

@ParkreinerParkreiner commentedJul 16, 2025
edited
Loading

Precursor to#18895
Splitting this off so that the changes are easier to review.

Changes made

  • Improve type-safety for thewithQuery Storybook decorator
  • Centralized almost all queries that deal with template versions to use a shared, exported query key prefix
  • UpdateduseFilter andusePagination to have much more strict return types, and decoupled them from React Router at the interface level
  • Also added some extra input validation and performance optimizations touseFilter andusePagination
  • Removed a stale data when working with checked workspaces for the/workspaces page
  • Removed hackyuseEffect call for syncing URL state to checked workspaces, in favor of explicit state syncs
  • Did some extra cleanup and removed unused values

Notes

  • Many of the changes here were in service of the main PR. I'll try to highlight notable areas, but if there's anything that's not clear, feel free to post a comment in this PR. Ideally you shouldn't really have to look at the other PR to understand this one, so if something's confusing, that's a sign that something's wrong

@ParkreinerParkreiner self-assigned thisJul 16, 2025
@ParkreinerParkreiner changed the titlefix(site): tighten up interface design for various frontend utility functionsfix(site): tighten interface design for various frontend utility functionsJul 16, 2025
Comment on lines -51 to -64
const getTemplatesByOrganizationQueryKey = (
organization: string,
options?: GetTemplatesOptions,
) => [organization, "templates", options?.deprecated];

const templatesByOrganization = (
organization: string,
options: GetTemplatesOptions = {},
) => {
return {
queryKey: getTemplatesByOrganizationQueryKey(organization, options),
queryFn: () => API.getTemplatesByOrganization(organization, options),
};
};
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Weren't used at all

@@ -121,9 +106,11 @@ export const templateExamples = () => {
};
};

export const templateVersionRoot: string = "templateVersion";
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

This ends up being more important for the upcoming PR, but I really, really think that we need to find the time to formalize our entire data layer, and be way more deliberate with our React Query caching

I fully expect our caching to create a super, super hard-to-recreate bug sometime in the future, because it's a bunch of semi-structured global mutable state right now

Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

Also added an explicitstring type annotation so that TypeScript doesn't over-infer the value and treat it as a literal. We shouldn't know what exact combination of characters it is – just what purpose the string serves in the greater codebase as an opaque value

Comment on lines +33 to +34
searchParams: URLSearchParams;
onSearchParamsChange: (newParams: URLSearchParams) => void;
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Decoupled type signature from React Router. Splitting this up gives us a lot more freedom, which comes up later in this PR

Comment on lines +38 to +45
export type UseFilterResult = Readonly<{
query: string;
values: FilterValues;
used: boolean;
update: (newValues: string | FilterValues) => void;
debounceUpdate: (newValues: string | FilterValues) => void;
cancelDebounce: () => void;
}>;
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

I feel like the previous type was super, super risky, because it was derived from a function that didn't have much type-safety

With this, the contract is way more explicit and simple, and there's basically no chance of the TypeScript LSP getting confused and dropping properties for tooltips

Comment on lines +10 to +15
type UsePaginationResult = Readonly<{
page: number;
limit: number;
offset: number;
goToPage: (page: number) => void;
}>;
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

We were missing a return type altogether before

Comment on lines +30 to +34
const abortNavigation =
page === newPage || !Number.isFinite(page) || !Number.isInteger(page);
if (abortNavigation) {
return;
}
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Another render optimization

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 this render optimization? Not re-rendering the page if it's the same page?

Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

So there's two parts:

  • The basic input validation from theNumber static methods
  • The equality check

I could maybe add a comment, but React only hasObject.is for change detection, so in general, a lot of render optimizations boil down to "only dispatch a new value (especially a non-primitive value) when you know the contents have changed". Most of the time, you don't have to worry about over-dispatching primitive values (as long as they're done outside a render, React will detect that nothing changed and do a no-op for the state update). But because JS is wacky, there are edge cases withObject.is and numbers, which is why there's the extra=== check

const [suggestedName, setSuggestedName] = useState(() =>
generateWorkspaceName(),
);
const [suggestedName, setSuggestedName] = useState(generateWorkspaceName);
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

The extra wrapper function was redundant

(touched, parameter) => {
if (autofillByName[parameter.name] !== undefined) {
touched[parameter.name] = true;
}
return touched;
},
{} as Record<string, boolean>,
{},
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Removed type assertion to remove risk of accidentally silencing the compiler

Comment on lines +70 to +72
const [checkedWorkspaceIds, setCheckedWorkspaceIds] = useState(
new Set<string>(),
);
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

This is part of a fix for a super nasty bug that I ran into when updating the batch-update UI

Because we were storing an entire copy of the original workspaces each time we checked off something, that also meant that as the query cache updated its data and triggered re-renders, anything that used the checked workspaces would receive the stale versions of the workspaces

"delete" | "update" | null
>(null);
const [urlSearchParams] = searchParamsResult;
const [activeBatchAction, setActiveBatchAction] = useState<BatchAction>();
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Slightly re-jiggeredconfirmingBatchAction

@@ -142,7 +164,18 @@ const WorkspacesPage: FC = () => {
canCreateTemplate={permissions.createTemplates}
canChangeVersions={permissions.updateTemplates}
checkedWorkspaces={checkedWorkspaces}
onCheckChange={setCheckedWorkspaces}
onCheckChange={(newWorkspaces) => {
setCheckedWorkspaceIds((current) => {
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Another render optimization

onBatchDeleteTransition={() => setActiveBatchAction("delete")}
onBatchStartTransition={() => batchActions.start(checkedWorkspaces)}
onBatchStopTransition={() => batchActions.stop(checkedWorkspaces)}
onBatchUpdateTransition={() => {
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

This change becomes a little more relevant for the other PR, but even if this were the only PR, this would still help the update UI avoid stale state

Comment on lines +15 to +25
type UseBatchActionsResult = Readonly<{
isProcessing: boolean;
start: (workspaces: readonly Workspace[]) => Promise<WorkspaceBuild[]>;
stop: (workspaces: readonly Workspace[]) => Promise<WorkspaceBuild[]>;
delete: (workspaces: readonly Workspace[]) => Promise<WorkspaceBuild[]>;
updateTemplateVersions: (
payload: UpdateAllPayload,
) => Promise<WorkspaceBuild[]>;
favorite: (payload: readonly Workspace[]) => Promise<void>;
unfavorite: (payload: readonly Workspace[]) => Promise<void>;
}>;
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

Added explicit return type, which was super important here, because the function's return type was getting inferred as something super complicated and nasty from React Query

Comment on lines +80 to +88
type WorkspaceFilterProps = Readonly<{
filter: UseFilterResult;
error: unknown;
templateMenu: TemplateFilterMenu;
statusMenu: StatusFilterMenu;

userMenu?: UserFilterMenu;
organizationsMenu?: OrganizationsFilterMenu;
}>;
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Decouples the main state from the actual component props

The way I think about it is that props are a hack to get JS function to support named arguments and make it mirror HTML more, so you really shouldn't ever have any direct dependencies on that

Copy link
Contributor

@brettkolodnybrettkolodny left a comment

Choose a reason for hiding this comment

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

Overall lgtm, I think these changes are a lot cleaner but would definitely wait for Kayla's review

Comment on lines +30 to +34
const abortNavigation =
page === newPage || !Number.isFinite(page) || !Number.isInteger(page);
if (abortNavigation) {
return;
}
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 this render optimization? Not re-rendering the page if it's the same page?

@@ -121,9 +106,11 @@ export const templateExamples = () => {
};
};

export const templateVersionRoot: string = "templateVersion";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: there seems to be inconsistencies within the code base between usingcamelCase orUPPER_SNAKE_CASE for file-wide constants so while out of scope for this PR I think deciding on one and sticking to it would be good

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Yeah, that's fair. The JS/TS way seems to be SCREAMING_CASE for constants, while the Go way seems to be to treat them as any other package value

I'm leaning towards switching this to SCREAMING_CASE, but I'll wait for Kayla to chime in

* workspace is in the middle of a transition and will eventually reach a more
* stable state/status.
*/
const ACTIVE_BUILD_STATUSES: readonly WorkspaceStatus[] = [
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

This becomes exported in the second PR, which is why I put more of a public-facing comment on it

Comment on lines -128 to -133
// We want to uncheck the selected workspaces always when the url changes
// because of filtering or pagination
// biome-ignore lint/correctness/useExhaustiveDependencies: consider refactoring
useEffect(() => {
setCheckedWorkspaces([]);
}, [urlSearchParams]);
Copy link
MemberAuthor

@ParkreinerParkreinerJul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

Forgot to comment on this, but theresetChecked logic above is meant to make sure that if we add more URL logic in the future, it doesn't break our filtering and pagination

We were coarsely syncing based on the entire URL params object. That worked fine for now, but if we were to add any logic on the same page that also dealt with URLs and had nothing to do with the filtering/pagination, it would still cause all the checkboxes to get unchecked on every change

Moving the state syncing to the event handlers for the filter/pagination hooks seemed like the better choice. That way, we're also not coupled to the hooks formatting the URLs a specific way either

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@brettkolodnybrettkolodnybrettkolodny approved these changes

@aslilacaslilacAwaiting requested review from aslilacaslilac is a code owner

Assignees

@ParkreinerParkreiner

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

2 participants
@Parkreiner@brettkolodny

[8]ページ先頭

©2009-2025 Movatter.jp