style: listviews closer to SIP-34 (#10094)
This commit is contained in:
parent
4d1d40989c
commit
be936c2eb8
|
|
@ -19,13 +19,14 @@
|
|||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MenuItem, Pagination } from 'react-bootstrap';
|
||||
import { MenuItem } from 'react-bootstrap';
|
||||
import Select from 'src/components/Select';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import ListViewFilters from 'src/components/ListView/Filters';
|
||||
import ListViewPagination from 'src/components/ListView/Pagination';
|
||||
import Pagination from 'src/components/Pagination';
|
||||
import { areArraysShallowEqual } from 'src/reduxUtils';
|
||||
import { ThemeProvider } from 'emotion-theming';
|
||||
import { supersetTheme } from '@superset-ui/style';
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { mount } from 'enzyme';
|
|||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { ThemeProvider } from 'emotion-theming';
|
||||
import { supersetTheme } from '@superset-ui/style';
|
||||
|
||||
import ChartList from 'src/views/chartList/ChartList';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
|
|
@ -77,6 +79,8 @@ describe('ChartList', () => {
|
|||
const mockedProps = {};
|
||||
const wrapper = mount(<ChartList {...mockedProps} />, {
|
||||
context: { store },
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { mount } from 'enzyme';
|
|||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { ThemeProvider } from 'emotion-theming';
|
||||
import { supersetTheme } from '@superset-ui/style';
|
||||
|
||||
import DashboardList from 'src/views/dashboardList/DashboardList';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
|
|
@ -67,6 +69,8 @@ describe('DashboardList', () => {
|
|||
const mockedProps = {};
|
||||
const wrapper = mount(<DashboardList {...mockedProps} />, {
|
||||
context: { store },
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { mount } from 'enzyme';
|
|||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { ThemeProvider } from 'emotion-theming';
|
||||
import { supersetTheme } from '@superset-ui/style';
|
||||
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import DashboardTable from 'src/welcome/DashboardTable';
|
||||
|
|
@ -36,7 +38,11 @@ fetchMock.get(dashboardsEndpoint, { result: mockDashboards });
|
|||
|
||||
function setup() {
|
||||
// use mount because data fetching is triggered on mount
|
||||
return mount(<DashboardTable />, { context: { store } });
|
||||
return mount(<DashboardTable />, {
|
||||
context: { store },
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
});
|
||||
}
|
||||
|
||||
describe('DashboardTable', () => {
|
||||
|
|
|
|||
|
|
@ -19,15 +19,16 @@
|
|||
import React from 'react';
|
||||
import styled from '@superset-ui/style';
|
||||
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
|
||||
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
import Avatar, { ConfigProvider } from 'react-avatar';
|
||||
import TooltipWrapper from 'src/components/TooltipWrapper';
|
||||
|
||||
interface Props {
|
||||
firstName: string;
|
||||
iconSize: string;
|
||||
lastName: string;
|
||||
tableName: string;
|
||||
userName: string;
|
||||
iconSize: number;
|
||||
textSize: number;
|
||||
}
|
||||
|
||||
const colorList = getCategoricalSchemeRegistry().get();
|
||||
|
|
@ -42,18 +43,26 @@ export default function AvatarIcon({
|
|||
lastName,
|
||||
userName,
|
||||
iconSize,
|
||||
textSize,
|
||||
}: Props) {
|
||||
const uniqueKey = `${tableName}-${userName}`;
|
||||
const fullName = `${firstName} ${lastName}`;
|
||||
|
||||
return (
|
||||
<ConfigProvider colors={colorList && colorList.colors}>
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={<Tooltip id={`${uniqueKey}-tooltip`}>{fullName}</Tooltip>}
|
||||
>
|
||||
<StyledAvatar key={uniqueKey} name={fullName} size={iconSize} round />
|
||||
</OverlayTrigger>
|
||||
</ConfigProvider>
|
||||
<TooltipWrapper
|
||||
placement="bottom"
|
||||
label={`${uniqueKey}-tooltip`}
|
||||
tooltip={fullName}
|
||||
>
|
||||
<ConfigProvider colors={colorList && colorList.colors}>
|
||||
<StyledAvatar
|
||||
key={uniqueKey}
|
||||
name={fullName}
|
||||
size={String(iconSize)}
|
||||
textSizeRatio={iconSize / textSize}
|
||||
round
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
import { t } from '@superset-ui/translation';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Col, DropdownButton, MenuItem, Row } from 'react-bootstrap';
|
||||
import IndeterminateCheckbox from '../IndeterminateCheckbox';
|
||||
import Loading from 'src/components/Loading';
|
||||
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
|
||||
import TableCollection from './TableCollection';
|
||||
import Pagination from './Pagination';
|
||||
import { FilterMenu, FilterInputs } from './LegacyFilters';
|
||||
|
|
@ -37,7 +38,6 @@ interface Props {
|
|||
fetchData: (conf: FetchDataConfig) => any;
|
||||
loading: boolean;
|
||||
className?: string;
|
||||
title?: string;
|
||||
initialSort?: SortColumn[];
|
||||
filters?: Filters;
|
||||
bulkActions?: Array<{
|
||||
|
|
@ -59,6 +59,7 @@ const bulkSelectColumnConfig = {
|
|||
/>
|
||||
),
|
||||
id: 'selection',
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
const ListView: FunctionComponent<Props> = ({
|
||||
|
|
@ -70,7 +71,6 @@ const ListView: FunctionComponent<Props> = ({
|
|||
loading,
|
||||
initialSort = [],
|
||||
className = '',
|
||||
title = '',
|
||||
filters = [],
|
||||
bulkActions = [],
|
||||
useNewUIFilters = false,
|
||||
|
|
@ -116,124 +116,111 @@ const ListView: FunctionComponent<Props> = ({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (loading && !data.length) {
|
||||
return <Loading />;
|
||||
}
|
||||
return (
|
||||
<div className={`superset-list-view ${className}`}>
|
||||
<div className="header">
|
||||
{!useNewUIFilters && (
|
||||
<>
|
||||
{title && filterable && (
|
||||
<>
|
||||
<Row>
|
||||
<Col md={11}>
|
||||
<h2>{t(title)}</h2>
|
||||
</Col>
|
||||
{filterable && (
|
||||
<Col md={1}>
|
||||
<FilterMenu
|
||||
filters={filters}
|
||||
internalFilters={internalFilters}
|
||||
setInternalFilters={setInternalFilters}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
<hr />
|
||||
<FilterInputs
|
||||
internalFilters={internalFilters}
|
||||
filters={filters}
|
||||
updateInternalFilter={updateInternalFilter}
|
||||
removeFilterAndApply={removeFilterAndApply}
|
||||
filtersApplied={filtersApplied}
|
||||
applyFilters={applyFilters}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{useNewUIFilters && (
|
||||
<>
|
||||
<Row>
|
||||
<Col md={10}>
|
||||
<h2>{t(title)}</h2>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
<div className="superset-list-view-container">
|
||||
<div className={`superset-list-view ${className}`}>
|
||||
<div className="header">
|
||||
{!useNewUIFilters && filterable && (
|
||||
<>
|
||||
<Row>
|
||||
<Col md={10} />
|
||||
<Col md={2}>
|
||||
<FilterMenu
|
||||
filters={filters}
|
||||
internalFilters={internalFilters}
|
||||
setInternalFilters={setInternalFilters}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr />
|
||||
<FilterInputs
|
||||
internalFilters={internalFilters}
|
||||
filters={filters}
|
||||
updateInternalFilter={updateInternalFilter}
|
||||
removeFilterAndApply={removeFilterAndApply}
|
||||
filtersApplied={filtersApplied}
|
||||
applyFilters={applyFilters}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{useNewUIFilters && filterable && (
|
||||
<FilterControls
|
||||
filters={filters}
|
||||
internalFilters={internalFilters}
|
||||
updateFilterValue={applyFilterValue}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="body">
|
||||
<TableCollection
|
||||
getTableProps={getTableProps}
|
||||
getTableBodyProps={getTableBodyProps}
|
||||
prepareRow={prepareRow}
|
||||
headerGroups={headerGroups}
|
||||
rows={rows}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<Row>
|
||||
<Col md={2}>
|
||||
<div className="form-actions-container">
|
||||
<div className="btn-group">
|
||||
{bulkActions.length > 0 && (
|
||||
<DropdownButton
|
||||
id="bulk-actions"
|
||||
bsSize="small"
|
||||
bsStyle="default"
|
||||
noCaret
|
||||
title={
|
||||
<>
|
||||
{t('Actions')} <span className="caret" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
{bulkActions.map(action => (
|
||||
// @ts-ignore
|
||||
<MenuItem
|
||||
key={action.key}
|
||||
eventKey={selectedFlatRows}
|
||||
)}
|
||||
</div>
|
||||
<div className="body">
|
||||
<TableCollection
|
||||
getTableProps={getTableProps}
|
||||
getTableBodyProps={getTableBodyProps}
|
||||
prepareRow={prepareRow}
|
||||
headerGroups={headerGroups}
|
||||
rows={rows}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<Row>
|
||||
<Col>
|
||||
<div className="form-actions-container">
|
||||
<div className="btn-group">
|
||||
{bulkActions.length > 0 && (
|
||||
<DropdownButton
|
||||
id="bulk-actions"
|
||||
bsSize="small"
|
||||
bsStyle="default"
|
||||
noCaret
|
||||
title={
|
||||
<>
|
||||
{t('Actions')} <span className="caret" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
{bulkActions.map(action => (
|
||||
// @ts-ignore
|
||||
onSelect={(selectedRows: typeof selectedFlatRows) => {
|
||||
action.onSelect(
|
||||
selectedRows.map((r: any) => r.original),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{action.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</DropdownButton>
|
||||
)}
|
||||
<MenuItem
|
||||
key={action.key}
|
||||
eventKey={selectedFlatRows}
|
||||
// @ts-ignore
|
||||
onSelect={(selectedRows: typeof selectedFlatRows) => {
|
||||
action.onSelect(
|
||||
selectedRows.map((r: any) => r.original),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{action.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</DropdownButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col md={8} className="text-center">
|
||||
<Pagination
|
||||
totalPages={pageCount || 0}
|
||||
currentPage={pageCount ? pageIndex + 1 : 0}
|
||||
onChange={(p: number) => gotoPage(p - 1)}
|
||||
hideFirstAndLastPageLinks
|
||||
/>
|
||||
</Col>
|
||||
<Col md={2}>
|
||||
<span className="pull-right">
|
||||
{t('showing')}{' '}
|
||||
<strong>
|
||||
{pageSize * pageIndex + (rows.length && 1)}-
|
||||
{pageSize * pageIndex + rows.length}
|
||||
</strong>{' '}
|
||||
{t('of')} <strong>{count}</strong>
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<span className="row-count-container">
|
||||
showing{' '}
|
||||
<strong>
|
||||
{pageSize * pageIndex + (rows.length && 1)}-
|
||||
{pageSize * pageIndex + rows.length}
|
||||
</strong>{' '}
|
||||
of <strong>{count}</strong>
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
totalPages={pageCount || 0}
|
||||
currentPage={pageCount ? pageIndex + 1 : 0}
|
||||
onChange={(p: number) => gotoPage(p - 1)}
|
||||
hideFirstAndLastPageLinks
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,92 +19,133 @@
|
|||
|
||||
@import '~stylesheets/less/variables.less';
|
||||
|
||||
.superset-list-view {
|
||||
.filter-dropdown {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.superset-list-view-container {
|
||||
text-align: center;
|
||||
|
||||
.filter-column {
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.superset-list-view {
|
||||
text-align: left;
|
||||
background-color: white;
|
||||
border-radius: 4px 0;
|
||||
margin: 0 16px;
|
||||
padding-bottom: 48px;
|
||||
|
||||
.filter-close {
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
.body {
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-loader {
|
||||
animation: shimmer 2s infinite;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#f6f7f8 0%,
|
||||
#edeef1 20%,
|
||||
#f6f7f8 40%,
|
||||
#f6f7f8 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
|
||||
span {
|
||||
visibility: hidden;
|
||||
.filter-dropdown {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
.filter-column {
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
width: 100px;
|
||||
.filter-close {
|
||||
height: 30px;
|
||||
padding: 5px;
|
||||
|
||||
svg {
|
||||
&:hover {
|
||||
path {
|
||||
fill: @primary-color;
|
||||
i {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell-loader {
|
||||
position: relative;
|
||||
|
||||
.loading-bar {
|
||||
background-color: @brand-secondary-light4;
|
||||
border-radius: 7px;
|
||||
|
||||
span {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
left: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background-image: linear-gradient(
|
||||
100deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.5) 60%,
|
||||
rgba(255, 255, 255, 0) 80%
|
||||
);
|
||||
background-size: 200px 48px;
|
||||
background-position: -100px 0;
|
||||
background-repeat: no-repeat;
|
||||
animation: loading-shimmer 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
white-space: nowrap;
|
||||
font-size: 24px;
|
||||
min-width: 100px;
|
||||
|
||||
svg,
|
||||
i {
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
path {
|
||||
fill: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin: 0 8px;
|
||||
}
|
||||
.table-row {
|
||||
&:hover {
|
||||
background-color: @brand-secondary-light5;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row {
|
||||
&:hover {
|
||||
background-color: @table-hover;
|
||||
.table-row-selected {
|
||||
background-color: @brand-secondary-light4;
|
||||
|
||||
&:hover {
|
||||
background-color: @brand-secondary-light4;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.form-actions-container {
|
||||
position: absolute;
|
||||
left: 28px;
|
||||
}
|
||||
|
||||
.row-count-container {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-selected {
|
||||
background-color: @table-selected;
|
||||
@keyframes loading-shimmer {
|
||||
40% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @table-selected;
|
||||
100% {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -1000px 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { Pagination } from 'react-bootstrap';
|
||||
import Pagination from 'src/components/Pagination';
|
||||
import {
|
||||
createUltimatePagination,
|
||||
ITEM_TYPES,
|
||||
|
|
@ -35,18 +34,14 @@ const ListViewPagination = createUltimatePagination({
|
|||
[ITEM_TYPES.ELLIPSIS]: ({ isActive, onClick }) => (
|
||||
<Pagination.Ellipsis disabled={isActive} onClick={onClick} />
|
||||
),
|
||||
[ITEM_TYPES.FIRST_PAGE_LINK]: ({ isActive, onClick }) => (
|
||||
<Pagination.First disabled={isActive} onClick={onClick} />
|
||||
),
|
||||
[ITEM_TYPES.PREVIOUS_PAGE_LINK]: ({ isActive, onClick }) => (
|
||||
<Pagination.Prev disabled={isActive} onClick={onClick} />
|
||||
),
|
||||
[ITEM_TYPES.NEXT_PAGE_LINK]: ({ isActive, onClick }) => (
|
||||
<Pagination.Next disabled={isActive} onClick={onClick} />
|
||||
),
|
||||
[ITEM_TYPES.LAST_PAGE_LINK]: ({ isActive, onClick }) => (
|
||||
<Pagination.Last disabled={isActive} onClick={onClick} />
|
||||
),
|
||||
[ITEM_TYPES.FIRST_PAGE_LINK]: () => null,
|
||||
[ITEM_TYPES.LAST_PAGE_LINK]: () => null,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import { TableInstance } from 'react-table';
|
||||
import styled from '@superset-ui/style';
|
||||
import Icon from 'src/components/Icon';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -29,6 +30,56 @@ interface Props {
|
|||
rows: TableInstance['rows'];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const Table = styled.table`
|
||||
th {
|
||||
&.xs {
|
||||
min-width: 25px;
|
||||
}
|
||||
&.sm {
|
||||
min-width: 50px;
|
||||
}
|
||||
&.md {
|
||||
min-width: 75px;
|
||||
}
|
||||
&.lg {
|
||||
min-width: 100px;
|
||||
}
|
||||
&.xl {
|
||||
min-width: 150px;
|
||||
}
|
||||
&.xxl {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block;
|
||||
top: 6px;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
td {
|
||||
&.xs {
|
||||
width: 25px;
|
||||
}
|
||||
&.sm {
|
||||
width: 50px;
|
||||
}
|
||||
&.md {
|
||||
width: 75px;
|
||||
}
|
||||
&.lg {
|
||||
width: 100px;
|
||||
}
|
||||
&.xl {
|
||||
width: 150px;
|
||||
}
|
||||
&.xxl {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function TableCollection({
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
|
|
@ -38,29 +89,31 @@ export default function TableCollection({
|
|||
loading,
|
||||
}: Props) {
|
||||
return (
|
||||
<table {...getTableProps()} className="table table-hover">
|
||||
<Table {...getTableProps()} className="table table-hover">
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => {
|
||||
let sortIcon = <Icon name="sort" />;
|
||||
if (column.isSortedDesc) {
|
||||
if (column.isSorted && column.isSortedDesc) {
|
||||
sortIcon = <Icon name="sort-desc" />;
|
||||
} else if (!column.isSortedDesc) {
|
||||
} else if (column.isSorted && !column.isSortedDesc) {
|
||||
sortIcon = <Icon name="sort-asc" />;
|
||||
}
|
||||
|
||||
return column.hidden ? null : (
|
||||
<th
|
||||
{...column.getHeaderProps(
|
||||
column.sortable ? column.getSortByToggleProps() : {},
|
||||
)}
|
||||
data-test="sort-header"
|
||||
className={cx({
|
||||
[column.size || '']: column.size,
|
||||
})}
|
||||
>
|
||||
<span>{column.render('Header')}</span>
|
||||
{column.sortable && (
|
||||
<span className="sort-icon">{sortIcon}</span>
|
||||
)}
|
||||
<span>
|
||||
{column.render('Header')}
|
||||
{column.sortable && sortIcon}
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
|
|
@ -74,7 +127,6 @@ export default function TableCollection({
|
|||
<tr
|
||||
{...row.getRowProps()}
|
||||
className={cx({
|
||||
'table-row-loader': loading,
|
||||
'table-row-selected': row.isSelected,
|
||||
})}
|
||||
onMouseEnter={() => row.setState && row.setState({ hover: true })}
|
||||
|
|
@ -86,14 +138,18 @@ export default function TableCollection({
|
|||
if (cell.column.hidden) return null;
|
||||
|
||||
const columnCellProps = cell.column.cellProps || {};
|
||||
|
||||
return (
|
||||
<td
|
||||
className="table-cell"
|
||||
className={cx('table-cell', {
|
||||
'table-cell-loader': loading,
|
||||
[cell.column.size || '']: cell.column.size,
|
||||
})}
|
||||
{...cell.getCellProps()}
|
||||
{...columnCellProps}
|
||||
>
|
||||
<span>{cell.render('Cell')}</span>
|
||||
<span className={cx({ 'loading-bar': loading })}>
|
||||
<span>{cell.render('Cell')}</span>
|
||||
</span>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
|
|
@ -101,6 +157,6 @@ export default function TableCollection({
|
|||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,11 +64,10 @@ const StyledHeader = styled.header`
|
|||
`;
|
||||
|
||||
interface SubMenuProps {
|
||||
createButton: { name: string; url: string | null };
|
||||
canCreate: boolean;
|
||||
label: string;
|
||||
createButton?: { name: string; url: string | null };
|
||||
canCreate?: boolean;
|
||||
name: string;
|
||||
childs: Array<{ label: string; name: string; url: string }>;
|
||||
childs?: Array<{ label: string; name: string; url: string }>;
|
||||
}
|
||||
|
||||
interface SubMenuState {
|
||||
|
|
@ -78,7 +77,10 @@ interface SubMenuState {
|
|||
|
||||
class SubMenu extends React.PureComponent<SubMenuProps, SubMenuState> {
|
||||
state: SubMenuState = {
|
||||
selectedMenu: this.props.childs[0] && this.props.childs[0].label,
|
||||
selectedMenu:
|
||||
this.props.childs && this.props.childs[0]
|
||||
? this.props.childs[0].label
|
||||
: '',
|
||||
isModalOpen: false,
|
||||
};
|
||||
|
||||
|
|
@ -99,7 +101,7 @@ class SubMenu extends React.PureComponent<SubMenuProps, SubMenuState> {
|
|||
<StyledHeader>
|
||||
<Navbar inverse fluid role="navigation">
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>{this.props.label}</Navbar.Brand>
|
||||
<Navbar.Brand>{this.props.name}</Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
<DatasetModal show={this.state.isModalOpen} onHide={this.onClose} />
|
||||
<Nav>
|
||||
|
|
@ -116,7 +118,7 @@ class SubMenu extends React.PureComponent<SubMenuProps, SubMenuState> {
|
|||
</MenuItem>
|
||||
))}
|
||||
</Nav>
|
||||
{this.props.canCreate && (
|
||||
{this.props.canCreate && this.props.createButton && (
|
||||
<Nav className="navbar-right">
|
||||
<Button onClick={this.onOpen}>
|
||||
<i className="fa fa-plus" /> {this.props.createButton.name}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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, { PureComponent } from 'react';
|
||||
import cx from 'classnames';
|
||||
import styled from '@superset-ui/style';
|
||||
|
||||
interface PaginationButton {
|
||||
disabled?: boolean;
|
||||
onClick: React.EventHandler<React.SyntheticEvent<HTMLElement>>;
|
||||
}
|
||||
|
||||
interface PaginationItemButton extends PaginationButton {
|
||||
active: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Prev({ disabled, onClick }: PaginationButton) {
|
||||
return (
|
||||
<li className={cx({ disabled })}>
|
||||
<span role="button" tabIndex={disabled ? -1 : 0} onClick={onClick}>
|
||||
«
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Next({ disabled, onClick }: PaginationButton) {
|
||||
return (
|
||||
<li className={cx({ disabled })}>
|
||||
<span role="button" tabIndex={disabled ? -1 : 0} onClick={onClick}>
|
||||
»
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Item({ active, children, onClick }: PaginationItemButton) {
|
||||
return (
|
||||
<li className={cx({ active })}>
|
||||
<span role="button" tabIndex={active ? -1 : 0} onClick={onClick}>
|
||||
{children}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Ellipsis({ disabled, onClick }: PaginationButton) {
|
||||
return (
|
||||
<li className={cx({ disabled })}>
|
||||
<span role="button" tabIndex={disabled ? -1 : 0} onClick={onClick}>
|
||||
…
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
interface PaginationProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PaginationList = styled.ul`
|
||||
display: inline-block;
|
||||
margin: 16px 0;
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
margin: 0 4px;
|
||||
|
||||
span {
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
z-index: 2;
|
||||
color: ${({ theme }) => theme.colors.grayscale.dark1};
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light3};
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
span {
|
||||
background-color: transparent;
|
||||
cursor: default;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
span {
|
||||
z-index: 3;
|
||||
color: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
cursor: default;
|
||||
background-color: ${({ theme }) => theme.colors.primary.base};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default class Pagination extends PureComponent<PaginationProps> {
|
||||
static Next = Next;
|
||||
static Prev = Prev;
|
||||
static Item = Item;
|
||||
static Ellipsis = Ellipsis;
|
||||
render() {
|
||||
return <PaginationList> {this.props.children}</PaginationList>;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,8 +64,10 @@ import {
|
|||
UseSortByOptions,
|
||||
UseSortByState,
|
||||
} from 'react-table';
|
||||
import { ColumnSizer } from 'react-virtualized';
|
||||
|
||||
declare module 'react-table' {
|
||||
type ColumnSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
export interface TableOptions<D extends object>
|
||||
extends UseExpandedOptions<D>,
|
||||
UseFiltersOptions<D>,
|
||||
|
|
@ -118,13 +120,19 @@ declare module 'react-table' {
|
|||
hidden?: boolean;
|
||||
sortable?: boolean;
|
||||
cellProps?: any;
|
||||
size?: ColumnSize;
|
||||
}
|
||||
|
||||
export interface ColumnInstance<D extends object = {}>
|
||||
extends UseFiltersColumnProps<D>,
|
||||
UseGroupByColumnProps<D>,
|
||||
UseResizeColumnsColumnProps<D>,
|
||||
UseSortByColumnProps<D> {}
|
||||
UseSortByColumnProps<D> {
|
||||
hidden?: boolean;
|
||||
sortable?: boolean;
|
||||
cellProps?: any;
|
||||
size?: ColumnSize;
|
||||
}
|
||||
|
||||
export interface Cell<D extends object = {}>
|
||||
extends UseGroupByCellProps<D>,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import getClientErrorObject from './getClientErrorObject';
|
|||
|
||||
export const NULL_STRING = '<NULL>';
|
||||
|
||||
// moment time format strings
|
||||
export const SHORT_DATE = 'MMM D, YYYY';
|
||||
export const SHORT_TIME = 'h:m a';
|
||||
|
||||
export function getParamFromQuery(query, param) {
|
||||
const vars = query.split('&');
|
||||
for (let i = 0; i < vars.length; i += 1) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import rison from 'rison';
|
|||
// @ts-ignore
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import {
|
||||
FetchDataConfig,
|
||||
|
|
@ -68,7 +69,7 @@ class ChartList extends React.PureComponent<Props, State> {
|
|||
filterOperators: {},
|
||||
filters: [],
|
||||
lastFetchDataConfig: null,
|
||||
loading: false,
|
||||
loading: true,
|
||||
permissions: [],
|
||||
sliceCurrentlyEditing: null,
|
||||
};
|
||||
|
|
@ -223,7 +224,7 @@ class ChartList extends React.PureComponent<Props, State> {
|
|||
</span>
|
||||
);
|
||||
},
|
||||
Header: 'Actions',
|
||||
Header: t('Actions'),
|
||||
id: 'actions',
|
||||
},
|
||||
];
|
||||
|
|
@ -517,58 +518,54 @@ class ChartList extends React.PureComponent<Props, State> {
|
|||
sliceCurrentlyEditing,
|
||||
} = this.state;
|
||||
return (
|
||||
<div className="container welcome">
|
||||
<Panel>
|
||||
<Panel.Body>
|
||||
{sliceCurrentlyEditing && (
|
||||
<PropertiesModal
|
||||
show
|
||||
onHide={this.closeChartEditModal}
|
||||
onSave={this.handleChartUpdated}
|
||||
slice={sliceCurrentlyEditing}
|
||||
<>
|
||||
<SubMenu name={t('Charts')} />
|
||||
{sliceCurrentlyEditing && (
|
||||
<PropertiesModal
|
||||
show
|
||||
onHide={this.closeChartEditModal}
|
||||
onSave={this.handleChartUpdated}
|
||||
slice={sliceCurrentlyEditing}
|
||||
/>
|
||||
)}
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected charts?',
|
||||
)}
|
||||
onConfirm={this.handleBulkChartDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-trash" /> {t('Delete')}
|
||||
</>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView
|
||||
className="chart-list-view"
|
||||
columns={this.columns}
|
||||
data={charts}
|
||||
count={chartCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
useNewUIFilters={this.isNewUIEnabled}
|
||||
/>
|
||||
)}
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected charts?',
|
||||
)}
|
||||
onConfirm={this.handleBulkChartDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-trash" /> Delete
|
||||
</>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView
|
||||
className="chart-list-view"
|
||||
title={'Charts'}
|
||||
columns={this.columns}
|
||||
data={charts}
|
||||
count={chartCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
useNewUIFilters={this.isNewUIEnabled}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</Panel.Body>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import rison from 'rison';
|
|||
// @ts-ignore
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import ExpandableList from 'src/components/ExpandableList';
|
||||
import {
|
||||
|
|
@ -76,7 +77,7 @@ class DashboardList extends React.PureComponent<Props, State> {
|
|||
filterOperators: {},
|
||||
filters: [],
|
||||
lastFetchDataConfig: null,
|
||||
loading: false,
|
||||
loading: true,
|
||||
permissions: [],
|
||||
dashboardToEdit: null,
|
||||
};
|
||||
|
|
@ -508,71 +509,67 @@ class DashboardList extends React.PureComponent<Props, State> {
|
|||
dashboardToEdit,
|
||||
} = this.state;
|
||||
return (
|
||||
<div className="container welcome">
|
||||
<Panel>
|
||||
<Panel.Body>
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected dashboards?',
|
||||
)}
|
||||
onConfirm={this.handleBulkDashboardDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-trash" /> Delete
|
||||
</>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
if (this.canExport) {
|
||||
bulkActions.push({
|
||||
key: 'export',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-database" /> Export
|
||||
</>
|
||||
),
|
||||
onSelect: this.handleBulkDashboardExport,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<SubMenu name={t('Dashboards')} />
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected dashboards?',
|
||||
)}
|
||||
onConfirm={this.handleBulkDashboardDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
{dashboardToEdit && (
|
||||
<PropertiesModal
|
||||
show
|
||||
dashboardId={dashboardToEdit.id}
|
||||
onHide={() => this.setState({ dashboardToEdit: null })}
|
||||
onDashboardSave={this.handleDashboardEdit}
|
||||
/>
|
||||
)}
|
||||
<ListView
|
||||
className="dashboard-list-view"
|
||||
title={'Dashboards'}
|
||||
columns={this.columns}
|
||||
data={dashboards}
|
||||
count={dashboardCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
useNewUIFilters={this.isNewUIEnabled}
|
||||
/>
|
||||
<i className="fa fa-trash" /> {t('Delete')}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</Panel.Body>
|
||||
</Panel>
|
||||
</div>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
if (this.canExport) {
|
||||
bulkActions.push({
|
||||
key: 'export',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-database" /> {t('Export')}
|
||||
</>
|
||||
),
|
||||
onSelect: this.handleBulkDashboardExport,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{dashboardToEdit && (
|
||||
<PropertiesModal
|
||||
show
|
||||
dashboardId={dashboardToEdit.id}
|
||||
onHide={() => this.setState({ dashboardToEdit: null })}
|
||||
onDashboardSave={this.handleDashboardEdit}
|
||||
/>
|
||||
)}
|
||||
<ListView
|
||||
className="dashboard-list-view"
|
||||
columns={this.columns}
|
||||
data={dashboards}
|
||||
count={dashboardCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
useNewUIFilters={this.isNewUIEnabled}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import React from 'react';
|
|||
import rison from 'rison';
|
||||
// @ts-ignore
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import { SHORT_DATE, SHORT_TIME } from 'src/utils/common';
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
|
|
@ -87,7 +88,7 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
filterOperators: {},
|
||||
filters: [],
|
||||
lastFetchDataConfig: null,
|
||||
loading: false,
|
||||
loading: true,
|
||||
owners: [],
|
||||
databases: [],
|
||||
permissions: [],
|
||||
|
|
@ -175,6 +176,7 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
);
|
||||
},
|
||||
accessor: 'kind_icon',
|
||||
size: 'xs',
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
|
|
@ -184,6 +186,7 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
}: any) => datasetTitle,
|
||||
Header: t('Name'),
|
||||
accessor: 'table_name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
|
|
@ -193,36 +196,51 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
}: any) => kind[0]?.toUpperCase() + kind.slice(1),
|
||||
Header: t('Type'),
|
||||
accessor: 'kind',
|
||||
size: 'md',
|
||||
},
|
||||
{
|
||||
Header: t('Source'),
|
||||
accessor: 'database_name',
|
||||
size: 'lg',
|
||||
},
|
||||
{
|
||||
Header: t('Schema'),
|
||||
accessor: 'schema',
|
||||
size: 'lg',
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
row: {
|
||||
original: { changed_on: changedOn },
|
||||
},
|
||||
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
|
||||
}: any) => {
|
||||
const momentTime = moment(changedOn);
|
||||
const time = momentTime.format(SHORT_DATE);
|
||||
const date = momentTime.format(SHORT_TIME);
|
||||
return (
|
||||
<TooltipWrapper
|
||||
label="last-modified"
|
||||
tooltip={time}
|
||||
placement="right"
|
||||
>
|
||||
<span>{date}</span>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
},
|
||||
Header: t('Last Modified'),
|
||||
accessor: 'changed_on',
|
||||
sortable: true,
|
||||
size: 'xl',
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
row: {
|
||||
original: {
|
||||
changed_by_name: changedByName,
|
||||
changed_by_url: changedByUrl,
|
||||
},
|
||||
original: { changed_by_name: changedByName },
|
||||
},
|
||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||
}: any) => changedByName,
|
||||
Header: t('Modified By'),
|
||||
accessor: 'changed_by_fk',
|
||||
size: 'xl',
|
||||
},
|
||||
{
|
||||
accessor: 'database',
|
||||
|
|
@ -241,16 +259,19 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
.slice(0, 5)
|
||||
.map((owner: Owner) => (
|
||||
<AvatarIcon
|
||||
key={owner.id}
|
||||
tableName={tableName}
|
||||
firstName={owner.first_name}
|
||||
lastName={owner.last_name}
|
||||
userName={owner.username}
|
||||
iconSize="20"
|
||||
iconSize={24}
|
||||
textSize={9}
|
||||
/>
|
||||
));
|
||||
},
|
||||
Header: t('Owners'),
|
||||
id: 'owners',
|
||||
size: 'lg',
|
||||
},
|
||||
{
|
||||
accessor: 'is_sqllab_view',
|
||||
|
|
@ -267,14 +288,20 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
<span
|
||||
className={`actions ${state && state.hover ? '' : 'invisible'}`}
|
||||
>
|
||||
<a
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
href={original.explore_url}
|
||||
<TooltipWrapper
|
||||
label="explore-action"
|
||||
tooltip={t('Explore')}
|
||||
placement="bottom"
|
||||
>
|
||||
<Icon name="compass" />
|
||||
</a>
|
||||
<a
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
href={original.explore_url}
|
||||
>
|
||||
<Icon name="compass" />
|
||||
</a>
|
||||
</TooltipWrapper>
|
||||
{this.canDelete && (
|
||||
<ConfirmStatusChange
|
||||
title={t('Please Confirm')}
|
||||
|
|
@ -287,26 +314,38 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
onConfirm={handleDelete}
|
||||
>
|
||||
{confirmDelete => (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={confirmDelete}
|
||||
<TooltipWrapper
|
||||
label="delete-action"
|
||||
tooltip={t('Delete')}
|
||||
placement="bottom"
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={confirmDelete}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
</ConfirmStatusChange>
|
||||
)}
|
||||
{this.canEdit && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleEdit}
|
||||
<TooltipWrapper
|
||||
label="edit-action"
|
||||
tooltip={t('Edit')}
|
||||
placement="bottom"
|
||||
>
|
||||
<Icon name="pencil" />
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="action-button"
|
||||
onClick={handleEdit}
|
||||
>
|
||||
<Icon name="pencil" />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
|
@ -317,8 +356,7 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
];
|
||||
|
||||
menu = {
|
||||
label: 'Data',
|
||||
name: 'Data',
|
||||
name: t('Data'),
|
||||
createButton: {
|
||||
name: t('Dataset'),
|
||||
url: '/tablemodelview/add',
|
||||
|
|
@ -487,49 +525,42 @@ class DatasetList extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<>
|
||||
<SubMenu {...this.menu} canCreate={this.canCreate} />
|
||||
<div className="container welcome">
|
||||
<Panel>
|
||||
<Panel.Body>
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected datasets?',
|
||||
)}
|
||||
onConfirm={this.handleBulkDatasetDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-trash" /> Delete
|
||||
</>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView
|
||||
className="dataset-list-view"
|
||||
title={'Datasets'}
|
||||
columns={this.columns}
|
||||
data={datasets}
|
||||
count={datasetCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</Panel.Body>
|
||||
</Panel>
|
||||
</div>
|
||||
<ConfirmStatusChange
|
||||
title={t('Please confirm')}
|
||||
description={t(
|
||||
'Are you sure you want to delete the selected datasets?',
|
||||
)}
|
||||
onConfirm={this.handleBulkDatasetDelete}
|
||||
>
|
||||
{confirmDelete => {
|
||||
const bulkActions = [];
|
||||
if (this.canDelete) {
|
||||
bulkActions.push({
|
||||
key: 'delete',
|
||||
name: (
|
||||
<>
|
||||
<i className="fa fa-trash" /> {t('Delete')}
|
||||
</>
|
||||
),
|
||||
onSelect: confirmDelete,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<ListView
|
||||
className="dataset-list-view"
|
||||
columns={this.columns}
|
||||
data={datasets}
|
||||
count={datasetCount}
|
||||
pageSize={PAGE_SIZE}
|
||||
fetchData={this.fetchData}
|
||||
loading={loading}
|
||||
initialSort={this.initialSort}
|
||||
filters={filters}
|
||||
bulkActions={bulkActions}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</ConfirmStatusChange>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,8 +209,4 @@
|
|||
/* in favor of custom/reusable CSS wherever possible */
|
||||
/************************************************************************/
|
||||
|
||||
// ***************************** SIP 34 UI *******************************
|
||||
@table-hover: rgba(236, 238, 242, 0.5);
|
||||
@table-selected: #eceef2;
|
||||
|
||||
@import '../less/cosmo/variables.less';
|
||||
|
|
|
|||
Loading…
Reference in New Issue