mirror of https://github.com/apache/superset.git
fix(dashboard): draft dashboards should be viewable (#14207)
* fix(dashboard): draft dashboards should have open access * Remove a duplicate test
This commit is contained in:
parent
852e840575
commit
2dd20df03d
|
@ -37,28 +37,6 @@ export interface CardSortSelectOption {
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterOperator =
|
|
||||||
| 'sw'
|
|
||||||
| 'ew'
|
|
||||||
| 'ct'
|
|
||||||
| 'eq'
|
|
||||||
| 'nsw'
|
|
||||||
| 'new'
|
|
||||||
| 'nct'
|
|
||||||
| 'neq'
|
|
||||||
| 'gt'
|
|
||||||
| 'lt'
|
|
||||||
| 'rel_m_m'
|
|
||||||
| 'rel_o_m'
|
|
||||||
| 'title_or_slug'
|
|
||||||
| 'name_or_description'
|
|
||||||
| 'all_text'
|
|
||||||
| 'chart_all_text'
|
|
||||||
| 'dataset_is_null_or_empty'
|
|
||||||
| 'between'
|
|
||||||
| 'dashboard_is_favorite'
|
|
||||||
| 'chart_is_favorite';
|
|
||||||
|
|
||||||
export interface Filter {
|
export interface Filter {
|
||||||
Header: ReactNode;
|
Header: ReactNode;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -104,7 +82,7 @@ export interface InternalFilter extends FilterValue {
|
||||||
Header?: string;
|
Header?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FilterOperators {
|
export enum FilterOperator {
|
||||||
startsWith = 'sw',
|
startsWith = 'sw',
|
||||||
endsWith = 'ew',
|
endsWith = 'ew',
|
||||||
contains = 'ct',
|
contains = 'ct',
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Button from 'src/components/Button';
|
||||||
import FacePile from 'src/components/FacePile';
|
import FacePile from 'src/components/FacePile';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import ListView, {
|
import ListView, {
|
||||||
FilterOperators,
|
FilterOperator,
|
||||||
Filters,
|
Filters,
|
||||||
ListViewProps,
|
ListViewProps,
|
||||||
} from 'src/components/ListView';
|
} from 'src/components/ListView';
|
||||||
|
@ -84,7 +84,7 @@ function AlertList({
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
id: 'type',
|
id: 'type',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
value: isReportEnabled ? 'Report' : 'Alert',
|
value: isReportEnabled ? 'Report' : 'Alert',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -373,7 +373,7 @@ function AlertList({
|
||||||
Header: t('Created by'),
|
Header: t('Created by'),
|
||||||
id: 'created_by',
|
id: 'created_by',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationOneMany,
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'report',
|
'report',
|
||||||
|
@ -389,7 +389,7 @@ function AlertList({
|
||||||
Header: t('Status'),
|
Header: t('Status'),
|
||||||
id: 'last_state',
|
id: 'last_state',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'Any',
|
unfilteredLabel: 'Any',
|
||||||
selects: [
|
selects: [
|
||||||
{ label: t(`${AlertState.success}`), value: AlertState.success },
|
{ label: t(`${AlertState.success}`), value: AlertState.success },
|
||||||
|
@ -403,7 +403,7 @@ function AlertList({
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'name',
|
id: 'name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: FilterOperators.contains,
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -27,7 +27,11 @@ import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
|
||||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
||||||
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
import ListView, {
|
||||||
|
ListViewProps,
|
||||||
|
Filters,
|
||||||
|
FilterOperator,
|
||||||
|
} from 'src/components/ListView';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
import DeleteModal from 'src/components/DeleteModal';
|
import DeleteModal from 'src/components/DeleteModal';
|
||||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
|
@ -286,7 +290,7 @@ function AnnotationLayersList({
|
||||||
Header: t('Created by'),
|
Header: t('Created by'),
|
||||||
id: 'created_by',
|
id: 'created_by',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_o_m',
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'annotation_layer',
|
'annotation_layer',
|
||||||
|
@ -305,7 +309,7 @@ function AnnotationLayersList({
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'name',
|
id: 'name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'ct',
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -44,7 +44,7 @@ import ListView, {
|
||||||
ListViewProps,
|
ListViewProps,
|
||||||
Filters,
|
Filters,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
FilterOperators,
|
FilterOperator,
|
||||||
} from 'src/components/ListView';
|
} from 'src/components/ListView';
|
||||||
import { getFromLocalStorage } from 'src/utils/localStorageHelpers';
|
import { getFromLocalStorage } from 'src/utils/localStorageHelpers';
|
||||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
|
@ -385,7 +385,7 @@ function ChartList(props: ChartListProps) {
|
||||||
Header: t('Owner'),
|
Header: t('Owner'),
|
||||||
id: 'owners',
|
id: 'owners',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationManyMany,
|
operator: FilterOperator.relationManyMany,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'chart',
|
'chart',
|
||||||
|
@ -406,7 +406,7 @@ function ChartList(props: ChartListProps) {
|
||||||
Header: t('Created by'),
|
Header: t('Created by'),
|
||||||
id: 'created_by',
|
id: 'created_by',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationOneMany,
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'chart',
|
'chart',
|
||||||
|
@ -427,7 +427,7 @@ function ChartList(props: ChartListProps) {
|
||||||
Header: t('Viz type'),
|
Header: t('Viz type'),
|
||||||
id: 'viz_type',
|
id: 'viz_type',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
selects: registry
|
selects: registry
|
||||||
.keys()
|
.keys()
|
||||||
|
@ -451,7 +451,7 @@ function ChartList(props: ChartListProps) {
|
||||||
Header: t('Dataset'),
|
Header: t('Dataset'),
|
||||||
id: 'datasource_id',
|
id: 'datasource_id',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
fetchSelects: createFetchDatasets(
|
fetchSelects: createFetchDatasets(
|
||||||
createErrorHandler(errMsg =>
|
createErrorHandler(errMsg =>
|
||||||
|
@ -470,7 +470,7 @@ function ChartList(props: ChartListProps) {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
urlDisplay: 'favorite',
|
urlDisplay: 'favorite',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.chartIsFav,
|
operator: FilterOperator.chartIsFav,
|
||||||
unfilteredLabel: t('Any'),
|
unfilteredLabel: t('Any'),
|
||||||
selects: [
|
selects: [
|
||||||
{ label: t('Yes'), value: true },
|
{ label: t('Yes'), value: true },
|
||||||
|
@ -481,7 +481,7 @@ function ChartList(props: ChartListProps) {
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'slice_name',
|
id: 'slice_name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: FilterOperators.chartAllText,
|
operator: FilterOperator.chartAllText,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,11 @@ import DeleteModal from 'src/components/DeleteModal';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
||||||
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
import ListView, {
|
||||||
|
ListViewProps,
|
||||||
|
Filters,
|
||||||
|
FilterOperator,
|
||||||
|
} from 'src/components/ListView';
|
||||||
import CssTemplateModal from './CssTemplateModal';
|
import CssTemplateModal from './CssTemplateModal';
|
||||||
import { TemplateObject } from './types';
|
import { TemplateObject } from './types';
|
||||||
|
|
||||||
|
@ -272,7 +276,7 @@ function CssTemplatesList({
|
||||||
Header: t('Created by'),
|
Header: t('Created by'),
|
||||||
id: 'created_by',
|
id: 'created_by',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_o_m',
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'css_template',
|
'css_template',
|
||||||
|
@ -291,7 +295,7 @@ function CssTemplatesList({
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'template_name',
|
id: 'template_name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'ct',
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -32,7 +32,7 @@ import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
import ListView, {
|
import ListView, {
|
||||||
ListViewProps,
|
ListViewProps,
|
||||||
Filters,
|
Filters,
|
||||||
FilterOperators,
|
FilterOperator,
|
||||||
} from 'src/components/ListView';
|
} from 'src/components/ListView';
|
||||||
import { getFromLocalStorage } from 'src/utils/localStorageHelpers';
|
import { getFromLocalStorage } from 'src/utils/localStorageHelpers';
|
||||||
import Owner from 'src/types/Owner';
|
import Owner from 'src/types/Owner';
|
||||||
|
@ -46,6 +46,7 @@ import ImportModelsModal from 'src/components/ImportModal/index';
|
||||||
|
|
||||||
import Dashboard from 'src/dashboard/containers/Dashboard';
|
import Dashboard from 'src/dashboard/containers/Dashboard';
|
||||||
import DashboardCard from './DashboardCard';
|
import DashboardCard from './DashboardCard';
|
||||||
|
import { DashboardStatus } from './types';
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
const PASSWORDS_NEEDED_MESSAGE = t(
|
const PASSWORDS_NEEDED_MESSAGE = t(
|
||||||
|
@ -230,9 +231,10 @@ function DashboardList(props: DashboardListProps) {
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { published },
|
original: { status },
|
||||||
},
|
},
|
||||||
}: any) => (published ? t('Published') : t('Draft')),
|
}: any) =>
|
||||||
|
status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'),
|
||||||
Header: t('Status'),
|
Header: t('Status'),
|
||||||
accessor: 'published',
|
accessor: 'published',
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
|
@ -362,7 +364,7 @@ function DashboardList(props: DashboardListProps) {
|
||||||
Header: t('Owner'),
|
Header: t('Owner'),
|
||||||
id: 'owners',
|
id: 'owners',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationManyMany,
|
operator: FilterOperator.relationManyMany,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'dashboard',
|
'dashboard',
|
||||||
|
@ -383,7 +385,7 @@ function DashboardList(props: DashboardListProps) {
|
||||||
Header: t('Created by'),
|
Header: t('Created by'),
|
||||||
id: 'created_by',
|
id: 'created_by',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationOneMany,
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: t('All'),
|
unfilteredLabel: t('All'),
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'dashboard',
|
'dashboard',
|
||||||
|
@ -404,11 +406,11 @@ function DashboardList(props: DashboardListProps) {
|
||||||
Header: t('Status'),
|
Header: t('Status'),
|
||||||
id: 'published',
|
id: 'published',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: t('Any'),
|
unfilteredLabel: t('Any'),
|
||||||
selects: [
|
selects: [
|
||||||
{ label: t('Published'), value: true },
|
{ label: t('Published'), value: true },
|
||||||
{ label: t('Unpublished'), value: false },
|
{ label: t('Draft'), value: false },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -416,7 +418,7 @@ function DashboardList(props: DashboardListProps) {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
urlDisplay: 'favorite',
|
urlDisplay: 'favorite',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.dashboardIsFav,
|
operator: FilterOperator.dashboardIsFav,
|
||||||
unfilteredLabel: t('Any'),
|
unfilteredLabel: t('Any'),
|
||||||
selects: [
|
selects: [
|
||||||
{ label: t('Yes'), value: true },
|
{ label: t('Yes'), value: true },
|
||||||
|
@ -427,7 +429,7 @@ function DashboardList(props: DashboardListProps) {
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'dashboard_title',
|
id: 'dashboard_title',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: FilterOperators.titleOrSlug,
|
operator: FilterOperator.titleOrSlug,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -24,3 +24,8 @@ export type DashboardObject = {
|
||||||
position?: string;
|
position?: string;
|
||||||
metadata?: string;
|
metadata?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum DashboardStatus {
|
||||||
|
PUBLISHED = 'published',
|
||||||
|
DRAFT = 'draft',
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu';
|
||||||
import DeleteModal from 'src/components/DeleteModal';
|
import DeleteModal from 'src/components/DeleteModal';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import ListView, { Filters } from 'src/components/ListView';
|
import ListView, { FilterOperator, Filters } from 'src/components/ListView';
|
||||||
import { commonMenuData } from 'src/views/CRUD/data/common';
|
import { commonMenuData } from 'src/views/CRUD/data/common';
|
||||||
import ImportModelsModal from 'src/components/ImportModal/index';
|
import ImportModelsModal from 'src/components/ImportModal/index';
|
||||||
import DatabaseModal from './DatabaseModal';
|
import DatabaseModal from './DatabaseModal';
|
||||||
|
@ -374,7 +374,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||||
Header: t('Expose in SQL Lab'),
|
Header: t('Expose in SQL Lab'),
|
||||||
id: 'expose_in_sqllab',
|
id: 'expose_in_sqllab',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'eq',
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
selects: [
|
selects: [
|
||||||
{ label: 'Yes', value: true },
|
{ label: 'Yes', value: true },
|
||||||
|
@ -393,7 +393,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||||
),
|
),
|
||||||
id: 'allow_run_async',
|
id: 'allow_run_async',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'eq',
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
selects: [
|
selects: [
|
||||||
{ label: 'Yes', value: true },
|
{ label: 'Yes', value: true },
|
||||||
|
@ -404,7 +404,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'database_name',
|
id: 'database_name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'ct',
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -33,7 +33,11 @@ import { useListViewResource } from 'src/views/CRUD/hooks';
|
||||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||||
import DatasourceModal from 'src/datasource/DatasourceModal';
|
import DatasourceModal from 'src/datasource/DatasourceModal';
|
||||||
import DeleteModal from 'src/components/DeleteModal';
|
import DeleteModal from 'src/components/DeleteModal';
|
||||||
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
import ListView, {
|
||||||
|
ListViewProps,
|
||||||
|
Filters,
|
||||||
|
FilterOperator,
|
||||||
|
} from 'src/components/ListView';
|
||||||
import SubMenu, {
|
import SubMenu, {
|
||||||
SubMenuProps,
|
SubMenuProps,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
|
@ -308,7 +312,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
{
|
{
|
||||||
Cell: ({
|
Cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { owners = [], table_name: tableName },
|
original: { owners = [] },
|
||||||
},
|
},
|
||||||
}: any) => <FacePile users={owners} />,
|
}: any) => <FacePile users={owners} />,
|
||||||
Header: t('Owners'),
|
Header: t('Owners'),
|
||||||
|
@ -397,7 +401,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
Header: t('Owner'),
|
Header: t('Owner'),
|
||||||
id: 'owners',
|
id: 'owners',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_m_m',
|
operator: FilterOperator.relationManyMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'dataset',
|
'dataset',
|
||||||
|
@ -416,7 +420,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
Header: t('Database'),
|
Header: t('Database'),
|
||||||
id: 'database',
|
id: 'database',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_o_m',
|
operator: FilterOperator.relationManyMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'dataset',
|
'dataset',
|
||||||
|
@ -431,7 +435,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
Header: t('Schema'),
|
Header: t('Schema'),
|
||||||
id: 'schema',
|
id: 'schema',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'eq',
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchDistinct(
|
fetchSelects: createFetchDistinct(
|
||||||
'dataset',
|
'dataset',
|
||||||
|
@ -446,7 +450,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
Header: t('Type'),
|
Header: t('Type'),
|
||||||
id: 'sql',
|
id: 'sql',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'dataset_is_null_or_empty',
|
operator: FilterOperator.datasetIsNullOrEmpty,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
selects: [
|
selects: [
|
||||||
{ label: 'Virtual', value: false },
|
{ label: 'Virtual', value: false },
|
||||||
|
@ -457,7 +461,7 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'table_name',
|
id: 'table_name',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'ct',
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
|
|
|
@ -32,7 +32,7 @@ import Popover from 'src/components/Popover';
|
||||||
import { commonMenuData } from 'src/views/CRUD/data/common';
|
import { commonMenuData } from 'src/views/CRUD/data/common';
|
||||||
import ListView, {
|
import ListView, {
|
||||||
Filters,
|
Filters,
|
||||||
FilterOperators,
|
FilterOperator,
|
||||||
ListViewProps,
|
ListViewProps,
|
||||||
} from 'src/components/ListView';
|
} from 'src/components/ListView';
|
||||||
import Icon, { IconName } from 'src/components/Icon';
|
import Icon, { IconName } from 'src/components/Icon';
|
||||||
|
@ -340,7 +340,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
||||||
Header: t('Database'),
|
Header: t('Database'),
|
||||||
id: 'database',
|
id: 'database',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationOneMany,
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'query',
|
'query',
|
||||||
|
@ -357,7 +357,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
||||||
Header: t('State'),
|
Header: t('State'),
|
||||||
id: 'status',
|
id: 'status',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.equals,
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchDistinct(
|
fetchSelects: createFetchDistinct(
|
||||||
'query',
|
'query',
|
||||||
|
@ -374,7 +374,7 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
||||||
Header: t('User'),
|
Header: t('User'),
|
||||||
id: 'user',
|
id: 'user',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: FilterOperators.relationOneMany,
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'query',
|
'query',
|
||||||
|
@ -391,13 +391,13 @@ function QueryList({ addDangerToast, addSuccessToast }: QueryListProps) {
|
||||||
Header: t('Time range'),
|
Header: t('Time range'),
|
||||||
id: 'start_time',
|
id: 'start_time',
|
||||||
input: 'datetime_range',
|
input: 'datetime_range',
|
||||||
operator: FilterOperators.between,
|
operator: FilterOperator.between,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: t('Search by query text'),
|
Header: t('Search by query text'),
|
||||||
id: 'sql',
|
id: 'sql',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: FilterOperators.contains,
|
operator: FilterOperator.contains,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[addDangerToast],
|
[addDangerToast],
|
||||||
|
|
|
@ -35,7 +35,11 @@ import SubMenu, {
|
||||||
SubMenuProps,
|
SubMenuProps,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
} from 'src/components/Menu/SubMenu';
|
} from 'src/components/Menu/SubMenu';
|
||||||
import ListView, { ListViewProps, Filters } from 'src/components/ListView';
|
import ListView, {
|
||||||
|
ListViewProps,
|
||||||
|
Filters,
|
||||||
|
FilterOperator,
|
||||||
|
} from 'src/components/ListView';
|
||||||
import DeleteModal from 'src/components/DeleteModal';
|
import DeleteModal from 'src/components/DeleteModal';
|
||||||
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
|
@ -411,7 +415,7 @@ function SavedQueryList({
|
||||||
Header: t('Database'),
|
Header: t('Database'),
|
||||||
id: 'database',
|
id: 'database',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'rel_o_m',
|
operator: FilterOperator.relationOneMany,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchRelated(
|
fetchSelects: createFetchRelated(
|
||||||
'saved_query',
|
'saved_query',
|
||||||
|
@ -431,7 +435,7 @@ function SavedQueryList({
|
||||||
Header: t('Schema'),
|
Header: t('Schema'),
|
||||||
id: 'schema',
|
id: 'schema',
|
||||||
input: 'select',
|
input: 'select',
|
||||||
operator: 'eq',
|
operator: FilterOperator.equals,
|
||||||
unfilteredLabel: 'All',
|
unfilteredLabel: 'All',
|
||||||
fetchSelects: createFetchDistinct(
|
fetchSelects: createFetchDistinct(
|
||||||
'saved_query',
|
'saved_query',
|
||||||
|
@ -448,7 +452,7 @@ function SavedQueryList({
|
||||||
Header: t('Search'),
|
Header: t('Search'),
|
||||||
id: 'label',
|
id: 'label',
|
||||||
input: 'search',
|
input: 'search',
|
||||||
operator: 'all_text',
|
operator: FilterOperator.allText,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[addDangerToast],
|
[addDangerToast],
|
||||||
|
|
|
@ -52,8 +52,8 @@ from superset.dashboards.commands.importers.dispatcher import ImportDashboardsCo
|
||||||
from superset.dashboards.commands.update import UpdateDashboardCommand
|
from superset.dashboards.commands.update import UpdateDashboardCommand
|
||||||
from superset.dashboards.dao import DashboardDAO
|
from superset.dashboards.dao import DashboardDAO
|
||||||
from superset.dashboards.filters import (
|
from superset.dashboards.filters import (
|
||||||
|
DashboardAccessFilter,
|
||||||
DashboardFavoriteFilter,
|
DashboardFavoriteFilter,
|
||||||
DashboardFilter,
|
|
||||||
DashboardTitleOrSlugFilter,
|
DashboardTitleOrSlugFilter,
|
||||||
FilterRelatedRoles,
|
FilterRelatedRoles,
|
||||||
)
|
)
|
||||||
|
@ -105,6 +105,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||||
list_columns = [
|
list_columns = [
|
||||||
"id",
|
"id",
|
||||||
"published",
|
"published",
|
||||||
|
"status",
|
||||||
"slug",
|
"slug",
|
||||||
"url",
|
"url",
|
||||||
"css",
|
"css",
|
||||||
|
@ -153,13 +154,13 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||||
|
|
||||||
search_columns = (
|
search_columns = (
|
||||||
"created_by",
|
"created_by",
|
||||||
|
"changed_by",
|
||||||
"dashboard_title",
|
"dashboard_title",
|
||||||
"id",
|
"id",
|
||||||
"owners",
|
"owners",
|
||||||
"roles",
|
|
||||||
"published",
|
"published",
|
||||||
|
"roles",
|
||||||
"slug",
|
"slug",
|
||||||
"changed_by",
|
|
||||||
)
|
)
|
||||||
search_filters = {
|
search_filters = {
|
||||||
"dashboard_title": [DashboardTitleOrSlugFilter],
|
"dashboard_title": [DashboardTitleOrSlugFilter],
|
||||||
|
@ -173,7 +174,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||||
dashboard_get_response_schema = DashboardGetResponseSchema()
|
dashboard_get_response_schema = DashboardGetResponseSchema()
|
||||||
dashboard_dataset_schema = DashboardDatasetSchema()
|
dashboard_dataset_schema = DashboardDatasetSchema()
|
||||||
|
|
||||||
base_filters = [["slice", DashboardFilter, lambda: []]]
|
base_filters = [["id", DashboardAccessFilter, lambda: []]]
|
||||||
|
|
||||||
order_rel_fields = {
|
order_rel_fields = {
|
||||||
"slices": ("slice_name", "asc"),
|
"slices": ("slice_name", "asc"),
|
||||||
|
|
|
@ -18,16 +18,15 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.orm import contains_eager
|
|
||||||
|
|
||||||
|
from superset import security_manager
|
||||||
from superset.dao.base import BaseDAO
|
from superset.dao.base import BaseDAO
|
||||||
from superset.dashboards.commands.exceptions import DashboardNotFoundError
|
from superset.dashboards.commands.exceptions import DashboardNotFoundError
|
||||||
from superset.dashboards.filters import DashboardFilter
|
from superset.dashboards.filters import DashboardAccessFilter
|
||||||
from superset.extensions import db
|
from superset.extensions import db
|
||||||
from superset.models.core import FavStar, FavStarClassName
|
from superset.models.core import FavStar, FavStarClassName
|
||||||
from superset.models.dashboard import Dashboard, id_or_slug_filter
|
from superset.models.dashboard import Dashboard
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
from superset.utils import core
|
from superset.utils import core
|
||||||
from superset.utils.dashboard_filter_scopes_converter import copy_filter_scopes
|
from superset.utils.dashboard_filter_scopes_converter import copy_filter_scopes
|
||||||
|
@ -37,42 +36,19 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class DashboardDAO(BaseDAO):
|
class DashboardDAO(BaseDAO):
|
||||||
model_cls = Dashboard
|
model_cls = Dashboard
|
||||||
base_filter = DashboardFilter
|
base_filter = DashboardAccessFilter
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_by_id_or_slug(id_or_slug: str) -> Dashboard:
|
def get_by_id_or_slug(id_or_slug: str) -> Dashboard:
|
||||||
query = (
|
dashboard = Dashboard.get(id_or_slug)
|
||||||
db.session.query(Dashboard)
|
|
||||||
.filter(id_or_slug_filter(id_or_slug))
|
|
||||||
.outerjoin(Slice, Dashboard.slices)
|
|
||||||
.outerjoin(Slice.table)
|
|
||||||
.outerjoin(Dashboard.owners)
|
|
||||||
.outerjoin(Dashboard.roles)
|
|
||||||
)
|
|
||||||
# Apply dashboard base filters
|
|
||||||
query = DashboardFilter("id", SQLAInterface(Dashboard, db.session)).apply(
|
|
||||||
query, None
|
|
||||||
)
|
|
||||||
dashboard = query.one_or_none()
|
|
||||||
if not dashboard:
|
if not dashboard:
|
||||||
raise DashboardNotFoundError()
|
raise DashboardNotFoundError()
|
||||||
|
security_manager.raise_for_dashboard_access(dashboard)
|
||||||
return dashboard
|
return dashboard
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_datasets_for_dashboard(id_or_slug: str) -> List[Any]:
|
def get_datasets_for_dashboard(id_or_slug: str) -> List[Any]:
|
||||||
query = (
|
dashboard = DashboardDAO.get_by_id_or_slug(id_or_slug)
|
||||||
db.session.query(Dashboard)
|
|
||||||
.filter(id_or_slug_filter(id_or_slug))
|
|
||||||
.outerjoin(Slice, Dashboard.slices)
|
|
||||||
.outerjoin(Slice.table)
|
|
||||||
)
|
|
||||||
# Apply dashboard base filters
|
|
||||||
query = DashboardFilter("id", SQLAInterface(Dashboard, db.session)).apply(
|
|
||||||
query, None
|
|
||||||
)
|
|
||||||
dashboard = query.one_or_none()
|
|
||||||
if not dashboard:
|
|
||||||
raise DashboardNotFoundError()
|
|
||||||
datasource_slices = core.indexed(dashboard.slices, "datasource")
|
datasource_slices = core.indexed(dashboard.slices, "datasource")
|
||||||
data = [
|
data = [
|
||||||
datasource.data_for_slices(slices)
|
datasource.data_for_slices(slices)
|
||||||
|
@ -83,22 +59,7 @@ class DashboardDAO(BaseDAO):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_charts_for_dashboard(id_or_slug: str) -> List[Slice]:
|
def get_charts_for_dashboard(id_or_slug: str) -> List[Slice]:
|
||||||
query = (
|
return DashboardDAO.get_by_id_or_slug(id_or_slug).slices
|
||||||
db.session.query(Dashboard)
|
|
||||||
.outerjoin(Slice, Dashboard.slices)
|
|
||||||
.outerjoin(Slice.table)
|
|
||||||
.filter(id_or_slug_filter(id_or_slug))
|
|
||||||
.options(contains_eager(Dashboard.slices))
|
|
||||||
)
|
|
||||||
# Apply dashboard base filters
|
|
||||||
query = DashboardFilter("id", SQLAInterface(Dashboard, db.session)).apply(
|
|
||||||
query, None
|
|
||||||
)
|
|
||||||
|
|
||||||
dashboard = query.one_or_none()
|
|
||||||
if not dashboard:
|
|
||||||
raise DashboardNotFoundError()
|
|
||||||
return dashboard.slices
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_slug_uniqueness(slug: str) -> bool:
|
def validate_slug_uniqueness(slug: str) -> bool:
|
||||||
|
|
|
@ -57,16 +57,16 @@ class DashboardFavoriteFilter(BaseFavoriteFilter):
|
||||||
model = Dashboard
|
model = Dashboard
|
||||||
|
|
||||||
|
|
||||||
class DashboardFilter(BaseFilter):
|
class DashboardAccessFilter(BaseFilter):
|
||||||
"""
|
"""
|
||||||
List dashboards with the following criteria:
|
List dashboards with the following criteria:
|
||||||
1. Those which the user owns
|
1. Those which the user owns
|
||||||
2. Those which the user has favorited
|
2. Those which the user has favorited
|
||||||
3. Those which have been published (if they have access to at least one slice)
|
3. Those which have been published (if they have access to at least one slice)
|
||||||
|
|
||||||
If the user is an admin show them all dashboards.
|
If the user is an admin then show all dashboards.
|
||||||
This means they do not get curation but can still sort by "published"
|
This means they do not get curation but can still sort by "published"
|
||||||
if they wish to see those dashboards which are published first
|
if they wish to see those dashboards which are published first.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def apply(self, query: Query, value: Any) -> Query:
|
def apply(self, query: Query, value: Any) -> Query:
|
||||||
|
|
|
@ -184,6 +184,12 @@ class Dashboard( # pylint: disable=too-many-instance-attributes
|
||||||
meta = MetaData(bind=self.get_sqla_engine())
|
meta = MetaData(bind=self.get_sqla_engine())
|
||||||
meta.reflect()
|
meta.reflect()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> utils.DashboardStatus:
|
||||||
|
if self.published:
|
||||||
|
return utils.DashboardStatus.PUBLISHED
|
||||||
|
return utils.DashboardStatus.DRAFT
|
||||||
|
|
||||||
@renders("dashboard_title")
|
@renders("dashboard_title")
|
||||||
def dashboard_link(self) -> Markup:
|
def dashboard_link(self) -> Markup:
|
||||||
title = escape(self.dashboard_title or "<empty>")
|
title = escape(self.dashboard_title or "<empty>")
|
||||||
|
|
|
@ -25,7 +25,7 @@ from marshmallow import ValidationError
|
||||||
|
|
||||||
from superset.charts.filters import ChartFilter
|
from superset.charts.filters import ChartFilter
|
||||||
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
|
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
|
||||||
from superset.dashboards.filters import DashboardFilter
|
from superset.dashboards.filters import DashboardAccessFilter
|
||||||
from superset.databases.filters import DatabaseFilter
|
from superset.databases.filters import DatabaseFilter
|
||||||
from superset.models.reports import ReportSchedule
|
from superset.models.reports import ReportSchedule
|
||||||
from superset.reports.commands.bulk_delete import BulkDeleteReportScheduleCommand
|
from superset.reports.commands.bulk_delete import BulkDeleteReportScheduleCommand
|
||||||
|
@ -170,7 +170,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
|
||||||
allowed_rel_fields = {"owners", "chart", "dashboard", "database", "created_by"}
|
allowed_rel_fields = {"owners", "chart", "dashboard", "database", "created_by"}
|
||||||
filter_rel_fields = {
|
filter_rel_fields = {
|
||||||
"chart": [["id", ChartFilter, lambda: []]],
|
"chart": [["id", ChartFilter, lambda: []]],
|
||||||
"dashboard": [["id", DashboardFilter, lambda: []]],
|
"dashboard": [["id", DashboardAccessFilter, lambda: []]],
|
||||||
"database": [["id", DatabaseFilter, lambda: []]],
|
"database": [["id", DatabaseFilter, lambda: []]],
|
||||||
}
|
}
|
||||||
text_field_rel_fields = {
|
text_field_rel_fields = {
|
||||||
|
|
|
@ -1139,7 +1139,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def can_access_based_on_dashboard(datasource: "BaseDatasource") -> bool:
|
def can_access_based_on_dashboard(datasource: "BaseDatasource") -> bool:
|
||||||
from superset import db
|
from superset import db
|
||||||
from superset.dashboards.filters import DashboardFilter
|
from superset.dashboards.filters import DashboardAccessFilter
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
from superset.models.dashboard import Dashboard
|
from superset.models.dashboard import Dashboard
|
||||||
|
|
||||||
|
@ -1150,7 +1150,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||||
.filter(datasource_class.id == datasource.id)
|
.filter(datasource_class.id == datasource.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
query = DashboardFilter("id", SQLAInterface(Dashboard, db.session)).apply(
|
query = DashboardAccessFilter("id", SQLAInterface(Dashboard, db.session)).apply(
|
||||||
query, None
|
query, None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,13 @@ class QueryStatus(str, Enum): # pylint: disable=too-few-public-methods
|
||||||
TIMED_OUT: str = "timed_out"
|
TIMED_OUT: str = "timed_out"
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardStatus(str, Enum):
|
||||||
|
"""Dashboard status used for frontend filters"""
|
||||||
|
|
||||||
|
PUBLISHED = "published"
|
||||||
|
DRAFT = "draft"
|
||||||
|
|
||||||
|
|
||||||
class ReservedUrlParameters(str, Enum):
|
class ReservedUrlParameters(str, Enum):
|
||||||
"""
|
"""
|
||||||
Reserved URL parameters that are used internally by Superset. These will not be
|
Reserved URL parameters that are used internally by Superset. These will not be
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
from flask import Markup
|
from flask import Markup
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
|
|
||||||
from superset.dashboards.filters import DashboardFilter
|
from superset.dashboards.filters import DashboardAccessFilter
|
||||||
from superset.views.chart.filters import SliceFilter
|
from superset.views.chart.filters import SliceFilter
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +88,6 @@ class SliceMixin: # pylint: disable=too-few-public-methods
|
||||||
"viz_type": _("Visualization Type"),
|
"viz_type": _("Visualization Type"),
|
||||||
}
|
}
|
||||||
|
|
||||||
add_form_query_rel_fields = {"dashboards": [["name", DashboardFilter, None]]}
|
add_form_query_rel_fields = {"dashboards": [["name", DashboardAccessFilter, None]]}
|
||||||
|
|
||||||
edit_form_query_rel_fields = add_form_query_rel_fields
|
edit_form_query_rel_fields = add_form_query_rel_fields
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
|
|
||||||
from ...dashboards.filters import DashboardFilter
|
from ...dashboards.filters import DashboardAccessFilter
|
||||||
from ..base import check_ownership
|
from ..base import check_ownership
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class DashboardMixin: # pylint: disable=too-few-public-methods
|
||||||
"visible in the list of all dashboards"
|
"visible in the list of all dashboards"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
base_filters = [["slice", DashboardFilter, lambda: []]]
|
base_filters = [["slice", DashboardAccessFilter, lambda: []]]
|
||||||
label_columns = {
|
label_columns = {
|
||||||
"dashboard_link": _("Dashboard"),
|
"dashboard_link": _("Dashboard"),
|
||||||
"dashboard_title": _("Title"),
|
"dashboard_title": _("Title"),
|
||||||
|
|
|
@ -437,11 +437,11 @@ class TestDashboard(SupersetTestCase):
|
||||||
self.test_save_dash("alpha")
|
self.test_save_dash("alpha")
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_energy_table_with_slice", "load_dashboard")
|
@pytest.mark.usefixtures("load_energy_table_with_slice", "load_dashboard")
|
||||||
def test_users_can_view_published_dashboard(self):
|
def test_users_can_list_published_dashboard(self):
|
||||||
self.login("alpha")
|
self.login("alpha")
|
||||||
resp = self.get_resp("/api/v1/dashboard/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn(f"/superset/dashboard/{pytest.hidden_dash_slug}/", resp)
|
assert f"/superset/dashboard/{pytest.hidden_dash_slug}/" not in resp
|
||||||
self.assertIn(f"/superset/dashboard/{pytest.published_dash_slug}/", resp)
|
assert f"/superset/dashboard/{pytest.published_dash_slug}/" in resp
|
||||||
|
|
||||||
def test_users_can_view_own_dashboard(self):
|
def test_users_can_view_own_dashboard(self):
|
||||||
user = security_manager.find_user("gamma")
|
user = security_manager.find_user("gamma")
|
||||||
|
|
|
@ -190,11 +190,14 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
||||||
def test_get_dashboard_datasets_not_allowed(self):
|
def test_get_draft_dashboard_datasets(self):
|
||||||
|
"""
|
||||||
|
All users should have access to dashboards without roles
|
||||||
|
"""
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
uri = "api/v1/dashboard/world_health/datasets"
|
uri = "api/v1/dashboard/world_health/datasets"
|
||||||
response = self.get_assert_metric(uri, "get_datasets")
|
response = self.get_assert_metric(uri, "get_datasets")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("create_dashboards")
|
@pytest.mark.usefixtures("create_dashboards")
|
||||||
def get_dashboard_by_slug(self):
|
def get_dashboard_by_slug(self):
|
||||||
|
@ -215,12 +218,15 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("create_dashboards")
|
@pytest.mark.usefixtures("create_dashboards")
|
||||||
def get_dashboard_by_slug_not_allowed(self):
|
def get_draft_dashboard_by_slug(self):
|
||||||
|
"""
|
||||||
|
All users should have access to dashboards without roles
|
||||||
|
"""
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
dashboard = self.dashboards[0]
|
dashboard = self.dashboards[0]
|
||||||
uri = f"api/v1/dashboard/{dashboard.slug}"
|
uri = f"api/v1/dashboard/{dashboard.slug}"
|
||||||
response = self.get_assert_metric(uri, "get")
|
response = self.get_assert_metric(uri, "get")
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("create_dashboards")
|
@pytest.mark.usefixtures("create_dashboards")
|
||||||
def test_get_dashboard_charts(self):
|
def test_get_dashboard_charts(self):
|
||||||
|
@ -266,15 +272,15 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("create_dashboards")
|
@pytest.mark.usefixtures("create_dashboards")
|
||||||
def test_get_dashboard_charts_not_allowed(self):
|
def test_get_draft_dashboard_charts(self):
|
||||||
"""
|
"""
|
||||||
Dashboard API: Test getting charts on a dashboard a user does not have access to
|
All users should have access to draft dashboards without roles
|
||||||
"""
|
"""
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
dashboard = self.dashboards[0]
|
dashboard = self.dashboards[0]
|
||||||
uri = f"api/v1/dashboard/{dashboard.id}/charts"
|
uri = f"api/v1/dashboard/{dashboard.id}/charts"
|
||||||
response = self.get_assert_metric(uri, "get_charts")
|
response = self.get_assert_metric(uri, "get_charts")
|
||||||
self.assertEqual(response.status_code, 404)
|
assert response.status_code == 200
|
||||||
|
|
||||||
@pytest.mark.usefixtures("create_dashboards")
|
@pytest.mark.usefixtures("create_dashboards")
|
||||||
def test_get_dashboard_charts_empty(self):
|
def test_get_dashboard_charts_empty(self):
|
||||||
|
@ -382,7 +388,7 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
uri = f"api/v1/dashboard/{dashboard.id}"
|
uri = f"api/v1/dashboard/{dashboard.id}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
self.assertEqual(rv.status_code, 404)
|
self.assertEqual(rv.status_code, 200)
|
||||||
# rollback changes
|
# rollback changes
|
||||||
db.session.delete(dashboard)
|
db.session.delete(dashboard)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -208,20 +208,6 @@ class TestDashboardDatasetSecurity(DashboardTestCase):
|
||||||
finally:
|
finally:
|
||||||
self.revoke_public_access_to_table(accessed_table)
|
self.revoke_public_access_to_table(accessed_table)
|
||||||
|
|
||||||
def test_get_dashboard_api_no_data_access(self):
|
|
||||||
"""
|
|
||||||
Dashboard API: Test get dashboard without data access
|
|
||||||
"""
|
|
||||||
admin = self.get_user("admin")
|
|
||||||
dashboard = create_dashboard_to_db(
|
|
||||||
random_title(), random_slug(), owners=[admin]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.login(username="gamma")
|
|
||||||
uri = DASHBOARD_API_URL_FORMAT.format(dashboard.id)
|
|
||||||
rv = self.client.get(uri)
|
|
||||||
self.assert404(rv)
|
|
||||||
|
|
||||||
def test_get_dashboards_api_no_data_access(self):
|
def test_get_dashboards_api_no_data_access(self):
|
||||||
"""
|
"""
|
||||||
Dashboard API: Test get dashboards no data access
|
Dashboard API: Test get dashboards no data access
|
||||||
|
|
Loading…
Reference in New Issue