Skip to content
Open
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
15 changes: 15 additions & 0 deletions querybook/server/datasources/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ def get_board_by_id(board_id, environment_id):
)


@register(
"/board/public/all/",
methods=["GET"],
)
def get_all_public_boards(environment_id, limit=20, offset=0):
with DBSession() as session:
verify_environment_permission([environment_id])
public_boards = logic.get_all_public_boards(
environment_id=environment_id, limit=limit, offset=offset, session=session
)
return {
"boards": [public_board.id for public_board in public_boards],
}


@register(
"/board/",
methods=["POST"],
Expand Down
14 changes: 11 additions & 3 deletions querybook/server/logic/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,22 @@ def update_es_boards_by_id(board_id: int):


@with_session
def get_all_public_boards(environment_id, session=None):
return (
def get_all_public_boards(environment_id, limit=None, offset=None, session=None):
query = (
session.query(Board)
.filter(Board.public.is_(True))
.filter(Board.environment_id == environment_id)
.all()
.filter(Board.deleted_at.is_(None))
.order_by(Board.updated_at.desc())
)

if offset is not None and offset > 0:
query = query.offset(offset)
if limit is not None:
return query.limit(limit).all()
else:
return query.all()


@with_session
def update_board_item(id, session=None, **fields):
Expand Down
96 changes: 83 additions & 13 deletions querybook/webapp/components/PublicBoardPage/PublicBoardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';

import { BoardBoardItem } from 'components/Board/BoardBoardItem';
import { BoardPageContext, IBoardPageContextType } from 'context/BoardPage';
import { fetchBoardIfNeeded } from 'redux/board/action';
import { Dispatch, IStoreState } from 'redux/store/types';
import { AccentText } from 'ui/StyledText/StyledText';
import { BoardResource } from 'resource/board';
import { IStoreState } from 'redux/store/types';
import { AccentText, EmptyText } from 'ui/StyledText/StyledText';
import { Button } from 'ui/Button/Button';

export const PublicBoardPage: React.FunctionComponent = () => {
const dispatch: Dispatch = useDispatch();
const BOARDS_PER_PAGE = 20;

const board = useSelector((state: IStoreState) => state.board.boardById[0]);
React.useEffect(() => {
dispatch(fetchBoardIfNeeded(0));
}, [dispatch]);
export const PublicBoardPage: React.FunctionComponent = () => {
const environmentId = useSelector(
(state: IStoreState) => state.environment.currentEnvironmentId
);

const boardItemDOM = board?.boards?.map((boardId) => (
<BoardBoardItem boardId={boardId} key={boardId} />
));
const [boardIds, setBoardIds] = React.useState<number[]>([]);
const [loading, setLoading] = React.useState(false);
const [hasMore, setHasMore] = React.useState(true);
const offset = React.useRef(0);

const boardContextValue: IBoardPageContextType = React.useMemo(
() => ({
Expand All @@ -29,6 +30,75 @@ export const PublicBoardPage: React.FunctionComponent = () => {
[]
);

const loadMoreBoards = React.useCallback(async () => {
if (!environmentId || loading) {
return;
}

setLoading(true);
try {
const response = await BoardResource.getAllPublic(
environmentId,
BOARDS_PER_PAGE,
offset.current
);

const newBoardIds = response.data.boards as unknown as number[];

setBoardIds((prevIds) => {
const updated = [...prevIds, ...newBoardIds];
offset.current = updated.length;
return updated;
});
setHasMore(newBoardIds.length === BOARDS_PER_PAGE);
} catch (error) {
console.error('Failed to load public boards:', error);
} finally {
setLoading(false);
}
}, [environmentId]);

React.useEffect(() => {
if (environmentId && boardIds.length === 0 && !loading) {
offset.current = 0;
loadMoreBoards();
}
}, [environmentId, loadMoreBoards]);

// Reset state when environment changes
React.useEffect(() => {
setBoardIds([]);
setHasMore(true);
offset.current = 0;
}, [environmentId]);

const boardItemRenderer = React.useCallback(
(boardId: number) => <BoardBoardItem boardId={boardId} key={boardId} />,
[]
);

const boardItemDOM =
boardIds.length === 0 ? (
<EmptyText className="m24">No public lists found.</EmptyText>
) : (
<div>
{boardIds.map(boardItemRenderer)}
{hasMore && (
<div className="flex-center mt16">
<Button
onClick={() => {
loadMoreBoards();
}}
disabled={loading}
theme="text"
>
{loading ? 'Loading...' : 'Load More'}
</Button>
</div>
)}
</div>
);

return (
<BoardPageContext.Provider value={boardContextValue}>
<div className="Board">
Expand Down
7 changes: 7 additions & 0 deletions querybook/webapp/resource/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export const BoardResource = {
environment_id: environmentId,
}),

getAllPublic: (environmentId: number, limit: number, offset: number) =>
ds.fetch<IBoardRaw>(`/board/public/all/`, {
environment_id: environmentId,
limit,
offset,
}),

create: (
name: string,
environmentId: number,
Expand Down
Loading