Skip to content

Commit f9c23bd

Browse files
committed
feat: virtualize all connections page (#13548)
1 parent 9717c1c commit f9c23bd

26 files changed

+251
-122
lines changed

airbyte-webapp/src/components/EntityTable/ConnectionTable.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { CellContext, ColumnDefTemplate, createColumnHelper } from "@tanstack/react-table";
2-
import React, { useMemo } from "react";
2+
import React, { useContext, useMemo } from "react";
33
import { FormattedMessage } from "react-intl";
44

55
import { Link } from "components/ui/Link";
6+
import { ScrollParentContext } from "components/ui/ScrollParent";
67
import { Table } from "components/ui/Table";
78

89
import { useCurrentWorkspaceLink } from "area/workspace/utils";
@@ -133,6 +134,7 @@ const ConnectionTable: React.FC<ConnectionTableProps> = ({ data, entity, variant
133134
[columnHelper, entity, EntityNameCell]
134135
);
135136

137+
const customScrollParent = useContext(ScrollParentContext);
136138
return (
137139
<Table
138140
rowId="connectionId"
@@ -143,6 +145,10 @@ const ConnectionTable: React.FC<ConnectionTableProps> = ({ data, entity, variant
143145
columnVisibility={{ "stream-status": streamCentricUIEnabled }}
144146
className={styles.connectionsTable}
145147
initialSortBy={[{ id: "entityName", desc: false }]}
148+
virtualized={!!customScrollParent}
149+
virtualizedProps={{
150+
customScrollParent: customScrollParent ?? undefined,
151+
}}
146152
/>
147153
);
148154
};

airbyte-webapp/src/components/destination/DestinationConnectionTable/DestinationConnectionTable.module.scss

-5
This file was deleted.

airbyte-webapp/src/components/destination/DestinationConnectionTable/DestinationConnectionTable.tsx

+7-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React from "react";
22

33
import { ConnectionTable } from "components/EntityTable";
44
import { getConnectionTableData } from "components/EntityTable/utils";
5+
import { Box } from "components/ui/Box";
6+
import { ScrollParent } from "components/ui/ScrollParent";
57

68
import { WebBackendConnectionListItem } from "core/api/types/AirbyteClient";
79

8-
import styles from "./DestinationConnectionTable.module.scss";
9-
1010
interface IProps {
1111
connections: WebBackendConnectionListItem[];
1212
}
@@ -15,8 +15,10 @@ export const DestinationConnectionTable: React.FC<IProps> = ({ connections }) =>
1515
const data = getConnectionTableData(connections, "destination");
1616

1717
return (
18-
<div className={styles.content}>
19-
<ConnectionTable data={data} entity="destination" />
20-
</div>
18+
<ScrollParent>
19+
<Box m="xl" mt="none">
20+
<ConnectionTable data={data} entity="destination" />
21+
</Box>
22+
</ScrollParent>
2123
);
2224
};

airbyte-webapp/src/components/ui/Box/Box.tsx

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import classNames from "classnames";
2-
import React, { PropsWithChildren } from "react";
2+
import React, { forwardRef, PropsWithChildren } from "react";
33

44
import styles from "./Box.module.scss";
55

@@ -34,13 +34,11 @@ function toClassName(key: keyof Omit<BoxProps, "className" | "as" | "data-testid
3434

3535
const keys = ["m", "my", "mx", "mt", "mr", "mb", "ml", "p", "py", "px", "pt", "pr", "pb", "pl"] as const;
3636

37-
export const Box: React.FC<PropsWithChildren<BoxProps>> = ({
38-
as = "div",
39-
children,
40-
className: classNameProp,
41-
...props
42-
}) => {
43-
const className = classNames(classNameProp, ...keys.map((key) => toClassName(key, props[key])));
37+
export const Box = forwardRef<HTMLElement, PropsWithChildren<BoxProps>>(
38+
({ as = "div", children, className: classNameProp, ...props }, ref) => {
39+
const className = classNames(classNameProp, ...keys.map((key) => toClassName(key, props[key])));
4440

45-
return React.createElement(as, { className, children, "data-testid": props["data-testid"] });
46-
};
41+
return React.createElement(as, { className, children, "data-testid": props["data-testid"], ref });
42+
}
43+
);
44+
Box.displayName = "Box";

airbyte-webapp/src/components/ui/PageHeader/PageHeader.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import { FlexContainer } from "../Flex";
77
interface PageHeaderProps {
88
leftComponent: string | React.ReactNode;
99
endComponent?: React.ReactNode;
10+
className?: string;
1011
}
11-
export const PageHeader: React.FC<PageHeaderProps> = ({ leftComponent, endComponent }) => (
12+
export const PageHeader: React.FC<PageHeaderProps> = ({ leftComponent, endComponent, className }) => (
1213
<FlexContainer
13-
className={classNames(styles.container)}
14+
className={classNames(styles.container, className)}
1415
alignItems="center"
1516
justifyContent="space-between"
1617
data-testid="page-header-container"

airbyte-webapp/src/components/ui/PageHeader/PageHeaderWithNavigation.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import { FlexContainer } from "../Flex";
88

99
interface PageHeaderWithNavigationProps {
1010
breadcrumbsData: BreadcrumbsDataItem[];
11+
className?: string;
1112
}
1213

1314
export const PageHeaderWithNavigation: React.FC<PropsWithChildren<PageHeaderWithNavigationProps>> = ({
1415
breadcrumbsData,
16+
className,
1517
children,
1618
}) => {
1719
return (
18-
<FlexContainer direction="column" gap="none" className={styles.container}>
20+
<FlexContainer direction="column" gap="none" className={classNames(styles.container, className)}>
1921
<Box px="xl" className={classNames(styles.section, styles.breadcrumbs)}>
2022
<Breadcrumbs data={breadcrumbsData} />
2123
</Box>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.container {
2+
overflow-y: auto;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import classnames from "classnames";
2+
import React from "react";
3+
4+
import styles from "./ScrollParent.module.scss";
5+
6+
export const ScrollParentContext = React.createContext<HTMLDivElement | null>(null);
7+
8+
interface ScrollParentProps<T> {
9+
as?: T extends React.ElementType ? T : never;
10+
props?: T extends React.ElementType ? React.ComponentPropsWithoutRef<T> : never;
11+
}
12+
13+
export const ScrollParent = <T = "div",>({ children, as, props }: React.PropsWithChildren<ScrollParentProps<T>>) => {
14+
const [ref, setRef] = React.useState<HTMLDivElement | null>(null);
15+
const Component = as || "div";
16+
17+
const { className, ...rest } = props || ({} as React.ComponentPropsWithoutRef<"div">);
18+
return (
19+
<ScrollParentContext.Provider value={ref}>
20+
<Component className={classnames(className, styles.container)} {...rest} ref={setRef}>
21+
{children}
22+
</Component>
23+
</ScrollParentContext.Provider>
24+
);
25+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ScrollParent, ScrollParentContext } from "./ScrollParent";
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
.container {
2+
display: grid;
3+
grid-template-columns: 1fr;
4+
grid-template-rows: auto 1fr;
5+
height: 100%;
6+
grid-template-areas:
7+
"page-header"
8+
"page-content";
9+
}
10+
11+
.pageHeader {
12+
grid-area: page-header;
13+
}
14+
15+
.pageBody {
16+
grid-area: page-content;
17+
overflow-y: auto;
18+
}
19+
120
.alignSelfStart {
221
align-self: flex-start;
322
}

airbyte-webapp/src/pages/connections/AllConnectionsPage/AllConnectionsPage.tsx

+40-37
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import React, { Suspense } from "react";
22
import { FormattedMessage } from "react-intl";
33
import { useNavigate } from "react-router-dom";
44

5-
import { LoadingPage, MainPageWithScroll } from "components";
5+
import { LoadingPage } from "components";
66
import { ConnectionOnboarding } from "components/connection/ConnectionOnboarding";
77
import { HeadTitle } from "components/HeadTitle";
8+
import { Box } from "components/ui/Box";
89
import { Button } from "components/ui/Button";
910
import { FlexContainer, FlexItem } from "components/ui/Flex";
1011
import { Heading } from "components/ui/Heading";
1112
import { PageHeader } from "components/ui/PageHeader";
13+
import { ScrollParent } from "components/ui/ScrollParent";
1214

1315
import { useCurrentWorkspace, useCurrentWorkspaceState } from "core/api";
1416
import { PageTrackingCodes, useTrackPage } from "core/services/analytics";
@@ -36,43 +38,44 @@ export const AllConnectionsPage: React.FC = () => {
3638
<>
3739
<HeadTitle titles={[{ id: "sidebar.connections" }]} />
3840
{hasConnections ? (
39-
<MainPageWithScroll
40-
softScrollEdge={false}
41-
pageTitle={
42-
<PageHeader
43-
leftComponent={
44-
<FlexContainer direction="column">
45-
<FlexItem>
46-
<Heading as="h1" size="lg">
47-
<FormattedMessage id="sidebar.connections" />
48-
</Heading>
49-
</FlexItem>
50-
<FlexItem>
51-
<Suspense fallback={null}>
52-
<ConnectionsSummary />
53-
</Suspense>
54-
</FlexItem>
55-
</FlexContainer>
56-
}
57-
endComponent={
58-
<FlexItem className={styles.alignSelfStart}>
59-
<Button
60-
disabled={!canCreateConnection}
61-
icon="plus"
62-
variant="primary"
63-
size="sm"
64-
onClick={() => onCreateClick()}
65-
data-testid="new-connection-button"
66-
>
67-
<FormattedMessage id="connection.newConnection" />
68-
</Button>
41+
<div className={styles.container}>
42+
<PageHeader
43+
className={styles.pageHeader}
44+
leftComponent={
45+
<FlexContainer direction="column">
46+
<FlexItem>
47+
<Heading as="h1" size="lg">
48+
<FormattedMessage id="sidebar.connections" />
49+
</Heading>
6950
</FlexItem>
70-
}
71-
/>
72-
}
73-
>
74-
<ConnectionsListCard />
75-
</MainPageWithScroll>
51+
<FlexItem>
52+
<Suspense fallback={null}>
53+
<ConnectionsSummary />
54+
</Suspense>
55+
</FlexItem>
56+
</FlexContainer>
57+
}
58+
endComponent={
59+
<FlexItem className={styles.alignSelfStart}>
60+
<Button
61+
disabled={!canCreateConnection}
62+
icon="plus"
63+
variant="primary"
64+
size="sm"
65+
onClick={() => onCreateClick()}
66+
data-testid="new-connection-button"
67+
>
68+
<FormattedMessage id="connection.newConnection" />
69+
</Button>
70+
</FlexItem>
71+
}
72+
/>
73+
<ScrollParent props={{ className: styles.pageBody }}>
74+
<Box m="xl">
75+
<ConnectionsListCard />
76+
</Box>
77+
</ScrollParent>
78+
</div>
7679
) : (
7780
<ConnectionOnboarding onCreate={onCreateClick} />
7881
)}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
1+
@use "scss/colors";
2+
@use "scss/variables";
3+
@use "scss/z-indices";
4+
5+
$filters-fixed-height: 108px;
6+
17
.connections {
28
min-width: 100%;
39
width: fit-content;
10+
position: relative;
11+
}
12+
13+
.filters {
14+
position: sticky;
15+
top: 0;
16+
z-index: z-indices.$tableStickyHeader;
17+
background-color: colors.$foreground;
18+
height: $filters-fixed-height;
19+
}
20+
21+
.table {
22+
thead {
23+
top: $filters-fixed-height;
24+
}
425
}

airbyte-webapp/src/pages/connections/AllConnectionsPage/ConnectionsListCard.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,19 @@ export const ConnectionsListCard = () => {
9797

9898
return (
9999
<Card noPadding className={styles.connections}>
100-
<ConnectionsFilters
101-
connections={connections}
102-
searchFilter={filterValues.search}
103-
setSearchFilter={(search) => setFilterValue("search", search)}
104-
filterValues={filterValues}
105-
setFilterValue={setFilterValue}
106-
resetFilters={resetFilters}
107-
/>
108-
<ConnectionsTable connections={filteredConnections} variant="white" />
100+
<div className={styles.filters}>
101+
<ConnectionsFilters
102+
connections={connections}
103+
searchFilter={filterValues.search}
104+
setSearchFilter={(search) => setFilterValue("search", search)}
105+
filterValues={filterValues}
106+
setFilterValue={setFilterValue}
107+
resetFilters={resetFilters}
108+
/>
109+
</div>
110+
<div className={styles.table}>
111+
<ConnectionsTable connections={filteredConnections} variant="white" />
112+
</div>
109113
{filteredConnections.length === 0 && (
110114
<Box pt="xl" pb="lg">
111115
<Text bold color="grey" align="center">

airbyte-webapp/src/pages/connections/StreamStatusPage/StreamStatusPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const StreamStatusPage = () => {
2525
<ScrollableContainer ref={ref} className={styles.container}>
2626
<ConnectionStatusMessages />
2727
<ConnectionSyncStatusCard />
28-
<StreamsList ref={ref} />
28+
<StreamsList customScrollParent={ref.current} />
2929
</ScrollableContainer>
3030
</StreamsListContextProvider>
3131
</ConnectionSyncContextProvider>

airbyte-webapp/src/pages/connections/StreamStatusPage/StreamsList.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe("StreamsList", () => {
4848
<TestWrapper>
4949
<VirtuosoMockContext.Provider value={{ viewportHeight: 1000, itemHeight: 50 }}>
5050
<StreamsListContextProvider>
51-
<StreamsList />
51+
<StreamsList customScrollParent={null} />
5252
</StreamsListContextProvider>
5353
</VirtuosoMockContext.Provider>
5454
</TestWrapper>

airbyte-webapp/src/pages/connections/StreamStatusPage/StreamsList.tsx

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createColumnHelper } from "@tanstack/react-table";
22
import classNames from "classnames";
3-
import { useRef, forwardRef, useMemo } from "react";
3+
import { useRef, useMemo } from "react";
44
import { FormattedMessage } from "react-intl";
55
import { useToggle } from "react-use";
66

@@ -27,7 +27,7 @@ import styles from "./StreamsList.module.scss";
2727
import { StatusCell } from "./StreamsListStatusCell";
2828
import { StreamsListSubtitle } from "./StreamsListSubtitle";
2929

30-
export const StreamsList = forwardRef<HTMLDivElement>((_, outerRef) => {
30+
export const StreamsList: React.FC<{ customScrollParent: HTMLElement | null }> = ({ customScrollParent }) => {
3131
const [showRelativeTime, setShowRelativeTime] = useToggle(true);
3232
const connection = useCurrentConnection();
3333
const streamEntries = useUiStreamStates(connection.connectionId);
@@ -114,9 +114,6 @@ export const StreamsList = forwardRef<HTMLDivElement>((_, outerRef) => {
114114
recordsLoaded,
115115
} = useConnectionStatus(connection.connectionId);
116116

117-
const customScrollParent =
118-
typeof outerRef !== "function" && outerRef && outerRef.current ? outerRef.current : undefined;
119-
120117
return (
121118
<Card noPadding>
122119
<Box p="xl" className={styles.cardHeader}>
@@ -151,10 +148,9 @@ export const StreamsList = forwardRef<HTMLDivElement>((_, outerRef) => {
151148
}
152149
sorting={false}
153150
virtualized
154-
virtualizedProps={{ customScrollParent, useWindowScroll: true }}
151+
virtualizedProps={{ customScrollParent: customScrollParent ?? undefined, useWindowScroll: true }}
155152
/>
156153
</FlexContainer>
157154
</Card>
158155
);
159-
});
160-
StreamsList.displayName = "StreamsList";
156+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.fullHeight {
2+
height: 100%;
3+
}

0 commit comments

Comments
 (0)