(key: K, value: TYPEMAP[K]) {
+ this.records[key] = value;
+ }
+}
+
+export default TypedRegistry;
diff --git a/superset-frontend/packages/superset-ui-core/src/models/index.ts b/superset-frontend/packages/superset-ui-core/src/models/index.ts
index 10d46c2a7e5a8..365ed391d3319 100644
--- a/superset-frontend/packages/superset-ui-core/src/models/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/models/index.ts
@@ -21,3 +21,4 @@ export { default as Plugin } from './Plugin';
export { default as Preset } from './Preset';
export { default as Registry, OverwritePolicy } from './Registry';
export { default as RegistryWithDefaultKey } from './RegistryWithDefaultKey';
+export { default as TypedRegistry } from './TypedRegistry';
diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/UiOverrideRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/UiOverrideRegistry.ts
new file mode 100644
index 0000000000000..fb74ae1ece493
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/UiOverrideRegistry.ts
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { TypedRegistry } from '../models';
+import { makeSingleton } from '../utils';
+
+/** A function (or component) which returns text (or marked-up text) */
+type UiGeneratorText = (props: P) => string | React.ReactElement;
+
+/**
+ * This type defines all the UI override options which replace elements of Superset's default UI.
+ * Idea with the keys here is generally to namespace following the form of 'domain.functonality.item'
+ *
+ * When defining a new option here, take care to keep any parameters to functions (or components) minimal.
+ * Any removal or alteration to a parameter will be considered a breaking change.
+ */
+export type UiOverrides = Partial<{
+ 'embedded.documentation.description': UiGeneratorText;
+ 'embedded.documentation.url': string;
+}>;
+
+/**
+ * A registry containing UI customizations to replace elements of Superset's default UI.
+ */
+class UiOverrideRegistry extends TypedRegistry {
+ name = 'UiOverrideRegistry';
+}
+
+export const getUiOverrideRegistry = makeSingleton(UiOverrideRegistry, {});
diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/index.tsx b/superset-frontend/packages/superset-ui-core/src/ui-overrides/index.tsx
new file mode 100644
index 0000000000000..d59afc216fb8b
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/index.tsx
@@ -0,0 +1,20 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export * from './UiOverrideRegistry';
diff --git a/superset-frontend/packages/superset-ui-core/test/models/TypedRegistry.test.ts b/superset-frontend/packages/superset-ui-core/test/models/TypedRegistry.test.ts
new file mode 100644
index 0000000000000..7eb0d88f9d7eb
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-core/test/models/TypedRegistry.test.ts
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TypedRegistry } from '@superset-ui/core';
+
+describe('TypedRegistry', () => {
+ it('gets a value', () => {
+ const reg = new TypedRegistry({ foo: 'bar' });
+ expect(reg.get('foo')).toBe('bar');
+ });
+
+ it('sets a value', () => {
+ const reg = new TypedRegistry({ foo: 'bar' });
+ reg.set('foo', 'blah');
+ expect(reg.get('foo')).toBe('blah');
+ });
+});
diff --git a/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx b/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx
index d20cc2460090b..a2c44f490243f 100644
--- a/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardEmbedControls.tsx
@@ -17,7 +17,13 @@
* under the License.
*/
import React, { useCallback, useEffect, useState } from 'react';
-import { makeApi, styled, SupersetApiError, t } from '@superset-ui/core';
+import {
+ makeApi,
+ styled,
+ SupersetApiError,
+ t,
+ getUiOverrideRegistry,
+} from '@superset-ui/core';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import Modal from 'src/components/Modal';
import Loading from 'src/components/Loading';
@@ -27,6 +33,8 @@ import { useToasts } from 'src/components/MessageToasts/withToasts';
import { FormItem } from 'src/components/Form';
import { EmbeddedDashboard } from '../types';
+const uiOverrideRegistry = getUiOverrideRegistry();
+
type Props = {
dashboardId: string;
show: boolean;
@@ -140,6 +148,13 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
return ;
}
+ const docsDescription = uiOverrideRegistry.get(
+ 'embedded.documentation.description',
+ );
+ const docsUrl =
+ uiOverrideRegistry.get('embedded.documentation.url') ??
+ 'https://www.npmjs.com/package/@superset-ui/embedded-sdk';
+
return (
<>
@@ -159,12 +174,10 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
{t('For further instructions, consult the')}{' '}
-
- {t('Superset Embedded SDK documentation.')}
+
+ {docsDescription
+ ? docsDescription()
+ : t('Superset Embedded SDK documentation.')}
Settings
From 7bc9123fe569c47ebb9eb049c96ff833478f7ded Mon Sep 17 00:00:00 2001
From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com>
Date: Thu, 14 Apr 2022 12:58:34 -0300
Subject: [PATCH 050/136] fix: Filter dependencies are not being applied in
default values (#19698)
---
.../FiltersConfigForm/FiltersConfigForm.tsx | 40 +++++++++++++++++--
1 file changed, 36 insertions(+), 4 deletions(-)
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
index 50fb6e8ffe761..ff04e0ebe593c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -73,7 +73,10 @@ import { waitForAsyncData } from 'src/middleware/asyncEvent';
import { cacheWrapper } from 'src/utils/cacheWrapper';
import { ClientErrorObject } from 'src/utils/getClientErrorObject';
import { SingleValueType } from 'src/filters/components/Range/SingleValueType';
-import { getFormData } from 'src/dashboard/components/nativeFilters/utils';
+import {
+ getFormData,
+ mergeExtraFormData,
+} from 'src/dashboard/components/nativeFilters/utils';
import {
ALLOW_DEPENDENCIES as TYPES_SUPPORT_DEPENDENCIES,
getFiltersConfigModalTestId,
@@ -346,10 +349,11 @@ const FiltersConfigForm = (
const forceUpdate = useForceUpdate();
const [datasetDetails, setDatasetDetails] = useState>();
const defaultFormFilter = useMemo(() => ({}), []);
- const formValues = form.getFieldValue('filters')?.[filterId];
+ const filters = form.getFieldValue('filters');
+ const formValues = filters?.[filterId];
const formFilter = formValues || undoFormValues || defaultFormFilter;
- const dependencies =
+ const dependencies: string[] =
formFilter?.dependencies || filterToEdit?.cascadeParentIds;
const nativeFilterItems = getChartMetadataRegistry().items;
@@ -439,6 +443,21 @@ const FiltersConfigForm = (
forceUpdate();
};
+ // Calculates the dependencies default values to be used
+ // to extract the available values to the filter
+ let dependenciesDefaultValues = {};
+ if (dependencies && dependencies.length > 0 && filters) {
+ dependencies.forEach(dependency => {
+ const extraFormData = filters[dependency]?.defaultDataMask?.extraFormData;
+ dependenciesDefaultValues = mergeExtraFormData(
+ dependenciesDefaultValues,
+ extraFormData,
+ );
+ });
+ }
+
+ const dependenciesText = JSON.stringify(dependenciesDefaultValues);
+
const refreshHandler = useCallback(
(force = false) => {
if (!hasDataset || !formFilter?.dataset?.value) {
@@ -450,6 +469,9 @@ const FiltersConfigForm = (
groupby: formFilter?.column,
...formFilter,
});
+
+ formData.extra_form_data = dependenciesDefaultValues;
+
setNativeFilterFieldValuesWrapper({
defaultValueQueriesData: null,
isDataDirty: false,
@@ -499,14 +521,19 @@ const FiltersConfigForm = (
});
});
},
- [filterId, forceUpdate, form, formFilter, hasDataset],
+ [filterId, forceUpdate, form, formFilter, hasDataset, dependenciesText],
);
+ // TODO: refreshHandler changes itself because of the dependencies. Needs refactor.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ useEffect(() => refreshHandler(), [dependenciesText]);
+
const newFormData = getFormData({
datasetId,
groupby: hasColumn ? formFilter?.column : undefined,
...formFilter,
});
+ newFormData.extra_form_data = dependenciesDefaultValues;
const [hasDefaultValue, isRequired, defaultValueTooltip, setHasDefaultValue] =
useDefaultValue(formFilter, filterToEdit);
@@ -1084,6 +1111,11 @@ const FiltersConfigForm = (
tooltip={defaultValueTooltip}
onChange={value => {
setHasDefaultValue(value);
+ if (!value) {
+ setNativeFilterFieldValues(form, filterId, {
+ defaultDataMask: null,
+ });
+ }
formChanged();
}}
>
From 34008f78c9456bd40c0893dd8e239b5c47b612e4 Mon Sep 17 00:00:00 2001
From: Evan Rusackas
Date: Thu, 14 Apr 2022 10:08:11 -0600
Subject: [PATCH 051/136] feat: Enabling source maps full time (#19710)
---
superset-frontend/webpack.config.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index c2c818dbeadd0..d6b280c7537e0 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -363,7 +363,7 @@ const config = {
{
loader: 'css-loader',
options: {
- sourceMap: isDevMode,
+ sourceMap: true,
},
},
],
@@ -376,13 +376,13 @@ const config = {
{
loader: 'css-loader',
options: {
- sourceMap: isDevMode,
+ sourceMap: true,
},
},
{
loader: 'less-loader',
options: {
- sourceMap: isDevMode,
+ sourceMap: true,
javascriptEnabled: true,
},
},
@@ -450,7 +450,7 @@ const config = {
'react/lib/ReactContext': true,
},
plugins,
- devtool: false,
+ devtool: 'source-map',
};
// find all the symlinked plugins and use their source code for imports
From ac2c66ccf60fa809f0db749ffa955c5822129ea0 Mon Sep 17 00:00:00 2001
From: Smart-Codi
Date: Thu, 14 Apr 2022 13:46:32 -0400
Subject: [PATCH 052/136] fix: Line Chart Annotation Info Update (#19001)
* fix Line Chart Annotation Info Updage
* wrap annotation text with transation
* resolve comment
---
.../AnnotationLayerControl/AnnotationLayer.jsx | 12 +++++++-----
superset/translations/zh/LC_MESSAGES/messages.json | 4 ++--
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
index e12ca06cc943c..82c5fef2c2c9d 100644
--- a/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
+++ b/superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx
@@ -385,11 +385,13 @@ export default class AnnotationLayer extends React.PureComponent {
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(', ')}]`;
+ description = t(
+ `Use another existing chart as a source for annotations and overlays.
+ Your chart must be one of these visualization types: [%s]`,
+ this.getSupportedSourceTypes(annotationType)
+ .map(x => x.label)
+ .join(', '),
+ );
}
} else if (annotationType === ANNOTATION_TYPES.FORMULA) {
label = 'Formula';
diff --git a/superset/translations/zh/LC_MESSAGES/messages.json b/superset/translations/zh/LC_MESSAGES/messages.json
index 3b3157bde9d27..3c09c4a3d5d71 100644
--- a/superset/translations/zh/LC_MESSAGES/messages.json
+++ b/superset/translations/zh/LC_MESSAGES/messages.json
@@ -2824,8 +2824,8 @@
"Annotation Source": ["注释来源"],
"No options": ["没有选项"],
"Superset annotation": ["Superset注释"],
- "Use a pre defined Superset Chart as a source for annotations and overlays. your chart must be one of these visualization types:": [
- "使用预定义的图表作为注释和覆盖的源。图表必须是以下可视化类型之一:"
+ "Use another existing chart as a source for annotations and overlays. Your chart must be one of these visualization types: [%s]": [
+ "使用预定义的图表作为注释和覆盖的源。图表必须是以下可视化类型之一: [%s]"
],
"Expects a formula with depending time parameter 'x'\n in milliseconds since epoch. mathjs is used to evaluate the formulas.\n Example: '2x+5'": [
"需要一个从Epoch(1970年1月1日00:00:00 UTC)时间点开始的时间参数“x”,并以此来计算的公式(以毫秒为单位)。我们使用“mathjs”来进行公式的计算。例如:'2x+5'"
From 158852d0744514ee88dcc858de949b711de06e20 Mon Sep 17 00:00:00 2001
From: AAfghahi <48933336+AAfghahi@users.noreply.github.com>
Date: Thu, 14 Apr 2022 16:02:02 -0400
Subject: [PATCH 053/136] bumping shillelagh (#19726)
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 26f709d7796f6..d7544ca3a3d99 100644
--- a/setup.py
+++ b/setup.py
@@ -140,7 +140,7 @@ def get_git_sha() -> str:
"excel": ["xlrd>=1.2.0, <1.3"],
"firebird": ["sqlalchemy-firebird>=0.7.0, <0.8"],
"firebolt": ["firebolt-sqlalchemy>=0.0.1"],
- "gsheets": ["shillelagh[gsheetsapi]>=1.0.3, <2"],
+ "gsheets": ["shillelagh[gsheetsapi]>=1.0.11, <2"],
"hana": ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"],
"hive": ["pyhive[hive]>=0.6.1", "tableschema", "thrift>=0.11.0, <1.0.0"],
"impala": ["impyla>0.16.2, <0.17"],
From 94075983f8abfcc7749cede5af9e24d2a9f1abe0 Mon Sep 17 00:00:00 2001
From: Smart-Codi
Date: Thu, 14 Apr 2022 16:34:15 -0400
Subject: [PATCH 054/136] feat: Remove legacy sql alchemy db connection link
from G Sheet connection (#19450)
* feat: Remove legacy sql alchemy db connection link from G Sheet connection
* resolve comment
---
.../data/database/DatabaseModal/index.tsx | 58 ++++++++++---------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
index dee6f4dd3f9dc..c4faa8a483ebe 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/index.tsx
@@ -139,6 +139,8 @@ const errorAlertMapping = {
),
},
};
+const googleSheetConnectionEngine = 'gsheets';
+
interface DatabaseModalProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
@@ -1575,32 +1577,36 @@ const DatabaseModal: FunctionComponent = ({
validationErrors={validationErrors}
/>
infoTooltip(theme)}>
-
-
+ {dbModel.engine !== googleSheetConnectionEngine && (
+ <>
+
+
+ >
+ )}
{/* Step 2 */}
{showDBError && errorAlert()}
From c320c295dcf73f2575d371eaf0c2ba9f9eef6141 Mon Sep 17 00:00:00 2001
From: smileydev <47900232+prosdev0107@users.noreply.github.com>
Date: Fri, 15 Apr 2022 07:43:25 -0400
Subject: [PATCH 055/136] fix(dnd&column): make to fix the blank state issue
when only one column select (#19651)
---
.../controls/DndColumnSelectControl/DndColumnSelect.tsx | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
index c68ee009ea178..529d4c7c78f8b 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
@@ -127,14 +127,7 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
);
const popoverOptions = useMemo(
- () =>
- Object.values(options).filter(
- col =>
- !optionSelector.values
- .filter(isColumnMeta)
- .map((val: ColumnMeta) => val.column_name)
- .includes(col.column_name),
- ),
+ () => Object.values(options),
[optionSelector.values, options],
);
From ce77d55396cad6428fcfa2fe6fe991514a16525e Mon Sep 17 00:00:00 2001
From: Cemre Mengu
Date: Fri, 15 Apr 2022 14:51:32 +0300
Subject: [PATCH 056/136] docs(install): ubuntu default-libmysqlclient-dev
(#19715)
add `default-libmysqlclient-dev` to required OS dependencies for ubuntu
---
docs/docs/installation/installing-superset-from-scratch.mdx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/docs/installation/installing-superset-from-scratch.mdx b/docs/docs/installation/installing-superset-from-scratch.mdx
index d64db45a84c2c..195f9cfd11e2b 100644
--- a/docs/docs/installation/installing-superset-from-scratch.mdx
+++ b/docs/docs/installation/installing-superset-from-scratch.mdx
@@ -18,13 +18,13 @@ level dependencies.
The following command will ensure that the required dependencies are installed:
```
-sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev
+sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
In Ubuntu 20.04 the following command will ensure that the required dependencies are installed:
```
-sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev
+sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python3-pip libsasl2-dev libldap2-dev default-libmysqlclient-dev
```
**Fedora and RHEL-derivative Linux distributions**
From b7759e6fd138aff83cc80eea23069268ecc717d9 Mon Sep 17 00:00:00 2001
From: Kamil Gabryjelski
Date: Fri, 15 Apr 2022 14:56:34 +0200
Subject: [PATCH 057/136] chore: Clean redundant dependency from useMemo dep
array (#19732)
---
.../controls/DndColumnSelectControl/DndColumnSelect.tsx | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
index 529d4c7c78f8b..e7b25908cf11c 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
@@ -126,10 +126,7 @@ export function DndColumnSelect(props: DndColumnSelectProps) {
[onChange, optionSelector],
);
- const popoverOptions = useMemo(
- () => Object.values(options),
- [optionSelector.values, options],
- );
+ const popoverOptions = useMemo(() => Object.values(options), [options]);
const valuesRenderer = useCallback(
() =>
From 86642025ba0bb555e6e5d6c058a74e98d997f2e8 Mon Sep 17 00:00:00 2001
From: "Hugh A. Miles II"
Date: Fri, 15 Apr 2022 13:17:05 -0400
Subject: [PATCH 058/136] fix: Removetime_range_endpoints from query context
object pt 2 (#19728)
---
...0b36b94_rm_time_range_endpoints_from_qc.py | 33 +----
.../cecc6bf46990_rm_time_range_endpoints_2.py | 74 ++++++++++
...f46990_rm_time_range_endpoints_2__tests.py | 130 ++++++++++++++++++
3 files changed, 205 insertions(+), 32 deletions(-)
create mode 100644 superset/migrations/versions/cecc6bf46990_rm_time_range_endpoints_2.py
create mode 100644 tests/integration_tests/migrations/cecc6bf46990_rm_time_range_endpoints_2__tests.py
diff --git a/superset/migrations/versions/2ed890b36b94_rm_time_range_endpoints_from_qc.py b/superset/migrations/versions/2ed890b36b94_rm_time_range_endpoints_from_qc.py
index 42d73bc33d335..e4e4718a41173 100644
--- a/superset/migrations/versions/2ed890b36b94_rm_time_range_endpoints_from_qc.py
+++ b/superset/migrations/versions/2ed890b36b94_rm_time_range_endpoints_from_qc.py
@@ -26,40 +26,9 @@
revision = "2ed890b36b94"
down_revision = "58df9d617f14"
-import json
-
-import sqlalchemy as sa
-from alembic import op
-from sqlalchemy.ext.declarative import declarative_base
-
-from superset import db
-
-Base = declarative_base()
-
-
-class Slice(Base):
- __tablename__ = "slices"
- id = sa.Column(sa.Integer, primary_key=True)
- query_context = sa.Column(sa.Text)
-
def upgrade():
- bind = op.get_bind()
- session = db.Session(bind=bind)
- for slc in session.query(Slice).filter(
- Slice.query_context.like("%time_range_endpoints%")
- ):
- try:
- query_context = json.loads(slc.query_context)
- except json.decoder.JSONDecodeError:
- continue
- queries = query_context.get("queries")
- for query in queries:
- query.get("extras", {}).pop("time_range_endpoints", None)
- slc.queries = json.dumps(queries)
-
- session.commit()
- session.close()
+ pass
def downgrade():
diff --git a/superset/migrations/versions/cecc6bf46990_rm_time_range_endpoints_2.py b/superset/migrations/versions/cecc6bf46990_rm_time_range_endpoints_2.py
new file mode 100644
index 0000000000000..20d797ddbaf20
--- /dev/null
+++ b/superset/migrations/versions/cecc6bf46990_rm_time_range_endpoints_2.py
@@ -0,0 +1,74 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""rm_time_range_endpoints_2
+
+Revision ID: cecc6bf46990
+Revises: 9d8a8d575284
+Create Date: 2022-04-14 17:21:53.996022
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "cecc6bf46990"
+down_revision = "9d8a8d575284"
+
+import json
+
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.ext.declarative import declarative_base
+
+from superset import db
+
+Base = declarative_base()
+
+
+class Slice(Base):
+ __tablename__ = "slices"
+ id = sa.Column(sa.Integer, primary_key=True)
+ query_context = sa.Column(sa.Text)
+ slice_name = sa.Column(sa.String(250))
+
+
+def upgrade_slice(slc: Slice):
+ try:
+ query_context = json.loads(slc.query_context)
+ except json.decoder.JSONDecodeError:
+ return
+
+ queries = query_context.get("queries")
+
+ for query in queries:
+ query.get("extras", {}).pop("time_range_endpoints", None)
+
+ slc.query_context = json.dumps(query_context)
+
+
+def upgrade():
+ bind = op.get_bind()
+ session = db.Session(bind=bind)
+ for slc in session.query(Slice).filter(
+ Slice.query_context.like("%time_range_endpoints%")
+ ):
+ upgrade_slice(slc)
+
+ session.commit()
+ session.close()
+
+
+def downgrade():
+ pass
diff --git a/tests/integration_tests/migrations/cecc6bf46990_rm_time_range_endpoints_2__tests.py b/tests/integration_tests/migrations/cecc6bf46990_rm_time_range_endpoints_2__tests.py
new file mode 100644
index 0000000000000..26d9eec0a5e75
--- /dev/null
+++ b/tests/integration_tests/migrations/cecc6bf46990_rm_time_range_endpoints_2__tests.py
@@ -0,0 +1,130 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import json
+
+from superset.migrations.versions.cecc6bf46990_rm_time_range_endpoints_2 import (
+ Slice,
+ upgrade_slice,
+)
+
+sample_query_context = {
+ "datasource": {"id": 27, "type": "table"},
+ "force": False,
+ "queries": [
+ {
+ "time_range": "No filter",
+ "filters": [],
+ "extras": {
+ "time_grain_sqla": "P1D",
+ "time_range_endpoints": ["inclusive", "exclusive"],
+ "having": "",
+ "having_druid": [],
+ "where": "",
+ },
+ "applied_time_extras": {},
+ "columns": ["a", "b"],
+ "orderby": [],
+ "annotation_layers": [],
+ "row_limit": 1000,
+ "timeseries_limit": 0,
+ "order_desc": True,
+ "url_params": {},
+ "custom_params": {},
+ "custom_form_data": {},
+ "post_processing": [],
+ }
+ ],
+ "form_data": {
+ "viz_type": "table",
+ "datasource": "27__table",
+ "slice_id": 545,
+ "url_params": {},
+ "time_grain_sqla": "P1D",
+ "time_range": "No filter",
+ "query_mode": "raw",
+ "groupby": [],
+ "metrics": [],
+ "all_columns": ["a", "b"],
+ "percent_metrics": [],
+ "adhoc_filters": [],
+ "order_by_cols": [],
+ "row_limit": 1000,
+ "server_page_length": 10,
+ "include_time": False,
+ "order_desc": True,
+ "table_timestamp_format": "smart_date",
+ "show_cell_bars": True,
+ "color_pn": True,
+ "extra_form_data": {},
+ "force": False,
+ "result_format": "json",
+ "result_type": "full",
+ },
+ "result_format": "json",
+ "result_type": "full",
+}
+
+
+sample_query_context = {
+ "datasource": {"id": 27, "type": "table"},
+ "force": False,
+ "queries": [
+ {
+ "time_range": "No filter",
+ "filters": [],
+ "extras": {
+ "time_grain_sqla": "P1D",
+ "time_range_endpoints": ["inclusive", "exclusive"],
+ "having": "",
+ "having_druid": [],
+ "where": "",
+ },
+ "applied_time_extras": {},
+ "columns": ["a", "b"],
+ "orderby": [],
+ "annotation_layers": [],
+ "row_limit": 1000,
+ "timeseries_limit": 0,
+ "order_desc": True,
+ "url_params": {},
+ "custom_params": {},
+ "custom_form_data": {},
+ "post_processing": [],
+ }
+ ],
+ "form_data": {},
+ "result_format": "json",
+ "result_type": "full",
+}
+
+
+def test_upgrade():
+ slc = Slice(slice_name="FOO", query_context=json.dumps(sample_query_context))
+
+ upgrade_slice(slc)
+
+ query_context = json.loads(slc.query_context)
+ queries = query_context.get("queries")
+ for q in queries:
+ extras = q.get("extras", {})
+ assert "time_range_endpoints" not in extras
+
+
+def test_upgrade_bad_json():
+ slc = Slice(slice_name="FOO", query_context="abc")
+
+ assert None == upgrade_slice(slc)
From 82c47f32a0a2a4615460a6af55883eda3043d5c0 Mon Sep 17 00:00:00 2001
From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com>
Date: Fri, 15 Apr 2022 14:37:52 -0300
Subject: [PATCH 059/136] chore: Updates the Select codeowners (#19736)
---
.github/CODEOWNERS | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 82efcf6558410..e2f6a79affc53 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,14 +1,19 @@
# Notify all committers of DB migration changes, per SIP-59
+
# https://github.com/apache/superset/issues/13351
+
/superset/migrations/ @apache/superset-committers
# Notify Preset team when ephemeral env settings are changed
+
.github/workflows/ecs-task-definition.json @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/docker-ephemeral-env.yml @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch
.github/workflows/ephemeral*.yml @robdiciuccio @craig-rueda @rusackas @eschutho @dpgaspar @nytai @mistercrunch
# Notify some committers of changes in the Select component
-/superset-frontend/src/components/Select/ @michael-s-molina @geido
+
+/superset-frontend/src/components/Select/ @michael-s-molina @geido @ktmud
# Notify Helm Chart maintainers about changes in it
+
/helm/superset/ @craig-rueda @dpgaspar @villebro
From b8e595413fa02b5f00c7b91df6283701a5f1b972 Mon Sep 17 00:00:00 2001
From: Geido <60598000+geido@users.noreply.github.com>
Date: Fri, 15 Apr 2022 20:15:30 +0200
Subject: [PATCH 060/136] fix: Redirect to full url on 401 (#19357)
* Redirect to full url
* Redirect to full url
* Update test
---
.../superset-ui-core/src/connection/SupersetClientClass.ts | 4 +---
.../test/connection/SupersetClientClass.test.ts | 5 ++---
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts b/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts
index 5e046bce7e929..7a6dfd97b0207 100644
--- a/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts
+++ b/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts
@@ -35,9 +35,7 @@ import { DEFAULT_FETCH_RETRY_OPTIONS, DEFAULT_BASE_URL } from './constants';
const defaultUnauthorizedHandler = () => {
if (!window.location.pathname.startsWith('/login')) {
- window.location.href = `/login?next=${
- window.location.pathname + window.location.search
- }`;
+ window.location.href = `/login?next=${window.location.href}`;
}
};
diff --git a/superset-frontend/packages/superset-ui-core/test/connection/SupersetClientClass.test.ts b/superset-frontend/packages/superset-ui-core/test/connection/SupersetClientClass.test.ts
index a17cbceb223d7..ef31e5d35d857 100644
--- a/superset-frontend/packages/superset-ui-core/test/connection/SupersetClientClass.test.ts
+++ b/superset-frontend/packages/superset-ui-core/test/connection/SupersetClientClass.test.ts
@@ -505,8 +505,7 @@ describe('SupersetClientClass', () => {
const mockRequestUrl = 'https://host/get/url';
const mockRequestPath = '/get/url';
const mockRequestSearch = '?param=1¶m=2';
- const mockRequestRelativeUrl = mockRequestPath + mockRequestSearch;
- const mockHref = `http://localhost${mockRequestRelativeUrl}`;
+ const mockHref = mockRequestUrl + mockRequestSearch;
beforeEach(() => {
originalLocation = window.location;
@@ -542,7 +541,7 @@ describe('SupersetClientClass', () => {
error = err;
} finally {
const redirectURL = window.location.href;
- expect(redirectURL).toBe(`/login?next=${mockRequestRelativeUrl}`);
+ expect(redirectURL).toBe(`/login?next=${mockHref}`);
expect(error.status).toBe(401);
}
});
From 56381f4ee8a7a1e36fcb33b2b3a5e16793ab0b8b Mon Sep 17 00:00:00 2001
From: Luis Casillas
Date: Fri, 15 Apr 2022 13:28:40 -0500
Subject: [PATCH 061/136] fix: sql lab ctrl t behaved differently from clicking
(#19420)
* initial approach
To have consistent behaviour when creating a new tab with ctrl+t
* consistent behaviour when creating tabs
* fixed bug and added tests
* updates to pass checks
---
.../src/SqlLab/components/SqlEditor/index.jsx | 10 ++--
.../components/TabbedSqlEditors/index.jsx | 15 +-----
.../src/SqlLab/utils/newQueryTabName.test.ts | 46 +++++++++++++++++
.../src/SqlLab/utils/newQueryTabName.ts | 49 +++++++++++++++++++
4 files changed, 102 insertions(+), 18 deletions(-)
create mode 100644 superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts
create mode 100644 superset-frontend/src/SqlLab/utils/newQueryTabName.ts
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index 7899cbf71908a..14e8caa0113dc 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -75,6 +75,7 @@ import ShareSqlLabQuery from '../ShareSqlLabQuery';
import SqlEditorLeftBar from '../SqlEditorLeftBar';
import AceEditorWrapper from '../AceEditorWrapper';
import RunQueryActionButton from '../RunQueryActionButton';
+import { newQueryTabName } from '../../utils/newQueryTabName';
const LIMIT_DROPDOWN = [10, 100, 1000, 10000, 100000];
const SQL_EDITOR_PADDING = 10;
@@ -333,10 +334,10 @@ class SqlEditor extends React.PureComponent {
key: userOS === 'Windows' ? 'ctrl+q' : 'ctrl+t',
descr: t('New tab'),
func: () => {
+ const title = newQueryTabName(this.props.queryEditors || []);
this.props.addQueryEditor({
...this.props.queryEditor,
- title: t('Untitled query'),
- sql: '',
+ title,
});
},
},
@@ -804,13 +805,12 @@ class SqlEditor extends React.PureComponent {
SqlEditor.defaultProps = defaultProps;
SqlEditor.propTypes = propTypes;
-function mapStateToProps(state, props) {
- const { sqlLab } = state;
+function mapStateToProps({ sqlLab }, props) {
const queryEditor = sqlLab.queryEditors.find(
editor => editor.id === props.queryEditorId,
);
- return { sqlLab, ...props, queryEditor };
+ return { sqlLab, ...props, queryEditor, queryEditors: sqlLab.queryEditors };
}
function mapDispatchToProps(dispatch) {
diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
index 8c20a493b0876..494ef9cba0ef7 100644
--- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
+++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx
@@ -31,6 +31,7 @@ import { Tooltip } from 'src/components/Tooltip';
import { detectOS } from 'src/utils/common';
import * as Actions from 'src/SqlLab/actions/sqlLab';
import { EmptyStateBig } from 'src/components/EmptyState';
+import { newQueryTabName } from '../../utils/newQueryTabName';
import SqlEditor from '../SqlEditor';
import TabStatusIcon from '../TabStatusIcon';
@@ -262,19 +263,7 @@ class TabbedSqlEditors extends React.PureComponent {
'-- Note: Unless you save your query, these tabs will NOT persist if you clear your cookies or change browsers.\n\n',
);
- let newTitle = 'Untitled Query 1';
-
- if (this.props.queryEditors.length > 0) {
- const untitledQueryNumbers = this.props.queryEditors
- .filter(x => x.title.match(/^Untitled Query (\d+)$/))
- .map(x => x.title.replace('Untitled Query ', ''));
- if (untitledQueryNumbers.length > 0) {
- // When there are query tabs open, and at least one is called "Untitled Query #"
- // Where # is a valid number
- const largestNumber = Math.max.apply(null, untitledQueryNumbers);
- newTitle = t('Untitled Query %s', largestNumber + 1);
- }
- }
+ const newTitle = newQueryTabName(this.props.queryEditors || []);
const qe = {
title: newTitle,
diff --git a/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts b/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts
new file mode 100644
index 0000000000000..d0d98c3cd5e29
--- /dev/null
+++ b/superset-frontend/src/SqlLab/utils/newQueryTabName.test.ts
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { newQueryTabName } from './newQueryTabName';
+
+const emptyEditor = {
+ title: '',
+ schema: '',
+ autorun: false,
+ sql: '',
+ remoteId: null,
+};
+
+describe('newQueryTabName', () => {
+ it("should return default title if queryEditor's length is 0", () => {
+ const defaultTitle = 'default title';
+ const title = newQueryTabName([], defaultTitle);
+ expect(title).toEqual(defaultTitle);
+ });
+ it('should return next available number if there are unsaved editors', () => {
+ const untitledQueryText = 'Untitled Query';
+ const unsavedEditors = [
+ { ...emptyEditor, title: `${untitledQueryText} 1` },
+ { ...emptyEditor, title: `${untitledQueryText} 2` },
+ ];
+
+ const nextTitle = newQueryTabName(unsavedEditors);
+ expect(nextTitle).toEqual(`${untitledQueryText} 3`);
+ });
+});
diff --git a/superset-frontend/src/SqlLab/utils/newQueryTabName.ts b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts
new file mode 100644
index 0000000000000..a719a74af59af
--- /dev/null
+++ b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { t } from '@superset-ui/core';
+import { QueryEditor } from '../types';
+
+const untitledQueryRegex = /^Untitled Query (\d+)$/; // Literal notation isn't recompiled
+const untitledQuery = 'Untitled Query ';
+
+export const newQueryTabName = (
+ queryEditors: QueryEditor[],
+ initialTitle = `${untitledQuery}1`,
+): string => {
+ const resultTitle = t(initialTitle);
+
+ if (queryEditors.length > 0) {
+ const mappedUntitled = queryEditors.filter(qe =>
+ qe.title.match(untitledQueryRegex),
+ );
+ const untitledQueryNumbers = mappedUntitled.map(
+ qe => +qe.title.replace(untitledQuery, ''),
+ );
+ if (untitledQueryNumbers.length > 0) {
+ // When there are query tabs open, and at least one is called "Untitled Query #"
+ // Where # is a valid number
+ const largestNumber: number = Math.max(...untitledQueryNumbers);
+ return t(`${untitledQuery}%s`, largestNumber + 1);
+ }
+ return resultTitle;
+ }
+
+ return resultTitle;
+};
From bbe0af348bd0c973ce62f1a0b95fd9b7c04fd97e Mon Sep 17 00:00:00 2001
From: Diego Medina
Date: Fri, 15 Apr 2022 14:49:32 -0400
Subject: [PATCH 062/136] fix(sql lab): Selecting edit on a query from query
history doesn't update the SQL Editor properly (#19290)
---
.../SqlLab/components/AceEditorWrapper/index.tsx | 16 ++++++++++++----
.../src/SqlLab/components/SqlEditor/index.jsx | 6 ++++++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
index 35722ba866066..e2e141608c61f 100644
--- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
+++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
@@ -66,7 +66,6 @@ interface Props {
interface State {
sql: string;
- selectedText: string;
words: AceCompleterKeyword[];
}
@@ -80,13 +79,20 @@ class AceEditorWrapper extends React.PureComponent {
extendedTables: [],
};
+ private currentSelectionCache;
+
constructor(props: Props) {
super(props);
this.state = {
sql: props.sql,
- selectedText: '',
words: [],
};
+
+ // The editor changeSelection is called multiple times in a row,
+ // faster than React reconciliation process, so the selected text
+ // needs to be stored out of the state to ensure changes to it
+ // get saved immediately
+ this.currentSelectionCache = '';
this.onChange = this.onChange.bind(this);
}
@@ -146,17 +152,19 @@ class AceEditorWrapper extends React.PureComponent {
editor.$blockScrolling = Infinity; // eslint-disable-line no-param-reassign
editor.selection.on('changeSelection', () => {
const selectedText = editor.getSelectedText();
+
// Backspace trigger 1 character selection, ignoring
if (
- selectedText !== this.state.selectedText &&
+ selectedText !== this.currentSelectionCache &&
selectedText.length !== 1
) {
- this.setState({ selectedText });
this.props.actions.queryEditorSetSelectedText(
this.props.queryEditor,
selectedText,
);
}
+
+ this.currentSelectionCache = selectedText;
});
}
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index 14e8caa0113dc..c6708b266c6f1 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -240,6 +240,12 @@ class SqlEditor extends React.PureComponent {
});
}
+ componentDidUpdate() {
+ if (this.props.queryEditor.sql !== this.state.sql) {
+ this.onSqlChanged(this.props.queryEditor.sql);
+ }
+ }
+
componentWillUnmount() {
window.removeEventListener('resize', this.handleWindowResize);
window.removeEventListener('beforeunload', this.onBeforeUnload);
From 8d4a52c9d014047baecbdab76f48eb729e3842dc Mon Sep 17 00:00:00 2001
From: Diego Medina
Date: Fri, 15 Apr 2022 14:55:04 -0400
Subject: [PATCH 063/136] fix(sql lab): add quotes when autocompleting table
names with spaces in the editor (#19311)
---
.../src/SqlLab/components/AceEditorWrapper/index.tsx | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
index e2e141608c61f..53ec3f808a62f 100644
--- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
+++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/index.tsx
@@ -227,11 +227,15 @@ class AceEditorWrapper extends React.PureComponent {
this.props.queryEditor.schema,
);
}
+
+ let { caption } = data;
+ if (data.meta === 'table' && caption.includes(' ')) {
+ caption = `"${caption}"`;
+ }
+
// executing https://github.com/thlorenz/brace/blob/3a00c5d59777f9d826841178e1eb36694177f5e6/ext/language_tools.js#L1448
editor.completer.insertMatch(
- `${data.caption}${
- ['function', 'schema'].includes(data.meta) ? '' : ' '
- }`,
+ `${caption}${['function', 'schema'].includes(data.meta) ? '' : ' '}`,
);
},
};
From 154f1ea8c92e38ba46a906d2121a85a1c9fac310 Mon Sep 17 00:00:00 2001
From: Diego Medina
Date: Fri, 15 Apr 2022 14:58:32 -0400
Subject: [PATCH 064/136] fix: Dashboard Edit View Tab Headers Hidden when
Dashboard Name is Long (#19472)
---
.../DashboardBuilder/DashboardBuilder.tsx | 29 ++++++++++++++-----
superset-frontend/src/dashboard/constants.ts | 2 --
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index ab1e95b9a1b47..11a5b28e561b2 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -18,7 +18,7 @@
*/
/* eslint-env browser */
import cx from 'classnames';
-import React, { FC, useCallback, useMemo } from 'react';
+import React, { FC, useCallback, useEffect, useState, useMemo } from 'react';
import { JsonObject, styled, css, t } from '@superset-ui/core';
import { Global } from '@emotion/react';
import { useDispatch, useSelector } from 'react-redux';
@@ -59,10 +59,8 @@ import {
CLOSED_FILTER_BAR_WIDTH,
FILTER_BAR_HEADER_HEIGHT,
FILTER_BAR_TABS_HEIGHT,
- HEADER_HEIGHT,
MAIN_HEADER_HEIGHT,
OPEN_FILTER_BAR_WIDTH,
- TABS_HEIGHT,
} from 'src/dashboard/constants';
import { shouldFocusTabs, getRootLevelTabsComponent } from './utils';
import DashboardContainer from './DashboardContainer';
@@ -252,6 +250,7 @@ const DashboardBuilder: FC = () => {
[dispatch],
);
+ const headerRef = React.useRef(null);
const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
const rootChildId = dashboardRoot?.children[0];
const topLevelTabs =
@@ -264,10 +263,26 @@ const DashboardBuilder: FC = () => {
uiConfig.hideTitle ||
standaloneMode === DashboardStandaloneMode.HIDE_NAV_AND_TITLE ||
isReport;
+ const [barTopOffset, setBarTopOffset] = useState(0);
- const barTopOffset =
- (hideDashboardHeader ? 0 : HEADER_HEIGHT) +
- (topLevelTabs ? TABS_HEIGHT : 0);
+ useEffect(() => {
+ setBarTopOffset(headerRef.current?.getBoundingClientRect()?.height || 0);
+
+ let observer: ResizeObserver;
+ if (typeof global.ResizeObserver !== 'undefined' && headerRef.current) {
+ observer = new ResizeObserver(entries => {
+ setBarTopOffset(
+ current => entries?.[0]?.contentRect?.height || current,
+ );
+ });
+
+ observer.observe(headerRef.current);
+ }
+
+ return () => {
+ observer?.disconnect();
+ };
+ }, []);
const {
showDashboard,
@@ -364,7 +379,7 @@ const DashboardBuilder: FC = () => {
)}
-
+
{/* @ts-ignore */}
Date: Fri, 15 Apr 2022 15:09:07 -0700
Subject: [PATCH 065/136] feat: add empty states to sqlab editor and select
(#19598)
* feat: add empty states to sqlab editor and select
* add suggestions and test
* update type
* lint fix and add suggestions
* fix typo
* run lint
* remove unused code
* fix test
* remove redux for propagation and other suggestions
* add t
* lint
* fix text and remove code
* ts and fix t in p
* fix spelling
* remove unused prop
* add fn to prop change state
* remove unused code
* remove unused types
* update code and test
* fix lint
* fix ts
* update ts
* add type export and fix test
* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
* Update superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
* remove handlerror and unused code
Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
---
.../components/SqlEditor/SqlEditor.test.jsx | 21 +++++++++-
.../src/SqlLab/components/SqlEditor/index.jsx | 25 +++++++++++-
.../components/SqlEditorLeftBar/index.tsx | 39 ++++++++++++++++++-
superset-frontend/src/SqlLab/types.ts | 1 +
.../src/assets/images/vector.svg | 21 ++++++++++
.../DatabaseSelector.test.tsx | 33 ++++++++++++----
.../src/components/DatabaseSelector/index.tsx | 16 +++++---
.../src/components/Select/Select.tsx | 2 +-
.../src/components/TableSelector/index.tsx | 6 +++
9 files changed, 147 insertions(+), 17 deletions(-)
create mode 100644 superset-frontend/src/assets/images/vector.svg
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx
index f3549b547f8b1..d946c675cc8c4 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx
@@ -38,6 +38,7 @@ import {
queryEditorSetSelectedText,
queryEditorSetSchemaOptions,
} from 'src/SqlLab/actions/sqlLab';
+import { EmptyStateBig } from 'src/components/EmptyState';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import { initialState, queries, table } from 'src/SqlLab/fixtures';
@@ -57,7 +58,19 @@ describe('SqlEditor', () => {
queryEditorSetSchemaOptions,
addDangerToast: jest.fn(),
},
- database: {},
+ database: {
+ allow_ctas: false,
+ allow_cvas: false,
+ allow_dml: false,
+ allow_file_upload: false,
+ allow_multi_schema_metadata_fetch: false,
+ allow_run_async: false,
+ backend: 'postgresql',
+ database_name: 'examples',
+ expose_in_sqllab: true,
+ force_ctas_schema: null,
+ id: 1,
+ },
queryEditorId: initialState.sqlLab.queryEditors[0].id,
latestQuery: queries[0],
tables: [table],
@@ -80,6 +93,12 @@ describe('SqlEditor', () => {
},
);
+ it('does not render SqlEditor if no db selected', () => {
+ const database = {};
+ const updatedProps = { ...mockedProps, database };
+ const wrapper = buildWrapper(updatedProps);
+ expect(wrapper.find(EmptyStateBig)).toExist();
+ });
it('render a SqlEditorLeftBar', async () => {
const wrapper = buildWrapper();
await waitForComponentToPaint(wrapper);
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
index c6708b266c6f1..df1a9a77c57a6 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx
@@ -66,6 +66,8 @@ import {
setItem,
} from 'src/utils/localStorageHelpers';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
+import { EmptyStateBig } from 'src/components/EmptyState';
+import { isEmpty } from 'lodash';
import TemplateParamsEditor from '../TemplateParamsEditor';
import ConnectedSouthPane from '../SouthPane/state';
import SaveQuery from '../SaveQuery';
@@ -180,6 +182,7 @@ class SqlEditor extends React.PureComponent {
),
showCreateAsModal: false,
createAs: '',
+ showEmptyState: false,
};
this.sqlEditorRef = React.createRef();
this.northPaneRef = React.createRef();
@@ -189,6 +192,7 @@ class SqlEditor extends React.PureComponent {
this.onResizeEnd = this.onResizeEnd.bind(this);
this.canValidateQuery = this.canValidateQuery.bind(this);
this.runQuery = this.runQuery.bind(this);
+ this.setEmptyState = this.setEmptyState.bind(this);
this.stopQuery = this.stopQuery.bind(this);
this.saveQuery = this.saveQuery.bind(this);
this.onSqlChanged = this.onSqlChanged.bind(this);
@@ -228,7 +232,11 @@ class SqlEditor extends React.PureComponent {
// We need to measure the height of the sql editor post render to figure the height of
// the south pane so it gets rendered properly
// eslint-disable-next-line react/no-did-mount-set-state
+ const db = this.props.database;
this.setState({ height: this.getSqlEditorHeight() });
+ if (!db || isEmpty(db)) {
+ this.setEmptyState(true);
+ }
window.addEventListener('resize', this.handleWindowResize);
window.addEventListener('beforeunload', this.onBeforeUnload);
@@ -369,6 +377,10 @@ class SqlEditor extends React.PureComponent {
return base;
}
+ setEmptyState(bool) {
+ this.setState({ showEmptyState: bool });
+ }
+
setQueryEditorSql(sql) {
this.props.queryEditorSetSql(this.props.queryEditor, sql);
}
@@ -760,10 +772,21 @@ class SqlEditor extends React.PureComponent {
queryEditor={this.props.queryEditor}
tables={this.props.tables}
actions={this.props.actions}
+ setEmptyState={this.setEmptyState}
/>