diff --git a/.pylintrc b/.pylintrc
index e69d5c4844..0835b7afc0 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -81,7 +81,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
-disable=standarderror-builtin,long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,missing-docstring,too-many-lines,ungrouped-imports,import-outside-toplevel,raise-missing-from,super-with-arguments,bad-option-value
+disable=long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,missing-docstring,too-many-lines,ungrouped-imports,import-outside-toplevel,raise-missing-from,super-with-arguments,bad-option-value
[REPORTS]
diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js
index 0ee38568f3..c2f918439f 100644
--- a/superset-frontend/.eslintrc.js
+++ b/superset-frontend/.eslintrc.js
@@ -122,6 +122,7 @@ module.exports = {
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-destructuring': ['error', { object: true, array: false }],
+ 'react/default-props-match-prop-types': 0, // disabled temporarily
'react/destructuring-assignment': 0, // re-enable up for discussion
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
@@ -232,6 +233,7 @@ module.exports = {
'prefer-arrow-callback': 0,
'prefer-object-spread': 1,
'prefer-destructuring': ['error', { object: true, array: false }],
+ 'react/default-props-match-prop-types': 0, // disabled temporarily
'react/destructuring-assignment': 0, // re-enable up for discussion
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
index f272c3804a..60debb6fb4 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts
@@ -18,7 +18,7 @@
*/
import { DASHBOARD_LIST } from './dashboard_list.helper';
-describe('dashboard filters', () => {
+describe('dashboard filters card view', () => {
beforeEach(() => {
cy.login();
cy.server();
@@ -36,14 +36,63 @@ describe('dashboard filters', () => {
cy.get('.ant-card').should('not.exist');
});
+ it('should filter by created by correctly', () => {
+ // filter by created by
+ cy.get('.Select__control').eq(1).click();
+ cy.get('.Select__menu').contains('alpha user').click();
+ cy.get('.ant-card').should('not.exist');
+ cy.get('.Select__control').eq(1).click();
+ cy.get('.Select__menu').contains('gamma user').click();
+ cy.get('.ant-card').should('not.exist');
+ });
+
it('should filter by published correctly', () => {
// filter by published
- cy.get('.Select__control').eq(1).click();
+ cy.get('.Select__control').eq(2).click();
cy.get('.Select__menu').contains('Published').click();
cy.get('.ant-card').should('have.length', 2);
cy.get('.ant-card').first().contains('USA Births Names').should('exist');
- cy.get('.Select__control').eq(1).click();
- cy.get('.Select__control').eq(1).type('unpub{enter}');
+ cy.get('.Select__control').eq(2).click();
+ cy.get('.Select__control').eq(2).type('unpub{enter}');
cy.get('.ant-card').should('have.length', 2);
});
});
+
+describe('dashboard filters list view', () => {
+ beforeEach(() => {
+ cy.login();
+ cy.server();
+ cy.visit(DASHBOARD_LIST);
+ });
+
+ it('should filter by owners correctly', () => {
+ // filter by owners
+ cy.get('.Select__control').first().click();
+ cy.get('.Select__menu').contains('alpha user').click();
+ cy.get('.table-row').should('not.exist');
+ cy.get('.Select__control').first().click();
+ cy.get('.Select__menu').contains('gamma user').click();
+ cy.get('.table-row').should('not.exist');
+ });
+
+ it('should filter by created by correctly', () => {
+ // filter by created by
+ cy.get('.Select__control').eq(1).click();
+ cy.get('.Select__menu').contains('alpha user').click();
+ cy.get('.table-row').should('not.exist');
+ cy.get('.Select__control').eq(1).click();
+ cy.get('.Select__menu').contains('gamma user').click();
+ cy.get('.table-row').should('not.exist');
+ });
+
+ it('should filter by published correctly', () => {
+ // filter by published
+ cy.get('.Select__control').eq(2).click();
+ cy.get('.Select__menu').contains('Published').click();
+ cy.get('.table-row').should('have.length', 2);
+ cy.get('.table-row').first().contains('USA Births Names').should('exist');
+ cy.get('.Select__control').eq(2).click();
+ cy.get('.Select__control').eq(2).type('unpub{enter}');
+ cy.get('.table-row').should('have.length', 2);
+ });
+});
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts
index 380a9cee5e..5afe356de3 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts
@@ -31,17 +31,22 @@ describe('dashboard list view', () => {
cy.get('table[role="table"]').should('be.visible');
// check dashboard list view header
cy.get('th[role="columnheader"]:nth-child(2)').contains('Title');
- cy.get('th[role="columnheader"]:nth-child(3)').contains('Owners');
- cy.get('th[role="columnheader"]:nth-child(4)').contains('Modified By');
- cy.get('th[role="columnheader"]:nth-child(5)').contains('Published');
- cy.get('th[role="columnheader"]:nth-child(6)').contains('Modified');
- cy.get('th[role="columnheader"]:nth-child(7)').contains('Actions');
+ cy.get('th[role="columnheader"]:nth-child(3)').contains('Modified By');
+ cy.get('th[role="columnheader"]:nth-child(4)').contains('Published');
+ cy.get('th[role="columnheader"]:nth-child(5)').contains('Modified');
+ cy.get('th[role="columnheader"]:nth-child(6)').contains('Created By');
+ cy.get('th[role="columnheader"]:nth-child(7)').contains('Owners');
+ cy.get('th[role="columnheader"]:nth-child(8)').contains('Actions');
cy.get('.table-row').should('have.length', 4);
});
it('should sort correctly', () => {
cy.get('th[role="columnheader"]:nth-child(2)').click();
cy.get('.table-row td:nth-child(2):eq(0)').contains('Tabbed Dashboard');
+ cy.get('th[role="columnheader"]:nth-child(3)').click();
+ cy.get('.table-row td:nth-child(2):eq(0)').contains('Tabbed Dashboard');
+ cy.get('th[role="columnheader"]:nth-child(5)').click();
+ cy.get('.table-row td:nth-child(2):eq(0)').contains("World Bank's Data");
cy.get('th[role="columnheader"]:nth-child(6)').click();
cy.get('.table-row td:nth-child(2):eq(0)').contains("World Bank's Data");
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
index 5de21354c2..82211c4cae 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/DashboardBuilder_spec.jsx
@@ -62,6 +62,9 @@ describe('DashboardBuilder', () => {
dashboardLayout,
deleteTopLevelTabs() {},
editMode: false,
+ showBuilderPane() {},
+ setColorSchemeAndUnsavedChanges() {},
+ colorScheme: undefined,
handleComponentDrop() {},
setDirectPathToChild: sinon.spy(),
};
diff --git a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx
index 9dc24811f6..9a0740cb01 100644
--- a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx
@@ -21,8 +21,6 @@ import React from 'react';
import sinon from 'sinon';
import { shallow, mount } from 'enzyme';
-import { supersetTheme, ThemeProvider } from '@superset-ui/core';
-
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
import ControlHeader from 'src/explore/components/ControlHeader';
import Checkbox from 'src/components/Checkbox';
@@ -50,10 +48,7 @@ describe('CheckboxControl', () => {
});
it('Checks the box when the label is clicked', () => {
- const fullComponent = mount(, {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: { theme: supersetTheme },
- });
+ const fullComponent = mount();
const spy = sinon.spy(fullComponent.instance(), 'onChange');
diff --git a/superset-frontend/src/CRUD/Field.jsx b/superset-frontend/src/CRUD/Field.jsx
index 3ed439f2ed..3d56019fa5 100644
--- a/superset-frontend/src/CRUD/Field.jsx
+++ b/superset-frontend/src/CRUD/Field.jsx
@@ -39,9 +39,10 @@ const propTypes = {
compact: PropTypes.bool,
};
const defaultProps = {
+ controlProps: {},
onChange: () => {},
compact: false,
- description: null,
+ desc: null,
};
export default class Field extends React.PureComponent {
diff --git a/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton.jsx b/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton.jsx
index 3c3e2036cd..0fac3d1159 100644
--- a/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton.jsx
+++ b/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton.jsx
@@ -37,6 +37,10 @@ const propTypes = {
templateParams: PropTypes.string,
};
+const defaultProps = {
+ vizRequest: {},
+};
+
class ExploreCtasResultsButton extends React.PureComponent {
constructor(props) {
super(props);
@@ -109,6 +113,7 @@ class ExploreCtasResultsButton extends React.PureComponent {
}
}
ExploreCtasResultsButton.propTypes = propTypes;
+ExploreCtasResultsButton.defaultProps = defaultProps;
function mapStateToProps({ sqlLab, common }) {
return {
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
index 66a538584b..552ba4ce6b 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
@@ -25,7 +25,7 @@ import TableSelector from '../../components/TableSelector';
const propTypes = {
queryEditor: PropTypes.object.isRequired,
- height: PropTypes.number,
+ height: PropTypes.number.isRequired,
tables: PropTypes.array,
actions: PropTypes.object,
database: PropTypes.object,
diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor.jsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor.jsx
index 4552576881..a15f5638b5 100644
--- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor.jsx
+++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor.jsx
@@ -33,6 +33,8 @@ const propTypes = {
};
const defaultProps = {
+ label: null,
+ description: null,
onChange: () => {},
code: '{}',
};
diff --git a/superset-frontend/src/components/Checkbox/index.tsx b/superset-frontend/src/components/Checkbox/index.tsx
index 6bff6d8947..621ce1755c 100644
--- a/superset-frontend/src/components/Checkbox/index.tsx
+++ b/superset-frontend/src/components/Checkbox/index.tsx
@@ -30,14 +30,8 @@ interface CheckboxProps {
}
const Styles = styled.span`
- cursor: pointer;
- &.primary {
- color: ${({ theme }) => theme.colors.primary.base};
- }
- &.grayscale {
- color: ${({ theme }) => theme.colors.grayscale.light1};
- }
- svg {
+ &,
+ & svg {
vertical-align: top;
}
`;
@@ -45,7 +39,6 @@ const Styles = styled.span`
export default function Checkbox({ checked, onChange, style }: CheckboxProps) {
return (
{
onChange(!checked);
diff --git a/superset-frontend/src/components/CheckboxIcons.tsx b/superset-frontend/src/components/CheckboxIcons.tsx
index a9addf8159..2c94c863fb 100644
--- a/superset-frontend/src/components/CheckboxIcons.tsx
+++ b/superset-frontend/src/components/CheckboxIcons.tsx
@@ -28,7 +28,7 @@ export const CheckboxChecked = () => (
>
@@ -44,7 +44,7 @@ export const CheckboxHalfChecked = () => (
>
@@ -60,7 +60,7 @@ export const CheckboxUnchecked = () => (
>
diff --git a/superset-frontend/src/components/Hotkeys.jsx b/superset-frontend/src/components/Hotkeys.jsx
index 546d3ed07b..c0d4707bf3 100644
--- a/superset-frontend/src/components/Hotkeys.jsx
+++ b/superset-frontend/src/components/Hotkeys.jsx
@@ -33,6 +33,10 @@ const propTypes = {
placement: PropTypes.string,
};
+const defaultProps = {
+ hotkeys: [],
+};
+
export default class Hotkeys extends React.PureComponent {
componentDidMount() {
this.props.hotkeys.forEach(keyConfig => {
@@ -82,3 +86,4 @@ export default class Hotkeys extends React.PureComponent {
}
Hotkeys.propTypes = propTypes;
+Hotkeys.defaultProps = defaultProps;
diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts
index 2efb5f0a18..634631a183 100644
--- a/superset-frontend/src/components/ListView/types.ts
+++ b/superset-frontend/src/components/ListView/types.ts
@@ -53,7 +53,8 @@ export interface Filter {
| 'rel_m_m'
| 'rel_o_m'
| 'title_or_slug'
- | 'name_or_description';
+ | 'name_or_description'
+ | 'all_text';
input?: 'text' | 'textarea' | 'select' | 'checkbox' | 'search';
unfilteredLabel?: string;
selects?: SelectOption[];
diff --git a/superset-frontend/src/components/Select/OnPasteSelect.jsx b/superset-frontend/src/components/Select/OnPasteSelect.jsx
index e7d08aea0a..fcf1e10343 100644
--- a/superset-frontend/src/components/Select/OnPasteSelect.jsx
+++ b/superset-frontend/src/components/Select/OnPasteSelect.jsx
@@ -81,12 +81,12 @@ export default class OnPasteSelect extends React.Component {
}
OnPasteSelect.propTypes = {
- separator: PropTypes.array,
+ separator: PropTypes.array.isRequired,
selectWrap: PropTypes.elementType,
selectRef: PropTypes.func,
onChange: PropTypes.func.isRequired,
- valueKey: PropTypes.string,
- labelKey: PropTypes.string,
+ valueKey: PropTypes.string.isRequired,
+ labelKey: PropTypes.string.isRequired,
options: PropTypes.array,
isMulti: PropTypes.bool,
value: PropTypes.any,
diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
index d60794640f..b2e14d1037 100644
--- a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
+++ b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
@@ -24,13 +24,13 @@ import { getCategoricalSchemeRegistry, t } from '@superset-ui/core';
import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeControl';
const propTypes = {
- onChange: PropTypes.func,
+ onChange: PropTypes.func.isRequired,
colorScheme: PropTypes.string,
};
const defaultProps = {
- onChange: () => {},
colorScheme: undefined,
+ onChange: () => {},
};
class ColorSchemeControlWrapper extends React.PureComponent {
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
index 3f798c1106..ecaf3cf2ea 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
@@ -54,6 +54,9 @@ const propTypes = {
dashboardLayout: PropTypes.object.isRequired,
deleteTopLevelTabs: PropTypes.func.isRequired,
editMode: PropTypes.bool.isRequired,
+ showBuilderPane: PropTypes.func.isRequired,
+ colorScheme: PropTypes.string,
+ setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired,
handleComponentDrop: PropTypes.func.isRequired,
directPathToChild: PropTypes.arrayOf(PropTypes.string),
setDirectPathToChild: PropTypes.func.isRequired,
@@ -61,7 +64,9 @@ const propTypes = {
};
const defaultProps = {
+ showBuilderPane: false,
directPathToChild: [],
+ colorScheme: undefined,
};
class DashboardBuilder extends React.Component {
@@ -150,7 +155,14 @@ class DashboardBuilder extends React.Component {
}
render() {
- const { handleComponentDrop, dashboardLayout, editMode } = this.props;
+ const {
+ handleComponentDrop,
+ dashboardLayout,
+ editMode,
+ showBuilderPane,
+ setColorSchemeAndUnsavedChanges,
+ colorScheme,
+ } = this.props;
const { tabIndex } = this.state;
const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
const rootChildId = dashboardRoot.children[0];
@@ -260,6 +272,9 @@ class DashboardBuilder extends React.Component {
{editMode && (
)}
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal.jsx b/superset-frontend/src/dashboard/components/PropertiesModal.jsx
index 2637f519e2..57f348282e 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal.jsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal.jsx
@@ -35,7 +35,7 @@ import '../stylesheets/buttons.less';
const propTypes = {
dashboardId: PropTypes.number.isRequired,
- show: PropTypes.bool,
+ show: PropTypes.bool.isRequired,
onHide: PropTypes.func,
colorScheme: PropTypes.object,
setColorSchemeAndUnsavedChanges: PropTypes.func,
diff --git a/superset-frontend/src/dashboard/components/SliceAdder.jsx b/superset-frontend/src/dashboard/components/SliceAdder.jsx
index 80d80cbf40..9034a8105a 100644
--- a/superset-frontend/src/dashboard/components/SliceAdder.jsx
+++ b/superset-frontend/src/dashboard/components/SliceAdder.jsx
@@ -39,7 +39,7 @@ const propTypes = {
lastUpdated: PropTypes.number.isRequired,
errorMessage: PropTypes.string,
userId: PropTypes.string.isRequired,
- selectedSliceIds: PropTypes.arrayOf(PropTypes.number),
+ selectedSliceIds: PropTypes.arrayOf(PropTypes.number).isRequired,
editMode: PropTypes.bool,
height: PropTypes.number,
};
diff --git a/superset-frontend/src/dashboard/components/SliceHeader.jsx b/superset-frontend/src/dashboard/components/SliceHeader.jsx
index 276d36a6dc..941c0ad258 100644
--- a/superset-frontend/src/dashboard/components/SliceHeader.jsx
+++ b/superset-frontend/src/dashboard/components/SliceHeader.jsx
@@ -53,6 +53,7 @@ const propTypes = {
const defaultProps = {
innerRef: null,
forceRefresh: () => ({}),
+ removeSlice: () => ({}),
updateSliceName: () => ({}),
toggleExpandSlice: () => ({}),
exploreChart: () => ({}),
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx
index f98cac9373..f9076bcf4e 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Row.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Row.jsx
@@ -56,6 +56,10 @@ const propTypes = {
updateComponents: PropTypes.func.isRequired,
};
+const defaultProps = {
+ rowHeight: null,
+};
+
class Row extends React.PureComponent {
constructor(props) {
super(props);
@@ -188,5 +192,6 @@ class Row extends React.PureComponent {
}
Row.propTypes = propTypes;
+Row.defaultProps = defaultProps;
export default Row;
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index 1765ebb0af..b765cbf93b 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -69,6 +69,7 @@ const propTypes = {
};
const defaultProps = {
+ children: null,
renderTabContent: true,
renderHoverMenu: true,
availableColumnCount: 0,
diff --git a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx
index eb43901157..42158dccfe 100644
--- a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx
+++ b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx
@@ -35,6 +35,7 @@ const defaultProps = {
children: null,
disableClick: false,
onChangeFocus: null,
+ onPressDelete() {},
menuItems: [],
isFocused: false,
shouldFocus: (event, container) =>
diff --git a/superset-frontend/src/dashboard/containers/DashboardBuilder.jsx b/superset-frontend/src/dashboard/containers/DashboardBuilder.jsx
index 9d06e773aa..dacdb7b810 100644
--- a/superset-frontend/src/dashboard/containers/DashboardBuilder.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardBuilder.jsx
@@ -21,6 +21,7 @@ import { connect } from 'react-redux';
import DashboardBuilder from '../components/DashboardBuilder';
import {
+ setColorSchemeAndUnsavedChanges,
showBuilderPane,
setDirectPathToChild,
setMountedTab,
@@ -34,7 +35,9 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState }) {
return {
dashboardLayout: undoableLayout.present,
editMode: dashboardState.editMode,
+ showBuilderPane: dashboardState.showBuilderPane,
directPathToChild: dashboardState.directPathToChild,
+ colorScheme: dashboardState.colorScheme,
};
}
@@ -44,6 +47,7 @@ function mapDispatchToProps(dispatch) {
deleteTopLevelTabs,
handleComponentDrop,
showBuilderPane,
+ setColorSchemeAndUnsavedChanges,
setDirectPathToChild,
setMountedTab,
},
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index aaa7da3ec0..d4a403f752 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -53,6 +53,7 @@ const propTypes = {
const defaultProps = {
directPathToChild: [],
directPathLastUpdated: 0,
+ isComponentVisible: true,
};
function mapStateToProps(
diff --git a/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx b/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
index d04cf9d8c4..e169dc4540 100644
--- a/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
+++ b/superset-frontend/src/explore/components/QueryAndSaveBtns.jsx
@@ -37,6 +37,7 @@ const propTypes = {
const defaultProps = {
onStop: () => {},
onSave: () => {},
+ disabled: false,
};
// Prolly need to move this to a global context
diff --git a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx b/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx
index 0d3a56e6fd..4b9d6e4f45 100644
--- a/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx
+++ b/superset-frontend/src/explore/components/controls/ColorSchemeControl.jsx
@@ -35,8 +35,8 @@ const propTypes = {
choices: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.array),
PropTypes.func,
- ]),
- schemes: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
+ ]).isRequired,
+ schemes: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
isLinear: PropTypes.bool,
};
diff --git a/superset-frontend/src/explore/components/controls/ViewportControl.jsx b/superset-frontend/src/explore/components/controls/ViewportControl.jsx
index 11731b14c6..9aa6be1663 100644
--- a/superset-frontend/src/explore/components/controls/ViewportControl.jsx
+++ b/superset-frontend/src/explore/components/controls/ViewportControl.jsx
@@ -37,7 +37,7 @@ export const DEFAULT_VIEWPORT = {
const PARAMS = ['longitude', 'latitude', 'zoom', 'bearing', 'pitch'];
const propTypes = {
- onChange: PropTypes.func,
+ onChange: PropTypes.func.isRequired,
value: PropTypes.shape({
longitude: PropTypes.number,
latitude: PropTypes.number,
diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
index 4efdd9ff87..983e14c818 100644
--- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
+++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx
@@ -55,6 +55,7 @@ interface Dashboard {
url: string;
thumbnail_url: string;
owners: Owner[];
+ created_by: object;
}
function DashboardList(props: DashboardListProps) {
@@ -195,24 +196,7 @@ function DashboardList(props: DashboardListProps) {
Header: t('Title'),
accessor: 'dashboard_title',
},
- {
- Cell: ({
- row: {
- original: { owners },
- },
- }: any) => (
-
- `${firstName} ${lastName}`,
- )}
- display={2}
- />
- ),
- Header: t('Owners'),
- accessor: 'owners',
- disableSortBy: true,
- },
+
{
Cell: ({
row: {
@@ -252,6 +236,35 @@ function DashboardList(props: DashboardListProps) {
hidden: true,
disableSortBy: true,
},
+ {
+ Cell: ({
+ row: {
+ original: { created_by: createdBy },
+ },
+ }: any) =>
+ createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
+ Header: t('Created By'),
+ accessor: 'created_by',
+ disableSortBy: true,
+ },
+ {
+ Cell: ({
+ row: {
+ original: { owners },
+ },
+ }: any) => (
+
+ `${firstName} ${lastName}`,
+ )}
+ display={2}
+ />
+ ),
+ Header: t('Owners'),
+ accessor: 'owners',
+ disableSortBy: true,
+ },
{
Cell: ({ row: { original } }: any) => {
const handleDelete = () => handleDashboardDelete(original);
@@ -329,7 +342,27 @@ function DashboardList(props: DashboardListProps) {
createErrorHandler(errMsg =>
props.addDangerToast(
t(
- 'An error occurred while fetching chart owner values: %s',
+ 'An error occurred while fetching dashboard owner values: %s',
+ errMsg,
+ ),
+ ),
+ ),
+ ),
+ paginate: true,
+ },
+ {
+ Header: t('Created By'),
+ id: 'created_by',
+ input: 'select',
+ operator: 'rel_o_m',
+ unfilteredLabel: 'All',
+ fetchSelects: createFetchRelated(
+ 'dashboard',
+ 'created_by',
+ createErrorHandler(errMsg =>
+ props.addDangerToast(
+ t(
+ 'An error occurred while fetching dashboard created by values: %s',
errMsg,
),
),
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index d691316836..c9c1fc3e24 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -113,6 +113,9 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"changed_by_url",
"changed_on_utc",
"changed_on_delta_humanized",
+ "created_by.first_name",
+ "created_by.id",
+ "created_by.last_name",
"dashboard_title",
"owners.id",
"owners.username",
@@ -121,10 +124,11 @@ class DashboardRestApi(BaseSupersetModelRestApi):
]
list_select_columns = list_columns + ["changed_on", "changed_by_fk"]
order_columns = [
- "dashboard_title",
+ "changed_by.first_name",
"changed_on_delta_humanized",
+ "created_by.first_name",
+ "dashboard_title",
"published",
- "changed_by.first_name",
]
add_columns = [
@@ -138,7 +142,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
]
edit_columns = add_columns
- search_columns = ("dashboard_title", "slug", "owners", "published")
+ search_columns = ("dashboard_title", "slug", "owners", "published", "created_by")
search_filters = {"dashboard_title": [DashboardTitleOrSlugFilter]}
base_order = ("changed_on", "desc")
@@ -152,9 +156,10 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"owners": ("first_name", "asc"),
}
related_field_filters = {
- "owners": RelatedFieldFilter("first_name", FilterRelatedOwners)
+ "owners": RelatedFieldFilter("first_name", FilterRelatedOwners),
+ "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
}
- allowed_rel_fields = {"owners"}
+ allowed_rel_fields = {"owners", "created_by"}
openapi_spec_tag = "Dashboards"
apispec_parameter_schemas = {
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index 23b2a047cf..86484791fc 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -55,7 +55,6 @@
from superset.utils.urls import get_url_path
if TYPE_CHECKING:
- # pylint: disable=unused-import
from superset.connectors.base.models import BaseDatasource
metadata = Model.metadata # pylint: disable=no-member
@@ -255,8 +254,11 @@ def position(self) -> Dict[str, Any]:
return {}
@classmethod
- def import_obj( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
- cls, dashboard_to_import: "Dashboard", import_time: Optional[int] = None,
+ def import_obj(
+ # pylint: disable=too-many-locals,too-many-branches,too-many-statements
+ cls,
+ dashboard_to_import: "Dashboard",
+ import_time: Optional[int] = None,
) -> int:
"""Imports the dashboard from the object to the database.
diff --git a/superset/models/datasource_access_request.py b/superset/models/datasource_access_request.py
index 974633294b..98362ec316 100644
--- a/superset/models/datasource_access_request.py
+++ b/superset/models/datasource_access_request.py
@@ -26,9 +26,7 @@
from superset.utils import core as utils
if TYPE_CHECKING:
- from superset.connectors.base.models import ( # pylint: disable=unused-import
- BaseDatasource,
- )
+ from superset.connectors.base.models import BaseDatasource
config = app.config
@@ -74,7 +72,6 @@ def roles_with_datasource(self) -> str:
for role in pv.role:
if role.name in self.ROLES_DENYLIST:
continue
- # pylint: disable=no-member
href = (
f"/superset/approve?datasource_type={self.datasource_type}&"
f"datasource_id={self.datasource_id}&"
@@ -87,8 +84,7 @@ def roles_with_datasource(self) -> str:
@property
def user_roles(self) -> str:
action_list = ""
- for role in self.created_by.roles: # pylint: disable=no-member
- # pylint: disable=no-member
+ for role in self.created_by.roles:
href = (
f"/superset/approve?datasource_type={self.datasource_type}&"
f"datasource_id={self.datasource_id}&"
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index fd55bba5ce..dd3a068d94 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -136,7 +136,7 @@ def import_from_dict(
parent: Optional[Any] = None,
recursive: bool = True,
sync: Optional[List[str]] = None,
- ) -> Any: # pylint: disable=too-many-arguments,too-many-locals,too-many-branches
+ ) -> Any:
"""Import obj from a dictionary"""
if sync is None:
sync = []
diff --git a/superset/models/slice.py b/superset/models/slice.py
index f9ec88e5fc..a238e194e4 100644
--- a/superset/models/slice.py
+++ b/superset/models/slice.py
@@ -43,7 +43,6 @@
from superset.viz import BaseViz, viz_types # type: ignore
if TYPE_CHECKING:
- # pylint: disable=unused-import
from superset.connectors.base.models import BaseDatasource
metadata = Model.metadata # pylint: disable=no-member
@@ -346,8 +345,7 @@ def url(self) -> str:
return f"/superset/explore/?form_data=%7B%22slice_id%22%3A%20{self.id}%7D"
-def set_related_perm(mapper: Mapper, connection: Connection, target: Slice) -> None:
- # pylint: disable=unused-argument
+def set_related_perm(_mapper: Mapper, _connection: Connection, target: Slice) -> None:
src_class = target.cls_model
id_ = target.datasource_id
if id_:
@@ -357,8 +355,8 @@ def set_related_perm(mapper: Mapper, connection: Connection, target: Slice) -> N
target.schema_perm = ds.schema_perm
-def event_after_chart_changed( # pylint: disable=unused-argument
- mapper: Mapper, connection: Connection, target: Slice
+def event_after_chart_changed(
+ _mapper: Mapper, _connection: Connection, target: Slice
) -> None:
url = get_url_path("Superset.slice", slice_id=target.id, standalone="true")
cache_chart_thumbnail.delay(url, target.digest, force=True)
diff --git a/superset/models/tags.py b/superset/models/tags.py
index c09bb16858..3f508ff6c6 100644
--- a/superset/models/tags.py
+++ b/superset/models/tags.py
@@ -29,10 +29,10 @@
from superset.models.helpers import AuditMixinNullable
if TYPE_CHECKING:
- from superset.models.core import FavStar # pylint: disable=unused-import
- from superset.models.dashboard import Dashboard # pylint: disable=unused-import
- from superset.models.slice import Slice # pylint: disable=unused-import
- from superset.models.sql_lab import Query # pylint: disable=unused-import
+ from superset.models.core import FavStar
+ from superset.models.dashboard import Dashboard
+ from superset.models.slice import Slice
+ from superset.models.sql_lab import Query
Session = sessionmaker(autoflush=False)
@@ -136,11 +136,10 @@ def _add_owners(
@classmethod
def after_insert(
cls,
- mapper: Mapper,
+ _mapper: Mapper,
connection: Connection,
target: Union["Dashboard", "FavStar", "Slice"],
) -> None:
- # pylint: disable=unused-argument
session = Session(bind=connection)
# add `owner:` tags
@@ -158,11 +157,10 @@ def after_insert(
@classmethod
def after_update(
cls,
- mapper: Mapper,
+ _mapper: Mapper,
connection: Connection,
target: Union["Dashboard", "FavStar", "Slice"],
) -> None:
- # pylint: disable=unused-argument
session = Session(bind=connection)
# delete current `owner:` tags
@@ -188,11 +186,10 @@ def after_update(
@classmethod
def after_delete(
cls,
- mapper: Mapper,
+ _mapper: Mapper,
connection: Connection,
target: Union["Dashboard", "FavStar", "Slice"],
) -> None:
- # pylint: disable=unused-argument
session = Session(bind=connection)
# delete row from `tagged_objects`
@@ -234,9 +231,8 @@ def get_owners_ids(cls, target: "Query") -> List[int]:
class FavStarUpdater:
@classmethod
def after_insert(
- cls, mapper: Mapper, connection: Connection, target: "FavStar"
+ cls, _mapper: Mapper, connection: Connection, target: "FavStar"
) -> None:
- # pylint: disable=unused-argument
session = Session(bind=connection)
name = "favorited_by:{0}".format(target.user_id)
tag = get_tag(name, session, TagTypes.favorited_by)
@@ -251,9 +247,8 @@ def after_insert(
@classmethod
def after_delete(
- cls, mapper: Mapper, connection: Connection, target: "FavStar"
+ cls, _mapper: Mapper, connection: Connection, target: "FavStar"
) -> None:
- # pylint: disable=unused-argument
session = Session(bind=connection)
name = "favorited_by:{0}".format(target.user_id)
query = (
diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py
index 81204a8b1c..af0dcd1c8d 100644
--- a/superset/queries/saved_queries/api.py
+++ b/superset/queries/saved_queries/api.py
@@ -32,7 +32,10 @@
SavedQueryBulkDeleteFailedError,
SavedQueryNotFoundError,
)
-from superset.queries.saved_queries.filters import SavedQueryFilter
+from superset.queries.saved_queries.filters import (
+ SavedQueryAllTextFilter,
+ SavedQueryFilter,
+)
from superset.queries.saved_queries.schemas import (
get_delete_ids_schema,
openapi_spec_methods_override,
@@ -93,6 +96,8 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
"database.database_name",
]
+ search_filters = {"label": [SavedQueryAllTextFilter]}
+
apispec_parameter_schemas = {
"get_delete_ids_schema": get_delete_ids_schema,
}
@@ -117,9 +122,7 @@ def pre_update(self, item: SavedQuery) -> None:
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
- def bulk_delete(
- self, **kwargs: Any
- ) -> Response: # pylint: disable=arguments-differ
+ def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk Saved Queries
---
delete:
diff --git a/superset/queries/saved_queries/filters.py b/superset/queries/saved_queries/filters.py
index 498a061edc..09636cc3a8 100644
--- a/superset/queries/saved_queries/filters.py
+++ b/superset/queries/saved_queries/filters.py
@@ -17,12 +17,33 @@
from typing import Any
from flask import g
+from flask_babel import lazy_gettext as _
from flask_sqlalchemy import BaseQuery
+from sqlalchemy import or_
+from sqlalchemy.orm.query import Query
from superset.models.sql_lab import SavedQuery
from superset.views.base import BaseFilter
+class SavedQueryAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
+ name = _("All Text")
+ arg_name = "all_text"
+
+ def apply(self, query: Query, value: Any) -> Query:
+ if not value:
+ return query
+ ilike_value = f"%{value}%"
+ return query.filter(
+ or_(
+ SavedQuery.schema.ilike(ilike_value),
+ SavedQuery.label.ilike(ilike_value),
+ SavedQuery.description.ilike(ilike_value),
+ SavedQuery.sql.ilike(ilike_value),
+ )
+ )
+
+
class SavedQueryFilter(BaseFilter): # pylint: disable=too-few-public-methods
def apply(self, query: BaseQuery, value: Any) -> BaseQuery:
"""
diff --git a/superset/security/manager.py b/superset/security/manager.py
index 858ecdc157..df2b3e0f8e 100644
--- a/superset/security/manager.py
+++ b/superset/security/manager.py
@@ -990,9 +990,7 @@ def raise_for_access( # pylint: disable=too-many-arguments,too-many-branches
self.get_datasource_access_error_object(datasource)
)
- def get_rls_filters( # pylint: disable=no-self-use
- self, table: "BaseDatasource"
- ) -> List[SqlaQuery]:
+ def get_rls_filters(self, table: "BaseDatasource") -> List[SqlaQuery]:
"""
Retrieves the appropriate row level security filters for the current user and
the passed table.
diff --git a/superset/sql_validators/presto_db.py b/superset/sql_validators/presto_db.py
index ccafb98474..e2de531f7d 100644
--- a/superset/sql_validators/presto_db.py
+++ b/superset/sql_validators/presto_db.py
@@ -53,10 +53,9 @@ def validate_statement(
sql = parsed_query.stripped()
# Hook to allow environment-specific mutation (usually comments) to the SQL
- # pylint: disable=invalid-name
- SQL_QUERY_MUTATOR = config["SQL_QUERY_MUTATOR"]
- if SQL_QUERY_MUTATOR:
- sql = SQL_QUERY_MUTATOR(sql, user_name, security_manager, database)
+ sql_query_mutator = config["SQL_QUERY_MUTATOR"]
+ if sql_query_mutator:
+ sql = sql_query_mutator(sql, user_name, security_manager, database)
# Transform the final statement to an explain call before sending it on
# to presto to validate
diff --git a/superset/tasks/alerts/observer.py b/superset/tasks/alerts/observer.py
index 482faedd80..b6805cf65e 100644
--- a/superset/tasks/alerts/observer.py
+++ b/superset/tasks/alerts/observer.py
@@ -22,8 +22,8 @@
import pandas as pd
from sqlalchemy.orm import Session
+from superset import jinja_context
from superset.models.alerts import Alert, SQLObservation
-from superset.sql_parse import ParsedQuery
logger = logging.getLogger("tasks.email_reports")
@@ -42,9 +42,9 @@ def observe(alert_id: int, session: Session) -> Optional[str]:
value = None
- parsed_query = ParsedQuery(sql_observer.sql)
- sql = parsed_query.stripped()
- df = sql_observer.database.get_df(sql)
+ tp = jinja_context.get_template_processor(database=sql_observer.database)
+ rendered_sql = tp.process_template(sql_observer.sql)
+ df = sql_observer.database.get_df(rendered_sql)
error_msg = validate_observer_result(df, alert.id, alert.label)
diff --git a/tests/alerts_tests.py b/tests/alerts_tests.py
index b09ac930e7..53245e69cb 100644
--- a/tests/alerts_tests.py
+++ b/tests/alerts_tests.py
@@ -154,6 +154,27 @@ def test_alert_observer(setup_database):
assert alert7.sql_observer[0].observations[-1].value is None
assert alert7.sql_observer[0].observations[-1].error_msg is not None
+ # Test multiline SQLObserver
+ alert8 = create_alert(
+ dbsession,
+ """
+ -- comment
+ SELECT
+ 1 -- comment
+ FROM test_table
+ WHERE first = 1
+ """,
+ )
+ observe(alert8.id, dbsession)
+ assert alert8.sql_observer[0].observations[-1].value == 1.0
+ assert alert8.sql_observer[0].observations[-1].error_msg is None
+
+ # Test jinja
+ alert9 = create_alert(dbsession, "SELECT {{ 2 }}")
+ observe(alert9.id, dbsession)
+ assert alert9.sql_observer[0].observations[-1].value == 2.0
+ assert alert9.sql_observer[0].observations[-1].error_msg is None
+
@patch("superset.tasks.schedules.deliver_alert")
def test_evaluate_alert(mock_deliver_alert, setup_database):
diff --git a/tests/dashboards/api_tests.py b/tests/dashboards/api_tests.py
index 6645e802fc..17cffd411b 100644
--- a/tests/dashboards/api_tests.py
+++ b/tests/dashboards/api_tests.py
@@ -51,6 +51,7 @@ def insert_dashboard(
dashboard_title: str,
slug: Optional[str],
owners: List[int],
+ created_by=None,
slices: Optional[List[Slice]] = None,
position_json: str = "",
css: str = "",
@@ -71,6 +72,7 @@ def insert_dashboard(
json_metadata=json_metadata,
slices=slices,
published=published,
+ created_by=created_by,
)
db.session.add(dashboard)
db.session.commit()
@@ -81,7 +83,7 @@ def test_get_dashboard(self):
Dashboard API: Test get dashboard
"""
admin = self.get_user("admin")
- dashboard = self.insert_dashboard("title", "slug1", [admin.id])
+ dashboard = self.insert_dashboard("title", "slug1", [admin.id], admin)
self.login(username="admin")
uri = f"api/v1/dashboard/{dashboard.id}"
rv = self.get_assert_metric(uri, "get")
@@ -91,6 +93,7 @@ def test_get_dashboard(self):
"changed_by_name": "",
"changed_by_url": "",
"charts": [],
+ "created_by": {"id": 1, "first_name": "admin", "last_name": "user",},
"id": dashboard.id,
"css": "",
"dashboard_title": "title",
diff --git a/tests/queries/saved_queries/api_tests.py b/tests/queries/saved_queries/api_tests.py
index b3ce625b1f..f268b1dc06 100644
--- a/tests/queries/saved_queries/api_tests.py
+++ b/tests/queries/saved_queries/api_tests.py
@@ -43,6 +43,7 @@ def insert_saved_query(
db_id: Optional[int] = None,
created_by=None,
schema: Optional[str] = "",
+ description: Optional[str] = "",
) -> SavedQuery:
database = None
if db_id:
@@ -53,6 +54,7 @@ def insert_saved_query(
sql=sql,
label=label,
schema=schema,
+ description=description,
)
db.session.add(query)
db.session.commit()
@@ -69,6 +71,7 @@ def insert_default_saved_query(
db_id=example_db.id,
created_by=admin,
schema=schema,
+ description="cool description",
)
@pytest.fixture()
@@ -195,6 +198,95 @@ def test_get_list_filter_saved_query(self):
data = json.loads(rv.data.decode("utf-8"))
assert data["count"] == len(all_queries)
+ @pytest.mark.usefixtures("create_saved_queries")
+ def test_get_list_custom_filter_schema_saved_query(self):
+ """
+ Saved Query API: Test get list and custom filter (schema) saved query
+ """
+ self.login(username="admin")
+ admin = self.get_user("admin")
+
+ all_queries = (
+ db.session.query(SavedQuery)
+ .filter(SavedQuery.created_by == admin)
+ .filter(SavedQuery.schema.ilike("%2%"))
+ .all()
+ )
+ query_string = {
+ "filters": [{"col": "label", "opr": "all_text", "value": "schema2"}],
+ }
+ uri = f"api/v1/saved_query/?q={prison.dumps(query_string)}"
+ rv = self.get_assert_metric(uri, "get_list")
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["count"] == len(all_queries)
+
+ @pytest.mark.usefixtures("create_saved_queries")
+ def test_get_list_custom_filter_label_saved_query(self):
+ """
+ Saved Query API: Test get list and custom filter (label) saved query
+ """
+ self.login(username="admin")
+ admin = self.get_user("admin")
+ all_queries = (
+ db.session.query(SavedQuery)
+ .filter(SavedQuery.created_by == admin)
+ .filter(SavedQuery.label.ilike("%3%"))
+ .all()
+ )
+ query_string = {
+ "filters": [{"col": "label", "opr": "all_text", "value": "label3"}],
+ }
+ uri = f"api/v1/saved_query/?q={prison.dumps(query_string)}"
+ rv = self.get_assert_metric(uri, "get_list")
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["count"] == len(all_queries)
+
+ @pytest.mark.usefixtures("create_saved_queries")
+ def test_get_list_custom_filter_sql_saved_query(self):
+ """
+ Saved Query API: Test get list and custom filter (sql) saved query
+ """
+ self.login(username="admin")
+ admin = self.get_user("admin")
+ all_queries = (
+ db.session.query(SavedQuery)
+ .filter(SavedQuery.created_by == admin)
+ .filter(SavedQuery.sql.ilike("%table%"))
+ .all()
+ )
+ query_string = {
+ "filters": [{"col": "label", "opr": "all_text", "value": "table"}],
+ }
+ uri = f"api/v1/saved_query/?q={prison.dumps(query_string)}"
+ rv = self.get_assert_metric(uri, "get_list")
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["count"] == len(all_queries)
+
+ @pytest.mark.usefixtures("create_saved_queries")
+ def test_get_list_custom_filter_description_saved_query(self):
+ """
+ Saved Query API: Test get list and custom filter (description) saved query
+ """
+ self.login(username="admin")
+ admin = self.get_user("admin")
+ all_queries = (
+ db.session.query(SavedQuery)
+ .filter(SavedQuery.created_by == admin)
+ .filter(SavedQuery.description.ilike("%cool%"))
+ .all()
+ )
+ query_string = {
+ "filters": [{"col": "label", "opr": "all_text", "value": "cool"}],
+ }
+ uri = f"api/v1/saved_query/?q={prison.dumps(query_string)}"
+ rv = self.get_assert_metric(uri, "get_list")
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["count"] == len(all_queries)
+
def test_info_saved_query(self):
"""
SavedQuery API: Test info
@@ -281,7 +373,7 @@ def test_get_saved_query(self):
expected_result = {
"id": saved_query.id,
"database": {"id": saved_query.database.id, "database_name": "examples"},
- "description": None,
+ "description": "cool description",
"created_by": {
"first_name": saved_query.created_by.first_name,
"id": saved_query.created_by.id,