Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Pagination #806

Merged
merged 68 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
92c1622
Setup shared component
niwsa Jan 17, 2024
85ca921
Add type overrides
niwsa Jan 17, 2024
dc5afb9
Expand Button prop to take in supported HTML attributes
niwsa Jan 17, 2024
6323924
WIP `Paginate`
niwsa Jan 17, 2024
00e055f
Type updates
niwsa Jan 17, 2024
3b63f3e
[Paginate] Consolidate callback prop into single `handlePageChange`
niwsa Jan 18, 2024
81fa6ec
[ConnectionList] Use <Paginate> component
niwsa Jan 18, 2024
3a6da60
[Wrapper] Handle pageChange action
niwsa Jan 18, 2024
a8801e9
Merge branch 'main' into paginate-support
niwsa Jan 31, 2024
39c7995
Merge branch 'main' into paginate-support
niwsa Feb 6, 2024
c9edc67
Update qs using `window.pushState` API
niwsa Feb 7, 2024
a9d3e64
Refetch list based on current offset from URL
niwsa Feb 7, 2024
58f5390
Rename identifier
niwsa Feb 7, 2024
ba4960a
Read the offset from the URL on mount
niwsa Feb 7, 2024
b3d23aa
Fix state shadowing (mitosis gotcha)
niwsa Feb 7, 2024
b35c358
Add missing offset state update
niwsa Feb 7, 2024
87b9034
Fix qs name for pagination, add pageLimit
niwsa Feb 7, 2024
76b2189
Support for toggling Pagination display
niwsa Feb 7, 2024
da37619
Merge branch 'main' into paginate-support
niwsa Feb 7, 2024
b92ff19
Tweak pushState call for fixing back nav
niwsa Feb 7, 2024
52e6c9e
Sync offset from query params with `onMount` hook
niwsa Feb 10, 2024
e4d626e
Trigger pageChange callback with `onUpdate` hook
niwsa Feb 10, 2024
259f8d3
Cleanup routing logic
niwsa Feb 10, 2024
448d451
Refactor
niwsa Feb 10, 2024
6a667f1
[ConnectionList] Fetch data on offset change
niwsa Feb 10, 2024
6f300e3
WIP Split refetch and route change callbacks for more predictability
niwsa Feb 10, 2024
88add95
Turn off vue2 for now
niwsa Feb 11, 2024
98394fe
Avoid identical names for state and variables - should fix vue2 build…
niwsa Feb 11, 2024
b1a2f19
Listen for browser back/forward interactions using popstate API
niwsa Feb 12, 2024
1becfa2
Button style fixes
niwsa Feb 12, 2024
e7a8d56
Paginate button style fixes
niwsa Feb 12, 2024
3035c5d
[WIP] Context seems to be of help
niwsa Feb 12, 2024
b07ce41
Merge branch 'main' into paginate-support
niwsa Feb 13, 2024
f44dfcc
Use getter for proper itemsPerPage value with fallback to default
niwsa Feb 13, 2024
cb3e195
Hide pagination nav if total items are contained in the first page
niwsa Feb 13, 2024
40d8c32
Cleanup context test files
niwsa Feb 13, 2024
91fa64c
Simplify by removing children prop
niwsa Feb 13, 2024
5f658e2
Table component support no more results
niwsa Feb 13, 2024
7ecdc05
Wrapper around table for using context
niwsa Feb 13, 2024
92f0c59
Support both Paginated/non paginated listings
niwsa Feb 13, 2024
9f8d589
Avoid duplicate fetch, ensure that paginated fetch and hook fetch don…
niwsa Feb 13, 2024
efc323d
Cleanup unused props
niwsa Feb 14, 2024
98f5c54
Consolidate non offset params in baseFetchUrl
niwsa Feb 14, 2024
8b9fb84
Type updates
niwsa Feb 14, 2024
507c2ba
Fix hover color for outline button
niwsa Feb 14, 2024
5406197
Merge branch 'main' into paginate-support
niwsa Feb 14, 2024
351efe5
Type updates
niwsa Feb 19, 2024
0b40b92
Use pageTokenMap to fetch data
niwsa Feb 19, 2024
bd32276
Extract pageToken from response header
niwsa Feb 19, 2024
88d5186
Refactor into Paginated and Non Paginated table
niwsa Feb 19, 2024
d4c7a20
Merge branch 'main' into paginate-support
niwsa Feb 19, 2024
c244592
Added left/right arrow in prev next buttons, style fixes
niwsa Feb 19, 2024
6fd0772
Merge branch 'main' into paginate-support
niwsa Feb 19, 2024
dac0041
Tweak types
niwsa Feb 19, 2024
700370c
Tweak type
niwsa Feb 19, 2024
619d9b2
Fix type error
niwsa Feb 19, 2024
4901df7
[Dsync] Support for pagination
niwsa Feb 20, 2024
acc2477
Fine tune types
niwsa Feb 20, 2024
4a3b57d
Cleanup
niwsa Feb 20, 2024
ee67cd3
Type tweak
niwsa Feb 20, 2024
acf06e1
System SSO badge fixes
niwsa Feb 20, 2024
9e3e680
Update mitosis
niwsa Feb 20, 2024
122f898
Merge branch 'main' into paginate-support
niwsa Feb 20, 2024
ac15405
Remove vue2 generation
niwsa Feb 20, 2024
539ae04
Spacing in badge display
niwsa Feb 20, 2024
e5ed068
Remove vue2 scripts
niwsa Feb 21, 2024
c6127bf
Add space only if required
niwsa Feb 21, 2024
5607e8f
Fix conditional render
niwsa Feb 21, 2024
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
2 changes: 1 addition & 1 deletion mitosis.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const VUE_OPTIONS = {
api: 'composition',
};

