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
160 changes: 116 additions & 44 deletions src/components/DataLineage/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,40 @@ function pickLabel(r: { id?: string; name?: string; title?: string }): string {
return r.title || r.name || r.id || "(unnamed)";
}

const SKELETON_WIDTHS = [
"w-32",
"w-40",
"w-28",
"w-44",
"w-36",
"w-24",
"w-48",
"w-32",
];

function SkeletonRows({ count }: { count: number }) {
const rows = React.useMemo(
() =>
Array.from({ length: count }, () => ({
id: crypto.randomUUID(),
width:
SKELETON_WIDTHS[Math.floor(Math.random() * SKELETON_WIDTHS.length)],
})),
[count],
);
return (
<>
{rows.map((r) => (
<HSComp.SidebarMenuSubItem key={r.id}>
<div className="flex items-center h-7 pl-[11px]">
<HSComp.Skeleton className={`h-3.5 ${r.width}`} />
</div>
</HSComp.SidebarMenuSubItem>
))}
</>
);
}

function useViewItems() {
const client = useAidboxClient();
return useQuery<SidebarItem[]>({
Expand Down Expand Up @@ -133,12 +167,16 @@ function ViewsSection({
currentPath,
currentTab,
items,
loading,
skeletonCount,
}: {
open: boolean;
onOpenChange: (v: boolean) => void;
currentPath: string;
currentTab: string | undefined;
items: SidebarItem[];
loading: boolean;
skeletonCount: number;
}) {
const editSearch = {
tab: mapToViewTab(currentTab),
Expand Down Expand Up @@ -180,35 +218,39 @@ function ViewsSection({
</div>
<HSComp.CollapsibleContent>
<HSComp.SidebarMenuSub>
{items.length === 0 && (
{loading && skeletonCount > 0 && (
<SkeletonRows count={skeletonCount} />
)}
{!loading && items.length === 0 && (
<HSComp.SidebarMenuSubItem>
<div className="pl-[11px] py-1 text-xs italic text-text-tertiary">
No views
</div>
</HSComp.SidebarMenuSubItem>
)}
{items.map((it) => {
const active = currentPath === `/analytics/views/edit/${it.id}`;
return (
<HSComp.SidebarMenuSubItem key={it.id}>
<HoverTooltip description={it.description}>
<HSComp.SidebarMenuSubButton
isActive={active}
asChild
className="text-xs font-normal pl-[11px] data-[active=true]:bg-bg-tertiary data-[active=true]:hover:bg-bg-tertiary"
>
<Link
to="/analytics/views/edit/$id"
params={{ id: it.id }}
search={editSearch}
{!loading &&
items.map((it) => {
const active = currentPath === `/analytics/views/edit/${it.id}`;
return (
<HSComp.SidebarMenuSubItem key={it.id}>
<HoverTooltip description={it.description}>
<HSComp.SidebarMenuSubButton
isActive={active}
asChild
className="text-xs font-normal pl-[11px] data-[active=true]:bg-bg-tertiary data-[active=true]:hover:bg-bg-tertiary"
>
<span className="truncate">{it.label}</span>
</Link>
</HSComp.SidebarMenuSubButton>
</HoverTooltip>
</HSComp.SidebarMenuSubItem>
);
})}
<Link
to="/analytics/views/edit/$id"
params={{ id: it.id }}
search={editSearch}
>
<span className="truncate">{it.label}</span>
</Link>
</HSComp.SidebarMenuSubButton>
</HoverTooltip>
</HSComp.SidebarMenuSubItem>
);
})}
</HSComp.SidebarMenuSub>
</HSComp.CollapsibleContent>
</HSComp.Collapsible>
Expand All @@ -222,12 +264,16 @@ function QueriesSection({
currentPath,
currentTab,
items,
loading,
skeletonCount,
}: {
open: boolean;
onOpenChange: (v: boolean) => void;
currentPath: string;
currentTab: string | undefined;
items: SidebarItem[];
loading: boolean;
skeletonCount: number;
}) {
const editSearch = {
tab: mapToQueryTab(currentTab),
Expand Down Expand Up @@ -269,35 +315,40 @@ function QueriesSection({
</div>
<HSComp.CollapsibleContent>
<HSComp.SidebarMenuSub>
{items.length === 0 && (
{loading && skeletonCount > 0 && (
<SkeletonRows count={skeletonCount} />
)}
{!loading && items.length === 0 && (
<HSComp.SidebarMenuSubItem>
<div className="pl-[11px] py-1 text-xs italic text-text-tertiary">
No queries
</div>
</HSComp.SidebarMenuSubItem>
)}
{items.map((it) => {
const active = currentPath === `/analytics/queries/edit/${it.id}`;
return (
<HSComp.SidebarMenuSubItem key={it.id}>
<HoverTooltip description={it.description}>
<HSComp.SidebarMenuSubButton
isActive={active}
asChild
className="text-xs font-normal pl-[11px] data-[active=true]:bg-bg-tertiary data-[active=true]:hover:bg-bg-tertiary"
>
<Link
to="/analytics/queries/edit/$id"
params={{ id: it.id }}
search={editSearch}
{!loading &&
items.map((it) => {
const active =
currentPath === `/analytics/queries/edit/${it.id}`;
return (
<HSComp.SidebarMenuSubItem key={it.id}>
<HoverTooltip description={it.description}>
<HSComp.SidebarMenuSubButton
isActive={active}
asChild
className="text-xs font-normal pl-[11px] data-[active=true]:bg-bg-tertiary data-[active=true]:hover:bg-bg-tertiary"
>
<span className="truncate">{it.label}</span>
</Link>
</HSComp.SidebarMenuSubButton>
</HoverTooltip>
</HSComp.SidebarMenuSubItem>
);
})}
<Link
to="/analytics/queries/edit/$id"
params={{ id: it.id }}
search={editSearch}
>
<span className="truncate">{it.label}</span>
</Link>
</HSComp.SidebarMenuSubButton>
</HoverTooltip>
</HSComp.SidebarMenuSubItem>
);
})}
</HSComp.SidebarMenuSub>
</HSComp.CollapsibleContent>
</HSComp.Collapsible>
Expand All @@ -323,8 +374,25 @@ export function DataLineageSidebar() {
defaultValue: true,
getInitialValueInEffect: false,
});
const [viewsCount, setViewsCount] = useLocalStorage<number | null>({
key: "data-lineage-sidebar:views-count",
defaultValue: null,
getInitialValueInEffect: false,
});
const [queriesCount, setQueriesCount] = useLocalStorage<number | null>({
key: "data-lineage-sidebar:queries-count",
defaultValue: null,
getInitialValueInEffect: false,
});
const [searchQ, setSearchQ] = React.useState("");

React.useEffect(() => {
if (views.data) setViewsCount(views.data.length);
}, [views.data, setViewsCount]);
React.useEffect(() => {
if (queries.data) setQueriesCount(queries.data.length);
}, [queries.data, setQueriesCount]);

const filteredViews = filterItems(views.data ?? [], searchQ);
const filteredQueries = filterItems(queries.data ?? [], searchQ);

Expand All @@ -346,13 +414,17 @@ export function DataLineageSidebar() {
currentPath={currentPath}
currentTab={currentTab}
items={filteredViews}
loading={views.isLoading}
skeletonCount={viewsCount ?? 0}
/>
<QueriesSection
open={openQueries || searchQ.trim() !== ""}
onOpenChange={setOpenQueries}
currentPath={currentPath}
currentTab={currentTab}
items={filteredQueries}
loading={queries.isLoading}
skeletonCount={queriesCount ?? 0}
/>
</HSComp.SidebarMenu>
</HSComp.SidebarContent>
Expand Down
1 change: 1 addition & 0 deletions src/components/ResourceEditor/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export const DeleteButton = ({
onSuccess: (_resource, _variables, _onMutateResult, _context) => {
HSComp.toast.success("Saved", defaultToastPlacement);
invalidateDataLineageSidebar(queryClient, resourceType);
window.dispatchEvent(new Event("aidbox-resource-deleted"));
if (onDeleted) {
onDeleted();
return;
Expand Down
2 changes: 2 additions & 0 deletions src/components/ResourceEditor/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ export const ResourceEditorPage = ({
};
try {
await deleteResource(client, resourceType, id);
setEditDirty(false);
window.dispatchEvent(new Event("aidbox-resource-deleted"));
if (onDeleted) {
onDeleted();
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/components/SQLQueryBuilder/lineage/detail-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ export function LineageDetailPanel({
<PanelHeader
icon={<FileCode2 size={14} />}
label="Query"
color="text-text-brand-primary"
color="text-text-warning-primary"
onClose={onClose}
/>
<div className="flex flex-col gap-1">
Expand Down
4 changes: 2 additions & 2 deletions src/components/SQLQueryBuilder/lineage/nodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ export function SQLQueryNode({ id, data, selected }: AnyNodeProps) {
const headerInner = (
<>
<div className="flex items-center gap-2">
<FileCode2 size={14} className="text-text-brand-primary shrink-0" />
<span className="font-mono text-xs text-text-brand-primary uppercase">
<FileCode2 size={14} className="text-text-warning-primary shrink-0" />
<span className="font-mono text-xs text-text-warning-primary uppercase">
Query
</span>
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/components/SQLQueryBuilder/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export function SQLQueryProvider({
const [baselineHash, setBaselineHash] = React.useState<string>(() =>
computeLibraryHash(initialResource as SQLLibrary),
);
const isDirty = computeLibraryHash(library) !== baselineHash;
const isDeletedRef = React.useRef(false);
const isDirty =
!isDeletedRef.current && computeLibraryHash(library) !== baselineHash;
const isDirtyRef = React.useRef(false);
isDirtyRef.current = isDirty;

Expand All @@ -55,6 +57,14 @@ export function SQLQueryProvider({
isDirtyRef.current = false;
}
}, []);
React.useEffect(() => {
const handler = () => {
isDeletedRef.current = true;
isDirtyRef.current = false;
};
window.addEventListener("aidbox-resource-deleted", handler);
return () => window.removeEventListener("aidbox-resource-deleted", handler);
}, []);
const [runResult, setRunResult] = React.useState<RunResult | null>(null);
const [runError, setRunError] =
React.useState<HSComp.OperationOutcome | null>(null);
Expand Down
Loading