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