const components = ['Login', 'CreateOIDCConnection', 'CreateSAMLConnection', 'ConnectionList'];
const components = ['Login', 'CreateOIDCConnection', 'CreateSAMLConnection'];
deepakprabhakara marked this conversation as resolved.
Show resolved Hide resolved

const isMitosisNode = (x) => x && x['@type'] === '@builder.io/mitosis/node';

Expand Down
3 changes: 3 additions & 0 deletions overrides/react/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,14 @@ export interface ConfirmationPromptProps {
cancelCallback: (event: Event) => void;
}

export type PageToken = string | null;

export type PaginatePayload = { offset: number; limit: number };

export interface PaginateProps {
handlePageChange?: (payload: Partial<PaginatePayload>) => void;
reFetch: (payload: PaginatePayload) => any;
pageTokenMap: Record<number, PageToken>;
itemsPerPage?: number;
currentPageItemsCount: number;
children?: any;
Expand Down
3 changes: 3 additions & 0 deletions overrides/svelte/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,14 @@ export interface ConfirmationPromptProps {
cancelCallback: (event: Event) => void;
}

export type PageToken = string | null;

export type PaginatePayload = { offset: number; limit: number };

export interface PaginateProps {
handlePageChange?: (payload: Partial<PaginatePayload>) => void;
reFetch: (payload: PaginatePayload) => any;
pageTokenMap: Record<number, PageToken>;
itemsPerPage?: number;
currentPageItemsCount: number;
children?: any;
Expand Down
3 changes: 3 additions & 0 deletions overrides/vue/vue3/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,14 @@ export interface ConfirmationPromptProps {
cancelCallback: (event: Event) => void;
}

export type PageToken = string | null;

export type PaginatePayload = { offset: number; limit: number };

export interface PaginateProps {
handlePageChange?: (payload: Partial<PaginatePayload>) => void;
reFetch: (payload: PaginatePayload) => any;
pageTokenMap: Record<number, PageToken>;
itemsPerPage?: number;
currentPageItemsCount: number;
children?: any;
Expand Down
21 changes: 17 additions & 4 deletions src/shared/Paginate/index.lite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,33 @@ export default function Paginate(props: PaginateProps) {
return props.currentPageItemsCount < state._itemsPerPage;
},
handlePreviousClick() {
const newOffset = state._offset - state._itemsPerPage;
const currentOffset = state._offset;
const newOffset = currentOffset - state._itemsPerPage;
state._offset = newOffset;

// Update query string in URL
typeof props.handlePageChange === 'function' && props.handlePageChange({ offset: newOffset });
// Trigger data re-fetch with new offset
typeof props.reFetch === 'function' && props.reFetch({ offset: newOffset, limit: state._itemsPerPage });
typeof props.reFetch === 'function' &&
props.reFetch({
offset: newOffset,
limit: state._itemsPerPage,
pageToken: props.pageTokenMap[newOffset - state._itemsPerPage],
});
},
handleNextClick() {
const newOffset = state._offset + state._itemsPerPage;
const currentOffset = state._offset;
const newOffset = currentOffset + state._itemsPerPage;
state._offset = newOffset;
// Update query string in URL
typeof props.handlePageChange === 'function' && props.handlePageChange({ offset: newOffset });
// Trigger data re-fetch with new offset
typeof props.reFetch === 'function' && props.reFetch({ offset: newOffset, limit: state._itemsPerPage });
typeof props.reFetch === 'function' &&
props.reFetch({
offset: newOffset,
limit: state._itemsPerPage,
pageToken: props.pageTokenMap[currentOffset],
});
},
});

Expand Down
31 changes: 31 additions & 0 deletions src/shared/Table/non-paginated.lite.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Show } from '@builder.io/mitosis';
import EmptyState from '../EmptyState/index.lite';
import Table from './index.lite';
import { TableProps } from '../types';
import styles from './index.module.css';

