From 7b0dabd7aa9dc8fb79d0f699b8c1dfe4a928f3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CA=88=E1=B5=83=E1=B5=A2?= Date: Fri, 9 Oct 2020 13:06:26 -0700 Subject: [PATCH] style(listview): various changes to get closer to SIP-34 designs (#11101) --- .../dashboard_list/list_view.test.ts | 2 +- superset-frontend/src/components/FaveStar.tsx | 22 +-- .../src/components/IndeterminateCheckbox.tsx | 8 +- .../components/ListView/CardCollection.tsx | 2 - .../components/ListView/CardSortSelect.tsx | 6 +- .../src/components/ListView/Filters.tsx | 4 +- .../src/components/ListView/ListView.tsx | 78 +++++++---- .../components/ListView/TableCollection.tsx | 15 +- .../src/components/ListView/utils.ts | 1 + .../components/ListViewCard/ImageLoader.tsx | 35 +++-- .../src/components/ListViewCard/index.tsx | 69 +++++---- .../src/components/Menu/SubMenu.tsx | 4 +- .../src/components/SearchInput.tsx | 5 +- .../src/views/CRUD/chart/ChartList.tsx | 105 ++++++++------ .../views/CRUD/dashboard/DashboardList.tsx | 132 +++++++++++------- .../views/CRUD/data/database/DatabaseList.tsx | 2 +- .../views/CRUD/data/dataset/DatasetList.tsx | 56 ++++---- .../CRUD/data/savedquery/SavedQueryList.tsx | 117 ++++++++-------- superset-frontend/stylesheets/superset.less | 2 + 19 files changed, 366 insertions(+), 299 deletions(-) diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts index 5afe356de3..6b6e39c10a 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts @@ -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'); diff --git a/superset-frontend/src/components/FaveStar.tsx b/superset-frontend/src/components/FaveStar.tsx index 2dba97ca86..f2f2cf778b 100644 --- a/superset-frontend/src/components/FaveStar.tsx +++ b/superset-frontend/src/components/FaveStar.tsx @@ -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 { 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 { label="fave-unfave" tooltip={t('Click to favorite/unfavorite')} > - + @@ -68,17 +60,11 @@ export default class FaveStar extends React.PureComponent { } return ( - + ); diff --git a/superset-frontend/src/components/IndeterminateCheckbox.tsx b/superset-frontend/src/components/IndeterminateCheckbox.tsx index 57b9ed6582..7133f89f71 100644 --- a/superset-frontend/src/components/IndeterminateCheckbox.tsx +++ b/superset-frontend/src/components/IndeterminateCheckbox.tsx @@ -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 ( - {indeterminate && } - {!indeterminate && checked && } + {indeterminate && } + {!indeterminate && checked && } {!indeterminate && !checked && } theme.gridUnit * 8}px; - padding: ${({ theme }) => theme.gridUnit * 2}px - ${({ theme }) => theme.gridUnit * 4}px; `; const CardWrapper = styled.div` diff --git a/superset-frontend/src/components/ListView/CardSortSelect.tsx b/superset-frontend/src/components/ListView/CardSortSelect.tsx index c28e472831..441d7589cc 100644 --- a/superset-frontend/src/components/ListView/CardSortSelect.tsx +++ b/superset-frontend/src/components/ListView/CardSortSelect.tsx @@ -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; diff --git a/superset-frontend/src/components/ListView/Filters.tsx b/superset-frontend/src/components/ListView/Filters.tsx index 3f17d2a3b6..9f1ad78378 100644 --- a/superset-frontend/src/components/ListView/Filters.tsx +++ b/superset-frontend/src/components/ListView/Filters.tsx @@ -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({ diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 40858042ef..e79261c5b8 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -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({ 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({ cardViewEnabled ? defaultViewMode : 'table', ); + useEffect(() => { + // discard selections if bulk select is disabled + if (!bulkSelectEnabled) toggleAllRowsSelected(false); + }, [bulkSelectEnabled, toggleAllRowsSelected]); + return (
- {cardViewEnabled && ( - - )} - {filterable && ( - - )} - {viewingMode === 'card' && cardSortSelectOptions && ( - - )} +
+ {cardViewEnabled && ( + + )} + {filterable && ( + + )} +
+
+ {viewingMode === 'card' && cardSortSelectOptions && ( + + )} +
{bulkSelectEnabled && ( @@ -318,9 +334,9 @@ function ListView({ 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={() => diff --git a/superset-frontend/src/components/ListView/TableCollection.tsx b/superset-frontend/src/components/ListView/TableCollection.tsx index 41b001853b..db799379b3 100644 --- a/superset-frontend/src/components/ListView/TableCollection.tsx +++ b/superset-frontend/src/components/ListView/TableCollection.tsx @@ -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; diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts index c8ecc95be5..727343764c 100644 --- a/superset-frontend/src/components/ListView/utils.ts +++ b/superset-frontend/src/components/ListView/utils.ts @@ -276,5 +276,6 @@ export const filterSelectStyles: PartialStylesConfig = { borderWidth: 0, boxShadow: 'none', cursor: 'pointer', + backgroundColor: 'transparent', }), }; diff --git a/superset-frontend/src/components/ListViewCard/ImageLoader.tsx b/superset-frontend/src/components/ListViewCard/ImageLoader.tsx index 4b33885e39..1bf6f57d90 100644 --- a/superset-frontend/src/components/ListViewCard/ImageLoader.tsx +++ b/superset-frontend/src/components/ListViewCard/ImageLoader.tsx @@ -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` + 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, @@ -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(fallback); @@ -71,5 +76,11 @@ export default function ImageLoader({ }; }, [src, fallback]); - return ; + return ( + + ); } diff --git a/superset-frontend/src/components/ListViewCard/index.tsx b/superset-frontend/src/components/ListViewCard/index.tsx index 4ddd2330aa..8ff0b734bd 100644 --- a/superset-frontend/src/components/ListViewCard/index.tsx +++ b/superset-frontend/src/components/ListViewCard/index.tsx @@ -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 ( - +
- +
{!loading && coverLeft && ( diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx index a96949eadd..e17ce7a2a6 100644 --- a/superset-frontend/src/components/Menu/SubMenu.tsx +++ b/superset-frontend/src/components/Menu/SubMenu.tsx @@ -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' diff --git a/superset-frontend/src/components/SearchInput.tsx b/superset-frontend/src/components/SearchInput.tsx index d5f30f324d..959a2c88f9 100644 --- a/superset-frontend/src/components/SearchInput.tsx +++ b/superset-frontend/src/components/SearchInput.tsx @@ -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({ diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 8577e2e954..00532da7fe 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -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(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) => {dsNameTxt}, Header: t('Dataset'), - accessor: 'datasource_name', + accessor: 'datasource_id', + size: 'xl', }, { Cell: ({ @@ -232,6 +235,7 @@ function ChartList(props: ChartListProps) { }: any) => {changedByName}, Header: t('Modified By'), accessor: 'changed_by.first_name', + size: 'xl', }, { Cell: ({ @@ -241,22 +245,13 @@ function ChartList(props: ChartListProps) { }: any) => {changedOn}, 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 => ( - - - + + + + )} )} {canEdit && ( - - - + + + + )} ); @@ -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 => ( ); } - + const subMenuButtons: SubMenuProps['buttons'] = []; + if (canDelete) { + subMenuButtons.push({ + name: t('Bulk Select'), + buttonStyle: 'secondary', + onClick: toggleBulkSelect, + }); + } + if (canCreate) { + subMenuButtons.push({ + name: ( + <> + {' '} + {t('Chart')} + + ), + buttonStyle: 'primary', + onClick: () => { + window.location.assign('/chart/add'); + }, + }); + } return ( <> - + {sliceCurrentlyEditing && ( ); } @@ -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) => {changedByName}, Header: t('Modified By'), accessor: 'changed_by.first_name', + size: 'xl', }, { Cell: ({ row: { original: { published }, }, - }: any) => ( - - {published ? : ''} - - ), - 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) => {changedOn}, 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 => ( - - - + + + + )} )} {canExport && ( - - - + + + + )} {canEdit && ( - - - + + + + )} ); @@ -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: ( + <> + {' '} + {t('Dashboard')} + + ), + buttonStyle: 'primary', + onClick: () => { + window.location.assign('/dashboard/new'); + }, + }); + } return ( <> - + = ({ 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 = ({ { Cell: ({ row: { - original: { table_name: datasetTitle }, + original: { table_name: datasetTitle, explore_url: exploreURL }, }, - }: any) => datasetTitle, + }: any) => {datasetTitle}, Header: t('Name'), accessor: 'table_name', }, @@ -263,20 +271,6 @@ const DatasetList: FunctionComponent = ({ } return ( - - - - - {canDelete && ( = ({ disableSortBy: true, }, ], - [canCreate, canEdit, canDelete], + [canEdit, canDelete, openDatasetEditModal], ); const filterTypes: Filters = useMemo( diff --git a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx index 93770c7c34..06c5c24b1a 100644 --- a/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx +++ b/superset-frontend/src/views/CRUD/data/savedquery/SavedQueryList.tsx @@ -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(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( diff --git a/superset-frontend/stylesheets/superset.less b/superset-frontend/stylesheets/superset.less index f1058dbdca..1cb08c07bb 100644 --- a/superset-frontend/stylesheets/superset.less +++ b/superset-frontend/stylesheets/superset.less @@ -76,6 +76,8 @@ input.form-control { .container-fluid { text-align: left; + padding-left: 16px; + padding-right: 16px; } input[type='checkbox'] {