Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slow-wasps-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'hive': patch
---

Fix first schema check landing width; fix flickering sidenav on schema check loading"
4 changes: 3 additions & 1 deletion packages/web/app/src/components/ui/empty-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ export const NoSchemaVersion = ({
<EmptyList
title="Hive is waiting for your first schema"
description="You can publish a schema with Hive CLI and Hive Client"
docsUrl="/features/schema-registry#publish-a-schema"
docsUrl={
recommendedAction === 'publish' ? '/features/schema-registry#publish-a-schema' : undefined
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

opted to hide for checks since there is a separate link to docs for them

}
>
{children}
</EmptyList>
Expand Down
12 changes: 6 additions & 6 deletions packages/web/app/src/pages/target-checks-single.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
import { Fragment, useCallback, useMemo, useState } from 'react';
import { format } from 'date-fns';
import { BadgeCheck, ChevronDown, GitCompareIcon, Loader2 } from 'lucide-react';
import { useMutation, useQuery } from 'urql';
Expand Down Expand Up @@ -281,20 +281,20 @@ function ConditionalBreakingChangesMetadataSection(props: {
{numberOfTargets <= 3 && (
<>
{allTargets.map((target, index) => (
<>
<Fragment key={target.slug}>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixes a react key warning

<span className="text-white">{target.slug}</span>
{index === allTargets.length - 1 ? null : ', '}
</>
</Fragment>
))}
</>
)}
{numberOfTargets > 3 && (
<>
{truncatedTargets.map((target, index) => (
<>
<Fragment key={target.slug}>
<span className="text-white">{target.slug}</span>
{index === truncatedTargets.length - 1 ? null : ', '}
</>
</Fragment>
))}
{' and '}
<Popover>
Expand All @@ -319,7 +319,7 @@ function ConditionalBreakingChangesMetadataSection(props: {
</PopoverContent>
</Popover>
</>
)}{' '}
)}
. <br />
Usage data ranges from{' '}
<span className="text-white">
Expand Down
98 changes: 69 additions & 29 deletions packages/web/app/src/pages/target-checks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useQuery } from 'urql';
import { Page, TargetLayout } from '@/components/layouts/target';
import { BadgeRounded } from '@/components/ui/badge';
Expand Down Expand Up @@ -230,7 +230,6 @@ function ChecksPageContent(props: {
const [paginationVariables, setPaginationVariables] = useState<Array<string | null>>(() => [
null,
]);
const [hasSchemaChecks, setHasSchemaChecks] = useState(false);
const navigate = useNavigate();
const { schemaCheckId } = useParams({
strict: false /* allows to read the $schemaCheckId param of its child route */,
Expand Down Expand Up @@ -266,9 +265,17 @@ function ChecksPageContent(props: {
);
}

if (!hasSchemaChecks && !!query.data?.target?.schemaChecks?.edges?.length) {
setHasSchemaChecks(true);
}
const [isLoading] = useDebouncedLoader(query.fetching || query.stale);

const [hasSchemaChecks, setHasSchemaChecks] = useState(
!!query.data?.target?.schemaChecks?.edges?.length,
);
useEffect(() => {
if (!query.stale && !query.fetching) {
setHasSchemaChecks(!!query.data?.target?.schemaChecks?.edges?.length);
}
}, [query.fetching, query.stale, !query.data?.target?.schemaChecks?.edges?.length]);

const hasFilteredSchemaChecks = !!query.data?.target?.filteredSchemaChecks?.edges?.length;
const hasActiveSchemaCheck = !!schemaCheckId;

Expand All @@ -290,9 +297,11 @@ function ChecksPageContent(props: {
});
};

const loadMore = (cursor: string) => setPaginationVariables(cursors => [...cursors, cursor]);

return (
<>
<div>
<div className={cn(!hasActiveSchemaCheck && !hasSchemaChecks && 'w-full')}>
<div className="w-[300px] py-6">
<Title>Schema Checks</Title>
<Subtitle>Recently checked schemas.</Subtitle>
Expand Down Expand Up @@ -337,40 +346,46 @@ function ChecksPageContent(props: {
schemaCheckId={schemaCheckId}
after={cursor}
isLastPage={index + 1 === paginationVariables.length}
onLoadMore={cursor => setPaginationVariables(cursors => [...cursors, cursor])}
onLoadMore={loadMore}
key={cursor ?? 'first'}
showOnlyChanged={showOnlyChanged}
showOnlyFailed={showOnlyFailed}
/>
))}
</div>
) : query.fetching || query.stale ? (
<Spinner />
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

moved outside this ternary so I could better control its rendering with the other elements.

Copy link
Member

Choose a reason for hiding this comment

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

Actually the whole ternaries look too bloated; maybe we should consider constructing the JSX parts outside of the return statement then combine 🫠

) : (
<div className="my-4 cursor-default text-center text-sm text-gray-400">
No schema checks found with the current filters
</div>
!query.fetching &&
!query.stale && (
<div className="my-4 cursor-default text-center text-sm text-gray-400">
No schema checks found with the current filters
</div>
)
)}
</div>
) : query.fetching ? (
<Spinner />
) : (
<div>
<div className="cursor-default text-sm">
{hasActiveSchemaCheck ? (
'List is empty'
) : (
<NoSchemaVersion
projectType={query.data?.target?.project.type ?? null}
recommendedAction="check"
/>
)}
!query.fetching &&
!query.stale && (
<div>
<div className="cursor-default text-sm">
{!hasActiveSchemaCheck && (
<NoSchemaVersion
projectType={query.data?.target?.project.type ?? null}
recommendedAction="check"
/>
)}
</div>
<DocsLink
href="/features/schema-registry#check-a-schema"
className="flex flex-row items-center"
>
Learn how to check your first schema
</DocsLink>
</div>
<DocsLink href="/features/schema-registry#check-a-schema">
{hasActiveSchemaCheck
? 'Check you first schema'
: 'Learn how to check your first schema with Hive CLI'}
</DocsLink>
)
)}
{isLoading && (
<div className="mt-4 flex w-full grow flex-col items-center">
<Spinner />
</div>
)}
</div>
Expand Down Expand Up @@ -410,3 +425,28 @@ export function TargetChecksPage(props: {
</>
);
}

const useDebouncedLoader = (isLoading: boolean, delay = 500) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also looked at using the use-debounce package's useDebounce helper but it debounces in both directions... I only want to delay the loading icon showing. Once loaded, it should hide immediately to keep the rendering as quick as possible.

Copy link
Member

Choose a reason for hiding this comment

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

Haven't deeply looked into, but looks like this lib offers a variety of options for scheduling 👀 Not sure whether it's worth adding a dep though
https://tanstack.com/pacer/latest/docs/framework/react/reference/index

const [showLoadingIcon, setShowLoadingIcon] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

useEffect(() => {
if (isLoading) {
// Start a timer to show the loading icon after the delay
timerRef.current = setTimeout(() => {
setShowLoadingIcon(true);
}, delay);
} else {
// If loading finishes, clear any pending timer and hide the icon
clearTimeout(timerRef.current);
setShowLoadingIcon(false);
}

// Cleanup function to clear the timer on unmount or if isLoading changes
return () => {
clearTimeout(timerRef.current);
};
}, [isLoading, delay]);

return [showLoadingIcon];
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't seem to have to be a tuple 😅

};
Loading