Compare commits

..

1 Commits

Author SHA1 Message Date
Elizabeth Thompson
1ac189963c chore: add release to pip requirements (#17752)
* add requests to pip requirements

* fix tests

* add redis typing for mypy

* fix test
2021-12-15 18:01:18 -08:00
38 changed files with 78 additions and 1641 deletions

View File

@@ -19,8 +19,6 @@ under the License.
## Change Log
### 1.4
**Database Migrations**
- [#17335](https://github.com/apache/superset/pull/17335) feat: Certify Charts and Dashboards (@geido)
- [#17078](https://github.com/apache/superset/pull/17078) chore(engine): Translate fractional time grains—requires @superset-ui bump (@john-bodley)
- [#16849](https://github.com/apache/superset/pull/16849) chore: db migrate timeseries_limit_metric to legacy_order_by (@zhaoyongjie)
- [#14015](https://github.com/apache/superset/pull/14015) feat(filter-set): Add filterset resource (@ofekisr)
- [#16454](https://github.com/apache/superset/pull/16454) feat: add certifiedby & certification details fields to the edit dataset columns fields (@pkdotson)
@@ -67,13 +65,6 @@ under the License.
- [#16136](https://github.com/apache/superset/pull/16136) feat: add profiling to Superset pages (@betodealmeida)
**Fixes**
- [#17945](https://github.com/apache/superset/pull/17945) fix(dashboard): scope status of filter not update in dashboard metadata (@stephenLYZ)
- [#17349](https://github.com/apache/superset/pull/17349) fix(Dashboard): Check validity of control item (@geido)
- [#17842](https://github.com/apache/superset/pull/17842) fix(dashboard): update native filter info in metadata is not updated (@stephenLYZ)
- [#17835](https://github.com/apache/superset/pull/17835) fix: resolve tests for 1.4 (@eschutho)
- [#17781](https://github.com/apache/superset/pull/17781) fix(dashboard): commit update once (@serenajiang)
- [#17766](https://github.com/apache/superset/pull/17766) fix: Remove positions from json_metadata (@geido)
- [#17330](https://github.com/apache/superset/pull/17330) fix: import should accept old keys (@eschutho)
- [#17570](https://github.com/apache/superset/pull/17570) fix: Save properties after applying changes in Dashboard (@geido)
- [#17707](https://github.com/apache/superset/pull/17707) fix(Dashboard): Copy dashboard with duplicating charts 500 error (@geido)
- [#16041](https://github.com/apache/superset/pull/16041) fix: set correct schema on config import (@betodealmeida)
@@ -233,11 +224,6 @@ under the License.
- [#15762](https://github.com/apache/superset/pull/15762) fix: Align alert solid small svg center (@duynguyenhoang)
**Others**
- [#17964](https://github.com/apache/superset/pull/17964) chore: bump FAB to 3.4.3 (@dpgaspar)
- [#17894](https://github.com/apache/superset/pull/17894) chore: bump gunicorn to 20.1.0 (@mporracindie)
- [#17420](https://github.com/apache/superset/pull/17420) chore: Bump FAB to 3.4.0 (@kamalkeshavani-aiinside)
- [#17752](https://github.com/apache/superset/pull/17752) chore: add release to pip requirements (@eschutho)
- [#17724](https://github.com/apache/superset/pull/17724) ci: temp fix for mysqlclient on an OS regression bug (@dpgaspar)
- [#17702](https://github.com/apache/superset/pull/17702) chore(sql): clean up invalid filter clause exception types (@villebro)
- [#17579](https://github.com/apache/superset/pull/17579) chore(datasets): Sanitizing /save response (@craig-rueda)
- [#17005](https://github.com/apache/superset/pull/17005) ci: skip unnecessary test steps (@villebro)

View File

@@ -85,7 +85,7 @@ flask==1.1.4
# flask-openid
# flask-sqlalchemy
# flask-wtf
flask-appbuilder==3.4.3
flask-appbuilder==3.3.4
# via apache-superset
flask-babel==1.0.0
# via flask-appbuilder
@@ -117,7 +117,7 @@ geopy==2.2.0
# via apache-superset
graphlib-backport==1.0.3
# via apache-superset
gunicorn==20.1.0
gunicorn==20.0.4
# via apache-superset
holidays==0.10.3
# via apache-superset

View File

@@ -75,7 +75,7 @@ setup(
"cryptography>=3.3.2",
"deprecation>=2.1.0, <2.2.0",
"flask>=1.1.0, <2.0.0",
"flask-appbuilder>=3.4.3, <4.0.0",
"flask-appbuilder>=3.3.4, <4.0.0",
"flask-caching>=1.10.0",
"flask-compress",
"flask-talisman",
@@ -83,7 +83,7 @@ setup(
"flask-wtf",
"geopy",
"graphlib-backport",
"gunicorn>=20.1.0",
"gunicorn>=20.0.2, <20.1",
"holidays==0.10.3", # PINNED! https://github.com/dr-prodigy/python-holidays/issues/406
"humanize",
"itsdangerous>=1.0.0, <2.0.0", # https://github.com/apache/superset/pull/14627

View File

@@ -111,6 +111,4 @@ export enum FilterOperator {
between = 'between',
dashboardIsFav = 'dashboard_is_favorite',
chartIsFav = 'chart_is_favorite',
chartIsCertified = 'chart_is_certified',
dashboardIsCertified = 'dashboard_is_certified',
}

View File

@@ -21,7 +21,6 @@ import { styled, useTheme } from '@superset-ui/core';
import { AntdCard, Skeleton, ThinSkeleton } from 'src/common/components';
import { Tooltip } from 'src/components/Tooltip';
import ImageLoader, { BackgroundPosition } from './ImageLoader';
import CertifiedIcon from '../CertifiedIcon';
const ActionsWrapper = styled.div`
width: 64px;
@@ -162,8 +161,6 @@ interface CardProps {
rows?: number | string;
avatar?: React.ReactElement | null;
cover?: React.ReactNode | null;
certifiedBy?: string;
certificationDetails?: string;
}
function ListViewCard({
@@ -181,8 +178,6 @@ function ListViewCard({
loading,
imgPosition = 'top',
cover,
certifiedBy,
certificationDetails,
}: CardProps) {
const Link = url && linkComponent ? linkComponent : AnchorLink;
const theme = useTheme();
@@ -254,17 +249,7 @@ function ListViewCard({
<TitleContainer>
<Tooltip title={title}>
<TitleLink>
<Link to={url!}>
{certifiedBy && (
<>
<CertifiedIcon
certifiedBy={certifiedBy}
details={certificationDetails}
/>{' '}
</>
)}
{title}
</Link>
<Link to={url!}>{title}</Link>
</TitleLink>
</Tooltip>
{titleRight && <TitleRight>{titleRight}</TitleRight>}

View File

@@ -55,7 +55,7 @@ describe('TimezoneSelector', () => {
expect(select).toBeInTheDocument();
userEvent.click(select);
const selection = await screen.findByTitle(
'GMT -10:00 (Hawaii Standard Time)',
'GMT -06:00 (Mountain Daylight Time)',
);
expect(selection).toBeInTheDocument();
userEvent.click(selection);

View File

@@ -46,7 +46,6 @@ import {
removeFilter,
updateDirectPathToFilter,
} from './dashboardFilters';
import { SET_FILTER_CONFIG_COMPLETE } from './nativeFilters';
export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
export function setUnsavedChanges(hasUnsavedChanges) {
@@ -282,12 +281,6 @@ export function saveDashboardRequest(data, id, saveType) {
chartConfiguration: metadata.chart_configuration,
});
}
if (metadata.native_filter_configuration) {
dispatch({
type: SET_FILTER_CONFIG_COMPLETE,
filterConfig: metadata.native_filter_configuration,
});
}
}
if (lastModifiedTime) {
dispatch(saveDashboardRequestSuccess(lastModifiedTime));

View File

@@ -146,25 +146,6 @@ export const setInScopeStatusOfFilters = (
type: SET_IN_SCOPE_STATUS_OF_FILTERS,
filterConfig: filtersWithScopes,
});
// need to update native_filter_configuration in the dashboard metadata
const { metadata } = getState().dashboardInfo;
const filterConfig: FilterConfiguration =
metadata.native_filter_configuration;
const mergedFilterConfig = filterConfig.map(filter => {
const filterWithScope = filtersWithScopes.find(
scope => scope.id === filter.id,
);
if (!filterWithScope) {
return filter;
}
return { ...filterWithScope, ...filter };
});
metadata.native_filter_configuration = mergedFilterConfig;
dispatch(
dashboardInfoChanged({
metadata,
}),
);
};
type BootstrapData = {

View File

@@ -22,7 +22,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { styled, t } from '@superset-ui/core';
import ButtonGroup from 'src/components/ButtonGroup';
import CertifiedIcon from 'src/components/CertifiedIcon';
import {
LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD,
@@ -494,14 +493,6 @@ class Header extends React.PureComponent {
data-test-id={`${dashboardInfo.id}`}
>
<div className="dashboard-component-header header-large">
{dashboardInfo.certified_by && (
<>
<CertifiedIcon
certifiedBy={dashboardInfo.certified_by}
details={dashboardInfo.certification_details}
/>{' '}
</>
)}
<EditableTitle
title={dashboardTitle}
canEdit={userCanEdit && editMode}

View File

@@ -90,8 +90,6 @@ fetchMock.get(
fetchMock.get('http://localhost/api/v1/dashboard/26', {
body: {
result: {
certified_by: 'John Doe',
certification_details: 'Sample certification',
changed_by: null,
changed_by_name: '',
changed_by_url: '',
@@ -124,8 +122,6 @@ fetchMock.get('http://localhost/api/v1/dashboard/26', {
});
const createProps = () => ({
certified_by: 'John Doe',
certification_details: 'Sample certification',
dashboardId: 26,
show: true,
colorScheme: 'supersetColors',
@@ -160,10 +156,7 @@ test('should render - FeatureFlag disabled', async () => {
expect(screen.getByRole('heading', { name: 'Access' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Colors' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Advanced' })).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: 'Certification' }),
).toBeInTheDocument();
expect(screen.getAllByRole('heading')).toHaveLength(5);
expect(screen.getAllByRole('heading')).toHaveLength(4);
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Advanced' })).toBeInTheDocument();
@@ -171,7 +164,7 @@ test('should render - FeatureFlag disabled', async () => {
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
expect(screen.getAllByRole('button')).toHaveLength(4);
expect(screen.getAllByRole('textbox')).toHaveLength(4);
expect(screen.getAllByRole('textbox')).toHaveLength(2);
expect(screen.getByRole('combobox')).toBeInTheDocument();
expect(spyColorSchemeControlWrapper).toBeCalledWith(
@@ -199,10 +192,7 @@ test('should render - FeatureFlag enabled', async () => {
).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Access' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Advanced' })).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: 'Certification' }),
).toBeInTheDocument();
expect(screen.getAllByRole('heading')).toHaveLength(4);
expect(screen.getAllByRole('heading')).toHaveLength(3);
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Advanced' })).toBeInTheDocument();
@@ -210,7 +200,7 @@ test('should render - FeatureFlag enabled', async () => {
expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
expect(screen.getAllByRole('button')).toHaveLength(4);
expect(screen.getAllByRole('textbox')).toHaveLength(4);
expect(screen.getAllByRole('textbox')).toHaveLength(2);
expect(screen.getAllByRole('combobox')).toHaveLength(2);
expect(spyColorSchemeControlWrapper).toBeCalledWith(
@@ -229,10 +219,10 @@ test('should open advance', async () => {
await screen.findByTestId('dashboard-edit-properties-form'),
).toBeInTheDocument();
expect(screen.getAllByRole('textbox')).toHaveLength(4);
expect(screen.getAllByRole('textbox')).toHaveLength(2);
expect(screen.getAllByRole('combobox')).toHaveLength(2);
userEvent.click(screen.getByRole('button', { name: 'Advanced' }));
expect(screen.getAllByRole('textbox')).toHaveLength(5);
expect(screen.getAllByRole('textbox')).toHaveLength(3);
expect(screen.getAllByRole('combobox')).toHaveLength(2);
});
@@ -331,18 +321,3 @@ test('submitting with onlyApply:true', async () => {
expect(props.onSubmit).toBeCalledTimes(1);
});
});
test('Empty "Certified by" should clear "Certification details"', async () => {
const props = createProps();
const noCertifiedByProps = {
...props,
certified_by: '',
};
render(<PropertiesModal {...noCertifiedByProps} />, {
useRedux: true,
});
expect(
screen.getByRole('textbox', { name: 'Certification details' }),
).toHaveValue('');
});

View File

@@ -161,15 +161,10 @@ const PropertiesModal = ({
form.setFieldsValue(dashboardInfo);
setDashboardInfo(dashboardInfo);
setJsonMetadata(metadata ? jsonStringify(metadata) : '');
setOwners(owners);
setRoles(roles);
setColorScheme(metadata.color_scheme);
// temporary fix to remove positions from dashboards' metadata
if (metadata?.positions) {
delete metadata.positions;
}
setJsonMetadata(metadata ? jsonStringify(metadata) : '');
},
[form],
);

View File

@@ -36,17 +36,16 @@ import {
} from 'src/reports/actions/reports';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import HeaderReportActionsDropdown from 'src/components/ReportModal/HeaderReportActionsDropdown';
import { chartPropShape } from 'src/dashboard/util/propShapes';
import EditableTitle from 'src/components/EditableTitle';
import AlteredSliceTag from 'src/components/AlteredSliceTag';
import FaveStar from 'src/components/FaveStar';
import Timer from 'src/components/Timer';
import CachedLabel from 'src/components/CachedLabel';
import PropertiesModal from 'src/explore/components/PropertiesModal';
import { sliceUpdated } from 'src/explore/actions/exploreActions';
import CertifiedIcon from 'src/components/CertifiedIcon';
import { chartPropShape } from '../../dashboard/util/propShapes';
import ExploreActionButtons from './ExploreActionButtons';
import RowCountLabel from './RowCountLabel';
import EditableTitle from '../../components/EditableTitle';
import AlteredSliceTag from '../../components/AlteredSliceTag';
import FaveStar from '../../components/FaveStar';
import Timer from '../../components/Timer';
import CachedLabel from '../../components/CachedLabel';
import PropertiesModal from './PropertiesModal';
import { sliceUpdated } from '../actions/exploreActions';
const CHART_STATUS_MAP = {
failed: 'danger',
@@ -166,10 +165,7 @@ export class ExploreChartHeader extends React.PureComponent {
}
getSliceName() {
const { sliceName, table_name: tableName } = this.props;
const title = sliceName || t('%s - untitled', tableName);
return title;
return this.props.sliceName || t('%s - untitled', this.props.table_name);
}
postChartFormData() {
@@ -245,7 +241,7 @@ export class ExploreChartHeader extends React.PureComponent {
}
render() {
const { user, form_data: formData, slice } = this.props;
const { user, form_data: formData } = this.props;
const {
chartStatus,
chartUpdateEndTime,
@@ -261,14 +257,6 @@ export class ExploreChartHeader extends React.PureComponent {
return (
<StyledHeader id="slice-header" className="panel-title-large">
<div className="title-panel">
{slice?.certified_by && (
<>
<CertifiedIcon
certifiedBy={slice.certified_by}
details={slice.certification_details}
/>{' '}
</>
)}
<EditableTitle
title={this.getSliceName()}
canEdit={!this.props.slice || this.props.can_overwrite}

View File

@@ -27,8 +27,6 @@ import PropertiesModal from '.';
const createProps = () => ({
slice: ({
cache_timeout: null,
certified_by: 'John Doe',
certification_details: 'Sample certification',
changed_on: '2021-03-19T16:30:56.750230',
changed_on_humanized: '7 days ago',
datasource: 'FCC 2018 Survey',
@@ -89,8 +87,6 @@ fetchMock.get('http://localhost/api/v1/chart/318', {
},
result: {
cache_timeout: null,
certified_by: 'John Doe',
certification_details: 'Sample certification',
dashboards: [
{
dashboard_title: 'FCC New Coder Survey 2018',
@@ -149,8 +145,6 @@ fetchMock.put('http://localhost/api/v1/chart/318', {
id: 318,
result: {
cache_timeout: null,
certified_by: 'John Doe',
certification_details: 'Sample certification',
description: null,
owners: [],
slice_name: 'Age distribution of respondents',
@@ -217,7 +211,7 @@ test('Should render all elements inside modal', async () => {
const props = createProps();
render(<PropertiesModal {...props} />);
await waitFor(() => {
expect(screen.getAllByRole('textbox')).toHaveLength(5);
expect(screen.getAllByRole('textbox')).toHaveLength(3);
expect(screen.getByRole('combobox')).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: 'Basic information' }),
@@ -232,12 +226,6 @@ test('Should render all elements inside modal', async () => {
expect(screen.getByRole('heading', { name: 'Access' })).toBeVisible();
expect(screen.getByText('Owners')).toBeVisible();
expect(
screen.getByRole('heading', { name: 'Configuration' }),
).toBeVisible();
expect(screen.getByText('Certified by')).toBeVisible();
expect(screen.getByText('Certification details')).toBeVisible();
});
});
@@ -287,19 +275,3 @@ test('"Save" button should call only "onSave"', async () => {
expect(props.onHide).toBeCalledTimes(1);
});
});
test('Empty "Certified by" should clear "Certification details"', async () => {
const props = createProps();
const noCertifiedByProps = {
...props,
slice: {
...props.slice,
certified_by: '',
},
};
render(<PropertiesModal {...noCertifiedByProps} />);
expect(
screen.getByRole('textbox', { name: 'Certification details' }),
).toHaveValue('');
});

View File

@@ -18,13 +18,14 @@
*/
import React, { useMemo, useState, useCallback, useEffect } from 'react';
import Modal from 'src/components/Modal';
import { Form, Row, Col, Input, TextArea } from 'src/common/components';
import { Row, Col, Input, TextArea } from 'src/common/components';
import Button from 'src/components/Button';
import { Select } from 'src/components';
import { SelectValue } from 'antd/lib/select';
import rison from 'rison';
import { t, SupersetClient, styled } from '@superset-ui/core';
import { t, SupersetClient } from '@superset-ui/core';
import Chart, { Slice } from 'src/types/Chart';
import { Form, FormItem } from 'src/components/Form';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
type PropertiesModalProps = {
@@ -36,16 +37,6 @@ type PropertiesModalProps = {
existingOwners?: SelectValue;
};
const FormItem = Form.Item;
const StyledFormItem = styled(Form.Item)`
margin-bottom: 0;
`;
const StyledHelpBlock = styled.span`
margin-bottom: 0;
`;
export default function PropertiesModal({
slice,
onHide,
@@ -53,12 +44,15 @@ export default function PropertiesModal({
show,
}: PropertiesModalProps) {
const [submitting, setSubmitting] = useState(false);
const [form] = Form.useForm();
// values of form inputs
const [name, setName] = useState(slice.slice_name || '');
const [selectedOwners, setSelectedOwners] = useState<SelectValue | null>(
null,
);
// values of form inputs
const [name, setName] = useState(slice.slice_name || '');
const [description, setDescription] = useState(slice.description || '');
const [cacheTimeout, setCacheTimeout] = useState(
slice.cache_timeout != null ? slice.cache_timeout : '',
);
function showError({ error, statusText, message }: any) {
let errorText = error || statusText || t('An error has occurred');
@@ -115,26 +109,14 @@ export default function PropertiesModal({
[],
);
const onSubmit = async (values: {
certified_by?: string;
certification_details?: string;
description?: string;
cache_timeout?: number;
}) => {
const onSubmit = async (event: React.FormEvent) => {
event.stopPropagation();
event.preventDefault();
setSubmitting(true);
const {
certified_by: certifiedBy,
certification_details: certificationDetails,
description,
cache_timeout: cacheTimeout,
} = values;
const payload: { [key: string]: any } = {
slice_name: name || null,
description: description || null,
cache_timeout: cacheTimeout || null,
certified_by: certifiedBy || null,
certification_details:
certifiedBy && certificationDetails ? certificationDetails : null,
};
if (selectedOwners) {
payload.owners = (selectedOwners as {
@@ -192,10 +174,11 @@ export default function PropertiesModal({
</Button>
<Button
data-test="properties-modal-save-button"
htmlType="submit"
htmlType="button"
buttonSize="small"
buttonStyle="primary"
onClick={form.submit}
// @ts-ignore
onClick={onSubmit}
disabled={submitting || !name}
cta
>
@@ -206,21 +189,7 @@ export default function PropertiesModal({
responsive
wrapProps={{ 'data-test': 'properties-edit-modal' }}
>
<Form
form={form}
onFinish={onSubmit}
layout="vertical"
initialValues={{
name: slice.slice_name || '',
description: slice.description || '',
cache_timeout: slice.cache_timeout != null ? slice.cache_timeout : '',
certified_by: slice.certified_by || '',
certification_details:
slice.certified_by && slice.certification_details
? slice.certification_details
: '',
}}
>
<Form onFinish={onSubmit} layout="vertical">
<Row gutter={16}>
<Col xs={24} md={12}>
<h3>{t('Basic information')}</h3>
@@ -235,50 +204,40 @@ export default function PropertiesModal({
}
/>
</FormItem>
<FormItem>
<StyledFormItem label={t('Description')} name="description">
<TextArea rows={3} style={{ maxWidth: '100%' }} />
</StyledFormItem>
<StyledHelpBlock className="help-block">
<FormItem label={t('Description')}>
<TextArea
rows={3}
name="description"
value={description}
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
setDescription(event.target.value ?? '')
}
style={{ maxWidth: '100%' }}
/>
<p className="help-block">
{t(
'The description can be displayed as widget headers in the dashboard view. Supports markdown.',
)}
</StyledHelpBlock>
</FormItem>
<h3>{t('Certification')}</h3>
<FormItem>
<StyledFormItem label={t('Certified by')} name="certified_by">
<Input />
</StyledFormItem>
<StyledHelpBlock className="help-block">
{t('Person or group that has certified this chart.')}
</StyledHelpBlock>
</FormItem>
<FormItem>
<StyledFormItem
label={t('Certification details')}
name="certification_details"
>
<Input />
</StyledFormItem>
<StyledHelpBlock className="help-block">
{t(
'Any additional detail to show in the certification tooltip.',
)}
</StyledHelpBlock>
</p>
</FormItem>
</Col>
<Col xs={24} md={12}>
<h3>{t('Configuration')}</h3>
<FormItem>
<StyledFormItem label={t('Cache timeout')} name="cacheTimeout">
<Input />
</StyledFormItem>
<StyledHelpBlock className="help-block">
<FormItem label={t('Cache timeout')}>
<Input
name="cacheTimeout"
type="text"
value={cacheTimeout}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const targetValue = event.target.value ?? '';
setCacheTimeout(targetValue.replace(/[^0-9]/, ''));
}}
/>
<p className="help-block">
{t(
"Duration (in seconds) of the caching timeout for this chart. Note this defaults to the dataset's timeout if undefined.",
)}
</StyledHelpBlock>
</p>
</FormItem>
<h3 style={{ marginTop: '1em' }}>{t('Access')}</h3>
<FormItem label={ownersLabel}>
@@ -292,11 +251,11 @@ export default function PropertiesModal({
disabled={!selectedOwners}
allowClear
/>
<StyledHelpBlock className="help-block">
<p className="help-block">
{t(
'A list of users who can alter the chart. Searchable by name or username.',
)}
</StyledHelpBlock>
</p>
</FormItem>
</Col>
</Row>

View File

@@ -29,7 +29,6 @@ import {
ControlState,
ControlType,
ControlValueValidator,
CustomControlItem,
} from '@superset-ui/chart-controls';
import { getSectionsToRender } from './getSectionsToRender';
import { getControlConfig } from './getControlConfig';
@@ -161,8 +160,8 @@ export function getAllControlsState(
const controlsState = {};
getSectionsToRender(vizType, datasourceType).forEach(section =>
section.controlSetRows.forEach(fieldsetRow =>
fieldsetRow.forEach((field: CustomControlItem) => {
if (field && field.config && field.name) {
fieldsetRow.forEach(field => {
if (field && 'config' in field && field.config && field.name) {
const { config, name } = field;
controlsState[name] = getControlStateFromControlConfig(
config,

View File

@@ -33,8 +33,6 @@ export interface Chart {
changed_on: string;
changed_on_delta_humanized?: string;
changed_on_utc?: string;
certified_by?: string;
certification_details?: string;
description: string | null;
cache_timeout: number | null;
thumbnail_url?: string;
@@ -48,8 +46,6 @@ export type Slice = {
slice_name: string;
description: string | null;
cache_timeout: number | null;
certified_by?: string;
certification_details?: string;
form_data?: QueryFormData;
query_context?: object;
};

View File

@@ -142,8 +142,6 @@ export default function ChartCard({
<ListViewCard
loading={loading}
title={chart.slice_name}
certifiedBy={chart.certified_by}
certificationDetails={chart.certification_details}
cover={
!isFeatureEnabled(FeatureFlag.THUMBNAILS) || !showThumbnails ? (
<></>

View File

@@ -58,7 +58,6 @@ import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
import { nativeFilterGate } from 'src/dashboard/components/nativeFilters/utils';
import setupPlugins from 'src/setup/setupPlugins';
import CertifiedIcon from 'src/components/CertifiedIcon';
import ChartCard from './ChartCard';
const PAGE_SIZE = 25;
@@ -238,23 +237,10 @@ function ChartList(props: ChartListProps) {
{
Cell: ({
row: {
original: {
url,
slice_name: sliceName,
certified_by: certifiedBy,
certification_details: certificationDetails,
},
original: { url, slice_name: sliceName },
},
}: any) => (
<a href={url} data-test={`${sliceName}-list-chart-title`}>
{certifiedBy && (
<>
<CertifiedIcon
certifiedBy={certifiedBy}
details={certificationDetails}
/>{' '}
</>
)}
{sliceName}
</a>
),
@@ -526,18 +512,6 @@ function ChartList(props: ChartListProps) {
paginate: true,
},
...(props.user.userId ? [favoritesFilter] : []),
{
Header: t('Certified'),
id: 'id',
urlDisplay: 'certified',
input: 'select',
operator: FilterOperator.chartIsCertified,
unfilteredLabel: t('Any'),
selects: [
{ label: t('Yes'), value: true },
{ label: t('No'), value: false },
],
},
{
Header: t('Search'),
id: 'slice_name',

View File

@@ -147,8 +147,6 @@ function DashboardCard({
<ListViewCard
loading={dashboard.loading || false}
title={dashboard.dashboard_title}
certifiedBy={dashboard.certified_by}
certificationDetails={dashboard.certification_details}
titleRight={
<Label>{dashboard.published ? t('published') : t('draft')}</Label>
}

View File

@@ -49,7 +49,6 @@ import ImportModelsModal from 'src/components/ImportModal/index';
import OmniContainer from 'src/components/OmniContainer';
import Dashboard from 'src/dashboard/containers/Dashboard';
import CertifiedIcon from 'src/components/CertifiedIcon';
import DashboardCard from './DashboardCard';
import { DashboardStatus } from './types';
@@ -174,8 +173,6 @@ function DashboardList(props: DashboardListProps) {
json_metadata = '',
changed_on_delta_humanized,
url = '',
certified_by = '',
certification_details = '',
} = json.result;
return {
...dashboard,
@@ -187,8 +184,6 @@ function DashboardList(props: DashboardListProps) {
json_metadata,
changed_on_delta_humanized,
url,
certified_by,
certification_details,
};
}
return dashboard;
@@ -255,26 +250,9 @@ function DashboardList(props: DashboardListProps) {
{
Cell: ({
row: {
original: {
url,
dashboard_title: dashboardTitle,
certified_by: certifiedBy,
certification_details: certificationDetails,
},
original: { url, dashboard_title: dashboardTitle },
},
}: any) => (
<Link to={url}>
{certifiedBy && (
<>
<CertifiedIcon
certifiedBy={certifiedBy}
details={certificationDetails}
/>{' '}
</>
)}
{dashboardTitle}
</Link>
),
}: any) => <Link to={url}>{dashboardTitle}</Link>,
Header: t('Title'),
accessor: 'dashboard_title',
},
@@ -500,18 +478,6 @@ function DashboardList(props: DashboardListProps) {
],
},
...(props.user.userId ? [favoritesFilter] : []),
{
Header: t('Certified'),
id: 'id',
urlDisplay: 'certified',
input: 'select',
operator: FilterOperator.dashboardIsCertified,
unfilteredLabel: t('Any'),
selects: [
{ label: t('Yes'), value: true },
{ label: t('No'), value: false },
],
},
{
Header: t('Search'),
id: 'dashboard_title',

View File

@@ -566,8 +566,6 @@ export const useChartEditModal = (
slice_name: chart.slice_name,
description: chart.description,
cache_timeout: chart.cache_timeout,
certified_by: chart.certified_by,
certification_details: chart.certification_details,
});
}

View File

@@ -48,8 +48,6 @@ export interface DashboardTableProps {
}
export interface Dashboard {
certified_by?: string;
certification_details?: string;
changed_by_name: string;
changed_by_url: string;
changed_on_delta_humanized?: string;

View File

@@ -52,12 +52,7 @@ from superset.charts.commands.export import ExportChartsCommand
from superset.charts.commands.importers.dispatcher import ImportChartsCommand
from superset.charts.commands.update import UpdateChartCommand
from superset.charts.dao import ChartDAO
from superset.charts.filters import (
ChartAllTextFilter,
ChartCertifiedFilter,
ChartFavoriteFilter,
ChartFilter,
)
from superset.charts.filters import ChartAllTextFilter, ChartFavoriteFilter, ChartFilter
from superset.charts.post_processing import apply_post_process
from superset.charts.schemas import (
CHART_SCHEMAS,
@@ -126,8 +121,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP
show_columns = [
"cache_timeout",
"certified_by",
"certification_details",
"dashboards.dashboard_title",
"dashboards.id",
"dashboards.json_metadata",
@@ -143,8 +136,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
]
show_select_columns = show_columns + ["table.id"]
list_columns = [
"certified_by",
"certification_details",
"cache_timeout",
"changed_by.first_name",
"changed_by.last_name",
@@ -211,7 +202,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
base_order = ("changed_on", "desc")
base_filters = [["id", ChartFilter, lambda: []]]
search_filters = {
"id": [ChartFavoriteFilter, ChartCertifiedFilter],
"id": [ChartFavoriteFilter],
"slice_name": [ChartAllTextFilter],
}

View File

@@ -17,7 +17,7 @@
from typing import Any
from flask_babel import lazy_gettext as _
from sqlalchemy import and_, or_
from sqlalchemy import or_
from sqlalchemy.orm.query import Query
from superset import security_manager
@@ -55,22 +55,6 @@ class ChartFavoriteFilter(BaseFavoriteFilter): # pylint: disable=too-few-public
model = Slice
class ChartCertifiedFilter(BaseFilter): # pylint: disable=too-few-public-methods
"""
Custom filter for the GET list that filters all certified charts
"""
name = _("Is certified")
arg_name = "chart_is_certified"
def apply(self, query: Query, value: Any) -> Query:
if value is True:
return query.filter(and_(Slice.certified_by.isnot(None)))
if value is False:
return query.filter(and_(Slice.certified_by.is_(None)))
return query
class ChartFilter(BaseFilter): # pylint: disable=too-few-public-methods
def apply(self, query: Query, value: Any) -> Query:
if security_manager.can_access_all_datasources():

View File

@@ -114,8 +114,6 @@ form_data_description = (
)
description_markeddown_description = "Sanitized HTML version of the chart description."
owners_name_description = "Name of an owner of the chart."
certified_by_description = "Person or group that has certified this chart"
certification_details_description = "Details of the certification"
#
# OpenAPI method specification overrides
@@ -159,8 +157,6 @@ class ChartEntityResponseSchema(Schema):
)
form_data = fields.Dict(description=form_data_description)
slice_url = fields.String(description=slice_url_description)
certified_by = fields.String(description=certified_by_description)
certification_details = fields.String(description=certification_details_description)
class ChartPostSchema(Schema):
@@ -202,10 +198,6 @@ class ChartPostSchema(Schema):
description=datasource_name_description, allow_none=True
)
dashboards = fields.List(fields.Integer(description=dashboards_description))
certified_by = fields.String(description=certified_by_description, allow_none=True)
certification_details = fields.String(
description=certification_details_description, allow_none=True
)
class ChartPutSchema(Schema):
@@ -243,10 +235,6 @@ class ChartPutSchema(Schema):
allow_none=True,
)
dashboards = fields.List(fields.Integer(description=dashboards_description))
certified_by = fields.String(description=certified_by_description, allow_none=True)
certification_details = fields.String(
description=certification_details_description, allow_none=True
)
class ChartGetDatasourceObjectDataResponseSchema(Schema):

View File

@@ -53,7 +53,6 @@ from superset.dashboards.commands.update import UpdateDashboardCommand
from superset.dashboards.dao import DashboardDAO
from superset.dashboards.filters import (
DashboardAccessFilter,
DashboardCertifiedFilter,
DashboardFavoriteFilter,
DashboardTitleOrSlugFilter,
FilterRelatedRoles,
@@ -122,8 +121,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"position_json",
"json_metadata",
"thumbnail_url",
"certified_by",
"certification_details",
"changed_by.first_name",
"changed_by.last_name",
"changed_by.username",
@@ -153,8 +150,6 @@ class DashboardRestApi(BaseSupersetModelRestApi):
]
add_columns = [
"certified_by",
"certification_details",
"dashboard_title",
"slug",
"owners",
@@ -178,7 +173,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
)
search_filters = {
"dashboard_title": [DashboardTitleOrSlugFilter],
"id": [DashboardFavoriteFilter, DashboardCertifiedFilter],
"id": [DashboardFavoriteFilter],
}
base_order = ("changed_on", "desc")

View File

@@ -34,7 +34,6 @@ from superset.dashboards.commands.exceptions import (
)
from superset.dashboards.dao import DashboardDAO
from superset.exceptions import SupersetSecurityException
from superset.extensions import db
from superset.models.dashboard import Dashboard
from superset.views.base import check_ownership
@@ -52,14 +51,13 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
self.validate()
try:
dashboard = DashboardDAO.update(self._model, self._properties, commit=False)
dashboard = DashboardDAO.update_charts_owners(dashboard, commit=False)
dashboard = DashboardDAO.update_charts_owners(dashboard, commit=True)
if self._properties.get("json_metadata"):
dashboard = DashboardDAO.set_dash_metadata(
dashboard,
data=json.loads(self._properties.get("json_metadata", "{}")),
commit=False,
commit=True,
)
db.session.commit()
except DAOUpdateFailedError as ex:
logger.exception(ex.exception)
raise DashboardUpdateFailedError() from ex

View File

@@ -239,9 +239,6 @@ class DashboardDAO(BaseDAO):
}
md["default_filters"] = json.dumps(applicable_filters)
# positions have its own column, no need to store it in metadata
md.pop("positions", None)
# The css and dashboard_title properties are not part of the metadata
# TODO (geido): remove by refactoring/deprecating save_dash endpoint
if data.get("css") is not None:

View File

@@ -158,19 +158,3 @@ class FilterRelatedRoles(BaseFilter): # pylint: disable=too-few-public-methods
if value:
return query.filter(role_model.name.ilike(f"%{value}%"),)
return query
class DashboardCertifiedFilter(BaseFilter): # pylint: disable=too-few-public-methods
"""
Custom filter for the GET list that filters all certified dashboards
"""
name = _("Is certified")
arg_name = "dashboard_is_certified"
def apply(self, query: Query, value: Any) -> Query:
if value is True:
return query.filter(and_(Dashboard.certified_by.isnot(None),))
if value is False:
return query.filter(and_(Dashboard.certified_by.is_(None),))
return query

View File

@@ -64,8 +64,6 @@ published_description = (
charts_description = (
"The names of the dashboard's charts. Names are used for legacy reasons."
)
certified_by_description = "Person or group that has certified this dashboard"
certification_details_description = "Details of the certification"
openapi_spec_methods_override = {
"get": {"get": {"description": "Get a dashboard detail information."}},
@@ -155,8 +153,6 @@ class DashboardGetResponseSchema(Schema):
css = fields.String(description=css_description)
json_metadata = fields.String(description=json_metadata_description)
position_json = fields.String(description=position_json_description)
certified_by = fields.String(description=certified_by_description)
certification_details = fields.String(description=certification_details_description)
changed_by_name = fields.String()
changed_by_url = fields.String()
changed_by = fields.Nested(UserSchema)
@@ -243,10 +239,6 @@ class DashboardPostSchema(BaseDashboardSchema):
description=json_metadata_description, validate=validate_json_metadata,
)
published = fields.Boolean(description=published_description)
certified_by = fields.String(description=certified_by_description, allow_none=True)
certification_details = fields.String(
description=certification_details_description, allow_none=True
)
class DashboardPutSchema(BaseDashboardSchema):
@@ -272,10 +264,6 @@ class DashboardPutSchema(BaseDashboardSchema):
validate=validate_json_metadata,
)
published = fields.Boolean(description=published_description, allow_none=True)
certified_by = fields.String(description=certified_by_description, allow_none=True)
certification_details = fields.String(
description=certification_details_description, allow_none=True
)
class ChartFavStarResponseResult(Schema):

View File

@@ -1,44 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""add_certifications_columns_to_dashboard
Revision ID: aea15018d53b
Revises: f9847149153d
Create Date: 2021-11-05 11:11:55.496618
"""
# revision identifiers, used by Alembic.
import sqlalchemy as sa
from alembic import op
revision = "aea15018d53b"
down_revision = "f9847149153d"
def upgrade():
with op.batch_alter_table("dashboards") as batch_op:
batch_op.add_column(sa.Column("certified_by", sa.Text(), nullable=True))
batch_op.add_column(
sa.Column("certification_details", sa.Text(), nullable=True)
)
def downgrade():
with op.batch_alter_table("dashboards") as batch_op:
batch_op.drop_column("certified_by")
batch_op.drop_column("certification_details")

View File

@@ -1,44 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""add_certifications_columns_to_slice
Revision ID: f9847149153d
Revises: 32646df09c64
Create Date: 2021-11-03 14:07:09.905194
"""
# revision identifiers, used by Alembic.
import sqlalchemy as sa
from alembic import op
revision = "f9847149153d"
down_revision = "32646df09c64"
def upgrade():
with op.batch_alter_table("slices") as batch_op:
batch_op.add_column(sa.Column("certified_by", sa.Text(), nullable=True))
batch_op.add_column(
sa.Column("certification_details", sa.Text(), nullable=True)
)
def downgrade():
with op.batch_alter_table("slices") as batch_op:
batch_op.drop_column("certified_by")
batch_op.drop_column("certification_details")

View File

@@ -142,8 +142,6 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
position_json = Column(utils.MediumText())
description = Column(Text)
css = Column(Text)
certified_by = Column(Text)
certification_details = Column(Text)
json_metadata = Column(Text)
slug = Column(String(255), unique=True)
slices = relationship(Slice, secondary=dashboard_slices, backref="dashboards")
@@ -267,8 +265,6 @@ class Dashboard(Model, AuditMixinNullable, ImportExportMixin):
return {
"id": self.id,
"metadata": self.params_dict,
"certified_by": self.certified_by,
"certification_details": self.certification_details,
"css": self.css,
"dashboard_title": self.dashboard_title,
"published": self.published,

View File

@@ -75,8 +75,6 @@ class Slice( # pylint: disable=too-many-public-methods
# when the database row was last written
last_saved_at = Column(DateTime, nullable=True)
last_saved_by_fk = Column(Integer, ForeignKey("ab_user.id"), nullable=True)
certified_by = Column(Text)
certification_details = Column(Text)
last_saved_by = relationship(
security_manager.user_model, foreign_keys=[last_saved_by_fk]
)
@@ -207,8 +205,6 @@ class Slice( # pylint: disable=too-many-public-methods
"slice_id": self.id,
"slice_name": self.slice_name,
"slice_url": self.slice_url,
"certified_by": self.certified_by,
"certification_details": self.certification_details,
}
@property

View File

@@ -109,7 +109,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
charts = []
admin = self.get_user("admin")
for cx in range(CHARTS_FIXTURE_COUNT - 1):
charts.append(self.insert_chart(f"name{cx}", [admin.id], 1,))
charts.append(self.insert_chart(f"name{cx}", [admin.id], 1))
fav_charts = []
for cx in range(round(CHARTS_FIXTURE_COUNT / 2)):
fav_star = FavStar(
@@ -127,29 +127,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
db.session.delete(fav_chart)
db.session.commit()
@pytest.fixture()
def create_certified_charts(self):
with self.create_app().app_context():
certified_charts = []
admin = self.get_user("admin")
for cx in range(CHARTS_FIXTURE_COUNT):
certified_charts.append(
self.insert_chart(
f"certified{cx}",
[admin.id],
1,
certified_by="John Doe",
certification_details="Sample certification",
)
)
yield certified_charts
# rollback changes
for chart in certified_charts:
db.session.delete(chart)
db.session.commit()
@pytest.fixture()
def create_chart_with_report(self):
with self.create_app().app_context():
@@ -472,8 +449,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
"datasource_id": 1,
"datasource_type": "table",
"dashboards": dashboards_ids,
"certified_by": "John Doe",
"certification_details": "Sample certification",
}
self.login(username="admin")
uri = f"api/v1/chart/"
@@ -589,8 +564,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
"datasource_id": birth_names_table_id,
"datasource_type": "table",
"dashboards": [dash_id],
"certified_by": "Mario Rossi",
"certification_details": "Edited certification",
}
self.login(username="admin")
uri = f"api/v1/chart/{chart_id}"
@@ -609,8 +582,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
self.assertEqual(model.datasource_id, birth_names_table_id)
self.assertEqual(model.datasource_type, "table")
self.assertEqual(model.datasource_name, full_table_name)
self.assertEqual(model.certified_by, "Mario Rossi")
self.assertEqual(model.certification_details, "Edited certification")
self.assertIn(model.id, [slice.id for slice in related_dashboard.slices])
db.session.delete(model)
db.session.commit()
@@ -761,8 +732,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
self.assertEqual(rv.status_code, 200)
expected_result = {
"cache_timeout": None,
"certified_by": None,
"certification_details": None,
"dashboards": [],
"description": None,
"owners": [
@@ -957,36 +926,6 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["count"], 8)
@pytest.mark.usefixtures("create_certified_charts")
def test_gets_certified_charts_filter(self):
arguments = {
"filters": [{"col": "id", "opr": "chart_is_certified", "value": True,}],
"keys": ["none"],
"columns": ["slice_name"],
}
self.login(username="admin")
uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["count"], CHARTS_FIXTURE_COUNT)
@pytest.mark.usefixtures("create_charts")
def test_gets_not_certified_charts_filter(self):
arguments = {
"filters": [{"col": "id", "opr": "chart_is_certified", "value": False,}],
"keys": ["none"],
"columns": ["slice_name"],
}
self.login(username="admin")
uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["count"], 17)
@pytest.mark.usefixtures("load_energy_charts")
def test_user_gets_none_filtered_energy_slices(self):
# test filtering on datasource_name

View File

@@ -86,8 +86,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
css: str = "",
json_metadata: str = "",
published: bool = False,
certified_by: Optional[str] = None,
certification_details: Optional[str] = None,
) -> Dashboard:
obj_owners = list()
obj_roles = list()
@@ -109,8 +107,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
slices=slices,
published=published,
created_by=created_by,
certified_by=certified_by,
certification_details=certification_details,
)
db.session.add(dashboard)
db.session.commit()
@@ -129,8 +125,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
f"slug{cx}",
[admin.id],
slices=charts if cx < half_dash_count else [],
certified_by="John Doe",
certification_details="Sample certification",
)
if cx < half_dash_count:
chart = self.insert_chart(f"slice{cx}", [admin.id], 1, params="{}")
@@ -321,8 +315,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
rv = self.get_assert_metric(uri, "get")
self.assertEqual(rv.status_code, 200)
expected_result = {
"certified_by": None,
"certification_details": None,
"changed_by": None,
"changed_by_name": "",
"changed_by_url": "",
@@ -619,38 +611,6 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
expected_model.dashboard_title == data["result"][i]["dashboard_title"]
)
@pytest.mark.usefixtures("create_dashboards")
def test_gets_certified_dashboards_filter(self):
arguments = {
"filters": [{"col": "id", "opr": "dashboard_is_certified", "value": True,}],
"keys": ["none"],
"columns": ["dashboard_title"],
}
self.login(username="admin")
uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["count"], DASHBOARDS_FIXTURE_COUNT)
@pytest.mark.usefixtures("create_dashboards")
def test_gets_not_certified_dashboards_filter(self):
arguments = {
"filters": [
{"col": "id", "opr": "dashboard_is_certified", "value": False,}
],
"keys": ["none"],
"columns": ["dashboard_title"],
}
self.login(username="admin")
uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["count"], 6)
def create_dashboard_import(self):
buf = BytesIO()
with ZipFile(buf, "w") as bundle:

View File

@@ -36,8 +36,6 @@ class InsertChartMixin:
viz_type: Optional[str] = None,
params: Optional[str] = None,
cache_timeout: Optional[int] = None,
certified_by: Optional[str] = None,
certification_details: Optional[str] = None,
) -> Slice:
obj_owners = list()
for owner in owners:
@@ -48,8 +46,6 @@ class InsertChartMixin:
)
slice = Slice(
cache_timeout=cache_timeout,
certified_by=certified_by,
certification_details=certification_details,
created_by=created_by,
datasource_id=datasource.id,
datasource_name=datasource.name,

File diff suppressed because it is too large Load Diff