type NonPaginatedTableProps = {
cols: TableProps['cols'];
data: TableProps['data'];
actions: TableProps['actions'];
tableProps?: TableProps;
showErrorComponent: boolean;
errorMessage: string;
emptyStateMessage: string;
};

export default function NonPaginatedTable(props: NonPaginatedTableProps) {
return (
<Show
when={props.data?.length > 0}
else={
<Show when={props.showErrorComponent} else={<EmptyState title={props.emptyStateMessage} />}>
<EmptyState title={props.errorMessage} variant='error' />
</Show>
}>
<div class={styles.tableContainer}>
<Table cols={props.cols} data={props.data} actions={props.actions} {...props.tableProps} />
</div>
</Show>
);
}
20 changes: 12 additions & 8 deletions src/shared/Table/paginated.lite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import EmptyState from '../EmptyState/index.lite';
import Table from './index.lite';
import { TableProps } from '../types';
import PaginateContext from '../Paginate/paginate.context.lite';
import styles from './index.module.css';

type PaginatedTableProps = {
cols: TableProps['cols'];
Expand All @@ -11,6 +12,7 @@ type PaginatedTableProps = {
tableProps?: TableProps;
showErrorComponent: boolean;
errorMessage: string;
emptyStateMessage: string;
};

export default function PaginatedTable(props: PaginatedTableProps) {
Expand All @@ -29,17 +31,19 @@ export default function PaginatedTable(props: PaginatedTableProps) {
<Show
when={!state.showEmptyData}
else={
<Show when={props.showErrorComponent} else={<EmptyState title='No connections found.' />}>
<Show when={props.showErrorComponent} else={<EmptyState title={props.emptyStateMessage} />}>
<EmptyState title={props.errorMessage} variant='error' />
</Show>
}>
<Table
cols={props.cols}
data={props.data}
actions={props.actions}
noMoreResults={state.showNoMoreResults}
{...props.tableProps}
/>
<div class={styles.tableContainer}>
<Table
cols={props.cols}
data={props.data}
actions={props.actions}
noMoreResults={state.showNoMoreResults}
{...props.tableProps}
/>
</div>
</Show>
);
}
7 changes: 6 additions & 1 deletion src/shared/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async function parseResponseContent(response: Response) {
}

/** undefined for 204 No content */
type ApiSuccess<T> = T | undefined;
type ApiSuccess<T> = T | { data: T; pageToken: string } | undefined;

export type ApiResponse<T = any> = ApiSuccess<T> | { error: { message: string } };

Expand All @@ -19,12 +19,17 @@ export async function sendHTTPRequest<U = any>(url: string, options?: RequestIni
if (response.status === 204) {
return;
}
const pageToken = response.headers.get('jackson-pagetoken');
const responseContent = await parseResponseContent(response);

if (!response.ok) {
throw new ApiError(response.status, responseContent.error.message);
}

if (pageToken && typeof responseContent === 'object') {
return { data: responseContent, pageToken };
}

return responseContent;
} catch (error: any) {
const message = error.message || 'Something went wrong';
Expand Down
5 changes: 4 additions & 1 deletion src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,14 @@ export interface ConfirmationPromptProps {
cancelCallback: (event: Event) => void;
}

export type PaginatePayload = { offset: number; limit: number };
export type PageToken = string | null;

export type PaginatePayload = { offset: number; limit: number; pageToken?: PageToken };

export interface PaginateProps {
handlePageChange?: (payload: Partial<PaginatePayload>) => void;
reFetch: (payload: PaginatePayload) => any;
pageTokenMap: Record<number, PageToken>;
itemsPerPage?: number;
currentPageItemsCount: number;
children?: any;
Expand Down
86 changes: 46 additions & 40 deletions src/sso/connections/ConnectionList/index.lite.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { useStore, onUpdate, Show } from '@builder.io/mitosis';
import type { ConnectionData, ConnectionListProps, OIDCSSORecord, SAMLSSORecord } from '../types';
import LoadingContainer from '../../../shared/LoadingContainer/index.lite';
import EmptyState from '../../../shared/EmptyState/index.lite';
import cssClassAssembler from '../../utils/cssClassAssembler';
import defaultClasses from './index.module.css';
import Table from '../../../shared/Table/index.lite';
import { BadgeProps, PaginatePayload, TableProps } from '../../../shared/types';
import { BadgeProps, PageToken, PaginatePayload, TableProps } from '../../../shared/types';
import { sendHTTPRequest } from '../../../shared/http';
import Paginate from '../../../shared/Paginate/index.lite';
import { ITEMS_PER_PAGE_DEFAULT } from '../../../shared/Paginate/utils';
import PaginatedTable from '../../../shared/Table/paginated.lite';
import NonPaginatedTable from '../../../shared/Table/non-paginated.lite';

const DEFAULT_VALUES = {
isSettingsView: false,
Expand All @@ -20,6 +17,7 @@ export default function ConnectionList(props: ConnectionListProps) {
const state = useStore({
connectionListData: DEFAULT_VALUES.connectionListData,
isConnectionListLoading: true,
pageTokenMap: {} as Record<number, PageToken>,
showErrorComponent: false,
errorMessage: '',
get getUrl() {
Expand All @@ -31,11 +29,7 @@ export default function ConnectionList(props: ConnectionListProps) {
get itemsPerPage() {
return props.paginate?.itemsPerPage ?? ITEMS_PER_PAGE_DEFAULT;
},
get classes() {
return {
tableContainer: cssClassAssembler(props.classNames?.tableContainer, defaultClasses.tableContainer),
};
},

get colsToDisplay() {
return (props.cols || ['name', 'provider', 'tenant', 'product', 'type', 'status', 'actions']).map(
(_col) => {
Expand Down Expand Up @@ -116,6 +110,10 @@ export default function ConnectionList(props: ConnectionListProps) {
urlParams.set('product', params.product);
}

if (params.pageToken) {
urlParams.set('pageToken', params.pageToken);
}

if (params.displaySorted) {
urlParams.set('sort', 'true');
}
Expand All @@ -140,6 +138,10 @@ export default function ConnectionList(props: ConnectionListProps) {
},
});

function updateTokenMap(offset: number, token: PageToken) {
return { ...state.pageTokenMap, [offset]: token };
}

async function getFieldsData(url: string) {
state.isConnectionListLoading = true;
const data = await sendHTTPRequest<ConnectionData<SAMLSSORecord | OIDCSSORecord>[]>(url);
Expand All @@ -151,7 +153,9 @@ export default function ConnectionList(props: ConnectionListProps) {
state.errorMessage = data.error.message;
typeof props.errorCallback === 'function' && props.errorCallback(data.error.message);
} else {
const _connectionsListData = data.map((connection: ConnectionData<any>) => {
const isTokenizedPagination = typeof data === 'object' && 'pageToken' in data;
const _data = isTokenizedPagination ? data.data : data;
const _connectionsListData = _data.map((connection: ConnectionData<any>) => {
return {
...connection,
provider: state.connectionProviderName(connection),
Expand All @@ -160,20 +164,29 @@ export default function ConnectionList(props: ConnectionListProps) {
isSystemSSO: connection.isSystemSSO,
};
});

state.connectionListData = _connectionsListData;

typeof props.handleListFetchComplete === 'function' &&
props.handleListFetchComplete(_connectionsListData);

if (isTokenizedPagination) {
return data.pageToken;
}
}
}
}

function reFetch(payload: PaginatePayload) {
getFieldsData(
async function reFetch(payload: PaginatePayload) {
const pageToken = await getFieldsData(
state.listFetchUrl({
getUrl: state.baseFetchUrl,
...payload,
})
);
if (pageToken) {
state.pageTokenMap = updateTokenMap(payload.offset, pageToken);
}
}

onUpdate(() => {
Expand All @@ -189,36 +202,29 @@ export default function ConnectionList(props: ConnectionListProps) {
itemsPerPage={props.paginate?.itemsPerPage}
currentPageItemsCount={state.connectionListData.length}
handlePageChange={props.paginate?.handlePageChange}
reFetch={reFetch}>
<div class={state.classes.tableContainer}>
<PaginatedTable
cols={state.colsToDisplay}
data={state.connectionListData}
actions={state.actions}
showErrorComponent={state.showErrorComponent}
errorMessage={state.errorMessage}
tableProps={props.tableProps}
/>
</div>
reFetch={reFetch}
pageTokenMap={state.pageTokenMap}>
<PaginatedTable
cols={state.colsToDisplay}
data={state.connectionListData}
actions={state.actions}
showErrorComponent={state.showErrorComponent}
errorMessage={state.errorMessage}
emptyStateMessage='No connections found.'
tableProps={props.tableProps}
/>
</Paginate>
</Show>
<Show when={!state.isPaginated}>
<Show
when={state.connectionListData?.length > 0}
else={
<Show when={state.showErrorComponent} else={<EmptyState title='No connections found.' />}>
<EmptyState title={state.errorMessage} variant='error' />
</Show>
}>
<div class={state.classes.tableContainer}>
<Table
cols={state.colsToDisplay}
data={state.connectionListData}
actions={state.actions}
{...props.tableProps}
/>
</div>
</Show>
<NonPaginatedTable
cols={state.colsToDisplay}
data={state.connectionListData}
actions={state.actions}
showErrorComponent={state.showErrorComponent}
errorMessage={state.errorMessage}
emptyStateMessage='No connections found.'
tableProps={props.tableProps}
/>
</Show>
</LoadingContainer>
);
Expand Down
3 changes: 0 additions & 3 deletions src/sso/connections/ConnectionList/index.module.css

This file was deleted.