- {this.renderWell()}
-
- {cols &&
- cols.map(col =>
)}
+ let latest = Object.entries(table.partitions?.latest || []).map(
+ ([key, value]) => `${key}=${value}`,
+ );
+ latest = latest.join('/');
+ header = (
+
+
+
+ {t('latest partition:')} {latest}
+ {' '}
+ {partitionClipBoard}
-
-
- );
- return metadata;
+
+ );
+ }
+ return header;
}
render() {
diff --git a/superset-frontend/src/chart/Chart.jsx b/superset-frontend/src/chart/Chart.jsx
index 3f1596cc45d6..b98b29094dc1 100644
--- a/superset-frontend/src/chart/Chart.jsx
+++ b/superset-frontend/src/chart/Chart.jsx
@@ -110,6 +110,25 @@ class Chart extends React.PureComponent {
}
}
+ handleRenderContainerFailure(error, info) {
+ const { actions, chartId } = this.props;
+ logging.warn(error);
+ actions.chartRenderingFailed(
+ error.toString(),
+ chartId,
+ info ? info.componentStack : null,
+ );
+
+ actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
+ slice_id: chartId,
+ has_err: true,
+ error_details: error.toString(),
+ start_offset: this.renderStartTime,
+ ts: new Date().getTime(),
+ duration: Logger.getTimestamp() - this.renderStartTime,
+ });
+ }
+
runQuery() {
if (this.props.chartId > 0 && isFeatureEnabled(FeatureFlag.CLIENT_CACHE)) {
// Load saved chart with a GET request
@@ -132,25 +151,6 @@ class Chart extends React.PureComponent {
}
}
- handleRenderContainerFailure(error, info) {
- const { actions, chartId } = this.props;
- logging.warn(error);
- actions.chartRenderingFailed(
- error.toString(),
- chartId,
- info ? info.componentStack : null,
- );
-
- actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
- slice_id: chartId,
- has_err: true,
- error_details: error.toString(),
- start_offset: this.renderStartTime,
- ts: new Date().getTime(),
- duration: Logger.getTimestamp() - this.renderStartTime,
- });
- }
-
renderErrorMessage(queryResponse) {
const { chartAlert, chartStackTrace, dashboardId, owners } = this.props;
diff --git a/superset-frontend/src/chart/ChartRenderer.jsx b/superset-frontend/src/chart/ChartRenderer.jsx
index 0c21a3a5c065..765a55b21c74 100644
--- a/superset-frontend/src/chart/ChartRenderer.jsx
+++ b/superset-frontend/src/chart/ChartRenderer.jsx
@@ -103,25 +103,6 @@ class ChartRenderer extends React.Component {
this.props.addFilter(col, vals, merge, refresh);
}
- handleRenderSuccess() {
- const { actions, chartStatus, chartId, vizType } = this.props;
- if (['loading', 'rendered'].indexOf(chartStatus) < 0) {
- actions.chartRenderingSucceeded(chartId);
- }
-
- // only log chart render time which is triggered by query results change
- // currently we don't log chart re-render time, like window resize etc
- if (this.hasQueryResponseChange) {
- actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
- slice_id: chartId,
- viz_type: vizType,
- start_offset: this.renderStartTime,
- ts: new Date().getTime(),
- duration: Logger.getTimestamp() - this.renderStartTime,
- });
- }
- }
-
handleRenderFailure(error, info) {
const { actions, chartId } = this.props;
logging.warn(error);
@@ -144,6 +125,25 @@ class ChartRenderer extends React.Component {
}
}
+ handleRenderSuccess() {
+ const { actions, chartStatus, chartId, vizType } = this.props;
+ if (['loading', 'rendered'].indexOf(chartStatus) < 0) {
+ actions.chartRenderingSucceeded(chartId);
+ }
+
+ // only log chart render time which is triggered by query results change
+ // currently we don't log chart re-render time, like window resize etc
+ if (this.hasQueryResponseChange) {
+ actions.logEvent(LOG_ACTIONS_RENDER_CHART, {
+ slice_id: chartId,
+ viz_type: vizType,
+ start_offset: this.renderStartTime,
+ ts: new Date().getTime(),
+ duration: Logger.getTimestamp() - this.renderStartTime,
+ });
+ }
+ }
+
handleSetControlValue(...args) {
const { setControlValue } = this.props;
if (setControlValue) {
diff --git a/superset-frontend/src/components/AlteredSliceTag.jsx b/superset-frontend/src/components/AlteredSliceTag.jsx
index 53a5b8ca23db..5a013afbc76e 100644
--- a/superset-frontend/src/components/AlteredSliceTag.jsx
+++ b/superset-frontend/src/components/AlteredSliceTag.jsx
@@ -71,14 +71,6 @@ export default class AlteredSliceTag extends React.Component {
}));
}
- getRowsFromDiffs(diffs, controlsMap) {
- return Object.entries(diffs).map(([key, diff]) => ({
- control: (controlsMap[key] && controlsMap[key].label) || key,
- before: this.formatValue(diff.before, key, controlsMap),
- after: this.formatValue(diff.after, key, controlsMap),
- }));
- }
-
getDiffs(props) {
// Returns all properties that differ in the
// current form data and the saved form data
@@ -101,8 +93,12 @@ export default class AlteredSliceTag extends React.Component {
return diffs;
}
- isEqualish(val1, val2) {
- return isEqual(alterForComparison(val1), alterForComparison(val2));
+ getRowsFromDiffs(diffs, controlsMap) {
+ return Object.entries(diffs).map(([key, diff]) => ({
+ control: (controlsMap[key] && controlsMap[key].label) || key,
+ before: this.formatValue(diff.before, key, controlsMap),
+ after: this.formatValue(diff.after, key, controlsMap),
+ }));
}
formatValue(value, key, controlsMap) {
@@ -146,6 +142,10 @@ export default class AlteredSliceTag extends React.Component {
return safeStringify(value);
}
+ isEqualish(val1, val2) {
+ return isEqual(alterForComparison(val1), alterForComparison(val2));
+ }
+
renderModalBody() {
const columns = [
{
diff --git a/superset-frontend/src/components/CachedLabel.jsx b/superset-frontend/src/components/CachedLabel.jsx
index db4bd2e46dba..7ed3fb85f1be 100644
--- a/superset-frontend/src/components/CachedLabel.jsx
+++ b/superset-frontend/src/components/CachedLabel.jsx
@@ -39,6 +39,15 @@ class CacheLabel extends React.PureComponent {
};
}
+ mouseOut() {
+ this.setState({ hovered: false });
+ }
+
+ mouseOver() {
+ this.updateTooltipContent();
+ this.setState({ hovered: true });
+ }
+
updateTooltipContent() {
const cachedText = this.props.cachedTimestamp ? (
@@ -57,15 +66,6 @@ class CacheLabel extends React.PureComponent {
this.setState({ tooltipContent });
}
- mouseOver() {
- this.updateTooltipContent();
- this.setState({ hovered: true });
- }
-
- mouseOut() {
- this.setState({ hovered: false });
- }
-
render() {
const labelStyle = this.state.hovered ? 'primary' : 'default';
return (
diff --git a/superset-frontend/src/components/CopyToClipboard.jsx b/superset-frontend/src/components/CopyToClipboard.jsx
index 5caf9d610cfa..86f9d677931c 100644
--- a/superset-frontend/src/components/CopyToClipboard.jsx
+++ b/superset-frontend/src/components/CopyToClipboard.jsx
@@ -56,11 +56,6 @@ class CopyToClipboard extends React.Component {
this.onClick = this.onClick.bind(this);
}
- onMouseOut() {
- // delay to avoid flash of text change on tooltip
- setTimeout(this.resetTooltipText, 200);
- }
-
onClick() {
if (this.props.getText) {
this.props.getText(d => {
@@ -71,6 +66,11 @@ class CopyToClipboard extends React.Component {
}
}
+ onMouseOut() {
+ // delay to avoid flash of text change on tooltip
+ setTimeout(this.resetTooltipText, 200);
+ }
+
getDecoratedCopyNode() {
return React.cloneElement(this.props.copyNode, {
style: { cursor: 'pointer' },
@@ -79,10 +79,6 @@ class CopyToClipboard extends React.Component {
});
}
- resetTooltipText() {
- this.setState({ tooltipText: this.props.tooltipText });
- }
-
copyToClipboard(textToCopy) {
copyTextToClipboard(textToCopy)
.then(() => {
@@ -100,20 +96,8 @@ class CopyToClipboard extends React.Component {
});
}
- renderNotWrapped() {
- return (
-
- {this.getDecoratedCopyNode()}
-
- );
+ resetTooltipText() {
+ this.setState({ tooltipText: this.props.tooltipText });
}
renderLink() {
@@ -136,6 +120,22 @@ class CopyToClipboard extends React.Component {
);
}
+ renderNotWrapped() {
+ return (
+
+ {this.getDecoratedCopyNode()}
+
+ );
+ }
+
render() {
const { wrapped } = this.props;
if (!wrapped) {
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
index 861f3ad7c7b1..822ef2bbb834 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx
@@ -112,15 +112,6 @@ const StyledDashboardContent = styled.div`
`;
class DashboardBuilder extends React.Component {
- static shouldFocusTabs(event, container) {
- // don't focus the tabs when we click on a tab
- return (
- event.target.className === 'ant-tabs-nav-wrap' ||
- (/icon-button/.test(event.target.className) &&
- container.contains(event.target))
- );
- }
-
static getRootLevelTabIndex(dashboardLayout, directPathToChild) {
return Math.max(
0,
@@ -141,6 +132,15 @@ class DashboardBuilder extends React.Component {
: dashboardLayout[rootChildId];
}
+ static shouldFocusTabs(event, container) {
+ // don't focus the tabs when we click on a tab
+ return (
+ event.target.className === 'ant-tabs-nav-wrap' ||
+ (/icon-button/.test(event.target.className) &&
+ container.contains(event.target))
+ );
+ }
+
constructor(props) {
super(props);
@@ -185,20 +185,6 @@ class DashboardBuilder extends React.Component {
}
}
- toggleDashboardFiltersOpen(visible) {
- if (visible === undefined) {
- this.setState(state => ({
- ...state,
- dashboardFiltersOpen: !state.dashboardFiltersOpen,
- }));
- } else {
- this.setState(state => ({
- ...state,
- dashboardFiltersOpen: visible,
- }));
- }
- }
-
handleChangeTab({ pathToTabIndex }) {
this.props.setDirectPathToChild(pathToTabIndex);
}
@@ -214,6 +200,20 @@ class DashboardBuilder extends React.Component {
this.props.setDirectPathToChild(firstTab);
}
+ toggleDashboardFiltersOpen(visible) {
+ if (visible === undefined) {
+ this.setState(state => ({
+ ...state,
+ dashboardFiltersOpen: !state.dashboardFiltersOpen,
+ }));
+ } else {
+ this.setState(state => ({
+ ...state,
+ dashboardFiltersOpen: visible,
+ }));
+ }
+ }
+
render() {
const {
handleComponentDrop,
diff --git a/superset-frontend/src/dashboard/components/DashboardGrid.jsx b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
index 6889c91ab3de..6214557bd82f 100644
--- a/superset-frontend/src/dashboard/components/DashboardGrid.jsx
+++ b/superset-frontend/src/dashboard/components/DashboardGrid.jsx
@@ -70,6 +70,16 @@ class DashboardGrid extends React.PureComponent {
this.grid = ref;
}
+ handleChangeTab({ pathToTabIndex }) {
+ this.props.setDirectPathToChild(pathToTabIndex);
+ }
+
+ handleResize({ ref, direction }) {
+ if (direction === 'bottom' || direction === 'bottomRight') {
+ this.setState(() => ({ rowGuideTop: this.getRowGuidePosition(ref) }));
+ }
+ }
+
handleResizeStart({ ref, direction }) {
let rowGuideTop = null;
if (direction === 'bottom' || direction === 'bottomRight') {
@@ -82,12 +92,6 @@ class DashboardGrid extends React.PureComponent {
}));
}
- handleResize({ ref, direction }) {
- if (direction === 'bottom' || direction === 'bottomRight') {
- this.setState(() => ({ rowGuideTop: this.getRowGuidePosition(ref) }));
- }
- }
-
handleResizeStop({ id, widthMultiple: width, heightMultiple: height }) {
this.props.resizeComponent({ id, width, height });
@@ -110,10 +114,6 @@ class DashboardGrid extends React.PureComponent {
}
}
- handleChangeTab({ pathToTabIndex }) {
- this.props.setDirectPathToChild(pathToTabIndex);
- }
-
render() {
const {
gridComponent,
diff --git a/superset-frontend/src/dashboard/components/Header.jsx b/superset-frontend/src/dashboard/components/Header.jsx
index 9ef547197f32..68ebda1ef9ef 100644
--- a/superset-frontend/src/dashboard/components/Header.jsx
+++ b/superset-frontend/src/dashboard/components/Header.jsx
@@ -174,6 +174,25 @@ class Header extends React.PureComponent {
clearTimeout(this.ctrlZTimeout);
}
+ forceRefresh() {
+ if (!this.props.isLoading) {
+ const chartList = Object.keys(this.props.charts);
+ this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
+ force: true,
+ interval: 0,
+ chartCount: chartList.length,
+ });
+
+ return this.props.fetchCharts(
+ chartList,
+ true,
+ 0,
+ this.props.dashboardInfo.id,
+ );
+ }
+ return false;
+ }
+
handleChangeText(nextText) {
const { updateDashboardTitle, onChange } = this.props;
if (nextText && this.props.dashboardTitle !== nextText) {
@@ -202,77 +221,8 @@ class Header extends React.PureComponent {
});
}
- forceRefresh() {
- if (!this.props.isLoading) {
- const chartList = Object.keys(this.props.charts);
- this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
- force: true,
- interval: 0,
- chartCount: chartList.length,
- });
-
- return this.props.fetchCharts(
- chartList,
- true,
- 0,
- this.props.dashboardInfo.id,
- );
- }
- return false;
- }
-
- startPeriodicRender(interval) {
- let intervalMessage;
- if (interval) {
- const predefinedValue = PeriodicRefreshOptions.find(
- option => option.value === interval / 1000,
- );
- if (predefinedValue) {
- intervalMessage = predefinedValue.label;
- } else {
- intervalMessage = moment.duration(interval, 'millisecond').humanize();
- }
- }
-
- const periodicRender = () => {
- const { fetchCharts, logEvent, charts, dashboardInfo } = this.props;
- const { metadata } = dashboardInfo;
- const immune = metadata.timed_refresh_immune_slices || [];
- const affectedCharts = Object.values(charts)
- .filter(chart => immune.indexOf(chart.id) === -1)
- .map(chart => chart.id);
-
- logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, {
- interval,
- chartCount: affectedCharts.length,
- });
- this.props.addWarningToast(
- t(
- `This dashboard is currently force refreshing; the next force refresh will be in %s.`,
- intervalMessage,
- ),
- );
-
- return fetchCharts(
- affectedCharts,
- true,
- interval * 0.2,
- dashboardInfo.id,
- );
- };
-
- this.refreshTimer = setPeriodicRunner({
- interval,
- periodicRender,
- refreshTimer: this.refreshTimer,
- });
- }
-
- toggleEditMode() {
- this.props.logEvent(LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, {
- edit_mode: !this.props.editMode,
- });
- this.props.setEditMode(!this.props.editMode);
+ hidePropertiesModal() {
+ this.setState({ showingPropertiesModal: false });
}
overwriteDashboard() {
@@ -344,8 +294,58 @@ class Header extends React.PureComponent {
this.setState({ showingPropertiesModal: true });
}
- hidePropertiesModal() {
- this.setState({ showingPropertiesModal: false });
+ startPeriodicRender(interval) {
+ let intervalMessage;
+ if (interval) {
+ const predefinedValue = PeriodicRefreshOptions.find(
+ option => option.value === interval / 1000,
+ );
+ if (predefinedValue) {
+ intervalMessage = predefinedValue.label;
+ } else {
+ intervalMessage = moment.duration(interval, 'millisecond').humanize();
+ }
+ }
+
+ const periodicRender = () => {
+ const { fetchCharts, logEvent, charts, dashboardInfo } = this.props;
+ const { metadata } = dashboardInfo;
+ const immune = metadata.timed_refresh_immune_slices || [];
+ const affectedCharts = Object.values(charts)
+ .filter(chart => immune.indexOf(chart.id) === -1)
+ .map(chart => chart.id);
+
+ logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, {
+ interval,
+ chartCount: affectedCharts.length,
+ });
+ this.props.addWarningToast(
+ t(
+ `This dashboard is currently force refreshing; the next force refresh will be in %s.`,
+ intervalMessage,
+ ),
+ );
+
+ return fetchCharts(
+ affectedCharts,
+ true,
+ interval * 0.2,
+ dashboardInfo.id,
+ );
+ };
+
+ this.refreshTimer = setPeriodicRunner({
+ interval,
+ periodicRender,
+ refreshTimer: this.refreshTimer,
+ });
+ }
+
+ toggleEditMode() {
+ this.props.logEvent(LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, {
+ edit_mode: !this.props.editMode,
+ });
+ this.props.setEditMode(!this.props.editMode);
}
render() {
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal.jsx b/superset-frontend/src/dashboard/components/PropertiesModal.jsx
index 1cc75b12ed6b..ff6e6879c14b 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal.jsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal.jsx
@@ -130,6 +130,11 @@ class PropertiesModal extends React.PureComponent {
JsonEditor.preload();
}
+ onChange(e) {
+ const { name, value } = e.target;
+ this.updateFormState(name, value);
+ }
+
onColorSchemeChange(value, { updateMetadata = true } = {}) {
// check that color_scheme is valid
const colorChoices = getCategoricalSchemeRegistry().keys();
@@ -159,17 +164,12 @@ class PropertiesModal extends React.PureComponent {
this.updateFormState('colorScheme', value);
}
- onOwnersChange(value) {
- this.updateFormState('owners', value);
- }
-
onMetadataChange(metadata) {
this.updateFormState('json_metadata', metadata);
}
- onChange(e) {
- const { name, value } = e.target;
- this.updateFormState(name, value);
+ onOwnersChange(value) {
+ this.updateFormState('owners', value);
}
fetchDashboardDetails() {
@@ -206,21 +206,6 @@ class PropertiesModal extends React.PureComponent {
}, handleErrorResponse);
}
- updateFormState(name, value) {
- this.setState(state => ({
- values: {
- ...state.values,
- [name]: value,
- },
- }));
- }
-
- toggleAdvanced() {
- this.setState(state => ({
- isAdvancedOpen: !state.isAdvancedOpen,
- }));
- }
-
submit(e) {
e.preventDefault();
e.stopPropagation();
@@ -282,6 +267,21 @@ class PropertiesModal extends React.PureComponent {
}
}
+ toggleAdvanced() {
+ this.setState(state => ({
+ isAdvancedOpen: !state.isAdvancedOpen,
+ }));
+ }
+
+ updateFormState(name, value) {
+ this.setState(state => ({
+ values: {
+ ...state.values,
+ [name]: value,
+ },
+ }));
+ }
+
render() {
const { values, isDashboardLoaded, isAdvancedOpen, errors } = this.state;
const { onHide, onlyApply } = this.props;
diff --git a/superset-frontend/src/dashboard/components/SliceAdder.jsx b/superset-frontend/src/dashboard/components/SliceAdder.jsx
index 141ccac14a72..0ea089850a1b 100644
--- a/superset-frontend/src/dashboard/components/SliceAdder.jsx
+++ b/superset-frontend/src/dashboard/components/SliceAdder.jsx
@@ -136,16 +136,6 @@ class SliceAdder extends React.Component {
}
}
- searchUpdated(searchTerm) {
- this.setState(prevState => ({
- searchTerm,
- filteredSlices: this.getFilteredSortedSlices(
- searchTerm,
- prevState.sortBy,
- ),
- }));
- }
-
handleSelect(sortBy) {
this.setState(prevState => ({
sortBy,
@@ -202,6 +192,16 @@ class SliceAdder extends React.Component {
);
}
+ searchUpdated(searchTerm) {
+ this.setState(prevState => ({
+ searchTerm,
+ filteredSlices: this.getFilteredSortedSlices(
+ searchTerm,
+ prevState.sortBy,
+ ),
+ }));
+ }
+
render() {
const slicesListHeight =
this.props.height -
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx b/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
index f985fe83c8cb..d75e58405ace 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls.jsx
@@ -114,21 +114,6 @@ class SliceHeaderControls extends React.PureComponent {
};
}
- refreshChart() {
- if (this.props.updatedDttm) {
- this.props.forceRefresh(
- this.props.slice.slice_id,
- this.props.dashboardId,
- );
- }
- }
-
- toggleControls() {
- this.setState(prevState => ({
- showControls: !prevState.showControls,
- }));
- }
-
handleMenuClick({ key, domEvent }) {
switch (key) {
case MENU_KEYS.FORCE_REFRESH:
@@ -166,6 +151,21 @@ class SliceHeaderControls extends React.PureComponent {
}
}
+ refreshChart() {
+ if (this.props.updatedDttm) {
+ this.props.forceRefresh(
+ this.props.slice.slice_id,
+ this.props.dashboardId,
+ );
+ }
+ }
+
+ toggleControls() {
+ this.setState(prevState => ({
+ showControls: !prevState.showControls,
+ }));
+ }
+
render() {
const {
slice,
diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx
index 6cfcc0eee0a8..4f19cff80921 100644
--- a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx
+++ b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx
@@ -178,6 +178,74 @@ export default class FilterScopeSelector extends React.PureComponent {
this.onSave = this.onSave.bind(this);
}
+ onChangeFilterField(filterField = {}) {
+ const { layout } = this.props;
+ const nextActiveFilterField = filterField.value;
+ const {
+ activeFilterField: currentActiveFilterField,
+ checkedFilterFields,
+ filterScopeMap,
+ } = this.state;
+
+ // we allow single edit and multiple edit in the same view.
+ // if user click on the single filter field,
+ // will show filter scope for the single field.
+ // if user click on the same filter filed again,
+ // will toggle off the single filter field,
+ // and allow multi-edit all checked filter fields.
+ if (nextActiveFilterField === currentActiveFilterField) {
+ const filterScopeTreeEntry = buildFilterScopeTreeEntry({
+ checkedFilterFields,
+ activeFilterField: null,
+ filterScopeMap,
+ layout,
+ });
+
+ this.setState({
+ activeFilterField: null,
+ filterScopeMap: {
+ ...filterScopeMap,
+ ...filterScopeTreeEntry,
+ },
+ });
+ } else if (this.allfilterFields.includes(nextActiveFilterField)) {
+ const filterScopeTreeEntry = buildFilterScopeTreeEntry({
+ checkedFilterFields,
+ activeFilterField: nextActiveFilterField,
+ filterScopeMap,
+ layout,
+ });
+
+ this.setState({
+ activeFilterField: nextActiveFilterField,
+ filterScopeMap: {
+ ...filterScopeMap,
+ ...filterScopeTreeEntry,
+ },
+ });
+ }
+ }
+
+ onCheckFilterField(checkedFilterFields = []) {
+ const { layout } = this.props;
+ const { filterScopeMap } = this.state;
+ const filterScopeTreeEntry = buildFilterScopeTreeEntry({
+ checkedFilterFields,
+ activeFilterField: null,
+ filterScopeMap,
+ layout,
+ });
+
+ this.setState(() => ({
+ activeFilterField: null,
+ checkedFilterFields,
+ filterScopeMap: {
+ ...filterScopeMap,
+ ...filterScopeTreeEntry,
+ },
+ }));
+ }
+
onCheckFilterScope(checked = []) {
const {
activeFilterField,
@@ -212,6 +280,16 @@ export default class FilterScopeSelector extends React.PureComponent {
}));
}
+ onClose() {
+ this.props.onCloseModal();
+ }
+
+ onExpandFilterField(expandedFilterIds = []) {
+ this.setState(() => ({
+ expandedFilterIds,
+ }));
+ }
+
onExpandFilterScope(expanded = []) {
const {
activeFilterField,
@@ -234,88 +312,6 @@ export default class FilterScopeSelector extends React.PureComponent {
}));
}
- onCheckFilterField(checkedFilterFields = []) {
- const { layout } = this.props;
- const { filterScopeMap } = this.state;
- const filterScopeTreeEntry = buildFilterScopeTreeEntry({
- checkedFilterFields,
- activeFilterField: null,
- filterScopeMap,
- layout,
- });
-
- this.setState(() => ({
- activeFilterField: null,
- checkedFilterFields,
- filterScopeMap: {
- ...filterScopeMap,
- ...filterScopeTreeEntry,
- },
- }));
- }
-
- onExpandFilterField(expandedFilterIds = []) {
- this.setState(() => ({
- expandedFilterIds,
- }));
- }
-
- onChangeFilterField(filterField = {}) {
- const { layout } = this.props;
- const nextActiveFilterField = filterField.value;
- const {
- activeFilterField: currentActiveFilterField,
- checkedFilterFields,
- filterScopeMap,
- } = this.state;
-
- // we allow single edit and multiple edit in the same view.
- // if user click on the single filter field,
- // will show filter scope for the single field.
- // if user click on the same filter filed again,
- // will toggle off the single filter field,
- // and allow multi-edit all checked filter fields.
- if (nextActiveFilterField === currentActiveFilterField) {
- const filterScopeTreeEntry = buildFilterScopeTreeEntry({
- checkedFilterFields,
- activeFilterField: null,
- filterScopeMap,
- layout,
- });
-
- this.setState({
- activeFilterField: null,
- filterScopeMap: {
- ...filterScopeMap,
- ...filterScopeTreeEntry,
- },
- });
- } else if (this.allfilterFields.includes(nextActiveFilterField)) {
- const filterScopeTreeEntry = buildFilterScopeTreeEntry({
- checkedFilterFields,
- activeFilterField: nextActiveFilterField,
- filterScopeMap,
- layout,
- });
-
- this.setState({
- activeFilterField: nextActiveFilterField,
- filterScopeMap: {
- ...filterScopeMap,
- ...filterScopeTreeEntry,
- },
- });
- }
- }
-
- onSearchInputChange(e) {
- this.setState({ searchText: e.target.value }, this.filterTree);
- }
-
- onClose() {
- this.props.onCloseModal();
- }
-
onSave() {
const { filterScopeMap } = this.state;
@@ -343,6 +339,27 @@ export default class FilterScopeSelector extends React.PureComponent {
this.props.onCloseModal();
}
+ onSearchInputChange(e) {
+ this.setState({ searchText: e.target.value }, this.filterTree);
+ }
+
+ filterNodes(filtered = [], node = {}) {
+ const { searchText } = this.state;
+ const children = (node.children || []).reduce(this.filterNodes, []);
+
+ if (
+ // Node's label matches the search string
+ node.label.toLocaleLowerCase().indexOf(searchText.toLocaleLowerCase()) >
+ -1 ||
+ // Or a children has a matching node
+ children.length
+ ) {
+ filtered.push({ ...node, children });
+ }
+
+ return filtered;
+ }
+
filterTree() {
// Reset nodes back to unfiltered state
if (!this.state.searchText) {
@@ -403,21 +420,27 @@ export default class FilterScopeSelector extends React.PureComponent {
}
}
- filterNodes(filtered = [], node = {}) {
- const { searchText } = this.state;
- const children = (node.children || []).reduce(this.filterNodes, []);
-
- if (
- // Node's label matches the search string
- node.label.toLocaleLowerCase().indexOf(searchText.toLocaleLowerCase()) >
- -1 ||
- // Or a children has a matching node
- children.length
- ) {
- filtered.push({ ...node, children });
- }
+ renderEditingFiltersName() {
+ const { dashboardFilters } = this.props;
+ const { activeFilterField, checkedFilterFields } = this.state;
+ const currentFilterLabels = []
+ .concat(activeFilterField || checkedFilterFields)
+ .map(key => {
+ const { chartId, column } = getChartIdAndColumnFromFilterKey(key);
+ return dashboardFilters[chartId].labels[column] || column;
+ });
- return filtered;
+ return (
+
+ {currentFilterLabels.length === 0 && t('No filter is selected.')}
+ {currentFilterLabels.length === 1 && t('Editing 1 filter:')}
+ {currentFilterLabels.length > 1 &&
+ t('Batch editing %d filters:', currentFilterLabels.length)}
+
+ {currentFilterLabels.join(', ')}
+
+
+ );
}
renderFilterFieldList() {
@@ -480,29 +503,6 @@ export default class FilterScopeSelector extends React.PureComponent {
);
}
- renderEditingFiltersName() {
- const { dashboardFilters } = this.props;
- const { activeFilterField, checkedFilterFields } = this.state;
- const currentFilterLabels = []
- .concat(activeFilterField || checkedFilterFields)
- .map(key => {
- const { chartId, column } = getChartIdAndColumnFromFilterKey(key);
- return dashboardFilters[chartId].labels[column] || column;
- });
-
- return (
-
- {currentFilterLabels.length === 0 && t('No filter is selected.')}
- {currentFilterLabels.length === 1 && t('Editing 1 filter:')}
- {currentFilterLabels.length > 1 &&
- t('Batch editing %d filters:', currentFilterLabels.length)}
-
- {currentFilterLabels.join(', ')}
-
-
- );
- }
-
render() {
const { showSelector } = this.state;
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
index 7dd01e97873e..dcae97bf5cfb 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
@@ -184,11 +184,6 @@ export default class Chart extends React.Component {
this.headerRef = ref;
}
- resize() {
- const { width, height } = this.props;
- this.setState(() => ({ width, height }));
- }
-
changeFilter(newSelectedValues = {}) {
this.props.logEvent(LOG_ACTIONS_CHANGE_DASHBOARD_FILTER, {
id: this.props.chart.id,
@@ -197,14 +192,6 @@ export default class Chart extends React.Component {
this.props.changeFilter(this.props.chart.id, newSelectedValues);
}
- handleFilterMenuOpen(chartId, column) {
- this.props.setFocusedFilterField(chartId, column);
- }
-
- handleFilterMenuClose(chartId, column) {
- this.props.unsetFocusedFilterField(chartId, column);
- }
-
exploreChart() {
this.props.logEvent(LOG_ACTIONS_EXPLORE_DASHBOARD_CHART, {
slice_id: this.props.slice.slice_id,
@@ -237,6 +224,19 @@ export default class Chart extends React.Component {
);
}
+ handleFilterMenuClose(chartId, column) {
+ this.props.unsetFocusedFilterField(chartId, column);
+ }
+
+ handleFilterMenuOpen(chartId, column) {
+ this.props.setFocusedFilterField(chartId, column);
+ }
+
+ resize() {
+ const { width, height } = this.props;
+ this.setState(() => ({ width, height }));
+ }
+
render() {
const {
id,
diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
index 61a0cf2e45e0..453570a9cce2 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx
@@ -120,17 +120,6 @@ const FilterFocusHighlight = React.forwardRef(
);
class ChartHolder extends React.Component {
- static renderInFocusCSS(columnName) {
- return (
-
- );
- }
-
static getDerivedStateFromProps(props, state) {
const { component, directPathToChild, directPathLastUpdated } = props;
const {
@@ -151,6 +140,17 @@ class ChartHolder extends React.Component {
return null;
}
+ static renderInFocusCSS(columnName) {
+ return (
+
+ );
+ }
+
constructor(props) {
super(props);
this.state = {
@@ -175,21 +175,6 @@ class ChartHolder extends React.Component {
this.hideOutline(prevState, this.state);
}
- hideOutline(prevState, state) {
- const { outlinedComponentId: timerKey } = state;
- const { outlinedComponentId: prevTimerKey } = prevState;
-
- // because of timeout, there might be multiple charts showing outline
- if (!!timerKey && !prevTimerKey) {
- setTimeout(() => {
- this.setState(() => ({
- outlinedComponentId: null,
- outlinedColumnName: null,
- }));
- }, 2000);
- }
- }
-
handleChangeFocus(nextFocus) {
this.setState(() => ({ isFocused: nextFocus }));
}
@@ -199,6 +184,10 @@ class ChartHolder extends React.Component {
deleteComponent(id, parentId);
}
+ handleToggleFullSize() {
+ this.setState(prevState => ({ isFullSize: !prevState.isFullSize }));
+ }
+
handleUpdateSliceName(nextName) {
const { component, updateComponents } = this.props;
updateComponents({
@@ -212,8 +201,19 @@ class ChartHolder extends React.Component {
});
}
- handleToggleFullSize() {
- this.setState(prevState => ({ isFullSize: !prevState.isFullSize }));
+ hideOutline(prevState, state) {
+ const { outlinedComponentId: timerKey } = state;
+ const { outlinedComponentId: prevTimerKey } = prevState;
+
+ // because of timeout, there might be multiple charts showing outline
+ if (!!timerKey && !prevTimerKey) {
+ setTimeout(() => {
+ this.setState(() => ({
+ outlinedComponentId: null,
+ outlinedColumnName: null,
+ }));
+ }, 2000);
+ }
}
render() {
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx
index 78d272b551c0..0c07bb3d1239 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Column.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Column.jsx
@@ -74,15 +74,15 @@ class Column extends React.PureComponent {
this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
}
+ handleChangeFocus(nextFocus) {
+ this.setState(() => ({ isFocused: Boolean(nextFocus) }));
+ }
+
handleDeleteComponent() {
const { deleteComponent, id, parentId } = this.props;
deleteComponent(id, parentId);
}
- handleChangeFocus(nextFocus) {
- this.setState(() => ({ isFocused: Boolean(nextFocus) }));
- }
-
handleUpdateMeta(metaKey, nextValue) {
const { updateComponents, component } = this.props;
if (nextValue && component.meta[metaKey] !== nextValue) {
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx
index 3e380773f185..8e5df059e6e6 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Header.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Header.jsx
@@ -73,6 +73,11 @@ class Header extends React.PureComponent {
this.setState(() => ({ isFocused: nextFocus }));
}
+ handleDeleteComponent() {
+ const { deleteComponent, id, parentId } = this.props;
+ deleteComponent(id, parentId);
+ }
+
handleUpdateMeta(metaKey, nextValue) {
const { updateComponents, component } = this.props;
if (nextValue && component.meta[metaKey] !== nextValue) {
@@ -88,11 +93,6 @@ class Header extends React.PureComponent {
}
}
- handleDeleteComponent() {
- const { deleteComponent, id, parentId } = this.props;
- deleteComponent(id, parentId);
- }
-
render() {
const { isFocused } = this.state;
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
index b071a9982dfe..d48dd846e404 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
@@ -89,33 +89,10 @@ function isSafeMarkup(node) {
}
class Markdown extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- isFocused: false,
- markdownSource: props.component.meta.code,
- editor: null,
- editorMode: 'preview',
- undoLength: props.undoLength,
- redoLength: props.redoLength,
+ static getDerivedStateFromError() {
+ return {
+ hasError: true,
};
- this.renderStartTime = Logger.getTimestamp();
-
- this.handleChangeFocus = this.handleChangeFocus.bind(this);
- this.handleChangeEditorMode = this.handleChangeEditorMode.bind(this);
- this.handleMarkdownChange = this.handleMarkdownChange.bind(this);
- this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
- this.handleResizeStart = this.handleResizeStart.bind(this);
- this.setEditor = this.setEditor.bind(this);
- }
-
- componentDidMount() {
- this.props.logEvent(LOG_ACTIONS_RENDER_CHART, {
- viz_type: 'markdown',
- start_offset: this.renderStartTime,
- ts: new Date().getTime(),
- duration: Logger.getTimestamp() - this.renderStartTime,
- });
}
static getDerivedStateFromProps(nextProps, state) {
@@ -155,10 +132,33 @@ class Markdown extends React.PureComponent {
return state;
}
- static getDerivedStateFromError() {
- return {
- hasError: true,
+ constructor(props) {
+ super(props);
+ this.state = {
+ isFocused: false,
+ markdownSource: props.component.meta.code,
+ editor: null,
+ editorMode: 'preview',
+ undoLength: props.undoLength,
+ redoLength: props.redoLength,
};
+ this.renderStartTime = Logger.getTimestamp();
+
+ this.handleChangeFocus = this.handleChangeFocus.bind(this);
+ this.handleChangeEditorMode = this.handleChangeEditorMode.bind(this);
+ this.handleMarkdownChange = this.handleMarkdownChange.bind(this);
+ this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
+ this.handleResizeStart = this.handleResizeStart.bind(this);
+ this.setEditor = this.setEditor.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.logEvent(LOG_ACTIONS_RENDER_CHART, {
+ viz_type: 'markdown',
+ start_offset: this.renderStartTime,
+ ts: new Date().getTime(),
+ duration: Logger.getTimestamp() - this.renderStartTime,
+ });
}
componentDidUpdate(prevProps) {
@@ -192,13 +192,6 @@ class Markdown extends React.PureComponent {
});
}
- handleChangeFocus(nextFocus) {
- const nextFocused = !!nextFocus;
- const nextEditMode = nextFocused ? 'edit' : 'preview';
- this.setState(() => ({ isFocused: nextFocused }));
- this.handleChangeEditorMode(nextEditMode);
- }
-
handleChangeEditorMode(mode) {
const nextState = {
...this.state,
@@ -212,19 +205,16 @@ class Markdown extends React.PureComponent {
this.setState(nextState);
}
- updateMarkdownContent() {
- const { updateComponents, component } = this.props;
- if (component.meta.code !== this.state.markdownSource) {
- updateComponents({
- [component.id]: {
- ...component,
- meta: {
- ...component.meta,
- code: this.state.markdownSource,
- },
- },
- });
- }
+ handleChangeFocus(nextFocus) {
+ const nextFocused = !!nextFocus;
+ const nextEditMode = nextFocused ? 'edit' : 'preview';
+ this.setState(() => ({ isFocused: nextFocused }));
+ this.handleChangeEditorMode(nextEditMode);
+ }
+
+ handleDeleteComponent() {
+ const { deleteComponent, id, parentId } = this.props;
+ deleteComponent(id, parentId);
}
handleMarkdownChange(nextValue) {
@@ -233,11 +223,6 @@ class Markdown extends React.PureComponent {
});
}
- handleDeleteComponent() {
- const { deleteComponent, id, parentId } = this.props;
- deleteComponent(id, parentId);
- }
-
handleResizeStart(e) {
const { editorMode } = this.state;
const { editMode, onResizeStart } = this.props;
@@ -248,6 +233,21 @@ class Markdown extends React.PureComponent {
}
}
+ updateMarkdownContent() {
+ const { updateComponents, component } = this.props;
+ if (component.meta.code !== this.state.markdownSource) {
+ updateComponents({
+ [component.id]: {
+ ...component,
+ meta: {
+ ...component.meta,
+ code: this.state.markdownSource,
+ },
+ },
+ });
+ }
+ }
+
renderEditMode() {
return (
({ isFocused: Boolean(nextFocus) }));
}
+ handleDeleteComponent() {
+ const { deleteComponent, component, parentId } = this.props;
+ deleteComponent(component.id, parentId);
+ }
+
handleUpdateMeta(metaKey, nextValue) {
const { updateComponents, component } = this.props;
if (nextValue && component.meta[metaKey] !== nextValue) {
@@ -90,11 +95,6 @@ class Row extends React.PureComponent {
}
}
- handleDeleteComponent() {
- const { deleteComponent, component, parentId } = this.props;
- deleteComponent(component.id, parentId);
- }
-
render() {
const {
component: rowComponent,
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
index cda808c024bb..7c6d0c6b8cff 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.jsx
@@ -108,6 +108,51 @@ export default class Tab extends React.PureComponent {
}
}
+ renderTab() {
+ const {
+ component,
+ parentComponent,
+ index,
+ depth,
+ editMode,
+ filters,
+ isFocused,
+ } = this.props;
+
+ return (
+
+ {({ dropIndicatorProps, dragSourceRef }) => (
+
+
+ {!editMode && (
+
= 5 ? 'left' : 'right'}
+ />
+ )}
+
+ {dropIndicatorProps &&
}
+
+ )}
+
+ );
+ }
+
renderTabContent() {
const {
component: tabComponent,
@@ -183,51 +228,6 @@ export default class Tab extends React.PureComponent {
);
}
- renderTab() {
- const {
- component,
- parentComponent,
- index,
- depth,
- editMode,
- filters,
- isFocused,
- } = this.props;
-
- return (
-
- {({ dropIndicatorProps, dragSourceRef }) => (
-
-
- {!editMode && (
-
= 5 ? 'left' : 'right'}
- />
- )}
-
- {dropIndicatorProps &&
}
-
- )}
-
- );
- }
-
render() {
const { renderType } = this.props;
return renderType === RENDER_TAB
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index 455a41a94c38..e84e50ec3661 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -163,48 +163,6 @@ class Tabs extends React.PureComponent {
}
}
- showDeleteConfirmModal = key => {
- const { component, deleteComponent } = this.props;
- Modal.confirm({
- title: t('Delete dashboard tab?'),
- content: (
-
- Deleting a tab will remove all content within it. You may still
- reverse this action with the undo button (cmd + z) until you
- save your changes.
-
- ),
- onOk: () => {
- deleteComponent(key, component.id);
- const tabIndex = component.children.indexOf(key);
- this.handleClickTab(Math.max(0, tabIndex - 1));
- },
- okType: 'danger',
- okText: 'DELETE',
- cancelText: 'CANCEL',
- icon: null,
- });
- };
-
- handleEdit = (key, action) => {
- const { component, createComponent } = this.props;
- if (action === 'add') {
- createComponent({
- destination: {
- id: component.id,
- type: component.type,
- index: component.children.length,
- },
- dragging: {
- id: NEW_TAB_ID,
- type: TAB_TYPE,
- },
- });
- } else if (action === 'remove') {
- this.showDeleteConfirmModal(key);
- }
- };
-
handleClickTab(tabIndex) {
const { component } = this.props;
@@ -248,6 +206,48 @@ class Tabs extends React.PureComponent {
}
}
+ handleEdit = (key, action) => {
+ const { component, createComponent } = this.props;
+ if (action === 'add') {
+ createComponent({
+ destination: {
+ id: component.id,
+ type: component.type,
+ index: component.children.length,
+ },
+ dragging: {
+ id: NEW_TAB_ID,
+ type: TAB_TYPE,
+ },
+ });
+ } else if (action === 'remove') {
+ this.showDeleteConfirmModal(key);
+ }
+ };
+
+ showDeleteConfirmModal = key => {
+ const { component, deleteComponent } = this.props;
+ Modal.confirm({
+ title: t('Delete dashboard tab?'),
+ content: (
+
+ Deleting a tab will remove all content within it. You may still
+ reverse this action with the undo button (cmd + z) until you
+ save your changes.
+
+ ),
+ onOk: () => {
+ deleteComponent(key, component.id);
+ const tabIndex = component.children.indexOf(key);
+ this.handleClickTab(Math.max(0, tabIndex - 1));
+ },
+ okType: 'danger',
+ okText: 'DELETE',
+ cancelText: 'CANCEL',
+ icon: null,
+ });
+ };
+
render() {
const {
depth,
diff --git a/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx b/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx
index ff576101f491..a179f164401b 100644
--- a/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx
+++ b/superset-frontend/src/dashboard/components/resizable/ResizableContainer.jsx
@@ -93,6 +93,13 @@ class ResizableContainer extends React.PureComponent {
this.handleResizeStop = this.handleResizeStop.bind(this);
}
+ handleResize(event, direction, ref) {
+ const { onResize, id } = this.props;
+ if (onResize) {
+ onResize({ id, direction, ref });
+ }
+ }
+
handleResizeStart(event, direction, ref) {
const { id, onResizeStart } = this.props;
@@ -103,13 +110,6 @@ class ResizableContainer extends React.PureComponent {
this.setState(() => ({ isResizing: true }));
}
- handleResize(event, direction, ref) {
- const { onResize, id } = this.props;
- if (onResize) {
- onResize({ id, direction, ref });
- }
- }
-
handleResizeStop(event, direction, ref, delta) {
const {
id,
diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx
index 347947aca30c..3b9d58074c58 100644
--- a/superset-frontend/src/datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/datasource/DatasourceEditor.jsx
@@ -358,8 +358,60 @@ class DatasourceEditor extends React.PureComponent {
this.setState(obj, this.validateAndChange);
}
- validateAndChange() {
- this.validate(this.onChange);
+ findDuplicates(arr, accessor) {
+ const seen = {};
+ const dups = [];
+ arr.forEach(obj => {
+ const item = accessor(obj);
+ if (item in seen) {
+ dups.push(item);
+ } else {
+ seen[item] = null;
+ }
+ });
+ return dups;
+ }
+
+ handleTabSelect(activeTabKey) {
+ this.setState({ activeTabKey });
+ }
+
+ syncMetadata() {
+ const { datasource } = this.state;
+ const endpoint = `/datasource/external_metadata/${
+ datasource.type || datasource.datasource_type
+ }/${datasource.id}/`;
+ this.setState({ metadataLoading: true });
+
+ SupersetClient.get({ endpoint })
+ .then(({ json }) => {
+ const results = this.updateColumns(json);
+ if (results.modified.length) {
+ this.props.addSuccessToast(
+ t('Modified columns: %s', results.modified.join(', ')),
+ );
+ }
+ if (results.removed.length) {
+ this.props.addSuccessToast(
+ t('Removed columns: %s', results.removed.join(', ')),
+ );
+ }
+ if (results.added.length) {
+ this.props.addSuccessToast(
+ t('New columns added: %s', results.added.join(', ')),
+ );
+ }
+ this.props.addSuccessToast(t('Metadata has been synced'));
+ this.setState({ metadataLoading: false });
+ })
+ .catch(response =>
+ getClientErrorObject(response).then(({ error, statusText }) => {
+ this.props.addDangerToast(
+ error || statusText || t('An error has occurred'),
+ );
+ this.setState({ metadataLoading: false });
+ }),
+ );
}
updateColumns(cols) {
@@ -414,58 +466,6 @@ class DatasourceEditor extends React.PureComponent {
return results;
}
- syncMetadata() {
- const { datasource } = this.state;
- const endpoint = `/datasource/external_metadata/${
- datasource.type || datasource.datasource_type
- }/${datasource.id}/`;
- this.setState({ metadataLoading: true });
-
- SupersetClient.get({ endpoint })
- .then(({ json }) => {
- const results = this.updateColumns(json);
- if (results.modified.length) {
- this.props.addSuccessToast(
- t('Modified columns: %s', results.modified.join(', ')),
- );
- }
- if (results.removed.length) {
- this.props.addSuccessToast(
- t('Removed columns: %s', results.removed.join(', ')),
- );
- }
- if (results.added.length) {
- this.props.addSuccessToast(
- t('New columns added: %s', results.added.join(', ')),
- );
- }
- this.props.addSuccessToast(t('Metadata has been synced'));
- this.setState({ metadataLoading: false });
- })
- .catch(response =>
- getClientErrorObject(response).then(({ error, statusText }) => {
- this.props.addDangerToast(
- error || statusText || t('An error has occurred'),
- );
- this.setState({ metadataLoading: false });
- }),
- );
- }
-
- findDuplicates(arr, accessor) {
- const seen = {};
- const dups = [];
- arr.forEach(obj => {
- const item = accessor(obj);
- if (item in seen) {
- dups.push(item);
- } else {
- seen[item] = null;
- }
- });
- return dups;
- }
-
validate(callback) {
let errors = [];
let dups;
@@ -496,8 +496,180 @@ class DatasourceEditor extends React.PureComponent {
this.setState({ errors }, callback);
}
- handleTabSelect(activeTabKey) {
- this.setState({ activeTabKey });
+ validateAndChange() {
+ this.validate(this.onChange);
+ }
+
+ renderAdvancedFieldset() {
+ const { datasource } = this.state;
+ return (
+
+ }
+ />
+ }
+ />
+ {this.state.isSqla && (
+ }
+ />
+ )}
+
+ );
+ }
+
+ renderErrors() {
+ if (this.state.errors.length > 0) {
+ return (
+
+ {this.state.errors.map(err => (
+ {err}
+ ))}
+
+ );
+ }
+ return null;
+ }
+
+ renderMetricCollection() {
+ return (
+
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+
+ }
+ collection={this.state.datasource.metrics}
+ allowAddItem
+ onChange={this.onDatasourcePropChange.bind(this, 'metrics')}
+ itemGenerator={() => ({
+ metric_name: '',
+ verbose_name: '',
+ expression: '',
+ })}
+ itemRenderers={{
+ metric_name: (v, onChange, _, record) => (
+
+ {record.is_certified && (
+
+ )}
+
+
+ ),
+ verbose_name: (v, onChange) => (
+
+ ),
+ expression: (v, onChange) => (
+
+ ),
+ description: (v, onChange, label) => (
+ }
+ />
+ ),
+ d3format: (v, onChange, label) => (
+ }
+ />
+ ),
+ }}
+ allowDeletes
+ />
+ );
}
renderSettingsFieldset() {
@@ -584,72 +756,6 @@ class DatasourceEditor extends React.PureComponent {
);
}
- renderAdvancedFieldset() {
- const { datasource } = this.state;
- return (
-
- }
- />
- }
- />
- {this.state.isSqla && (
- }
- />
- )}
-
- );
- }
-
- renderSpatialTab() {
- const { datasource } = this.state;
- const { spatials, all_cols: allCols } = datasource;
- return (
- }
- key={4}
- >
- ({
- name: '',
- type: '',
- config: null,
- })}
- collection={spatials}
- allowDeletes
- itemRenderers={{
- name: (d, onChange) => (
-
- ),
- config: (v, onChange) => (
-
- ),
- }}
- />
-
- );
- }
-
renderSourceFieldset() {
const { datasource } = this.state;
return (
@@ -810,140 +916,34 @@ class DatasourceEditor extends React.PureComponent {
);
}
- renderErrors() {
- if (this.state.errors.length > 0) {
- return (
-
- {this.state.errors.map(err => (
- {err}
- ))}
-
- );
- }
- return null;
- }
-
- renderMetricCollection() {
+ renderSpatialTab() {
+ const { datasource } = this.state;
+ const { spatials, all_cols: allCols } = datasource;
return (
-
-
- }
- />
-
- }
- />
-
- }
- />
-
- }
- />
-
- }
- />
-
- }
- />
-
-
- }
- collection={this.state.datasource.metrics}
- allowAddItem
- onChange={this.onDatasourcePropChange.bind(this, 'metrics')}
- itemGenerator={() => ({
- metric_name: '',
- verbose_name: '',
- expression: '',
- })}
- itemRenderers={{
- metric_name: (v, onChange, _, record) => (
-
- {record.is_certified && (
-
- )}
-
-
- ),
- verbose_name: (v, onChange) => (
-
- ),
- expression: (v, onChange) => (
-
- ),
- description: (v, onChange, label) => (
- }
- />
- ),
- d3format: (v, onChange, label) => (
- }
- />
- ),
- }}
- allowDeletes
- />
+ }
+ key={4}
+ >
+ ({
+ name: '',
+ type: '',
+ config: null,
+ })}
+ collection={spatials}
+ allowDeletes
+ itemRenderers={{
+ name: (d, onChange) => (
+
+ ),
+ config: (v, onChange) => (
+
+ ),
+ }}
+ />
+
);
}
diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx
index 005d43774298..5d97e12cebfc 100644
--- a/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx
+++ b/superset-frontend/src/explore/components/AdhocFilterEditPopover.jsx
@@ -85,11 +85,6 @@ export default class AdhocFilterEditPopover extends React.Component {
this.setState({ adhocFilter });
}
- onSave() {
- this.props.onChange(this.state.adhocFilter);
- this.props.onClose();
- }
-
onDragDown(e) {
this.dragStartX = e.clientX;
this.dragStartY = e.clientY;
@@ -116,6 +111,11 @@ export default class AdhocFilterEditPopover extends React.Component {
document.removeEventListener('mousemove', this.onMouseMove);
}
+ onSave() {
+ this.props.onChange(this.state.adhocFilter);
+ this.props.onClose();
+ }
+
adjustHeight(heightDifference) {
this.setState(state => ({ height: state.height + heightDifference }));
}
diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
index 5856bfafe78e..420f64a966ec 100644
--- a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
+++ b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
@@ -117,35 +117,19 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
}
}
- onSubjectChange(id) {
- const option = this.props.options.find(
- option => option.id === id || option.optionName === id,
- );
-
- let subject;
- let clause;
- // infer the new clause based on what subject was selected.
- if (option && option.column_name) {
- subject = option.column_name;
- clause = CLAUSES.WHERE;
- } else if (option && (option.saved_metric_name || option.label)) {
- subject = option.saved_metric_name || option.label;
- clause = CLAUSES.HAVING;
- }
- const { operator } = this.props.adhocFilter;
+ onComparatorChange(comparator) {
this.props.onChange(
this.props.adhocFilter.duplicateWith({
- subject,
- clause,
- operator:
- operator && this.isOperatorRelevant(operator, subject)
- ? operator
- : null,
+ comparator,
expressionType: EXPRESSION_TYPES.SIMPLE,
}),
);
}
+ onInputComparatorChange(event) {
+ this.onComparatorChange(event.target.value);
+ }
+
onOperatorChange(operator) {
const currentComparator = this.props.adhocFilter.comparator;
let newComparator;
@@ -182,19 +166,73 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
}
}
- onInputComparatorChange(event) {
- this.onComparatorChange(event.target.value);
- }
+ onSubjectChange(id) {
+ const option = this.props.options.find(
+ option => option.id === id || option.optionName === id,
+ );
- onComparatorChange(comparator) {
+ let subject;
+ let clause;
+ // infer the new clause based on what subject was selected.
+ if (option && option.column_name) {
+ subject = option.column_name;
+ clause = CLAUSES.WHERE;
+ } else if (option && (option.saved_metric_name || option.label)) {
+ subject = option.saved_metric_name || option.label;
+ clause = CLAUSES.HAVING;
+ }
+ const { operator } = this.props.adhocFilter;
this.props.onChange(
this.props.adhocFilter.duplicateWith({
- comparator,
+ subject,
+ clause,
+ operator:
+ operator && this.isOperatorRelevant(operator, subject)
+ ? operator
+ : null,
expressionType: EXPRESSION_TYPES.SIMPLE,
}),
);
}
+ createSuggestionsPlaceholder() {
+ const optionsRemaining = this.optionsRemaining();
+ const placeholder = t('%s option(s)', optionsRemaining);
+ return optionsRemaining ? placeholder : '';
+ }
+
+ focusComparator(ref, shouldFocus) {
+ if (ref && shouldFocus) {
+ ref.focus();
+ }
+ }
+
+ isOperatorRelevant(operator, subject) {
+ if (operator && CUSTOM_OPERATORS.has(operator)) {
+ const { partitionColumn } = this.props;
+ return partitionColumn && subject && subject === partitionColumn;
+ }
+
+ return !(
+ (this.props.datasource.type === 'druid' &&
+ TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) ||
+ (this.props.datasource.type === 'table' &&
+ DRUID_ONLY_OPERATORS.indexOf(operator) >= 0) ||
+ (this.props.adhocFilter.clause === CLAUSES.HAVING &&
+ HAVING_OPERATORS.indexOf(operator) === -1)
+ );
+ }
+
+ optionsRemaining() {
+ const { suggestions } = this.state;
+ const { comparator } = this.props.adhocFilter;
+ // if select is multi/value is array, we show the options not selected
+ const valuesFromSuggestionsLength = Array.isArray(comparator)
+ ? comparator.filter(v => suggestions.includes(v)).length
+ : 0;
+ return suggestions?.length - valuesFromSuggestionsLength ?? 0;
+ }
+
refreshComparatorSuggestions() {
const { datasource } = this.props;
const col = this.props.adhocFilter.subject;
@@ -230,44 +268,6 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
}
}
- isOperatorRelevant(operator, subject) {
- if (operator && CUSTOM_OPERATORS.has(operator)) {
- const { partitionColumn } = this.props;
- return partitionColumn && subject && subject === partitionColumn;
- }
-
- return !(
- (this.props.datasource.type === 'druid' &&
- TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) ||
- (this.props.datasource.type === 'table' &&
- DRUID_ONLY_OPERATORS.indexOf(operator) >= 0) ||
- (this.props.adhocFilter.clause === CLAUSES.HAVING &&
- HAVING_OPERATORS.indexOf(operator) === -1)
- );
- }
-
- focusComparator(ref, shouldFocus) {
- if (ref && shouldFocus) {
- ref.focus();
- }
- }
-
- optionsRemaining() {
- const { suggestions } = this.state;
- const { comparator } = this.props.adhocFilter;
- // if select is multi/value is array, we show the options not selected
- const valuesFromSuggestionsLength = Array.isArray(comparator)
- ? comparator.filter(v => suggestions.includes(v)).length
- : 0;
- return suggestions?.length - valuesFromSuggestionsLength ?? 0;
- }
-
- createSuggestionsPlaceholder() {
- const optionsRemaining = this.optionsRemaining();
- const placeholder = t('%s option(s)', optionsRemaining);
- return optionsRemaining ? placeholder : '';
- }
-
renderSubjectOptionLabel(option) {
return ;
}
diff --git a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
index ffd01751f5c1..500231ce1505 100644
--- a/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
+++ b/superset-frontend/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
@@ -61,19 +61,19 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
}
}
- onSqlExpressionClauseChange(clause) {
+ onSqlExpressionChange(sqlExpression) {
this.props.onChange(
this.props.adhocFilter.duplicateWith({
- clause,
+ sqlExpression,
expressionType: EXPRESSION_TYPES.SQL,
}),
);
}
- onSqlExpressionChange(sqlExpression) {
+ onSqlExpressionClauseChange(clause) {
this.props.onChange(
this.props.adhocFilter.duplicateWith({
- sqlExpression,
+ clause,
expressionType: EXPRESSION_TYPES.SQL,
}),
);
diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
index fe6b0194fc40..a2f4180e4b70 100644
--- a/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
+++ b/superset-frontend/src/explore/components/AdhocMetricEditPopover.jsx
@@ -93,6 +93,64 @@ export default class AdhocMetricEditPopover extends React.Component {
document.removeEventListener('mousemove', this.onMouseMove);
}
+ onAggregateChange(aggregate) {
+ // we construct this object explicitly to overwrite the value in the case aggregate is null
+ this.setState(prevState => ({
+ adhocMetric: prevState.adhocMetric.duplicateWith({
+ aggregate,
+ expressionType: EXPRESSION_TYPES.SIMPLE,
+ }),
+ savedMetric: undefined,
+ }));
+ }
+
+ onColumnChange(columnId) {
+ const column = this.props.columns.find(column => column.id === columnId);
+ this.setState(prevState => ({
+ adhocMetric: prevState.adhocMetric.duplicateWith({
+ column,
+ expressionType: EXPRESSION_TYPES.SIMPLE,
+ }),
+ savedMetric: undefined,
+ }));
+ }
+
+ onDragDown(e) {
+ this.dragStartX = e.clientX;
+ this.dragStartY = e.clientY;
+ this.dragStartWidth = this.state.width;
+ this.dragStartHeight = this.state.height;
+ document.addEventListener('mousemove', this.onMouseMove);
+ }
+
+ onMouseMove(e) {
+ this.props.onResize();
+ this.setState({
+ width: Math.max(
+ this.dragStartWidth + (e.clientX - this.dragStartX),
+ startingWidth,
+ ),
+ height: Math.max(
+ this.dragStartHeight + (e.clientY - this.dragStartY) * 2,
+ startingHeight,
+ ),
+ });
+ }
+
+ onMouseUp() {
+ document.removeEventListener('mousemove', this.onMouseMove);
+ }
+
+ onResetStateAndClose() {
+ this.setState(
+ {
+ adhocMetric: this.props.adhocMetric,
+ savedMetric: this.props.savedMetric,
+ },
+ this.props.onClose,
+ );
+ }
+
onSave() {
const { title } = this.props;
const { hasCustomLabel } = title;
@@ -118,38 +176,6 @@ export default class AdhocMetricEditPopover extends React.Component {
this.props.onClose();
}
- onResetStateAndClose() {
- this.setState(
- {
- adhocMetric: this.props.adhocMetric,
- savedMetric: this.props.savedMetric,
- },
- this.props.onClose,
- );
- }
-
- onColumnChange(columnId) {
- const column = this.props.columns.find(column => column.id === columnId);
- this.setState(prevState => ({
- adhocMetric: prevState.adhocMetric.duplicateWith({
- column,
- expressionType: EXPRESSION_TYPES.SIMPLE,
- }),
- savedMetric: undefined,
- }));
- }
-
- onAggregateChange(aggregate) {
- // we construct this object explicitly to overwrite the value in the case aggregate is null
- this.setState(prevState => ({
- adhocMetric: prevState.adhocMetric.duplicateWith({
- aggregate,
- expressionType: EXPRESSION_TYPES.SIMPLE,
- }),
- savedMetric: undefined,
- }));
- }
-
onSavedMetricChange(savedMetricId) {
const savedMetric = this.props.savedMetrics.find(
metric => metric.id === savedMetricId,
@@ -175,32 +201,6 @@ export default class AdhocMetricEditPopover extends React.Component {
}));
}
- onDragDown(e) {
- this.dragStartX = e.clientX;
- this.dragStartY = e.clientY;
- this.dragStartWidth = this.state.width;
- this.dragStartHeight = this.state.height;
- document.addEventListener('mousemove', this.onMouseMove);
- }
-
- onMouseMove(e) {
- this.props.onResize();
- this.setState({
- width: Math.max(
- this.dragStartWidth + (e.clientX - this.dragStartX),
- startingWidth,
- ),
- height: Math.max(
- this.dragStartHeight + (e.clientY - this.dragStartY) * 2,
- startingHeight,
- ),
- });
- }
-
- onMouseUp() {
- document.removeEventListener('mousemove', this.onMouseMove);
- }
-
handleAceEditorRef(ref) {
if (ref) {
this.aceEditorRef = ref;
diff --git a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
index 08eb9e8553fa..875e879a6648 100644
--- a/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
+++ b/superset-frontend/src/explore/components/AdhocMetricEditPopoverTitle.jsx
@@ -43,22 +43,14 @@ export default class AdhocMetricEditPopoverTitle extends React.Component {
};
}
- onMouseOver() {
- this.setState({ isHovered: true });
- }
-
- onMouseOut() {
- this.setState({ isHovered: false });
+ onBlur() {
+ this.setState({ isEditable: false });
}
onClick() {
this.setState({ isEditable: true });
}
- onBlur() {
- this.setState({ isEditable: false });
- }
-
onInputBlur(e) {
if (e.target.value === '') {
this.props.onChange(e);
@@ -66,6 +58,14 @@ export default class AdhocMetricEditPopoverTitle extends React.Component {
this.onBlur();
}
+ onMouseOut() {
+ this.setState({ isHovered: false });
+ }
+
+ onMouseOver() {
+ this.setState({ isHovered: true });
+ }
+
render() {
const { title, onChange } = this.props;
diff --git a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
index 70c347cffd00..90d65feb85cd 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
@@ -87,6 +87,10 @@ class ControlPanelsContainer extends React.Component {
this.renderControlPanelSection = this.renderControlPanelSection.bind(this);
}
+ removeAlert() {
+ this.props.actions.removeControlPanelAlert();
+ }
+
sectionsToRender() {
return sectionsToRender(
this.props.form_data.viz_type,
@@ -94,10 +98,6 @@ class ControlPanelsContainer extends React.Component {
);
}
- removeAlert() {
- this.props.actions.removeControlPanelAlert();
- }
-
renderControl({ name, config }) {
const { actions, controls, form_data: formData } = this.props;
const { visibility } = config;
diff --git a/superset-frontend/src/explore/components/EmbedCodeButton.jsx b/superset-frontend/src/explore/components/EmbedCodeButton.jsx
index 04efde16fa2c..d219de6cd22a 100644
--- a/superset-frontend/src/explore/components/EmbedCodeButton.jsx
+++ b/superset-frontend/src/explore/components/EmbedCodeButton.jsx
@@ -56,13 +56,6 @@ export default class EmbedCodeButton extends React.Component {
.catch(this.props.addDangerToast);
}
- handleInputChange(e) {
- const { value, name } = e.currentTarget;
- const data = {};
- data[name] = value;
- this.setState(data);
- }
-
generateEmbedHTML() {
const srcLink = `${window.location.origin + getURIDirectory()}?r=${
this.state.shortUrlId
@@ -80,6 +73,13 @@ export default class EmbedCodeButton extends React.Component {
);
}
+ handleInputChange(e) {
+ const { value, name } = e.currentTarget;
+ const data = {};
+ data[name] = value;
+ this.setState(data);
+ }
+
renderPopoverContent() {
const html = this.generateEmbedHTML();
return (
diff --git a/superset-frontend/src/explore/components/ExploreChartHeader.jsx b/superset-frontend/src/explore/components/ExploreChartHeader.jsx
index 9c1df9f3a8d6..61ca255654bd 100644
--- a/superset-frontend/src/explore/components/ExploreChartHeader.jsx
+++ b/superset-frontend/src/explore/components/ExploreChartHeader.jsx
@@ -102,13 +102,10 @@ export class ExploreChartHeader extends React.PureComponent {
return this.props.sliceName || t('%s - untitled', this.props.table_name);
}
- postChartFormData() {
- this.props.actions.postChartFormData(
- this.props.form_data,
- true,
- this.props.timeout,
- this.props.chart.id,
- );
+ closePropertiesModal() {
+ this.setState({
+ isPropertiesModalOpen: false,
+ });
}
openPropertiesModal() {
@@ -117,10 +114,13 @@ export class ExploreChartHeader extends React.PureComponent {
});
}
- closePropertiesModal() {
- this.setState({
- isPropertiesModalOpen: false,
- });
+ postChartFormData() {
+ this.props.actions.postChartFormData(
+ this.props.form_data,
+ true,
+ this.props.timeout,
+ this.props.chart.id,
+ );
}
render() {
diff --git a/superset-frontend/src/explore/components/controls/AdhocFilterControl.jsx b/superset-frontend/src/explore/components/controls/AdhocFilterControl.jsx
index f5c9f5d88102..8914c1bc039f 100644
--- a/superset-frontend/src/explore/components/controls/AdhocFilterControl.jsx
+++ b/superset-frontend/src/explore/components/controls/AdhocFilterControl.jsx
@@ -166,14 +166,22 @@ class AdhocFilterControl extends React.Component {
}
}
- onRemoveFilter(index) {
- const valuesCopy = [...this.state.values];
- valuesCopy.splice(index, 1);
- this.setState(prevState => ({
- ...prevState,
- values: valuesCopy,
- }));
- this.props.onChange(valuesCopy);
+ onChange(opts) {
+ const options = (opts || [])
+ .map(option => this.mapOption(option))
+ .filter(option => option);
+ this.props.onChange(options);
+ }
+
+ onFilterEdit(changedFilter) {
+ this.props.onChange(
+ this.state.values.map(value => {
+ if (value.filterOptionName === changedFilter.filterOptionName) {
+ return changedFilter;
+ }
+ return value;
+ }),
+ );
}
onNewFilter(newFilter) {
@@ -191,22 +199,14 @@ class AdhocFilterControl extends React.Component {
}
}
- onFilterEdit(changedFilter) {
- this.props.onChange(
- this.state.values.map(value => {
- if (value.filterOptionName === changedFilter.filterOptionName) {
- return changedFilter;
- }
- return value;
- }),
- );
- }
-
- onChange(opts) {
- const options = (opts || [])
- .map(option => this.mapOption(option))
- .filter(option => option);
- this.props.onChange(options);
+ onRemoveFilter(index) {
+ const valuesCopy = [...this.state.values];
+ valuesCopy.splice(index, 1);
+ this.setState(prevState => ({
+ ...prevState,
+ values: valuesCopy,
+ }));
+ this.props.onChange(valuesCopy);
}
getMetricExpression(savedMetricName) {
@@ -215,15 +215,18 @@ class AdhocFilterControl extends React.Component {
).expression;
}
- moveLabel(dragIndex, hoverIndex) {
- const { values } = this.state;
-
- const newValues = [...values];
- [newValues[hoverIndex], newValues[dragIndex]] = [
- newValues[dragIndex],
- newValues[hoverIndex],
- ];
- this.setState({ values: newValues });
+ addNewFilterPopoverTrigger(trigger) {
+ return (
+
+ {trigger}
+
+ );
}
mapOption(option) {
@@ -277,6 +280,17 @@ class AdhocFilterControl extends React.Component {
return null;
}
+ moveLabel(dragIndex, hoverIndex) {
+ const { values } = this.state;
+
+ const newValues = [...values];
+ [newValues[hoverIndex], newValues[dragIndex]] = [
+ newValues[dragIndex],
+ newValues[hoverIndex],
+ ];
+ this.setState({ values: newValues });
+ }
+
optionsForSelect(props) {
const options = [
...props.columns,
@@ -316,20 +330,6 @@ class AdhocFilterControl extends React.Component {
);
}
- addNewFilterPopoverTrigger(trigger) {
- return (
-
- {trigger}
-
- );
- }
-
render() {
const { theme } = this.props;
return (
diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayer.jsx b/superset-frontend/src/explore/components/controls/AnnotationLayer.jsx
index 1c65c56e7c55..a6405f9f43eb 100644
--- a/superset-frontend/src/explore/components/controls/AnnotationLayer.jsx
+++ b/superset-frontend/src/explore/components/controls/AnnotationLayer.jsx
@@ -203,69 +203,45 @@ export default class AnnotationLayer extends React.PureComponent {
return sources;
}
- isValidFormula(value, annotationType) {
- if (annotationType === ANNOTATION_TYPES.FORMULA) {
- try {
- mathjsParse(value).compile().evaluate({ x: 0 });
- } catch (err) {
- return true;
- }
- }
- return false;
- }
+ applyAnnotation() {
+ if (this.isValidForm()) {
+ const annotationFields = [
+ 'name',
+ 'annotationType',
+ 'sourceType',
+ 'color',
+ 'opacity',
+ 'style',
+ 'width',
+ 'showMarkers',
+ 'hideLine',
+ 'value',
+ 'overrides',
+ 'show',
+ 'titleColumn',
+ 'descriptionColumns',
+ 'timeColumn',
+ 'intervalEndColumn',
+ ];
+ const newAnnotation = {};
+ annotationFields.forEach(field => {
+ if (this.state[field] !== null) {
+ newAnnotation[field] = this.state[field];
+ }
+ });
- isValidForm() {
- const {
- name,
- annotationType,
- sourceType,
- value,
- timeColumn,
- intervalEndColumn,
- } = this.state;
- const errors = [
- validateNonEmpty(name),
- validateNonEmpty(annotationType),
- validateNonEmpty(value),
- ];
- if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) {
- if (annotationType === ANNOTATION_TYPES.EVENT) {
- errors.push(validateNonEmpty(timeColumn));
- }
- if (annotationType === ANNOTATION_TYPES.INTERVAL) {
- errors.push(validateNonEmpty(timeColumn));
- errors.push(validateNonEmpty(intervalEndColumn));
+ if (newAnnotation.color === AUTOMATIC_COLOR) {
+ newAnnotation.color = null;
}
- }
- errors.push(this.isValidFormula(value, annotationType));
- return !errors.filter(x => x).length;
- }
-
- handleAnnotationType(annotationType) {
- this.setState({
- annotationType,
- sourceType: null,
- value: null,
- });
- }
-
- handleAnnotationSourceType(sourceType) {
- const { sourceType: prevSourceType } = this.state;
- if (prevSourceType !== sourceType) {
- this.setState({ sourceType, value: null, isLoadingOptions: true });
+ this.props.addAnnotationLayer(newAnnotation);
+ this.setState({ isNew: false });
}
}
- handleValue(value) {
- this.setState({
- value,
- descriptionColumns: null,
- intervalEndColumn: null,
- timeColumn: null,
- titleColumn: null,
- overrides: { time_range: null },
- });
+ deleteAnnotation() {
+ this.props.removeAnnotationLayer();
+ this.props.close();
}
fetchOptions(annotationType, sourceType, isLoadingOptions) {
@@ -311,45 +287,69 @@ export default class AnnotationLayer extends React.PureComponent {
}
}
- deleteAnnotation() {
- this.props.removeAnnotationLayer();
- this.props.close();
+ handleAnnotationSourceType(sourceType) {
+ const { sourceType: prevSourceType } = this.state;
+
+ if (prevSourceType !== sourceType) {
+ this.setState({ sourceType, value: null, isLoadingOptions: true });
+ }
}
- applyAnnotation() {
- if (this.isValidForm()) {
- const annotationFields = [
- 'name',
- 'annotationType',
- 'sourceType',
- 'color',
- 'opacity',
- 'style',
- 'width',
- 'showMarkers',
- 'hideLine',
- 'value',
- 'overrides',
- 'show',
- 'titleColumn',
- 'descriptionColumns',
- 'timeColumn',
- 'intervalEndColumn',
- ];
- const newAnnotation = {};
- annotationFields.forEach(field => {
- if (this.state[field] !== null) {
- newAnnotation[field] = this.state[field];
- }
- });
+ handleAnnotationType(annotationType) {
+ this.setState({
+ annotationType,
+ sourceType: null,
+ value: null,
+ });
+ }
- if (newAnnotation.color === AUTOMATIC_COLOR) {
- newAnnotation.color = null;
+ handleValue(value) {
+ this.setState({
+ value,
+ descriptionColumns: null,
+ intervalEndColumn: null,
+ timeColumn: null,
+ titleColumn: null,
+ overrides: { time_range: null },
+ });
+ }
+
+ isValidForm() {
+ const {
+ name,
+ annotationType,
+ sourceType,
+ value,
+ timeColumn,
+ intervalEndColumn,
+ } = this.state;
+ const errors = [
+ validateNonEmpty(name),
+ validateNonEmpty(annotationType),
+ validateNonEmpty(value),
+ ];
+ if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) {
+ if (annotationType === ANNOTATION_TYPES.EVENT) {
+ errors.push(validateNonEmpty(timeColumn));
+ }
+ if (annotationType === ANNOTATION_TYPES.INTERVAL) {
+ errors.push(validateNonEmpty(timeColumn));
+ errors.push(validateNonEmpty(intervalEndColumn));
}
+ }
+ errors.push(this.isValidFormula(value, annotationType));
+ return !errors.filter(x => x).length;
+ }
- this.props.addAnnotationLayer(newAnnotation);
- this.setState({ isNew: false });
+ isValidFormula(value, annotationType) {
+ if (annotationType === ANNOTATION_TYPES.FORMULA) {
+ try {
+ mathjsParse(value).compile().evaluate({ x: 0 });
+ } catch (err) {
+ return true;
+ }
}
+ return false;
}
submitAnnotation() {
@@ -357,78 +357,115 @@ export default class AnnotationLayer extends React.PureComponent {
this.props.close();
}
- renderOption(option) {
- return (
-
- {option.label}
-
- );
- }
-
- renderValueConfiguration() {
+ renderDisplayConfiguration() {
const {
+ color,
+ opacity,
+ style,
+ width,
+ showMarkers,
+ hideLine,
annotationType,
- sourceType,
- value,
- valueOptions,
- isLoadingOptions,
} = this.state;
- let label = '';
- let description = '';
- if (requiresQuery(sourceType)) {
- if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
- label = 'Annotation Layer';
- description = 'Select the Annotation Layer you would like to use.';
- } else {
- label = t('Chart');
- description = `Use a pre defined Superset Chart as a source for annotations and overlays.
- your chart must be one of these visualization types:
- [${this.getSupportedSourceTypes(annotationType)
- .map(x => x.label)
- .join(', ')}]`;
- }
- } else if (annotationType === ANNOTATION_TYPES.FORMULA) {
- label = 'Formula';
- description = `Expects a formula with depending time parameter 'x'
- in milliseconds since epoch. mathjs is used to evaluate the formulas.
- Example: '2x+5'`;
+ const colorScheme = getCategoricalSchemeRegistry()
+ .get(this.props.colorScheme)
+ .colors.concat();
+ if (
+ color &&
+ color !== AUTOMATIC_COLOR &&
+ !colorScheme.find(x => x.toLowerCase() === color.toLowerCase())
+ ) {
+ colorScheme.push(color);
}
- if (requiresQuery(sourceType)) {
- return (
+ return (
+ {}}
+ title={t('Display configuration')}
+ info={t('Configure your how you overlay is displayed here.')}
+ >
this.setState({ style: v })}
/>
- );
- }
- if (annotationType === ANNOTATION_TYPES.FORMULA) {
- return (
+ this.setState({ opacity: v })}
+ />
+
+
+
+ this.setState({ color: v.hex })}
+ />
+ this.setState({ color: AUTOMATIC_COLOR })}
+ >
+ Automatic Color
+
+
+
this.setState({ width: v })}
/>
- );
- }
- return '';
+ {annotationType === ANNOTATION_TYPES.TIME_SERIES && (
+ this.setState({ showMarkers: v })}
+ />
+ )}
+ {annotationType === ANNOTATION_TYPES.TIME_SERIES && (
+ this.setState({ hideLine: v })}
+ />
+ )}
+
+ );
+ }
+
+ renderOption(option) {
+ return (
+
+ {option.label}
+
+ );
}
renderSliceConfiguration() {
@@ -574,107 +611,70 @@ export default class AnnotationLayer extends React.PureComponent {
return '';
}
- renderDisplayConfiguration() {
+ renderValueConfiguration() {
const {
- color,
- opacity,
- style,
- width,
- showMarkers,
- hideLine,
annotationType,
+ sourceType,
+ value,
+ valueOptions,
+ isLoadingOptions,
} = this.state;
- const colorScheme = getCategoricalSchemeRegistry()
- .get(this.props.colorScheme)
- .colors.concat();
- if (
- color &&
- color !== AUTOMATIC_COLOR &&
- !colorScheme.find(x => x.toLowerCase() === color.toLowerCase())
- ) {
- colorScheme.push(color);
+ let label = '';
+ let description = '';
+ if (requiresQuery(sourceType)) {
+ if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+ label = 'Annotation Layer';
+ description = 'Select the Annotation Layer you would like to use.';
+ } else {
+ label = t('Chart');
+ description = `Use a pre defined Superset Chart as a source for annotations and overlays.
+ your chart must be one of these visualization types:
+ [${this.getSupportedSourceTypes(annotationType)
+ .map(x => x.label)
+ .join(', ')}]`;
+ }
+ } else if (annotationType === ANNOTATION_TYPES.FORMULA) {
+ label = 'Formula';
+ description = `Expects a formula with depending time parameter 'x'
+ in milliseconds since epoch. mathjs is used to evaluate the formulas.
+ Example: '2x+5'`;
}
- return (
- {}}
- title={t('Display configuration')}
- info={t('Configure your how you overlay is displayed here.')}
- >
- this.setState({ style: v })}
- />
+ if (requiresQuery(sourceType)) {
+ return (
this.setState({ opacity: v })}
+ name="annotation-layer-value"
+ showHeader
+ hovered
+ description={description}
+ label={label}
+ placeholder=""
+ options={valueOptions}
+ isLoading={isLoadingOptions}
+ value={value}
+ onChange={this.handleValue}
+ validationErrors={!value ? ['Mandatory'] : []}
+ optionRenderer={this.renderOption}
/>
-
-
-
- this.setState({ color: v.hex })}
- />
- this.setState({ color: AUTOMATIC_COLOR })}
- >
- Automatic Color
-
-
-
+ );
+ }
+ if (annotationType === ANNOTATION_TYPES.FORMULA) {
+ return (
this.setState({ width: v })}
+ name="annotation-layer-value"
+ hovered
+ showHeader
+ description={description}
+ label={label}
+ placeholder=""
+ value={value}
+ onChange={this.handleValue}
+ validationErrors={
+ this.isValidFormula(value, annotationType) ? ['Bad formula.'] : []
+ }
/>
- {annotationType === ANNOTATION_TYPES.TIME_SERIES && (
- this.setState({ showMarkers: v })}
- />
- )}
- {annotationType === ANNOTATION_TYPES.TIME_SERIES && (
- this.setState({ hideLine: v })}
- />
- )}
-
- );
+ );
+ }
+ return '';
}
render() {
diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayerControl.jsx b/superset-frontend/src/explore/components/controls/AnnotationLayerControl.jsx
index e47388c3470a..63b5dd128ee6 100644
--- a/superset-frontend/src/explore/components/controls/AnnotationLayerControl.jsx
+++ b/superset-frontend/src/explore/components/controls/AnnotationLayerControl.jsx
@@ -109,6 +109,28 @@ class AnnotationLayerControl extends React.PureComponent {
this.props.onChange(annotations);
}
+ renderInfo(anno) {
+ const { annotationError, annotationQuery } = this.props;
+ if (annotationQuery[anno.name]) {
+ return (
+
+ );
+ }
+ if (annotationError[anno.name]) {
+ return (
+
+ );
+ }
+ if (!anno.show) {
+ return Hidden ;
+ }
+ return '';
+ }
+
renderPopover(popoverKey, annotation, error) {
const id = annotation?.name || '_new';
@@ -132,28 +154,6 @@ class AnnotationLayerControl extends React.PureComponent {
);
}
- renderInfo(anno) {
- const { annotationError, annotationQuery } = this.props;
- if (annotationQuery[anno.name]) {
- return (
-
- );
- }
- if (annotationError[anno.name]) {
- return (
-
- );
- }
- if (!anno.show) {
- return Hidden ;
- }
- return '';
- }
-
render() {
const { addedAnnotationIndex } = this.state;
const addedAnnotation = this.props.value[addedAnnotationIndex];
diff --git a/superset-frontend/src/explore/components/controls/BoundsControl.jsx b/superset-frontend/src/explore/components/controls/BoundsControl.jsx
index b56b840a8c7c..e7510e49f750 100644
--- a/superset-frontend/src/explore/components/controls/BoundsControl.jsx
+++ b/superset-frontend/src/explore/components/controls/BoundsControl.jsx
@@ -46,26 +46,6 @@ export default class BoundsControl extends React.Component {
this.onMaxChange = this.onMaxChange.bind(this);
}
- onMinChange(event) {
- const min = event.target.value;
- this.setState(
- prevState => ({
- minMax: [min, prevState.minMax[1]],
- }),
- this.onChange,
- );
- }
-
- onMaxChange(event) {
- const max = event.target.value;
- this.setState(
- prevState => ({
- minMax: [prevState.minMax[0], max],
- }),
- this.onChange,
- );
- }
-
onChange() {
const mm = this.state.minMax;
const errors = [];
@@ -82,6 +62,26 @@ export default class BoundsControl extends React.Component {
}
}
+ onMaxChange(event) {
+ const max = event.target.value;
+ this.setState(
+ prevState => ({
+ minMax: [prevState.minMax[0], max],
+ }),
+ this.onChange,
+ );
+ }
+
+ onMinChange(event) {
+ const min = event.target.value;
+ this.setState(
+ prevState => ({
+ minMax: [min, prevState.minMax[1]],
+ }),
+ this.onChange,
+ );
+ }
+
render() {
return (
diff --git a/superset-frontend/src/explore/components/controls/CollectionControl.jsx b/superset-frontend/src/explore/components/controls/CollectionControl.jsx
index 198df6d5cc51..bb860eee46b3 100644
--- a/superset-frontend/src/explore/components/controls/CollectionControl.jsx
+++ b/superset-frontend/src/explore/components/controls/CollectionControl.jsx
@@ -69,15 +69,15 @@ export default class CollectionControl extends React.Component {
this.onAdd = this.onAdd.bind(this);
}
+ onAdd() {
+ this.props.onChange(this.props.value.concat([this.props.itemGenerator()]));
+ }
+
onChange(i, value) {
Object.assign(this.props.value[i], value);
this.props.onChange(this.props.value);
}
- onAdd() {
- this.props.onChange(this.props.value.concat([this.props.itemGenerator()]));
- }
-
onSortEnd({ oldIndex, newIndex }) {
this.props.onChange(arrayMove(this.props.value, oldIndex, newIndex));
}
diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl.jsx
index e30aebe8763c..1526125a4a1b 100644
--- a/superset-frontend/src/explore/components/controls/DatasourceControl.jsx
+++ b/superset-frontend/src/explore/components/controls/DatasourceControl.jsx
@@ -114,24 +114,6 @@ class DatasourceControl extends React.PureComponent {
}
}
- toggleShowDatasource() {
- this.setState(({ showDatasource }) => ({
- showDatasource: !showDatasource,
- }));
- }
-
- toggleChangeDatasourceModal() {
- this.setState(({ showChangeDatasourceModal }) => ({
- showChangeDatasourceModal: !showChangeDatasourceModal,
- }));
- }
-
- toggleEditDatasourceModal() {
- this.setState(({ showEditDatasourceModal }) => ({
- showEditDatasourceModal: !showEditDatasourceModal,
- }));
- }
-
handleMenuItemClick({ key }) {
if (key === CHANGE_DATASET) {
this.toggleChangeDatasourceModal();
@@ -149,6 +131,24 @@ class DatasourceControl extends React.PureComponent {
}
}
+ toggleChangeDatasourceModal() {
+ this.setState(({ showChangeDatasourceModal }) => ({
+ showChangeDatasourceModal: !showChangeDatasourceModal,
+ }));
+ }
+
+ toggleEditDatasourceModal() {
+ this.setState(({ showEditDatasourceModal }) => ({
+ showEditDatasourceModal: !showEditDatasourceModal,
+ }));
+ }
+
+ toggleShowDatasource() {
+ this.setState(({ showDatasource }) => ({
+ showDatasource: !showDatasource,
+ }));
+ }
+
render() {
const { showChangeDatasourceModal, showEditDatasourceModal } = this.state;
const { datasource, onChange } = this.props;
diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl.jsx b/superset-frontend/src/explore/components/controls/DateFilterControl.jsx
index 5b0f15a5e7e2..a2b0d6a79601 100644
--- a/superset-frontend/src/explore/components/controls/DateFilterControl.jsx
+++ b/superset-frontend/src/explore/components/controls/DateFilterControl.jsx
@@ -308,22 +308,6 @@ class DateFilterControl extends React.Component {
}
}
- handleClick(e) {
- const { target } = e;
- // switch to `TYPES.CUSTOM_START_END` when the calendar is clicked
- if (this.startEndSectionRef && this.startEndSectionRef.contains(target)) {
- this.setTypeCustomStartEnd();
- }
-
- // if user click outside popover, popover will hide and we will call onCloseDateFilterControl,
- // but need to exclude OverlayTrigger component to avoid handle click events twice.
- if (target.getAttribute('name') !== 'popover-trigger') {
- if (this.popoverContainer && !this.popoverContainer.contains(target)) {
- this.props.onCloseDateFilterControl();
- }
- }
- }
-
close() {
let val;
if (
@@ -345,6 +329,31 @@ class DateFilterControl extends React.Component {
});
}
+ handleClick(e) {
+ const { target } = e;
+ // switch to `TYPES.CUSTOM_START_END` when the calendar is clicked
+ if (this.startEndSectionRef && this.startEndSectionRef.contains(target)) {
+ this.setTypeCustomStartEnd();
+ }
+
+ // if user click outside popover, popover will hide and we will call onCloseDateFilterControl,
+ // but need to exclude OverlayTrigger component to avoid handle click events twice.
+ if (target.getAttribute('name') !== 'popover-trigger') {
+ if (this.popoverContainer && !this.popoverContainer.contains(target)) {
+ this.props.onCloseDateFilterControl();
+ }
+ }
+ }
+
+ handleVisibleChange(visible) {
+ if (visible) {
+ this.props.onOpenDateFilterControl();
+ } else {
+ this.props.onCloseDateFilterControl();
+ }
+ this.setState({ popoverVisible: visible });
+ }
+
isValidSince(date) {
return (
!isValidMoment(this.state.until) ||
@@ -375,15 +384,6 @@ class DateFilterControl extends React.Component {
this.setState(nextState);
}
- handleVisibleChange(visible) {
- if (visible) {
- this.props.onOpenDateFilterControl();
- } else {
- this.props.onCloseDateFilterControl();
- }
- this.setState({ popoverVisible: visible });
- }
-
renderInput(props, key) {
return (
diff --git a/superset-frontend/src/explore/components/controls/FixedOrMetricControl.jsx b/superset-frontend/src/explore/components/controls/FixedOrMetricControl.jsx
index d7cf050b2150..e5fd7f548168 100644
--- a/superset-frontend/src/explore/components/controls/FixedOrMetricControl.jsx
+++ b/superset-frontend/src/explore/components/controls/FixedOrMetricControl.jsx
@@ -77,10 +77,6 @@ export default class FixedOrMetricControl extends React.Component {
});
}
- setType(type) {
- this.setState({ type }, this.onChange);
- }
-
setFixedValue(fixedValue) {
this.setState({ fixedValue }, this.onChange);
}
@@ -89,6 +85,10 @@ export default class FixedOrMetricControl extends React.Component {
this.setState({ metricValue }, this.onChange);
}
+ setType(type) {
+ this.setState({ type }, this.onChange);
+ }
+
toggle() {
this.setState(prevState => ({
expanded: !prevState.expanded,
diff --git a/superset-frontend/src/explore/components/controls/MetricsControl.jsx b/superset-frontend/src/explore/components/controls/MetricsControl.jsx
index f643e2b769ee..cba2ae218a3c 100644
--- a/superset-frontend/src/explore/components/controls/MetricsControl.jsx
+++ b/superset-frontend/src/explore/components/controls/MetricsControl.jsx
@@ -171,16 +171,29 @@ class MetricsControl extends React.PureComponent {
}
}
- onNewMetric(newMetric) {
- this.setState(
- prevState => ({
- ...prevState,
- value: [...prevState.value, newMetric],
- }),
- () => {
- this.onChange(this.state.value);
- },
- );
+ onChange(opts) {
+ // if clear out options
+ if (opts === null) {
+ this.props.onChange(null);
+ return;
+ }
+
+ let transformedOpts;
+ if (Array.isArray(opts)) {
+ transformedOpts = opts;
+ } else {
+ transformedOpts = opts ? [opts] : [];
+ }
+ const optionValues = transformedOpts
+ .map(option => {
+ // pre-defined metric
+ if (option.metric_name) {
+ return option.metric_name;
+ }
+ return option;
+ })
+ .filter(option => option);
+ this.props.onChange(this.props.multi ? optionValues : optionValues[0]);
}
onMetricEdit(changedMetric, oldMetric) {
@@ -204,6 +217,18 @@ class MetricsControl extends React.PureComponent {
);
}
+ onNewMetric(newMetric) {
+ this.setState(
+ prevState => ({
+ ...prevState,
+ value: [...prevState.value, newMetric],
+ }),
+ () => {
+ this.onChange(this.state.value);
+ },
+ );
+ }
+
onRemoveMetric(index) {
if (!Array.isArray(this.state.value)) {
return;
@@ -217,46 +242,6 @@ class MetricsControl extends React.PureComponent {
this.props.onChange(valuesCopy);
}
- onChange(opts) {
- // if clear out options
- if (opts === null) {
- this.props.onChange(null);
- return;
- }
-
- let transformedOpts;
- if (Array.isArray(opts)) {
- transformedOpts = opts;
- } else {
- transformedOpts = opts ? [opts] : [];
- }
- const optionValues = transformedOpts
- .map(option => {
- // pre-defined metric
- if (option.metric_name) {
- return option.metric_name;
- }
- return option;
- })
- .filter(option => option);
- this.props.onChange(this.props.multi ? optionValues : optionValues[0]);
- }
-
- moveLabel(dragIndex, hoverIndex) {
- const { value } = this.state;
-
- const newValues = [...value];
- [newValues[hoverIndex], newValues[dragIndex]] = [
- newValues[dragIndex],
- newValues[hoverIndex],
- ];
- this.setState({ value: newValues });
- }
-
- isAddNewMetricDisabled() {
- return !this.props.multi && this.state.value.length > 0;
- }
-
addNewMetricPopoverTrigger(trigger) {
if (this.isAddNewMetricDisabled()) {
return trigger;
@@ -286,6 +271,28 @@ class MetricsControl extends React.PureComponent {
this.setState({ aggregateInInput });
}
+ isAddNewMetricDisabled() {
+ return !this.props.multi && this.state.value.length > 0;
+ }
+
+ isAutoGeneratedMetric(savedMetric) {
+ if (this.props.datasourceType === 'druid') {
+ return druidAutoGeneratedMetricRegex.test(savedMetric.verbose_name);
+ }
+ return sqlaAutoGeneratedMetricNameRegex.test(savedMetric.metric_name);
+ }
+
+ moveLabel(dragIndex, hoverIndex) {
+ const { value } = this.state;
+
+ const newValues = [...value];
+ [newValues[hoverIndex], newValues[dragIndex]] = [
+ newValues[dragIndex],
+ newValues[hoverIndex],
+ ];
+ this.setState({ value: newValues });
+ }
+
optionsForSelect(props) {
const { columns, savedMetrics } = props;
const aggregates =
@@ -315,13 +322,6 @@ class MetricsControl extends React.PureComponent {
}, []);
}
- isAutoGeneratedMetric(savedMetric) {
- if (this.props.datasourceType === 'druid') {
- return druidAutoGeneratedMetricRegex.test(savedMetric.verbose_name);
- }
- return sqlaAutoGeneratedMetricNameRegex.test(savedMetric.metric_name);
- }
-
selectFilterOption({ data: option }, filterValue) {
if (this.state.aggregateInInput) {
let endIndex = filterValue.length;
diff --git a/superset-frontend/src/explore/components/controls/SelectControl.jsx b/superset-frontend/src/explore/components/controls/SelectControl.jsx
index ccd0574f579d..284226ede6b3 100644
--- a/superset-frontend/src/explore/components/controls/SelectControl.jsx
+++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx
@@ -125,13 +125,6 @@ export default class SelectControl extends React.PureComponent {
this.props.onChange(optionValue);
}
- getSelectRef(instance) {
- this.select = instance;
- if (this.props.selectRef) {
- this.props.selectRef(instance);
- }
- }
-
getOptions(props) {
let options = [];
if (props.options) {
@@ -172,6 +165,26 @@ export default class SelectControl extends React.PureComponent {
return options;
}
+ getSelectRef(instance) {
+ this.select = instance;
+ if (this.props.selectRef) {
+ this.props.selectRef(instance);
+ }
+ }
+
+ createMetaSelectAllOption() {
+ const option = { label: 'Select All', meta: true };
+ option[this.props.valueKey] = 'Select All';
+ return option;
+ }
+
+ createPlaceholder() {
+ const optionsRemaining = this.optionsRemaining();
+ const placeholder =
+ this.props.placeholder || t('%s option(s)', optionsRemaining);
+ return optionsRemaining ? placeholder : '';
+ }
+
handleKeyDownForCreate(event) {
const { key } = event;
if (key === 'Tab' || (this.props.commaChoosesOption && key === ',')) {
@@ -203,19 +216,6 @@ export default class SelectControl extends React.PureComponent {
return remainingOptions;
}
- createPlaceholder() {
- const optionsRemaining = this.optionsRemaining();
- const placeholder =
- this.props.placeholder || t('%s option(s)', optionsRemaining);
- return optionsRemaining ? placeholder : '';
- }
-
- createMetaSelectAllOption() {
- const option = { label: 'Select All', meta: true };
- option[this.props.valueKey] = 'Select All';
- return option;
- }
-
render() {
// Tab, comma or Enter will trigger a new option created for FreeFormSelect
const {
diff --git a/superset-frontend/src/explore/components/controls/SpatialControl.jsx b/superset-frontend/src/explore/components/controls/SpatialControl.jsx
index 5c34ec3ca685..fcda7a5cc9f1 100644
--- a/superset-frontend/src/explore/components/controls/SpatialControl.jsx
+++ b/superset-frontend/src/explore/components/controls/SpatialControl.jsx
@@ -131,35 +131,6 @@ export default class SpatialControl extends React.Component {
return null;
}
- renderSelect(name, type) {
- return (
- {
- this.setType(type);
- }}
- onChange={value => {
- this.setState({ [name]: value }, this.onChange);
- }}
- />
- );
- }
-
- renderReverseCheckbox() {
- return (
-
- {t('Reverse lat/long ')}
-
-
- );
- }
-
renderPopoverContent() {
return (
@@ -213,6 +184,35 @@ export default class SpatialControl extends React.Component {
);
}
+ renderReverseCheckbox() {
+ return (
+
+ {t('Reverse lat/long ')}
+
+
+ );
+ }
+
+ renderSelect(name, type) {
+ return (
+
{
+ this.setType(type);
+ }}
+ onChange={value => {
+ this.setState({ [name]: value }, this.onChange);
+ }}
+ />
+ );
+ }
+
render() {
return (
diff --git a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx
index 0bcf2d12860d..5a9698bcd4a1 100644
--- a/superset-frontend/src/explore/components/controls/TextAreaControl.jsx
+++ b/superset-frontend/src/explore/components/controls/TextAreaControl.jsx
@@ -66,14 +66,14 @@ export default class TextAreaControl extends React.Component {
}, 300);
}
- onControlChange(event) {
- this.props.onChange(event.target.value);
- }
-
onAceChange(value) {
this.props.onChange(value);
}
+ onControlChange(event) {
+ this.props.onChange(event.target.value);
+ }
+
renderEditor(inModal = false) {
const value = this.props.value || '';
const minLines = inModal ? 40 : this.props.minLines || 12;
diff --git a/superset-frontend/src/explore/components/controls/TimeSeriesColumnControl.jsx b/superset-frontend/src/explore/components/controls/TimeSeriesColumnControl.jsx
index e98172053784..3943a61ceba0 100644
--- a/superset-frontend/src/explore/components/controls/TimeSeriesColumnControl.jsx
+++ b/superset-frontend/src/explore/components/controls/TimeSeriesColumnControl.jsx
@@ -98,10 +98,18 @@ export default class TimeSeriesColumnControl extends React.Component {
this.onChange = this.onChange.bind(this);
}
+ onBoundsChange(bounds) {
+ this.setState({ bounds }, this.onChange);
+ }
+
onChange() {
this.props.onChange(this.state);
}
+ onCheckboxChange(attr, value) {
+ this.setState({ [attr]: value }, this.onChange);
+ }
+
onSelectChange(attr, opt) {
this.setState({ [attr]: opt.value }, this.onChange);
}
@@ -110,24 +118,12 @@ export default class TimeSeriesColumnControl extends React.Component {
this.setState({ [attr]: event.target.value }, this.onChange);
}
- onCheckboxChange(attr, value) {
- this.setState({ [attr]: value }, this.onChange);
- }
-
- onBoundsChange(bounds) {
- this.setState({ bounds }, this.onChange);
- }
-
onYAxisBoundsChange(yAxisBounds) {
this.setState({ yAxisBounds }, this.onChange);
}
setType() {}
- textSummary() {
- return `${this.state.label}`;
- }
-
edit() {}
formRow(label, tooltip, ttLabel, control) {
@@ -146,6 +142,10 @@ export default class TimeSeriesColumnControl extends React.Component {
);
}
+ textSummary() {
+ return `${this.state.label}`;
+ }
+
renderPopover() {
return (
diff --git a/superset-frontend/src/explore/components/controls/ViewportControl.jsx b/superset-frontend/src/explore/components/controls/ViewportControl.jsx
index b76e6dd34aa4..51631d563475 100644
--- a/superset-frontend/src/explore/components/controls/ViewportControl.jsx
+++ b/superset-frontend/src/explore/components/controls/ViewportControl.jsx
@@ -68,6 +68,23 @@ export default class ViewportControl extends React.Component {
});
}
+ renderLabel() {
+ if (this.props.value.longitude && this.props.value.latitude) {
+ return `${decimal2sexagesimal(
+ this.props.value.longitude,
+ )} | ${decimal2sexagesimal(this.props.value.latitude)}`;
+ }
+ return 'N/A';
+ }
+
+ renderPopover() {
+ return (
+
+ {PARAMS.map(ctrl => this.renderTextControl(ctrl))}
+
+ );
+ }
+
renderTextControl(ctrl) {
return (
@@ -81,23 +98,6 @@ export default class ViewportControl extends React.Component {
);
}
- renderPopover() {
- return (
-
- {PARAMS.map(ctrl => this.renderTextControl(ctrl))}
-
- );
- }
-
- renderLabel() {
- if (this.props.value.longitude && this.props.value.latitude) {
- return `${decimal2sexagesimal(
- this.props.value.longitude,
- )} | ${decimal2sexagesimal(this.props.value.latitude)}`;
- }
- return 'N/A';
- }
-
render() {
return (
diff --git a/superset-frontend/src/visualizations/FilterBox/FilterBox.jsx b/superset-frontend/src/visualizations/FilterBox/FilterBox.jsx
index 4880a385b335..0504b616e225 100644
--- a/superset-frontend/src/visualizations/FilterBox/FilterBox.jsx
+++ b/superset-frontend/src/visualizations/FilterBox/FilterBox.jsx
@@ -118,20 +118,20 @@ class FilterBox extends React.PureComponent {
this.onFilterMenuClose = this.onFilterMenuClose.bind(this);
}
- onFilterMenuOpen(column) {
- return this.props.onFilterMenuOpen(this.props.chartId, column);
- }
+ onCloseDateFilterControl = () => this.onFilterMenuClose(TIME_RANGE);
onFilterMenuClose(column) {
return this.props.onFilterMenuClose(this.props.chartId, column);
}
+ onFilterMenuOpen(column) {
+ return this.props.onFilterMenuOpen(this.props.chartId, column);
+ }
+
onOpenDateFilterControl() {
return this.onFilterMenuOpen(TIME_RANGE);
}
- onCloseDateFilterControl = () => this.onFilterMenuClose(TIME_RANGE);
-
getControlData(controlName) {
const { selectedValues } = this.state;
const control = {
@@ -156,13 +156,6 @@ class FilterBox extends React.PureComponent {
return this.maxValueCache[key];
}
- clickApply() {
- const { selectedValues } = this.state;
- this.setState({ hasChanged: false }, () => {
- this.props.onChange(selectedValues, false);
- });
- }
-
changeFilter(filter, options) {
const fltr = TIME_FILTER_MAP[filter] || filter;
let vals = null;
@@ -192,6 +185,13 @@ class FilterBox extends React.PureComponent {
);
}
+ clickApply() {
+ const { selectedValues } = this.state;
+ this.setState({ hasChanged: false }, () => {
+ this.props.onChange(selectedValues, false);
+ });
+ }
+
/**
* Generate a debounce function that loads options for a specific column
*/
@@ -204,26 +204,6 @@ class FilterBox extends React.PureComponent {
return this.debouncerCache[key];
}
- /**
- * Transform select options, add bar background
- */
- transformOptions(options, max) {
- const maxValue = max === undefined ? d3Max(options, x => x.metric) : max;
- return options.map(opt => {
- const perc = Math.round((opt.metric / maxValue) * 100);
- const color = 'lightgrey';
- const backgroundImage = `linear-gradient(to right, ${color}, ${color} ${perc}%, rgba(0,0,0,0) ${perc}%`;
- const style = { backgroundImage };
- let label = opt.id;
- if (label === true) {
- label = BOOL_TRUE_DISPLAY;
- } else if (label === false) {
- label = BOOL_FALSE_DISPLAY;
- }
- return { value: opt.id, label, style };
- });
- }
-
async loadOptions(key, inputValue = '') {
const input = inputValue.toLowerCase();
const sortAsc = this.props.filtersFields.find(x => x.key === key).asc;
@@ -267,32 +247,24 @@ class FilterBox extends React.PureComponent {
return this.transformOptions(options, this.getKnownMax(key, options));
}
- renderDateFilter() {
- const { showDateFilter } = this.props;
- const label = TIME_FILTER_LABELS.time_range;
- if (showDateFilter) {
- return (
-
-
- {
- this.changeFilter(TIME_RANGE, newValue);
- }}
- onOpenDateFilterControl={this.onOpenDateFilterControl}
- onCloseDateFilterControl={this.onCloseDateFilterControl}
- value={this.state.selectedValues[TIME_RANGE] || 'No filter'}
- />
-
-
- );
- }
- return null;
+ /**
+ * Transform select options, add bar background
+ */
+ transformOptions(options, max) {
+ const maxValue = max === undefined ? d3Max(options, x => x.metric) : max;
+ return options.map(opt => {
+ const perc = Math.round((opt.metric / maxValue) * 100);
+ const color = 'lightgrey';
+ const backgroundImage = `linear-gradient(to right, ${color}, ${color} ${perc}%, rgba(0,0,0,0) ${perc}%`;
+ const style = { backgroundImage };
+ let label = opt.id;
+ if (label === true) {
+ label = BOOL_TRUE_DISPLAY;
+ } else if (label === false) {
+ label = BOOL_FALSE_DISPLAY;
+ }
+ return { value: opt.id, label, style };
+ });
}
renderDatasourceFilters() {
@@ -334,6 +306,47 @@ class FilterBox extends React.PureComponent {
return datasourceFilters;
}
+ renderDateFilter() {
+ const { showDateFilter } = this.props;
+ const label = TIME_FILTER_LABELS.time_range;
+ if (showDateFilter) {
+ return (
+
+
+ {
+ this.changeFilter(TIME_RANGE, newValue);
+ }}
+ onOpenDateFilterControl={this.onOpenDateFilterControl}
+ onCloseDateFilterControl={this.onCloseDateFilterControl}
+ value={this.state.selectedValues[TIME_RANGE] || 'No filter'}
+ />
+
+
+ );
+ }
+ return null;
+ }
+
+ renderFilters() {
+ const { filtersFields = [] } = this.props;
+ return filtersFields.map(filterConfig => {
+ const { label, key } = filterConfig;
+ return (
+
+ {label}
+ {this.renderSelect(filterConfig)}
+
+ );
+ });
+ }
+
renderSelect(filterConfig) {
const { filtersChoices } = this.props;
const { selectedValues } = this.state;
@@ -413,19 +426,6 @@ class FilterBox extends React.PureComponent {
);
}
- renderFilters() {
- const { filtersFields = [] } = this.props;
- return filtersFields.map(filterConfig => {
- const { label, key } = filterConfig;
- return (
-
- {label}
- {this.renderSelect(filterConfig)}
-
- );
- });
- }
-
render() {
const { instantFiltering } = this.props;
return (