This repository has been archived by the owner on Jan 16, 2025. It is now read-only.
forked from apache/superset
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revert delete unused files in custom Katalon plugin
- Loading branch information
1 parent
49d742d
commit 7879367
Showing
51 changed files
with
4,602 additions
and
2 deletions.
There are no files selected for viewing
397 changes: 397 additions & 0 deletions
397
superset-frontend/src/katalon/CustomDataTable/DataTable/DataTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,397 @@ | ||
/** | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
import React, { | ||
useCallback, | ||
useRef, | ||
ReactNode, | ||
HTMLProps, | ||
MutableRefObject, | ||
CSSProperties, | ||
} from 'react'; | ||
import { | ||
useTable, | ||
usePagination, | ||
useSortBy, | ||
useGlobalFilter, | ||
useColumnOrder, | ||
PluginHook, | ||
TableOptions, | ||
FilterType, | ||
IdType, | ||
Row, | ||
} from 'react-table'; | ||
import { matchSorter, rankings } from 'match-sorter'; | ||
import { typedMemo, usePrevious } from '@superset-ui/core'; | ||
import { isEqual } from 'lodash'; | ||
import GlobalFilter, { GlobalFilterProps } from './components/GlobalFilter'; | ||
import SelectPageSize, { | ||
SelectPageSizeProps, | ||
SizeOption, | ||
} from './components/SelectPageSize'; | ||
import SimplePagination from './components/Pagination'; | ||
import useSticky from './hooks/useSticky'; | ||
import { PAGE_SIZE_OPTIONS } from '../consts'; | ||
import { sortAlphanumericCaseInsensitive } from './utils/sortAlphanumericCaseInsensitive'; | ||
|
||
export interface DataTableProps<D extends object> extends TableOptions<D> { | ||
tableClassName?: string; | ||
searchInput?: boolean | GlobalFilterProps<D>['searchInput']; | ||
selectPageSize?: boolean | SelectPageSizeProps['selectRenderer']; | ||
pageSizeOptions?: SizeOption[]; // available page size options | ||
maxPageItemCount?: number; | ||
hooks?: PluginHook<D>[]; // any additional hooks | ||
width?: string | number; | ||
height?: string | number; | ||
serverPagination?: boolean; | ||
onServerPaginationChange: (pageNumber: number, pageSize: number) => void; | ||
serverPaginationData: { pageSize?: number; currentPage?: number }; | ||
pageSize?: number; | ||
noResults?: string | ((filterString: string) => ReactNode); | ||
sticky?: boolean; | ||
rowCount: number; | ||
wrapperRef?: MutableRefObject<HTMLDivElement>; | ||
onColumnOrderChange: () => void; | ||
} | ||
|
||
export interface RenderHTMLCellProps extends HTMLProps<HTMLTableCellElement> { | ||
cellContent: ReactNode; | ||
} | ||
|
||
const sortTypes = { | ||
alphanumeric: sortAlphanumericCaseInsensitive, | ||
}; | ||
|
||
// Be sure to pass our updateMyData and the skipReset option | ||
export default typedMemo(function DataTable<D extends object>({ | ||
tableClassName, | ||
columns, | ||
data, | ||
serverPaginationData, | ||
width: initialWidth = '100%', | ||
height: initialHeight = 300, | ||
pageSize: initialPageSize = 0, | ||
initialState: initialState_ = {}, | ||
pageSizeOptions = PAGE_SIZE_OPTIONS, | ||
maxPageItemCount = 9, | ||
sticky: doSticky, | ||
searchInput = true, | ||
onServerPaginationChange, | ||
rowCount, | ||
selectPageSize, | ||
noResults: noResultsText = 'No data found', | ||
hooks, | ||
serverPagination, | ||
wrapperRef: userWrapperRef, | ||
onColumnOrderChange, | ||
...moreUseTableOptions | ||
}: DataTableProps<D>): JSX.Element { | ||
const tableHooks: PluginHook<D>[] = [ | ||
useGlobalFilter, | ||
useSortBy, | ||
usePagination, | ||
useColumnOrder, | ||
doSticky ? useSticky : [], | ||
hooks || [], | ||
].flat(); | ||
const columnNames = Object.keys(data?.[0] || {}); | ||
const previousColumnNames = usePrevious(columnNames); | ||
const resultsSize = serverPagination ? rowCount : data.length; | ||
const sortByRef = useRef([]); // cache initial `sortby` so sorting doesn't trigger page reset | ||
const pageSizeRef = useRef([initialPageSize, resultsSize]); | ||
const hasPagination = initialPageSize > 0 && resultsSize > 0; // pageSize == 0 means no pagination | ||
const hasGlobalControl = hasPagination || !!searchInput; | ||
const initialState = { | ||
...initialState_, | ||
// zero length means all pages, the `usePagination` plugin does not | ||
// understand pageSize = 0 | ||
sortBy: sortByRef.current, | ||
pageSize: initialPageSize > 0 ? initialPageSize : resultsSize || 10, | ||
}; | ||
const defaultWrapperRef = useRef<HTMLDivElement>(null); | ||
const globalControlRef = useRef<HTMLDivElement>(null); | ||
const paginationRef = useRef<HTMLDivElement>(null); | ||
const wrapperRef = userWrapperRef || defaultWrapperRef; | ||
const paginationData = JSON.stringify(serverPaginationData); | ||
|
||
const defaultGetTableSize = useCallback(() => { | ||
if (wrapperRef.current) { | ||
// `initialWidth` and `initialHeight` could be also parameters like `100%` | ||
// `Number` returns `NaN` on them, then we fallback to computed size | ||
const width = Number(initialWidth) || wrapperRef.current.clientWidth; | ||
const height = | ||
(Number(initialHeight) || wrapperRef.current.clientHeight) - | ||
(globalControlRef.current?.clientHeight || 0) - | ||
(paginationRef.current?.clientHeight || 0); | ||
return { width, height }; | ||
} | ||
return undefined; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [ | ||
initialHeight, | ||
initialWidth, | ||
wrapperRef, | ||
hasPagination, | ||
hasGlobalControl, | ||
paginationRef, | ||
resultsSize, | ||
paginationData, | ||
]); | ||
|
||
const defaultGlobalFilter: FilterType<D> = useCallback( | ||
(rows: Row<D>[], columnIds: IdType<D>[], filterValue: string) => { | ||
// allow searching by "col1_value col2_value" | ||
const joinedString = (row: Row<D>) => | ||
columnIds.map(x => row.values[x]).join(' '); | ||
return matchSorter(rows, filterValue, { | ||
keys: [...columnIds, joinedString], | ||
threshold: rankings.ACRONYM, | ||
}) as typeof rows; | ||
}, | ||
[], | ||
); | ||
|
||
const { | ||
getTableProps, | ||
getTableBodyProps, | ||
prepareRow, | ||
headerGroups, | ||
footerGroups, | ||
page, | ||
pageCount, | ||
gotoPage, | ||
preGlobalFilteredRows, | ||
setGlobalFilter, | ||
setPageSize: setPageSize_, | ||
wrapStickyTable, | ||
setColumnOrder, | ||
allColumns, | ||
state: { pageIndex, pageSize, globalFilter: filterValue, sticky = {} }, | ||
} = useTable<D>( | ||
{ | ||
columns, | ||
data, | ||
initialState, | ||
getTableSize: defaultGetTableSize, | ||
globalFilter: defaultGlobalFilter, | ||
sortTypes, | ||
autoResetSortBy: !isEqual(columnNames, previousColumnNames), | ||
...moreUseTableOptions, | ||
}, | ||
...tableHooks, | ||
); | ||
// make setPageSize accept 0 | ||
const setPageSize = (size: number) => { | ||
if (serverPagination) { | ||
onServerPaginationChange(0, size); | ||
} | ||
// keep the original size if data is empty | ||
if (size || resultsSize !== 0) { | ||
setPageSize_(size === 0 ? resultsSize : size); | ||
} | ||
}; | ||
|
||
const noResults = | ||
typeof noResultsText === 'function' | ||
? noResultsText(filterValue as string) | ||
: noResultsText; | ||
|
||
const getNoResults = () => <div className="dt-no-results">{noResults}</div>; | ||
|
||
if (!columns || columns.length === 0) { | ||
return ( | ||
wrapStickyTable ? wrapStickyTable(getNoResults) : getNoResults() | ||
) as JSX.Element; | ||
} | ||
|
||
const shouldRenderFooter = columns.some(x => !!x.Footer); | ||
|
||
let columnBeingDragged = -1; | ||
|
||
const onDragStart = (e: React.DragEvent) => { | ||
const el = e.target as HTMLTableCellElement; | ||
columnBeingDragged = allColumns.findIndex( | ||
col => col.id === el.dataset.columnName, | ||
); | ||
e.dataTransfer.setData('text/plain', `${columnBeingDragged}`); | ||
}; | ||
|
||
const onDrop = (e: React.DragEvent) => { | ||
const el = e.target as HTMLTableCellElement; | ||
const newPosition = allColumns.findIndex( | ||
col => col.id === el.dataset.columnName, | ||
); | ||
|
||
if (newPosition !== -1) { | ||
const currentCols = allColumns.map(c => c.id); | ||
const colToBeMoved = currentCols.splice(columnBeingDragged, 1); | ||
currentCols.splice(newPosition, 0, colToBeMoved[0]); | ||
setColumnOrder(currentCols); | ||
// toggle value in TableChart to trigger column width recalc | ||
onColumnOrderChange(); | ||
} | ||
e.preventDefault(); | ||
}; | ||
|
||
const renderTable = () => ( | ||
<table {...getTableProps({ className: tableClassName })}> | ||
<thead> | ||
{headerGroups.map(headerGroup => { | ||
const { key: headerGroupKey, ...headerGroupProps } = | ||
headerGroup.getHeaderGroupProps(); | ||
return ( | ||
<tr key={headerGroupKey || headerGroup.id} {...headerGroupProps}> | ||
{headerGroup.headers.map(column => | ||
column.render('Header', { | ||
key: column.id, | ||
...column.getSortByToggleProps(), | ||
onDragStart, | ||
onDrop, | ||
}), | ||
)} | ||
</tr> | ||
); | ||
})} | ||
</thead> | ||
<tbody {...getTableBodyProps()}> | ||
{page && page.length > 0 ? ( | ||
page.map(row => { | ||
prepareRow(row); | ||
const { key: rowKey, ...rowProps } = row.getRowProps(); | ||
return ( | ||
<tr key={rowKey || row.id} {...rowProps}> | ||
{row.cells.map(cell => | ||
cell.render('Cell', { key: cell.column.id }), | ||
)} | ||
</tr> | ||
); | ||
}) | ||
) : ( | ||
<tr> | ||
<td className="dt-no-results" colSpan={columns.length}> | ||
{noResults} | ||
</td> | ||
</tr> | ||
)} | ||
</tbody> | ||
{shouldRenderFooter && ( | ||
<tfoot> | ||
{footerGroups.map(footerGroup => { | ||
const { key: footerGroupKey, ...footerGroupProps } = | ||
footerGroup.getHeaderGroupProps(); | ||
return ( | ||
<tr key={footerGroupKey || footerGroup.id} {...footerGroupProps}> | ||
{footerGroup.headers.map(column => | ||
column.render('Footer', { key: column.id }), | ||
)} | ||
</tr> | ||
); | ||
})} | ||
</tfoot> | ||
)} | ||
</table> | ||
); | ||
|
||
// force update the pageSize when it's been update from the initial state | ||
if ( | ||
pageSizeRef.current[0] !== initialPageSize || | ||
// when initialPageSize stays as zero, but total number of records changed, | ||
// we'd also need to update page size | ||
(initialPageSize === 0 && pageSizeRef.current[1] !== resultsSize) | ||
) { | ||
pageSizeRef.current = [initialPageSize, resultsSize]; | ||
setPageSize(initialPageSize); | ||
} | ||
|
||
const paginationStyle: CSSProperties = sticky.height | ||
? {} | ||
: { visibility: 'hidden' }; | ||
|
||
let resultPageCount = pageCount; | ||
let resultCurrentPageSize = pageSize; | ||
let resultCurrentPage = pageIndex; | ||
let resultOnPageChange: (page: number) => void = gotoPage; | ||
if (serverPagination) { | ||
const serverPageSize = serverPaginationData?.pageSize ?? initialPageSize; | ||
resultPageCount = Math.ceil(rowCount / serverPageSize); | ||
if (!Number.isFinite(resultPageCount)) { | ||
resultPageCount = 0; | ||
} | ||
resultCurrentPageSize = serverPageSize; | ||
const foundPageSizeIndex = pageSizeOptions.findIndex( | ||
([option]) => option >= resultCurrentPageSize, | ||
); | ||
if (foundPageSizeIndex === -1) { | ||
resultCurrentPageSize = 0; | ||
} | ||
resultCurrentPage = serverPaginationData?.currentPage ?? 0; | ||
resultOnPageChange = (pageNumber: number) => | ||
onServerPaginationChange(pageNumber, serverPageSize); | ||
} | ||
return ( | ||
<div | ||
ref={wrapperRef} | ||
style={{ width: initialWidth, height: initialHeight }} | ||
> | ||
{hasGlobalControl ? ( | ||
<div ref={globalControlRef} className="form-inline dt-controls"> | ||
<div className="row"> | ||
<div className="col-sm-6"> | ||
{hasPagination ? ( | ||
<SelectPageSize | ||
total={resultsSize} | ||
current={resultCurrentPageSize} | ||
options={pageSizeOptions} | ||
selectRenderer={ | ||
typeof selectPageSize === 'boolean' | ||
? undefined | ||
: selectPageSize | ||
} | ||
onChange={setPageSize} | ||
/> | ||
) : null} | ||
</div> | ||
{searchInput ? ( | ||
<div className="col-sm-6"> | ||
<GlobalFilter<D> | ||
searchInput={ | ||
typeof searchInput === 'boolean' ? undefined : searchInput | ||
} | ||
preGlobalFilteredRows={preGlobalFilteredRows} | ||
setGlobalFilter={setGlobalFilter} | ||
filterValue={filterValue} | ||
/> | ||
</div> | ||
) : null} | ||
</div> | ||
</div> | ||
) : null} | ||
{wrapStickyTable ? wrapStickyTable(renderTable) : renderTable()} | ||
{hasPagination && resultPageCount > 1 ? ( | ||
<SimplePagination | ||
ref={paginationRef} | ||
style={paginationStyle} | ||
maxPageItemCount={maxPageItemCount} | ||
pageCount={resultPageCount} | ||
currentPage={resultCurrentPage} | ||
onPageChange={resultOnPageChange} | ||
/> | ||
) : null} | ||
</div> | ||
); | ||
}); |
Oops, something went wrong.