mirror of
https://github.com/apache/superset.git
synced 2024-09-06 05:47:43 -04:00
style(listview): various changes to get closer to SIP-34 designs (#11101)
This commit is contained in:
parent
56d5e8a1cb
commit
7b0dabd7aa
@ -32,7 +32,7 @@ describe('dashboard list view', () => {
|
|||||||
// check dashboard list view header
|
// check dashboard list view header
|
||||||
cy.get('th[role="columnheader"]:nth-child(2)').contains('Title');
|
cy.get('th[role="columnheader"]:nth-child(2)').contains('Title');
|
||||||
cy.get('th[role="columnheader"]:nth-child(3)').contains('Modified By');
|
cy.get('th[role="columnheader"]:nth-child(3)').contains('Modified By');
|
||||||
cy.get('th[role="columnheader"]:nth-child(4)').contains('Published');
|
cy.get('th[role="columnheader"]:nth-child(4)').contains('Status');
|
||||||
cy.get('th[role="columnheader"]:nth-child(5)').contains('Modified');
|
cy.get('th[role="columnheader"]:nth-child(5)').contains('Modified');
|
||||||
cy.get('th[role="columnheader"]:nth-child(6)').contains('Created By');
|
cy.get('th[role="columnheader"]:nth-child(6)').contains('Created By');
|
||||||
cy.get('th[role="columnheader"]:nth-child(7)').contains('Owners');
|
cy.get('th[role="columnheader"]:nth-child(7)').contains('Owners');
|
||||||
|
@ -26,8 +26,6 @@ interface FaveStarProps {
|
|||||||
fetchFaveStar(id: number): any;
|
fetchFaveStar(id: number): any;
|
||||||
saveFaveStar(id: number, isStarred: boolean): any;
|
saveFaveStar(id: number, isStarred: boolean): any;
|
||||||
isStarred: boolean;
|
isStarred: boolean;
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
showTooltip?: boolean;
|
showTooltip?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,10 +34,10 @@ export default class FaveStar extends React.PureComponent<FaveStarProps> {
|
|||||||
this.props.fetchFaveStar(this.props.itemId);
|
this.props.fetchFaveStar(this.props.itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(e: React.MouseEvent) {
|
onClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.saveFaveStar(this.props.itemId, this.props.isStarred);
|
this.props.saveFaveStar(this.props.itemId, this.props.isStarred);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.showTooltip) {
|
if (this.props.showTooltip) {
|
||||||
@ -48,19 +46,13 @@ export default class FaveStar extends React.PureComponent<FaveStarProps> {
|
|||||||
label="fave-unfave"
|
label="fave-unfave"
|
||||||
tooltip={t('Click to favorite/unfavorite')}
|
tooltip={t('Click to favorite/unfavorite')}
|
||||||
>
|
>
|
||||||
<a
|
<a href="#" onClick={this.onClick} className="fave-unfave-icon">
|
||||||
href="#"
|
|
||||||
onClick={this.onClick.bind(this)}
|
|
||||||
className="fave-unfave-icon"
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
name={
|
name={
|
||||||
this.props.isStarred
|
this.props.isStarred
|
||||||
? 'favorite-selected'
|
? 'favorite-selected'
|
||||||
: 'favorite-unselected'
|
: 'favorite-unselected'
|
||||||
}
|
}
|
||||||
width={this.props.width || 20}
|
|
||||||
height={this.props.height || 'auto'}
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
@ -68,17 +60,11 @@ export default class FaveStar extends React.PureComponent<FaveStarProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a href="#" onClick={this.onClick} className="fave-unfave-icon">
|
||||||
href="#"
|
|
||||||
onClick={this.onClick.bind(this)}
|
|
||||||
className="fave-unfave-icon"
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
name={
|
name={
|
||||||
this.props.isStarred ? 'favorite-selected' : 'favorite-unselected'
|
this.props.isStarred ? 'favorite-selected' : 'favorite-unselected'
|
||||||
}
|
}
|
||||||
width={this.props.width || 20}
|
|
||||||
height={this.props.height || 'auto'}
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -33,6 +33,10 @@ const CheckboxLabel = styled.label`
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const IconWithColor = styled(Icon)`
|
||||||
|
color: ${({ theme }) => theme.colors.primary.dark1};
|
||||||
|
`;
|
||||||
|
|
||||||
const HiddenInput = styled.input`
|
const HiddenInput = styled.input`
|
||||||
visibility: none;
|
visibility: none;
|
||||||
`;
|
`;
|
||||||
@ -57,8 +61,8 @@ const IndeterminateCheckbox = React.forwardRef(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CheckboxLabel title={title}>
|
<CheckboxLabel title={title}>
|
||||||
{indeterminate && <Icon name="checkbox-half" />}
|
{indeterminate && <IconWithColor name="checkbox-half" />}
|
||||||
{!indeterminate && checked && <Icon name="checkbox-on" />}
|
{!indeterminate && checked && <IconWithColor name="checkbox-on" />}
|
||||||
{!indeterminate && !checked && <Icon name="checkbox-off" />}
|
{!indeterminate && !checked && <Icon name="checkbox-off" />}
|
||||||
<HiddenInput
|
<HiddenInput
|
||||||
className="hidden"
|
className="hidden"
|
||||||
|
@ -33,8 +33,6 @@ const CardContainer = styled.div`
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(459px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(459px, 1fr));
|
||||||
grid-gap: ${({ theme }) => theme.gridUnit * 8}px;
|
grid-gap: ${({ theme }) => theme.gridUnit * 8}px;
|
||||||
padding: ${({ theme }) => theme.gridUnit * 2}px
|
|
||||||
${({ theme }) => theme.gridUnit * 4}px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
|
@ -30,11 +30,9 @@ const SortTitle = styled.label`
|
|||||||
|
|
||||||
const SortContainer = styled.div`
|
const SortContainer = styled.div`
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
float: right;
|
|
||||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||||
padding: 24px 24px 0 0;
|
padding: ${({ theme }) => theme.gridUnit * 3}px 0 0 0;
|
||||||
position: relative;
|
text-align: left;
|
||||||
top: 8px;
|
|
||||||
`;
|
`;
|
||||||
interface CardViewSelectSortProps {
|
interface CardViewSelectSortProps {
|
||||||
onChange: (conf: FetchDataConfig) => any;
|
onChange: (conf: FetchDataConfig) => any;
|
||||||
|
@ -209,9 +209,7 @@ interface UIFiltersProps {
|
|||||||
|
|
||||||
const FilterWrapper = styled.div`
|
const FilterWrapper = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: ${({ theme }) => theme.gridUnit * 6}px
|
padding: 0 0 ${({ theme }) => theme.gridUnit * 8}px;
|
||||||
${({ theme }) => theme.gridUnit * 4}px
|
|
||||||
${({ theme }) => theme.gridUnit * 2}px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function UIFilters({
|
function UIFilters({
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { t, styled } from '@superset-ui/core';
|
import { t, styled } from '@superset-ui/core';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Alert } from 'react-bootstrap';
|
import { Alert } from 'react-bootstrap';
|
||||||
import { Empty } from 'src/common/components';
|
import { Empty } from 'src/common/components';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
@ -42,14 +42,21 @@ const ListViewStyles = styled.div`
|
|||||||
|
|
||||||
.superset-list-view {
|
.superset-list-view {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-color: white;
|
|
||||||
border-radius: 4px 0;
|
border-radius: 4px 0;
|
||||||
margin: 0 16px;
|
margin: 0 16px;
|
||||||
padding-bottom: 48px;
|
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
flex: 5;
|
||||||
|
}
|
||||||
|
.header-right {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
.body {
|
.body {
|
||||||
overflow: scroll;
|
|
||||||
max-height: 64vh;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +64,7 @@ const ListViewStyles = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-count-container {
|
.row-count-container {
|
||||||
@ -114,9 +122,8 @@ const bulkSelectColumnConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ViewModeContainer = styled.div`
|
const ViewModeContainer = styled.div`
|
||||||
padding: ${({ theme }) => theme.gridUnit * 6}px 0px
|
padding: 0 ${({ theme }) => theme.gridUnit * 4}px
|
||||||
${({ theme }) => theme.gridUnit * 2}px
|
${({ theme }) => theme.gridUnit * 8}px 0;
|
||||||
${({ theme }) => theme.gridUnit * 4}px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
@ -250,7 +257,7 @@ function ListView<T extends object = any>({
|
|||||||
const filterable = Boolean(filters.length);
|
const filterable = Boolean(filters.length);
|
||||||
if (filterable) {
|
if (filterable) {
|
||||||
const columnAccessors = columns.reduce(
|
const columnAccessors = columns.reduce(
|
||||||
(acc, col) => ({ ...acc, [col.accessor || col.id]: true }),
|
(acc, col) => ({ ...acc, [col.id || col.accessor]: true }),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
filters.forEach(f => {
|
filters.forEach(f => {
|
||||||
@ -267,29 +274,38 @@ function ListView<T extends object = any>({
|
|||||||
cardViewEnabled ? defaultViewMode : 'table',
|
cardViewEnabled ? defaultViewMode : 'table',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// discard selections if bulk select is disabled
|
||||||
|
if (!bulkSelectEnabled) toggleAllRowsSelected(false);
|
||||||
|
}, [bulkSelectEnabled, toggleAllRowsSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListViewStyles>
|
<ListViewStyles>
|
||||||
<div className={`superset-list-view ${className}`}>
|
<div className={`superset-list-view ${className}`}>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
{cardViewEnabled && (
|
<div className="header-left">
|
||||||
<ViewModeToggle mode={viewingMode} setMode={setViewingMode} />
|
{cardViewEnabled && (
|
||||||
)}
|
<ViewModeToggle mode={viewingMode} setMode={setViewingMode} />
|
||||||
{filterable && (
|
)}
|
||||||
<FilterControls
|
{filterable && (
|
||||||
filters={filters}
|
<FilterControls
|
||||||
internalFilters={internalFilters}
|
filters={filters}
|
||||||
updateFilterValue={applyFilterValue}
|
internalFilters={internalFilters}
|
||||||
/>
|
updateFilterValue={applyFilterValue}
|
||||||
)}
|
/>
|
||||||
{viewingMode === 'card' && cardSortSelectOptions && (
|
)}
|
||||||
<CardSortSelect
|
</div>
|
||||||
initialSort={initialSort}
|
<div className="header-right">
|
||||||
onChange={fetchData}
|
{viewingMode === 'card' && cardSortSelectOptions && (
|
||||||
options={cardSortSelectOptions}
|
<CardSortSelect
|
||||||
pageIndex={pageIndex}
|
initialSort={initialSort}
|
||||||
pageSize={pageSize}
|
onChange={fetchData}
|
||||||
/>
|
options={cardSortSelectOptions}
|
||||||
)}
|
pageIndex={pageIndex}
|
||||||
|
pageSize={pageSize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="body">
|
<div className="body">
|
||||||
{bulkSelectEnabled && (
|
{bulkSelectEnabled && (
|
||||||
@ -318,9 +334,9 @@ function ListView<T extends object = any>({
|
|||||||
data-test="bulk-select-action"
|
data-test="bulk-select-action"
|
||||||
key={action.key}
|
key={action.key}
|
||||||
className={cx({
|
className={cx({
|
||||||
danger: action.type === 'danger',
|
'btn-danger': action.type === 'danger',
|
||||||
primary: action.type === 'primary',
|
'btn-primary': action.type === 'primary',
|
||||||
secondary: action.type === 'secondary',
|
'btn-secondary': action.type === 'secondary',
|
||||||
})}
|
})}
|
||||||
cta
|
cta
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -34,8 +34,19 @@ interface TableCollectionProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Table = styled.table`
|
const Table = styled.table`
|
||||||
|
background-color: white;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
|
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||||
|
|
||||||
|
thead > tr > th {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
tr:first-of-type > td {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
th {
|
th {
|
||||||
background: ${({ theme }) => theme.colors.grayscale.light5};
|
background: ${({ theme }) => theme.colors.grayscale.light5};
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@ -177,10 +188,6 @@ const Table = styled.table`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sort-icon {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loading-shimmer {
|
@keyframes loading-shimmer {
|
||||||
40% {
|
40% {
|
||||||
background-position: 100% 0;
|
background-position: 100% 0;
|
||||||
|
@ -276,5 +276,6 @@ export const filterSelectStyles: PartialStylesConfig = {
|
|||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,20 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { styled, logging } from '@superset-ui/core';
|
import { styled, logging } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export type BackgroundPosition = 'top' | 'bottom';
|
||||||
|
interface ImageContainerProps {
|
||||||
|
src: string;
|
||||||
|
position: BackgroundPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageContainer = styled.div<ImageContainerProps>`
|
||||||
|
background-image: url(${({ src }) => src});
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center ${({ position }) => position};
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
interface ImageLoaderProps
|
interface ImageLoaderProps
|
||||||
extends React.DetailedHTMLProps<
|
extends React.DetailedHTMLProps<
|
||||||
React.HTMLAttributes<HTMLDivElement>,
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
@ -27,23 +41,14 @@ interface ImageLoaderProps
|
|||||||
fallback: string;
|
fallback: string;
|
||||||
src: string;
|
src: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
position: BackgroundPosition;
|
||||||
}
|
}
|
||||||
type ImageContainerProps = {
|
|
||||||
src: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ImageContainer = styled.div`
|
|
||||||
background-image: url(${({ src }: ImageContainerProps) => src});
|
|
||||||
background-size: cover;
|
|
||||||
display: inline-block;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function ImageLoader({
|
export default function ImageLoader({
|
||||||
src,
|
src,
|
||||||
fallback,
|
fallback,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
position,
|
||||||
...rest
|
...rest
|
||||||
}: ImageLoaderProps) {
|
}: ImageLoaderProps) {
|
||||||
const [imgSrc, setImgSrc] = React.useState<string>(fallback);
|
const [imgSrc, setImgSrc] = React.useState<string>(fallback);
|
||||||
@ -71,5 +76,11 @@ export default function ImageLoader({
|
|||||||
};
|
};
|
||||||
}, [src, fallback]);
|
}, [src, fallback]);
|
||||||
|
|
||||||
return <ImageContainer src={isLoading ? fallback : imgSrc} {...rest} />;
|
return (
|
||||||
|
<ImageContainer
|
||||||
|
src={isLoading ? fallback : imgSrc}
|
||||||
|
{...rest}
|
||||||
|
position={position}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import React from 'react';
|
|||||||
import { styled } from '@superset-ui/core';
|
import { styled } from '@superset-ui/core';
|
||||||
import Icon from 'src/components/Icon';
|
import Icon from 'src/components/Icon';
|
||||||
import { Card, Skeleton, ThinSkeleton } from 'src/common/components';
|
import { Card, Skeleton, ThinSkeleton } from 'src/common/components';
|
||||||
import ImageLoader from './ImageLoader';
|
import ImageLoader, { BackgroundPosition } from './ImageLoader';
|
||||||
|
|
||||||
const MenuIcon = styled(Icon)`
|
const MenuIcon = styled(Icon)`
|
||||||
width: ${({ theme }) => theme.gridUnit * 4}px;
|
width: ${({ theme }) => theme.gridUnit * 4}px;
|
||||||
@ -36,6 +36,8 @@ const ActionsWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCard = styled(Card)`
|
const StyledCard = styled(Card)`
|
||||||
|
border: 1px solid #d9dbe4;
|
||||||
|
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: ${({ theme }) => theme.gridUnit * 4}px
|
padding: ${({ theme }) => theme.gridUnit * 4}px
|
||||||
${({ theme }) => theme.gridUnit * 2}px;
|
${({ theme }) => theme.gridUnit * 2}px;
|
||||||
@ -43,6 +45,37 @@ const StyledCard = styled(Card)`
|
|||||||
.ant-card-meta-detail > div:not(:last-child) {
|
.ant-card-meta-detail > div:not(:last-child) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
.gradient-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 8px 8px 28px 0px rgba(0, 0, 0, 0.24);
|
||||||
|
transition: box-shadow ${({ theme }) => theme.transitionTiming}s ease-in-out;
|
||||||
|
|
||||||
|
.gradient-container:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(0, 0, 0, 0) 47.83%,
|
||||||
|
rgba(0, 0, 0, 0.219135) 79.64%,
|
||||||
|
rgba(0, 0, 0, 0.5) 100%
|
||||||
|
);
|
||||||
|
|
||||||
|
transition: background ${({ theme }) => theme.transitionTiming}s
|
||||||
|
ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-footer {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Cover = styled.div`
|
const Cover = styled.div`
|
||||||
@ -53,33 +86,6 @@ const Cover = styled.div`
|
|||||||
transform: translateY(${({ theme }) => theme.gridUnit * 9}px);
|
transform: translateY(${({ theme }) => theme.gridUnit * 9}px);
|
||||||
transition: ${({ theme }) => theme.transitionTiming}s ease-out;
|
transition: ${({ theme }) => theme.transitionTiming}s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.cover-footer {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const GradientContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
background: linear-gradient(
|
|
||||||
180deg,
|
|
||||||
rgba(0, 0, 0, 0) 47.83%,
|
|
||||||
rgba(0, 0, 0, 0.219135) 79.64%,
|
|
||||||
rgba(0, 0, 0, 0.5) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TitleContainer = styled.div`
|
const TitleContainer = styled.div`
|
||||||
@ -139,6 +145,7 @@ interface CardProps {
|
|||||||
url?: string;
|
url?: string;
|
||||||
imgURL: string;
|
imgURL: string;
|
||||||
imgFallbackURL: string;
|
imgFallbackURL: string;
|
||||||
|
imgPosition?: BackgroundPosition;
|
||||||
description: string;
|
description: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
titleRight?: React.ReactNode;
|
titleRight?: React.ReactNode;
|
||||||
@ -158,19 +165,21 @@ function ListViewCard({
|
|||||||
coverRight,
|
coverRight,
|
||||||
actions,
|
actions,
|
||||||
loading,
|
loading,
|
||||||
|
imgPosition = 'top',
|
||||||
}: CardProps) {
|
}: CardProps) {
|
||||||
return (
|
return (
|
||||||
<StyledCard
|
<StyledCard
|
||||||
cover={
|
cover={
|
||||||
<Cover>
|
<Cover>
|
||||||
<a href={url}>
|
<a href={url}>
|
||||||
<GradientContainer>
|
<div className="gradient-container">
|
||||||
<ImageLoader
|
<ImageLoader
|
||||||
src={imgURL}
|
src={imgURL}
|
||||||
fallback={imgFallbackURL}
|
fallback={imgFallbackURL}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
|
position={imgPosition}
|
||||||
/>
|
/>
|
||||||
</GradientContainer>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<CoverFooter className="cover-footer">
|
<CoverFooter className="cover-footer">
|
||||||
{!loading && coverLeft && (
|
{!loading && coverLeft && (
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { styled } from '@superset-ui/core';
|
import { styled } from '@superset-ui/core';
|
||||||
import { Nav, Navbar, MenuItem } from 'react-bootstrap';
|
import { Nav, Navbar, MenuItem } from 'react-bootstrap';
|
||||||
@ -69,7 +69,7 @@ type MenuChild = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ButtonProps {
|
export interface ButtonProps {
|
||||||
name: any;
|
name: ReactNode;
|
||||||
onClick: OnClickHandler;
|
onClick: OnClickHandler;
|
||||||
buttonStyle:
|
buttonStyle:
|
||||||
| 'primary'
|
| 'primary'
|
||||||
|
@ -35,6 +35,7 @@ const SearchInputWrapper = styled.div`
|
|||||||
|
|
||||||
const StyledInput = styled.input`
|
const StyledInput = styled.input`
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
height: ${({ theme }) => theme.gridUnit * 8}px;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light2};
|
border: 1px solid ${({ theme }) => theme.colors.secondary.light2};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -54,14 +55,14 @@ const commonStyles = `
|
|||||||
`;
|
`;
|
||||||
const SearchIcon = styled(Icon)`
|
const SearchIcon = styled(Icon)`
|
||||||
${commonStyles};
|
${commonStyles};
|
||||||
top: 1px;
|
top: 4px;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ClearIcon = styled(Icon)`
|
const ClearIcon = styled(Icon)`
|
||||||
${commonStyles};
|
${commonStyles};
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 1px;
|
top: 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function SearchInput({
|
export default function SearchInput({
|
||||||
|
@ -24,7 +24,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
|||||||
import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
|
import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
|
||||||
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
||||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
import SubMenu from 'src/components/Menu/SubMenu';
|
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
import AvatarIcon from 'src/components/AvatarIcon';
|
import AvatarIcon from 'src/components/AvatarIcon';
|
||||||
import Icon from 'src/components/Icon';
|
import Icon from 'src/components/Icon';
|
||||||
import FaveStar from 'src/components/FaveStar';
|
import FaveStar from 'src/components/FaveStar';
|
||||||
@ -39,6 +39,7 @@ import Chart from 'src/types/Chart';
|
|||||||
import ListViewCard from 'src/components/ListViewCard';
|
import ListViewCard from 'src/components/ListViewCard';
|
||||||
import Label from 'src/components/Label';
|
import Label from 'src/components/Label';
|
||||||
import { Dropdown, Menu } from 'src/common/components';
|
import { Dropdown, Menu } from 'src/common/components';
|
||||||
|
import TooltipWrapper from 'src/components/TooltipWrapper';
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
|
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
|
||||||
@ -109,6 +110,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
setSliceCurrentlyEditing,
|
setSliceCurrentlyEditing,
|
||||||
] = useState<Slice | null>(null);
|
] = useState<Slice | null>(null);
|
||||||
|
|
||||||
|
const canCreate = hasPerm('can_add');
|
||||||
const canEdit = hasPerm('can_edit');
|
const canEdit = hasPerm('can_edit');
|
||||||
const canDelete = hasPerm('can_delete');
|
const canDelete = hasPerm('can_delete');
|
||||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||||
@ -173,8 +175,6 @@ function ChartList(props: ChartListProps) {
|
|||||||
fetchFaveStar={fetchFaveStar}
|
fetchFaveStar={fetchFaveStar}
|
||||||
saveFaveStar={saveFaveStar}
|
saveFaveStar={saveFaveStar}
|
||||||
isStarred={!!favoriteStatusRef.current[id]}
|
isStarred={!!favoriteStatusRef.current[id]}
|
||||||
height={20}
|
|
||||||
width={20}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -190,6 +190,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
Header: '',
|
Header: '',
|
||||||
id: 'favorite',
|
id: 'favorite',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
size: 'xs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -208,6 +209,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
}: any) => vizType,
|
}: any) => vizType,
|
||||||
Header: t('Visualization Type'),
|
Header: t('Visualization Type'),
|
||||||
accessor: 'viz_type',
|
accessor: 'viz_type',
|
||||||
|
size: 'xxl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -219,7 +221,8 @@ function ChartList(props: ChartListProps) {
|
|||||||
},
|
},
|
||||||
}: any) => <a href={dsUrl}>{dsNameTxt}</a>,
|
}: any) => <a href={dsUrl}>{dsNameTxt}</a>,
|
||||||
Header: t('Dataset'),
|
Header: t('Dataset'),
|
||||||
accessor: 'datasource_name',
|
accessor: 'datasource_id',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -232,6 +235,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
Header: t('Modified By'),
|
Header: t('Modified By'),
|
||||||
accessor: 'changed_by.first_name',
|
accessor: 'changed_by.first_name',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -241,22 +245,13 @@ function ChartList(props: ChartListProps) {
|
|||||||
}: any) => <span className="no-wrap">{changedOn}</span>,
|
}: any) => <span className="no-wrap">{changedOn}</span>,
|
||||||
Header: t('Last Modified'),
|
Header: t('Last Modified'),
|
||||||
accessor: 'changed_on_delta_humanized',
|
accessor: 'changed_on_delta_humanized',
|
||||||
},
|
size: 'xl',
|
||||||
{
|
|
||||||
accessor: 'description',
|
|
||||||
hidden: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'owners',
|
accessor: 'owners',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessor: 'datasource_id',
|
|
||||||
hidden: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
@ -267,6 +262,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
Header: t('Created By'),
|
Header: t('Created By'),
|
||||||
accessor: 'created_by',
|
accessor: 'created_by',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({ row: { original } }: any) => {
|
Cell: ({ row: { original } }: any) => {
|
||||||
@ -290,26 +286,38 @@ function ChartList(props: ChartListProps) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
>
|
>
|
||||||
{confirmDelete => (
|
{confirmDelete => (
|
||||||
<span
|
<TooltipWrapper
|
||||||
role="button"
|
label="delete-action"
|
||||||
tabIndex={0}
|
tooltip={t('Delete')}
|
||||||
className="action-button"
|
placement="bottom"
|
||||||
onClick={confirmDelete}
|
|
||||||
>
|
>
|
||||||
<Icon name="trash" />
|
<span
|
||||||
</span>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="action-button"
|
||||||
|
onClick={confirmDelete}
|
||||||
|
>
|
||||||
|
<Icon name="trash" />
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
</ConfirmStatusChange>
|
</ConfirmStatusChange>
|
||||||
)}
|
)}
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<span
|
<TooltipWrapper
|
||||||
role="button"
|
label="edit-action"
|
||||||
tabIndex={0}
|
tooltip={t('Edit')}
|
||||||
className="action-button"
|
placement="bottom"
|
||||||
onClick={openEditModal}
|
|
||||||
>
|
>
|
||||||
<Icon name="edit-alt" />
|
<span
|
||||||
</span>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="action-button"
|
||||||
|
onClick={openEditModal}
|
||||||
|
>
|
||||||
|
<Icon name="edit-alt" />
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -319,7 +327,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[canEdit, canDelete, favoriteStatusRef],
|
[canEdit, canDelete],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filters: Filters = [
|
const filters: Filters = [
|
||||||
@ -467,6 +475,7 @@ function ChartList(props: ChartListProps) {
|
|||||||
url={bulkSelectEnabled ? undefined : chart.url}
|
url={bulkSelectEnabled ? undefined : chart.url}
|
||||||
imgURL={chart.thumbnail_url ?? ''}
|
imgURL={chart.thumbnail_url ?? ''}
|
||||||
imgFallbackURL="/static/assets/images/chart-card-fallback.png"
|
imgFallbackURL="/static/assets/images/chart-card-fallback.png"
|
||||||
|
imgPosition="bottom"
|
||||||
description={t('Last modified %s', chart.changed_on_delta_humanized)}
|
description={t('Last modified %s', chart.changed_on_delta_humanized)}
|
||||||
coverLeft={(chart.owners || []).slice(0, 5).map(owner => (
|
coverLeft={(chart.owners || []).slice(0, 5).map(owner => (
|
||||||
<AvatarIcon
|
<AvatarIcon
|
||||||
@ -492,23 +501,31 @@ function ChartList(props: ChartListProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const subMenuButtons: SubMenuProps['buttons'] = [];
|
||||||
|
if (canDelete) {
|
||||||
|
subMenuButtons.push({
|
||||||
|
name: t('Bulk Select'),
|
||||||
|
buttonStyle: 'secondary',
|
||||||
|
onClick: toggleBulkSelect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (canCreate) {
|
||||||
|
subMenuButtons.push({
|
||||||
|
name: (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<i className="fa fa-plus" /> {t('Chart')}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
buttonStyle: 'primary',
|
||||||
|
onClick: () => {
|
||||||
|
window.location.assign('/chart/add');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SubMenu
|
<SubMenu name={t('Charts')} buttons={subMenuButtons} />
|
||||||
name={t('Charts')}
|
|
||||||
buttons={
|
|
||||||
canDelete
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: t('Bulk Select'),
|
|
||||||
buttonStyle: 'secondary',
|
|
||||||
onClick: toggleBulkSelect,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{sliceCurrentlyEditing && (
|
{sliceCurrentlyEditing && (
|
||||||
<PropertiesModal
|
<PropertiesModal
|
||||||
onHide={closeChartEditModal}
|
onHide={closeChartEditModal}
|
||||||
|
@ -23,7 +23,7 @@ import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
|||||||
import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
|
import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
|
||||||
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
|
||||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
import SubMenu from 'src/components/Menu/SubMenu';
|
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
import AvatarIcon from 'src/components/AvatarIcon';
|
import AvatarIcon from 'src/components/AvatarIcon';
|
||||||
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
||||||
import ExpandableList from 'src/components/ExpandableList';
|
import ExpandableList from 'src/components/ExpandableList';
|
||||||
@ -35,6 +35,7 @@ import FaveStar from 'src/components/FaveStar';
|
|||||||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||||
import ListViewCard from 'src/components/ListViewCard';
|
import ListViewCard from 'src/components/ListViewCard';
|
||||||
import { Dropdown, Menu } from 'src/common/components';
|
import { Dropdown, Menu } from 'src/common/components';
|
||||||
|
import TooltipWrapper from 'src/components/TooltipWrapper';
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
|
const FAVESTAR_BASE_URL = '/superset/favstar/Dashboard';
|
||||||
@ -86,6 +87,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canCreate = hasPerm('can_add');
|
||||||
const canEdit = hasPerm('can_edit');
|
const canEdit = hasPerm('can_edit');
|
||||||
const canDelete = hasPerm('can_delete');
|
const canDelete = hasPerm('can_delete');
|
||||||
const canExport = hasPerm('can_mulexport');
|
const canExport = hasPerm('can_mulexport');
|
||||||
@ -169,8 +171,6 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
fetchFaveStar={fetchFaveStar}
|
fetchFaveStar={fetchFaveStar}
|
||||||
saveFaveStar={saveFaveStar}
|
saveFaveStar={saveFaveStar}
|
||||||
isStarred={!!favoriteStatusRef.current[id]}
|
isStarred={!!favoriteStatusRef.current[id]}
|
||||||
height={20}
|
|
||||||
width={20}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -186,6 +186,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
Header: '',
|
Header: '',
|
||||||
id: 'favorite',
|
id: 'favorite',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
size: 'xs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -208,19 +209,17 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
}: any) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
Header: t('Modified By'),
|
Header: t('Modified By'),
|
||||||
accessor: 'changed_by.first_name',
|
accessor: 'changed_by.first_name',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { published },
|
original: { published },
|
||||||
},
|
},
|
||||||
}: any) => (
|
}: any) => (published ? t('Published') : t('Draft')),
|
||||||
<span className="no-wrap">
|
Header: t('Status'),
|
||||||
{published ? <Icon name="check" /> : ''}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
Header: t('Published'),
|
|
||||||
accessor: 'published',
|
accessor: 'published',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -230,11 +229,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
}: any) => <span className="no-wrap">{changedOn}</span>,
|
}: any) => <span className="no-wrap">{changedOn}</span>,
|
||||||
Header: t('Modified'),
|
Header: t('Modified'),
|
||||||
accessor: 'changed_on_delta_humanized',
|
accessor: 'changed_on_delta_humanized',
|
||||||
},
|
size: 'xl',
|
||||||
{
|
|
||||||
accessor: 'slug',
|
|
||||||
hidden: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -246,6 +241,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
Header: t('Created By'),
|
Header: t('Created By'),
|
||||||
accessor: 'created_by',
|
accessor: 'created_by',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -264,6 +260,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
Header: t('Owners'),
|
Header: t('Owners'),
|
||||||
accessor: 'owners',
|
accessor: 'owners',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({ row: { original } }: any) => {
|
Cell: ({ row: { original } }: any) => {
|
||||||
@ -287,36 +284,54 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
>
|
>
|
||||||
{confirmDelete => (
|
{confirmDelete => (
|
||||||
<span
|
<TooltipWrapper
|
||||||
role="button"
|
label="delete-action"
|
||||||
tabIndex={0}
|
tooltip={t('Delete')}
|
||||||
className="action-button"
|
placement="bottom"
|
||||||
onClick={confirmDelete}
|
|
||||||
>
|
>
|
||||||
<Icon name="trash" />
|
<span
|
||||||
</span>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="action-button"
|
||||||
|
onClick={confirmDelete}
|
||||||
|
>
|
||||||
|
<Icon name="trash" />
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
</ConfirmStatusChange>
|
</ConfirmStatusChange>
|
||||||
)}
|
)}
|
||||||
{canExport && (
|
{canExport && (
|
||||||
<span
|
<TooltipWrapper
|
||||||
role="button"
|
label="export-action"
|
||||||
tabIndex={0}
|
tooltip={t('Export')}
|
||||||
className="action-button"
|
placement="bottom"
|
||||||
onClick={handleExport}
|
|
||||||
>
|
>
|
||||||
<Icon name="share" />
|
<span
|
||||||
</span>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="action-button"
|
||||||
|
onClick={handleExport}
|
||||||
|
>
|
||||||
|
<Icon name="share" />
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<span
|
<TooltipWrapper
|
||||||
role="button"
|
label="edit-action"
|
||||||
tabIndex={0}
|
tooltip={t('Edit')}
|
||||||
className="action-button"
|
placement="bottom"
|
||||||
onClick={handleEdit}
|
|
||||||
>
|
>
|
||||||
<Icon name="edit-alt" />
|
<span
|
||||||
</span>
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="action-button"
|
||||||
|
onClick={handleEdit}
|
||||||
|
>
|
||||||
|
<Icon name="edit-alt" />
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -331,7 +346,7 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
|
|
||||||
const filters: Filters = [
|
const filters: Filters = [
|
||||||
{
|
{
|
||||||
Header: 'Owner',
|
Header: t('Owner'),
|
||||||
id: 'owners',
|
id: 'owners',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_m_m',
|
operator: 'rel_m_m',
|
||||||
@ -371,18 +386,18 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
paginate: true,
|
paginate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Published',
|
Header: t('Status'),
|
||||||
id: 'published',
|
id: 'published',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'eq',
|
operator: 'eq',
|
||||||
unfilteredLabel: 'Any',
|
unfilteredLabel: 'Any',
|
||||||
selects: [
|
selects: [
|
||||||
{ label: 'Published', value: true },
|
{ label: t('Published'), value: true },
|
||||||
{ label: 'Unpublished', value: false },
|
{ label: t('Unpublished'), value: false },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Search',
|
Header: t('Search'),
|
||||||
id: 'dashboard_title',
|
id: 'dashboard_title',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'title_or_slug',
|
operator: 'title_or_slug',
|
||||||
@ -495,22 +510,31 @@ function DashboardList(props: DashboardListProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subMenuButtons: SubMenuProps['buttons'] = [];
|
||||||
|
if (canDelete || canExport) {
|
||||||
|
subMenuButtons.push({
|
||||||
|
name: t('Bulk Select'),
|
||||||
|
buttonStyle: 'secondary',
|
||||||
|
onClick: toggleBulkSelect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (canCreate) {
|
||||||
|
subMenuButtons.push({
|
||||||
|
name: (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<i className="fa fa-plus" /> {t('Dashboard')}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
buttonStyle: 'primary',
|
||||||
|
onClick: () => {
|
||||||
|
window.location.assign('/dashboard/new');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SubMenu
|
<SubMenu name={t('Dashboards')} buttons={subMenuButtons} />
|
||||||
name={t('Dashboards')}
|
|
||||||
buttons={
|
|
||||||
canDelete || canExport
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: t('Bulk Select'),
|
|
||||||
buttonStyle: 'secondary',
|
|
||||||
onClick: toggleBulkSelect,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ConfirmStatusChange
|
<ConfirmStatusChange
|
||||||
title={t('Please confirm')}
|
title={t('Please confirm')}
|
||||||
description={t(
|
description={t(
|
||||||
|
@ -285,7 +285,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[canDelete, canCreate],
|
[canDelete, canEdit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filters: Filters = useMemo(
|
const filters: Filters = useMemo(
|
||||||
|
@ -17,7 +17,12 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { SupersetClient, t } from '@superset-ui/core';
|
import { SupersetClient, t } from '@superset-ui/core';
|
||||||
import React, { FunctionComponent, useState, useMemo } from 'react';
|
import React, {
|
||||||
|
FunctionComponent,
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import {
|
import {
|
||||||
createFetchRelated,
|
createFetchRelated,
|
||||||
@ -101,20 +106,23 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
|
|
||||||
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
const initialSort = [{ id: 'changed_on_delta_humanized', desc: true }];
|
||||||
|
|
||||||
const openDatasetEditModal = ({ id }: Dataset) => {
|
const openDatasetEditModal = useCallback(
|
||||||
SupersetClient.get({
|
({ id }: Dataset) => {
|
||||||
endpoint: `/api/v1/dataset/${id}`,
|
SupersetClient.get({
|
||||||
})
|
endpoint: `/api/v1/dataset/${id}`,
|
||||||
.then(({ json = {} }) => {
|
|
||||||
const owners = json.result.owners.map((owner: any) => owner.id);
|
|
||||||
setDatasetCurrentlyEditing({ ...json.result, owners });
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.then(({ json = {} }) => {
|
||||||
addDangerToast(
|
const owners = json.result.owners.map((owner: any) => owner.id);
|
||||||
t('An error occurred while fetching dataset related data'),
|
setDatasetCurrentlyEditing({ ...json.result, owners });
|
||||||
);
|
})
|
||||||
});
|
.catch(() => {
|
||||||
};
|
addDangerToast(
|
||||||
|
t('An error occurred while fetching dataset related data'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[addDangerToast],
|
||||||
|
);
|
||||||
|
|
||||||
const openDatasetDeleteModal = (dataset: Dataset) =>
|
const openDatasetDeleteModal = (dataset: Dataset) =>
|
||||||
SupersetClient.get({
|
SupersetClient.get({
|
||||||
@ -170,9 +178,9 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { table_name: datasetTitle },
|
original: { table_name: datasetTitle, explore_url: exploreURL },
|
||||||
},
|
},
|
||||||
}: any) => datasetTitle,
|
}: any) => <a href={exploreURL}>{datasetTitle}</a>,
|
||||||
Header: t('Name'),
|
Header: t('Name'),
|
||||||
accessor: 'table_name',
|
accessor: 'table_name',
|
||||||
},
|
},
|
||||||
@ -263,20 +271,6 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="actions">
|
<span className="actions">
|
||||||
<TooltipWrapper
|
|
||||||
label="explore-action"
|
|
||||||
tooltip={t('Explore')}
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
className="action-button"
|
|
||||||
href={original.explore_url}
|
|
||||||
>
|
|
||||||
<Icon name="nav-explore" />
|
|
||||||
</a>
|
|
||||||
</TooltipWrapper>
|
|
||||||
{canDelete && (
|
{canDelete && (
|
||||||
<TooltipWrapper
|
<TooltipWrapper
|
||||||
label="delete-action"
|
label="delete-action"
|
||||||
@ -318,7 +312,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[canCreate, canEdit, canDelete],
|
[canEdit, canDelete, openDatasetEditModal],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterTypes: Filters = useMemo(
|
const filterTypes: Filters = useMemo(
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupersetClient, t, styled } from '@superset-ui/core';
|
import { SupersetClient, t, styled } from '@superset-ui/core';
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo, useCallback } from 'react';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
@ -104,7 +104,6 @@ function SavedQueryList({
|
|||||||
setSavedQueryCurrentlyPreviewing,
|
setSavedQueryCurrentlyPreviewing,
|
||||||
] = useState<SavedQueryObject | null>(null);
|
] = useState<SavedQueryObject | null>(null);
|
||||||
|
|
||||||
const canCreate = hasPerm('can_add');
|
|
||||||
const canEdit = hasPerm('can_edit');
|
const canEdit = hasPerm('can_edit');
|
||||||
const canDelete = hasPerm('can_delete');
|
const canDelete = hasPerm('can_delete');
|
||||||
|
|
||||||
@ -112,20 +111,23 @@ function SavedQueryList({
|
|||||||
window.open(`${window.location.origin}/superset/sqllab?new=true`);
|
window.open(`${window.location.origin}/superset/sqllab?new=true`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSavedQueryPreview = (id: number) => {
|
const handleSavedQueryPreview = useCallback(
|
||||||
SupersetClient.get({
|
(id: number) => {
|
||||||
endpoint: `/api/v1/saved_query/${id}`,
|
SupersetClient.get({
|
||||||
}).then(
|
endpoint: `/api/v1/saved_query/${id}`,
|
||||||
({ json = {} }) => {
|
}).then(
|
||||||
setSavedQueryCurrentlyPreviewing({ ...json.result });
|
({ json = {} }) => {
|
||||||
},
|
setSavedQueryCurrentlyPreviewing({ ...json.result });
|
||||||
createErrorHandler(errMsg =>
|
},
|
||||||
addDangerToast(
|
createErrorHandler(errMsg =>
|
||||||
t('There was an issue previewing the selected query %s', errMsg),
|
addDangerToast(
|
||||||
|
t('There was an issue previewing the selected query %s', errMsg),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
};
|
[addDangerToast],
|
||||||
|
);
|
||||||
|
|
||||||
const menuData: SubMenuProps = {
|
const menuData: SubMenuProps = {
|
||||||
activeChild: 'Saved Queries',
|
activeChild: 'Saved Queries',
|
||||||
@ -155,41 +157,44 @@ function SavedQueryList({
|
|||||||
window.open(`${window.location.origin}/superset/sqllab?savedQueryId=${id}`);
|
window.open(`${window.location.origin}/superset/sqllab?savedQueryId=${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyQueryLink = (id: number) => {
|
const copyQueryLink = useCallback(
|
||||||
const selection: Selection | null = document.getSelection();
|
(id: number) => {
|
||||||
|
const selection: Selection | null = document.getSelection();
|
||||||
|
|
||||||
if (selection) {
|
if (selection) {
|
||||||
selection.removeAllRanges();
|
|
||||||
const range = document.createRange();
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.textContent = `${window.location.origin}/superset/sqllab?savedQueryId=${id}`;
|
|
||||||
span.style.position = 'fixed';
|
|
||||||
span.style.top = '0';
|
|
||||||
span.style.clip = 'rect(0, 0, 0, 0)';
|
|
||||||
span.style.whiteSpace = 'pre';
|
|
||||||
|
|
||||||
document.body.appendChild(span);
|
|
||||||
range.selectNode(span);
|
|
||||||
selection.addRange(range);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!document.execCommand('copy')) {
|
|
||||||
throw new Error(t('Not successful'));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
addDangerToast(t('Sorry, your browser does not support copying.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.removeChild(span);
|
|
||||||
if (selection.removeRange) {
|
|
||||||
selection.removeRange(range);
|
|
||||||
} else {
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
}
|
const range = document.createRange();
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.textContent = `${window.location.origin}/superset/sqllab?savedQueryId=${id}`;
|
||||||
|
span.style.position = 'fixed';
|
||||||
|
span.style.top = '0';
|
||||||
|
span.style.clip = 'rect(0, 0, 0, 0)';
|
||||||
|
span.style.whiteSpace = 'pre';
|
||||||
|
|
||||||
addSuccessToast(t('Link Copied!'));
|
document.body.appendChild(span);
|
||||||
}
|
range.selectNode(span);
|
||||||
};
|
selection.addRange(range);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!document.execCommand('copy')) {
|
||||||
|
throw new Error(t('Not successful'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
addDangerToast(t('Sorry, your browser does not support copying.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(span);
|
||||||
|
if (selection.removeRange) {
|
||||||
|
selection.removeRange(range);
|
||||||
|
} else {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
addSuccessToast(t('Link Copied!'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[addDangerToast, addSuccessToast],
|
||||||
|
);
|
||||||
|
|
||||||
const handleQueryDelete = ({ id, label }: SavedQueryObject) => {
|
const handleQueryDelete = ({ id, label }: SavedQueryObject) => {
|
||||||
SupersetClient.delete({
|
SupersetClient.delete({
|
||||||
@ -232,22 +237,15 @@ function SavedQueryList({
|
|||||||
Header: t('Name'),
|
Header: t('Name'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'database',
|
||||||
accessor: 'database.database_name',
|
accessor: 'database.database_name',
|
||||||
Header: t('Database'),
|
Header: t('Database'),
|
||||||
},
|
size: 'xl',
|
||||||
{
|
|
||||||
accessor: 'database',
|
|
||||||
hidden: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
Cell: ({
|
|
||||||
row: {
|
|
||||||
original: { database },
|
|
||||||
},
|
|
||||||
}: any) => `${database.database_name}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'schema',
|
accessor: 'schema',
|
||||||
Header: t('Schema'),
|
Header: t('Schema'),
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -284,6 +282,7 @@ function SavedQueryList({
|
|||||||
},
|
},
|
||||||
accessor: 'sql_tables',
|
accessor: 'sql_tables',
|
||||||
Header: t('Tables'),
|
Header: t('Tables'),
|
||||||
|
size: 'xl',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -309,6 +308,7 @@ function SavedQueryList({
|
|||||||
},
|
},
|
||||||
Header: t('Created On'),
|
Header: t('Created On'),
|
||||||
accessor: 'created_on',
|
accessor: 'created_on',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
@ -318,6 +318,7 @@ function SavedQueryList({
|
|||||||
}: any) => changedOn,
|
}: any) => changedOn,
|
||||||
Header: t('Modified'),
|
Header: t('Modified'),
|
||||||
accessor: 'changed_on_delta_humanized',
|
accessor: 'changed_on_delta_humanized',
|
||||||
|
size: 'xl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({ row: { original } }: any) => {
|
Cell: ({ row: { original } }: any) => {
|
||||||
@ -374,7 +375,7 @@ function SavedQueryList({
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[canDelete, canCreate],
|
[canDelete, canEdit, copyQueryLink, handleSavedQueryPreview],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filters: Filters = useMemo(
|
const filters: Filters = useMemo(
|
||||||
|
@ -76,6 +76,8 @@ input.form-control {
|
|||||||
|
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='checkbox'] {
|
input[type='checkbox'] {
|
||||||
|
Loading…
Reference in New Issue
Block a user