mirror of
https://github.com/apache/superset.git
synced 2026-07-03 21:35:32 +00:00
Compare commits
37 Commits
0.32.0rc2.
...
test_tag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11bace3808 | ||
|
|
ba64ae6974 | ||
|
|
7846bae8e0 | ||
|
|
501340b5db | ||
|
|
19b3753d2c | ||
|
|
416534add5 | ||
|
|
713b0ae4f4 | ||
|
|
5669a82350 | ||
|
|
a09348d0ec | ||
|
|
adc9a6b495 | ||
|
|
2631558ac4 | ||
|
|
b70a9ae524 | ||
|
|
817783f466 | ||
|
|
36176f3e20 | ||
|
|
11a7ad00b7 | ||
|
|
c44ae612df | ||
|
|
c4fb7a0a87 | ||
|
|
5938ac30d6 | ||
|
|
7c02587924 | ||
|
|
744135c7fe | ||
|
|
83ee917832 | ||
|
|
a62a8d3d98 | ||
|
|
a93219f291 | ||
|
|
8100a8fa97 | ||
|
|
6b0ab2100d | ||
|
|
f38cea3ee3 | ||
|
|
01689c38ea | ||
|
|
879c553b0a | ||
|
|
97cb10dbc8 | ||
|
|
594cd70960 | ||
|
|
1ffee8b236 | ||
|
|
3ae7d32caa | ||
|
|
954e42bafe | ||
|
|
bab7ee7ecf | ||
|
|
bbd781b66e | ||
|
|
cf1a35b94b | ||
|
|
d65059b06a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,6 +33,7 @@ _modules
|
||||
_static
|
||||
build
|
||||
app.db
|
||||
apache_superset.egg-info/
|
||||
changelog.sh
|
||||
dist
|
||||
dump.rdb
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
.*pyc
|
||||
.*lock
|
||||
.*geojson
|
||||
DISCLAIMER
|
||||
licenses/*
|
||||
node_modules/*
|
||||
rat-results.txt
|
||||
|
||||
@@ -35,7 +35,7 @@ little bit helps, and credit will always be given.
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
- [Protocol](#protocol)
|
||||
- [Managing Issues and PRs](#managing-issues-and-prs)
|
||||
- [Local development](#local-development)
|
||||
- [Setup Local Environment for Development](#setup-local-environment-for-development)
|
||||
- [Documentation](#documentation)
|
||||
- [Flask server](#flask-server)
|
||||
- [Frontend assets](#frontend-assets)
|
||||
@@ -410,6 +410,10 @@ export enum FeatureFlag {
|
||||
}
|
||||
```
|
||||
|
||||
`superset/config.py` contains `DEFAULT_FEATURE_FLAGS` which will be overwritten by
|
||||
those specified under FEATURE_FLAGS in `superset_config.py`. For example, `DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False }` in `superset/config.py` and `FEATURE_FLAGS = { 'BAR': True, 'BAZ': True }` in `superset_config.py` will result
|
||||
in combined feature flags of `{ 'FOO': True, 'BAR': True, 'BAZ': True }`.
|
||||
|
||||
## Linting
|
||||
|
||||
Lint the project with:
|
||||
|
||||
11
DISCLAIMER
Normal file
11
DISCLAIMER
Normal file
@@ -0,0 +1,11 @@
|
||||
DISCLAIMER
|
||||
|
||||
Apache Superset (incubating) is an effort undergoing incubation at the Apache
|
||||
Software Foundation (ASF), sponsored by the Apache Incubator PMC.
|
||||
Incubation is required of all newly accepted projects until a further review
|
||||
indicates that the infrastructure, communications, and decision making process
|
||||
have stabilized in a manner consistent with other successful ASF projects.
|
||||
|
||||
While incubation status is not necessarily a reflection of the completeness or
|
||||
stability of the code, it does indicate that the project has yet to be fully
|
||||
endorsed by the ASF.
|
||||
@@ -195,6 +195,7 @@ the world know they are using Superset. Join our growing community!
|
||||
1. [Lyft](https://www.lyft.com/)
|
||||
1. [Maieutical Labs](https://maieuticallabs.it)
|
||||
1. [Myra Labs](http://www.myralabs.com/)
|
||||
1. [Now](https://www.now.vn/)
|
||||
1. [PeopleDoc](https://www.people-doc.com)
|
||||
1. [Ona](https://ona.io)
|
||||
1. [Pronto Tools](http://www.prontotools.io)
|
||||
|
||||
@@ -37,6 +37,12 @@ favor of good old `npm install`. While yarn should still work just fine,
|
||||
you should probably align to guarantee builds similar to the ones we
|
||||
use in testing and across the community in general.
|
||||
|
||||
## Superset 0.30.0
|
||||
* 0.30.0 includes a db_migration that removes allow_run_sync. This may
|
||||
require downtime because during the migration if the db is migrated first,
|
||||
superset will get 500 errors when the code can't find the field (until
|
||||
the deploy finishes).
|
||||
|
||||
## Superset 0.29.0
|
||||
* India was removed from the "Country Map" visualization as the geojson
|
||||
file included in the package was very large
|
||||
|
||||
@@ -117,7 +117,7 @@ that the required dependencies are installed: ::
|
||||
**OSX**, system python is not recommended. brew's python also ships with pip ::
|
||||
|
||||
brew install pkg-config libffi openssl python
|
||||
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography==1.9
|
||||
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography==2.4.2
|
||||
|
||||
**Windows** isn't officially supported at this point, but if you want to
|
||||
attempt it, download `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, and run ``python get-pip.py`` which may need admin access. Then run the following: ::
|
||||
@@ -405,6 +405,11 @@ You can also use `PyAthena` library(no java required) like this ::
|
||||
|
||||
See `PyAthena <https://github.com/laughingman7743/PyAthena#sqlalchemy>`_.
|
||||
|
||||
MSSQL
|
||||
-----
|
||||
|
||||
Full Unicode support requires SQLAlchemy 1.3 or later.
|
||||
|
||||
Snowflake
|
||||
---------
|
||||
|
||||
@@ -616,6 +621,18 @@ Upgrading should be as straightforward as running::
|
||||
superset db upgrade
|
||||
superset init
|
||||
|
||||
We recommend to follow standard best practices when upgrading Superset, such
|
||||
as taking a database backup prior to the upgrade, upgrading a staging
|
||||
environment prior to upgrading production, and upgrading production while less
|
||||
users are active on the platform.
|
||||
|
||||
.. note ::
|
||||
Some upgrades may contain backward-incompatible changes, or require
|
||||
scheduling downtime, when that is the case, contributors attach notes in
|
||||
``UPDATING.md`` in the repository. It's recommended to review this
|
||||
file prior to running an upgrade.
|
||||
|
||||
|
||||
Celery Tasks
|
||||
------------
|
||||
On large analytic databases, it's common to run background jobs, reports
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{ if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "superset.fullname" . -}}
|
||||
{{- $ingressPath := .Values.ingress.path -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
|
||||
@@ -42,8 +42,8 @@ kombu==4.2.1 # via celery
|
||||
mako==1.0.7 # via alembic
|
||||
markdown==3.0
|
||||
markupsafe==1.0 # via jinja2, mako
|
||||
numpy==1.15.2 # via pandas
|
||||
pandas==0.23.1
|
||||
numpy==1.15.2 # via numpy
|
||||
pandas==0.23.4 # via pandas
|
||||
parsedatetime==2.0.0
|
||||
pathlib2==2.3.0
|
||||
polyline==1.3.2
|
||||
|
||||
@@ -88,20 +88,7 @@ ERRORS="$(cat rat-results.txt | grep -e "??")"
|
||||
if test ! -z "$ERRORS"; then
|
||||
echo >&2 "Could not find Apache license headers in the following files:"
|
||||
echo >&2 "$ERRORS"
|
||||
COUNT=`echo "${ERRORS}" | wc -l`
|
||||
if [ ! -f ${TRAVIS_CACHE}/rat-error-count-builds ]; then
|
||||
[ "${TRAVIS_PULL_REQUEST}" = "false" ] && echo ${COUNT} > ${TRAVIS_CACHE}/rat-error-count-builds
|
||||
OLD_COUNT=${COUNT}
|
||||
else
|
||||
typeset -i OLD_COUNT=$(cat ${TRAVIS_CACHE}/rat-error-count-builds)
|
||||
fi
|
||||
if [ ${COUNT} -gt ${OLD_COUNT} ]; then
|
||||
echo "New missing licenses (${COUNT} vs ${OLD_COUNT}) detected. Please correct them by adding them to to header of your files"
|
||||
exit 1
|
||||
else
|
||||
[ "${TRAVIS_PULL_REQUEST}" = "false" ] && echo ${COUNT} > ${TRAVIS_CACHE}/rat-error-count-builds
|
||||
fi
|
||||
exit 0
|
||||
exit 1
|
||||
else
|
||||
echo -e "RAT checks passed."
|
||||
fi
|
||||
|
||||
6
setup.py
6
setup.py
@@ -71,7 +71,7 @@ setup(
|
||||
install_requires=[
|
||||
'bleach>=3.0.2, <4.0.0',
|
||||
'celery>=4.2.0, <5.0.0',
|
||||
'click>=6.0, <7.0.0', # click >=7 forces "-" instead of "_"
|
||||
'click>=6.0, <7.0.0', # `click`>=7 forces "-" instead of "_"
|
||||
'colorama',
|
||||
'contextlib2',
|
||||
'croniter>=0.3.26',
|
||||
@@ -88,7 +88,7 @@ setup(
|
||||
'idna',
|
||||
'isodate',
|
||||
'markdown>=3.0',
|
||||
'pandas>=0.18.0',
|
||||
'pandas>=0.18.0, <0.24.0', # `pandas`>=0.24.0 changes datetimelike API
|
||||
'parsedatetime',
|
||||
'pathlib2',
|
||||
'polyline',
|
||||
@@ -109,7 +109,7 @@ setup(
|
||||
'cors': ['flask-cors>=2.0.0'],
|
||||
'console_log': ['console_log==0.2.10'],
|
||||
'hive': [
|
||||
'pyhive>=0.4.0',
|
||||
'pyhive>=0.6.1',
|
||||
'tableschema',
|
||||
'thrift-sasl>=0.2.1',
|
||||
'thrift>=0.9.3',
|
||||
|
||||
@@ -208,6 +208,18 @@ security_manager = appbuilder.sm
|
||||
|
||||
results_backend = app.config.get('RESULTS_BACKEND')
|
||||
|
||||
# Merge user defined feature flags with default feature flags
|
||||
feature_flags = app.config.get('DEFAULT_FEATURE_FLAGS')
|
||||
feature_flags.update(app.config.get('FEATURE_FLAGS') or {})
|
||||
|
||||
|
||||
def is_feature_enabled(feature):
|
||||
"""
|
||||
Utility function for checking whether a feature is turned on
|
||||
"""
|
||||
return feature_flags.get(feature)
|
||||
|
||||
|
||||
# Registering sources
|
||||
module_datasource_map = app.config.get('DEFAULT_MODULE_DS_MAP')
|
||||
module_datasource_map.update(app.config.get('ADDITIONAL_MODULE_DS_MAP'))
|
||||
|
||||
@@ -89,8 +89,8 @@ describe('AdhocMetrics', () => {
|
||||
cy.get('[data-test=metrics]').within(() => {
|
||||
cy.get('.select-clear').click();
|
||||
cy.get('.Select-control').click({ force: true });
|
||||
cy.get('input').type('num{downarrow}', { force: true });
|
||||
cy.get('.VirtualizedSelectFocusedOption')
|
||||
cy.get('input').type('num', { force: true });
|
||||
cy.get('.VirtualizedSelectOption[data-test=_col_num]')
|
||||
.trigger('mousedown')
|
||||
.click();
|
||||
});
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import readResponseBlob from '../../../utils/readResponseBlob';
|
||||
|
||||
export default () => describe('Area', () => {
|
||||
const AREA_FORM_DATA = {
|
||||
datasource: '2__table',
|
||||
@@ -71,11 +73,12 @@ export default () => describe('Area', () => {
|
||||
...AREA_FORM_DATA,
|
||||
groupby: ['region'],
|
||||
});
|
||||
|
||||
cy.get('.nv-area').should('have.length', 7);
|
||||
});
|
||||
|
||||
it('should work with groupby and filter', () => {
|
||||
verify({
|
||||
cy.visitChartByParams(JSON.stringify({
|
||||
...AREA_FORM_DATA,
|
||||
groupby: ['region'],
|
||||
adhoc_filters: [{
|
||||
@@ -88,6 +91,18 @@ export default () => describe('Area', () => {
|
||||
fromFormData: true,
|
||||
filterOptionName: 'filter_txje2ikiv6_wxmn0qwd1xo',
|
||||
}],
|
||||
}));
|
||||
|
||||
cy.wait('@getJson').then(async (xhr) => {
|
||||
cy.verifyResponseCodes(xhr);
|
||||
|
||||
const responseBody = await readResponseBlob(xhr.response.body);
|
||||
|
||||
// Make sure data is sorted correctly
|
||||
const firstRow = responseBody.data[0].values;
|
||||
const secondRow = responseBody.data[1].values;
|
||||
expect(firstRow[firstRow.length - 1].y).to.be.greaterThan(secondRow[secondRow.length - 1].y);
|
||||
cy.verifySliceContainer('svg');
|
||||
});
|
||||
cy.get('.nv-area').should('have.length', 2);
|
||||
});
|
||||
|
||||
@@ -98,10 +98,40 @@ export default () => describe('Line', () => {
|
||||
metrics,
|
||||
time_compare: ['1+year'],
|
||||
comparison_type: 'values',
|
||||
groupby: ['gender'],
|
||||
};
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||
|
||||
// Offset color should match original line color
|
||||
cy.get('.nv-legend-text')
|
||||
.contains('boy')
|
||||
.siblings()
|
||||
.first()
|
||||
.should('have.attr', 'style')
|
||||
.then((style) => {
|
||||
cy.get('.nv-legend-text')
|
||||
.contains('boy, 1 year offset')
|
||||
.siblings()
|
||||
.first()
|
||||
.should('have.attr', 'style')
|
||||
.and('eq', style);
|
||||
});
|
||||
|
||||
cy.get('.nv-legend-text')
|
||||
.contains('girl')
|
||||
.siblings()
|
||||
.first()
|
||||
.should('have.attr', 'style')
|
||||
.then((style) => {
|
||||
cy.get('.nv-legend-text')
|
||||
.contains('girl, 1 year offset')
|
||||
.siblings()
|
||||
.first()
|
||||
.should('have.attr', 'style')
|
||||
.and('eq', style);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test line chart with time shift yoy', () => {
|
||||
|
||||
2
superset/assets/package-lock.json
generated
2
superset/assets/package-lock.json
generated
@@ -2228,7 +2228,7 @@
|
||||
"dependencies": {
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
|
||||
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
|
||||
}
|
||||
}
|
||||
|
||||
110
superset/assets/spec/javascripts/utils/safeStringify_spec.ts
Normal file
110
superset/assets/spec/javascripts/utils/safeStringify_spec.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 { safeStringify } from '../../../src/utils/safeStringify';
|
||||
|
||||
class Noise {
|
||||
public next: Noise;
|
||||
}
|
||||
|
||||
describe('Stringify utility testing', () => {
|
||||
it('correctly parses a simple object just like JSON', () => {
|
||||
const noncircular = {
|
||||
b: 'foo',
|
||||
c: 'bar',
|
||||
d: [
|
||||
{
|
||||
e: 'hello',
|
||||
f: ['world'],
|
||||
},
|
||||
{
|
||||
e: 'hello',
|
||||
f: ['darkness', 'my', 'old', 'friend'],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(safeStringify(noncircular)).toEqual(JSON.stringify(noncircular));
|
||||
// Checking that it works with quick-deepish-copies as well.
|
||||
expect(JSON.parse(safeStringify(noncircular))).toEqual(JSON.parse(JSON.stringify(noncircular)));
|
||||
});
|
||||
|
||||
it('handles simple circular json as expected', () => {
|
||||
const ping = new Noise();
|
||||
const pong = new Noise();
|
||||
const pang = new Noise();
|
||||
ping.next = pong;
|
||||
pong.next = ping;
|
||||
|
||||
// ping.next is pong (the circular reference) now
|
||||
const safeString = safeStringify(ping);
|
||||
ping.next = pang;
|
||||
|
||||
// ping.next is pang now, which has no circular reference, so it's safe to use JSON.stringify
|
||||
const ordinaryString = JSON.stringify(ping);
|
||||
expect(safeString).toEqual(ordinaryString);
|
||||
});
|
||||
|
||||
it('creates a parseable object even when the input is circular', () => {
|
||||
const ping = new Noise();
|
||||
const pong = new Noise();
|
||||
ping.next = pong;
|
||||
pong.next = ping;
|
||||
|
||||
const newNoise: Noise = JSON.parse(safeStringify(ping));
|
||||
expect(newNoise).toBeTruthy();
|
||||
expect(newNoise.next).toEqual({});
|
||||
});
|
||||
|
||||
it('does not remove noncircular duplicates', () => {
|
||||
const a = {
|
||||
foo: 'bar',
|
||||
};
|
||||
|
||||
const repeating = {
|
||||
first: a,
|
||||
second: a,
|
||||
third: a,
|
||||
};
|
||||
|
||||
expect(safeStringify(repeating)).toEqual(JSON.stringify(repeating));
|
||||
});
|
||||
|
||||
it('does not remove nodes with empty objects', () => {
|
||||
const emptyObjectValues = {
|
||||
a: {},
|
||||
b: 'foo',
|
||||
c: {
|
||||
d: 'good data here',
|
||||
e: {},
|
||||
},
|
||||
};
|
||||
expect(safeStringify(emptyObjectValues)).toEqual(JSON.stringify(emptyObjectValues));
|
||||
});
|
||||
|
||||
it('does not remove nested same keys', () => {
|
||||
const nestedKeys = {
|
||||
a: 'b',
|
||||
c: {
|
||||
a: 'd',
|
||||
x: 'y',
|
||||
},
|
||||
};
|
||||
|
||||
expect(safeStringify(nestedKeys)).toEqual(JSON.stringify(nestedKeys));
|
||||
});
|
||||
});
|
||||
@@ -72,6 +72,8 @@ class QueryAutoRefresh extends React.PureComponent {
|
||||
}).catch(() => {
|
||||
this.props.actions.setUserOffline(true);
|
||||
});
|
||||
} else {
|
||||
this.props.actions.setUserOffline(false);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
|
||||
@@ -152,6 +152,13 @@ export function updateQueryFormData(value, key) {
|
||||
return { type: UPDATE_QUERY_FORM_DATA, value, key };
|
||||
}
|
||||
|
||||
// in the sql lab -> explore flow, user can inline edit chart title,
|
||||
// then the chart will be assigned a new slice_id
|
||||
export const UPDATE_CHART_ID = 'UPDATE_CHART_ID';
|
||||
export function updateChartId(newId, key = 0) {
|
||||
return { type: UPDATE_CHART_ID, newId, key };
|
||||
}
|
||||
|
||||
export const ADD_CHART = 'ADD_CHART';
|
||||
export function addChart(chart, key) {
|
||||
return { type: ADD_CHART, chart, key };
|
||||
|
||||
@@ -168,6 +168,14 @@ export default function chartReducer(charts = {}, action) {
|
||||
if (action.type === actions.REMOVE_CHART) {
|
||||
delete charts[action.key];
|
||||
return charts;
|
||||
} else if (action.type === actions.UPDATE_CHART_ID) {
|
||||
const { newId, key } = action;
|
||||
charts[newId] = {
|
||||
...charts[key],
|
||||
id: newId,
|
||||
};
|
||||
delete charts[key];
|
||||
return charts;
|
||||
}
|
||||
|
||||
if (action.type in actionHandlers) {
|
||||
|
||||
@@ -18,49 +18,26 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Label } from 'react-bootstrap';
|
||||
import TooltipWrapper from './TooltipWrapper';
|
||||
|
||||
import './RefreshLabel.less';
|
||||
|
||||
const propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
tooltipContent: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class RefreshLabel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
mouseOver() {
|
||||
this.setState({ hovered: true });
|
||||
}
|
||||
|
||||
mouseOut() {
|
||||
this.setState({ hovered: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const labelStyle = this.state.hovered ? 'primary' : 'default';
|
||||
const tooltip = 'Click to ' + this.props.tooltipContent;
|
||||
return (
|
||||
<TooltipWrapper
|
||||
tooltip={tooltip}
|
||||
tooltip={this.props.tooltipContent}
|
||||
label="cache-desc"
|
||||
>
|
||||
<Label
|
||||
className={this.props.className}
|
||||
bsStyle={labelStyle}
|
||||
style={{ fontSize: '13px', marginRight: '5px', cursor: 'pointer' }}
|
||||
<i
|
||||
className="RefreshLabel fa fa-refresh pointer"
|
||||
onClick={this.props.onClick}
|
||||
onMouseOver={this.mouseOver.bind(this)}
|
||||
onMouseOut={this.mouseOut.bind(this)}
|
||||
>
|
||||
<i className="fa fa-refresh" />
|
||||
</Label>
|
||||
/>
|
||||
</TooltipWrapper>);
|
||||
}
|
||||
}
|
||||
|
||||
27
superset/assets/src/components/RefreshLabel.less
Normal file
27
superset/assets/src/components/RefreshLabel.less
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 "../../stylesheets/less/cosmo/variables.less";
|
||||
|
||||
.RefreshLabel:hover {
|
||||
color: @brand-primary;
|
||||
}
|
||||
|
||||
.RefreshLabel {
|
||||
color: @gray-light;
|
||||
}
|
||||
24
superset/assets/src/components/TableSelector.css
Normal file
24
superset/assets/src/components/TableSelector.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
.TableSelector .fa-refresh {
|
||||
padding-top: 7px
|
||||
}
|
||||
.TableSelector .refresh-col {
|
||||
padding-left: 0px;
|
||||
}
|
||||
@@ -20,12 +20,13 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Select from 'react-virtualized-select';
|
||||
import createFilterOptions from 'react-select-fast-filter-options';
|
||||
import { ControlLabel, Col, Label } from 'react-bootstrap';
|
||||
import { ControlLabel, Col, Label, Row } from 'react-bootstrap';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { SupersetClient } from '@superset-ui/connection';
|
||||
|
||||
import AsyncSelect from './AsyncSelect';
|
||||
import RefreshLabel from './RefreshLabel';
|
||||
import './TableSelector.css';
|
||||
|
||||
const propTypes = {
|
||||
dbId: PropTypes.number.isRequired,
|
||||
@@ -196,8 +197,16 @@ export default class TableSelector extends React.PureComponent {
|
||||
{db.database_name}
|
||||
</span>);
|
||||
}
|
||||
renderDatabaseSelect() {
|
||||
renderSelectRow(select, refreshBtn) {
|
||||
return (
|
||||
<Row>
|
||||
<Col md={11}>{select}</Col>
|
||||
<Col md={1} className="refresh-col">{refreshBtn}</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
renderDatabaseSelect() {
|
||||
return this.renderSelectRow(
|
||||
<AsyncSelect
|
||||
dataEndpoint={
|
||||
'/databaseasync/api/' +
|
||||
@@ -225,30 +234,26 @@ export default class TableSelector extends React.PureComponent {
|
||||
renderSchema() {
|
||||
return (
|
||||
<div className="m-t-5">
|
||||
<div className="row">
|
||||
<div className="col-md-11 col-xs-11 p-r-2">
|
||||
<Select
|
||||
name="select-schema"
|
||||
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
|
||||
options={this.state.schemaOptions}
|
||||
value={this.props.schema}
|
||||
valueRenderer={o => (
|
||||
<div>
|
||||
<span className="text-muted">{t('Schema:')}</span> {o.label}
|
||||
</div>
|
||||
)}
|
||||
isLoading={this.state.schemaLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeSchema}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-1 col-xs-1 p-l-0 p-t-8">
|
||||
<RefreshLabel
|
||||
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
|
||||
tooltipContent={t('force refresh schema list')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderSelectRow(
|
||||
<Select
|
||||
name="select-schema"
|
||||
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
|
||||
options={this.state.schemaOptions}
|
||||
value={this.props.schema}
|
||||
valueRenderer={o => (
|
||||
<div>
|
||||
<span className="text-muted">{t('Schema:')}</span> {o.label}
|
||||
</div>
|
||||
)}
|
||||
isLoading={this.state.schemaLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeSchema}
|
||||
/>,
|
||||
<RefreshLabel
|
||||
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
|
||||
tooltipContent={t('Force refresh schema list')}
|
||||
/>,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -262,43 +267,37 @@ export default class TableSelector extends React.PureComponent {
|
||||
tableSelectDisabled = true;
|
||||
}
|
||||
const options = this.addOptionIfMissing(this.state.tableOptions, this.state.tableName);
|
||||
const select = this.props.schema ? (
|
||||
<Select
|
||||
name="select-table"
|
||||
ref="selectTable"
|
||||
isLoading={this.state.tableLoading}
|
||||
placeholder={t('Select table or type table name')}
|
||||
autosize={false}
|
||||
onChange={this.changeTable}
|
||||
filterOptions={this.state.filterOptions}
|
||||
options={options}
|
||||
value={this.state.tableName}
|
||||
/>) : (
|
||||
<Select
|
||||
async
|
||||
name="async-select-table"
|
||||
ref="selectTable"
|
||||
placeholder={tableSelectPlaceholder}
|
||||
disabled={tableSelectDisabled}
|
||||
autosize={false}
|
||||
onChange={this.changeTable}
|
||||
value={this.state.tableName}
|
||||
loadOptions={this.getTableNamesBySubStr}
|
||||
/>);
|
||||
return (
|
||||
<div className="m-t-5">
|
||||
<div className="row">
|
||||
<div className="col-md-11 col-xs-11 p-r-2">
|
||||
{this.props.schema ? (
|
||||
<Select
|
||||
name="select-table"
|
||||
ref="selectTable"
|
||||
isLoading={this.state.tableLoading}
|
||||
placeholder={t('Select table or type table name')}
|
||||
autosize={false}
|
||||
onChange={this.changeTable}
|
||||
filterOptions={this.state.filterOptions}
|
||||
options={options}
|
||||
value={this.state.tableName}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
async
|
||||
name="async-select-table"
|
||||
ref="selectTable"
|
||||
placeholder={tableSelectPlaceholder}
|
||||
disabled={tableSelectDisabled}
|
||||
autosize={false}
|
||||
onChange={this.changeTable}
|
||||
value={this.state.tableName}
|
||||
loadOptions={this.getTableNamesBySubStr}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-md-1 col-xs-1 p-l-0 p-t-8">
|
||||
<RefreshLabel
|
||||
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
|
||||
tooltipContent={t('force refresh table list')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderSelectRow(
|
||||
select,
|
||||
<RefreshLabel
|
||||
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
|
||||
tooltipContent={t('Force refresh table list')}
|
||||
/>)}
|
||||
</div>);
|
||||
}
|
||||
renderSeeTableLabel() {
|
||||
@@ -318,21 +317,22 @@ export default class TableSelector extends React.PureComponent {
|
||||
</div>);
|
||||
}
|
||||
render() {
|
||||
if (this.props.horizontal) {
|
||||
return (
|
||||
<div>
|
||||
<Col md={4}>{this.renderDatabaseSelect()}</Col>
|
||||
<Col md={4}>{this.renderSchema()}</Col>
|
||||
<Col md={4}>{this.renderTable()}</Col>
|
||||
</div>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div>{this.renderDatabaseSelect()}</div>
|
||||
<div className="m-t-5">{this.renderSchema()}</div>
|
||||
{this.props.sqlLabMode && this.renderSeeTableLabel()}
|
||||
<div className="m-t-5">{this.renderTable()}</div>
|
||||
</div>);
|
||||
<div className="TableSelector">
|
||||
{this.props.horizontal ?
|
||||
<div>
|
||||
<Col md={4}>{this.renderDatabaseSelect()}</Col>
|
||||
<Col md={4}>{this.renderSchema()}</Col>
|
||||
<Col md={4}>{this.renderTable()}</Col>
|
||||
</div> :
|
||||
<div>
|
||||
<div>{this.renderDatabaseSelect()}</div>
|
||||
<div className="m-t-5">{this.renderSchema()}</div>
|
||||
{this.props.sqlLabMode && this.renderSeeTableLabel()}
|
||||
<div className="m-t-5">{this.renderTable()}</div>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
TableSelector.propTypes = propTypes;
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function VirtualizedRendererWrap(renderer) {
|
||||
key={key}
|
||||
style={{ ...(option.style || {}), ...style }}
|
||||
title={option.title}
|
||||
data-test={option.optionName}
|
||||
{...events}
|
||||
>
|
||||
{renderer(option)}
|
||||
|
||||
@@ -65,6 +65,7 @@ class ExploreChartHeader extends React.PureComponent {
|
||||
.then((json) => {
|
||||
const { data } = json;
|
||||
if (isNewSlice) {
|
||||
this.props.actions.updateChartId(data.slice.slice_id, 0);
|
||||
this.props.actions.createNewSlice(
|
||||
data.can_add, data.can_download, data.can_overwrite,
|
||||
data.slice, data.form_data);
|
||||
|
||||
@@ -109,6 +109,9 @@ class ExploreViewContainer extends React.Component {
|
||||
const wasRendered =
|
||||
['rendered', 'failed', 'stopped'].indexOf(this.props.chart.chartStatus) > -1;
|
||||
const isRendered = ['rendered', 'failed', 'stopped'].indexOf(nextProps.chart.chartStatus) > -1;
|
||||
if (nextProps.chart.id !== this.props.chart.id) {
|
||||
this.loadingLog.sourceId = nextProps.chart.id;
|
||||
}
|
||||
if (!wasRendered && isRendered) {
|
||||
Logger.send(this.loadingLog);
|
||||
}
|
||||
|
||||
@@ -176,10 +176,12 @@ AnnotationLayerControl.defaultProps = defaultProps;
|
||||
// directly, could not figure out how to get access to the color_scheme
|
||||
function mapStateToProps({ charts, explore }) {
|
||||
const chartKey = getChartKey(explore);
|
||||
const chart = charts[chartKey] || charts[0] || {};
|
||||
|
||||
return {
|
||||
colorScheme: (explore.controls || {}).color_scheme.value,
|
||||
annotationError: charts[chartKey].annotationError,
|
||||
annotationQuery: charts[chartKey].annotationQuery,
|
||||
annotationError: chart.annotationError,
|
||||
annotationQuery: chart.annotationQuery,
|
||||
vizType: explore.controls.viz_type.value,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
/* eslint camelcase: 0 */
|
||||
import URI from 'urijs';
|
||||
import { availableDomains } from '../utils/hostNamesConfig';
|
||||
import { safeStringify } from '../utils/safeStringify';
|
||||
|
||||
const MAX_URL_LENGTH = 8000;
|
||||
|
||||
@@ -71,7 +72,7 @@ export function getExploreLongUrl(formData, endpointType, allowOverflow = true,
|
||||
Object.keys(extraSearch).forEach((key) => {
|
||||
search[key] = extraSearch[key];
|
||||
});
|
||||
search.form_data = JSON.stringify(formData);
|
||||
search.form_data = safeStringify(formData);
|
||||
if (endpointType === 'standalone') {
|
||||
search.standalone = 'true';
|
||||
}
|
||||
|
||||
45
superset/assets/src/utils/safeStringify.ts
Normal file
45
superset/assets/src/utils/safeStringify.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Stringify function that will not crash when it runs into circular JSON references,
|
||||
* unlike JSON.stringify. Any circular references are simply omitted, as if there had
|
||||
* been no data present
|
||||
* @param object any JSON object to be stringified
|
||||
*/
|
||||
export function safeStringify(object: any): string {
|
||||
const cache = new Set();
|
||||
return JSON.stringify(object, (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.has(value)) {
|
||||
// We've seen this object before
|
||||
try {
|
||||
// Quick deep copy to duplicate if this is a repeat rather than a circle.
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
} catch (err) {
|
||||
// Discard key if value cannot be duplicated.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store the value in our cache.
|
||||
cache.add(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
58
superset/assets/src/visualizations/Iframe/Iframe.jsx
Normal file
58
superset/assets/src/visualizations/Iframe/Iframe.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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 Mustache from 'mustache';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
url: PropTypes.string,
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
class Iframe extends React.PureComponent {
|
||||
render() {
|
||||
const { className, url, width, height } = this.props;
|
||||
const completeUrl = Mustache.render(url, {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
return (
|
||||
<iframe
|
||||
className={className}
|
||||
title="superset-iframe"
|
||||
src={completeUrl}
|
||||
style={{
|
||||
width: '100%',
|
||||
height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Iframe.propTypes = propTypes;
|
||||
Iframe.defaultProps = defaultProps;
|
||||
|
||||
export default Iframe;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
name: t('IFrame'),
|
||||
description: 'HTML Inline Frame',
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class IframeChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Iframe.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
superset/assets/src/visualizations/Iframe/images/thumbnail.png
Normal file
BIN
superset/assets/src/visualizations/Iframe/images/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
@@ -16,23 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import Mustache from 'mustache';
|
||||
|
||||
export default function iframeWidget(slice) {
|
||||
const { selector, formData } = slice;
|
||||
export default function transformProps(chartProps) {
|
||||
const { width, height, formData } = chartProps;
|
||||
const { url } = formData;
|
||||
const width = slice.width();
|
||||
const height = slice.height();
|
||||
const container = document.querySelector(selector);
|
||||
|
||||
const completedUrl = Mustache.render(url, {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = height;
|
||||
iframe.setAttribute('src', completedUrl);
|
||||
container.appendChild(iframe);
|
||||
url,
|
||||
};
|
||||
}
|
||||
@@ -17,16 +17,16 @@
|
||||
* under the License.
|
||||
*/
|
||||
.markup.slice_container {
|
||||
margin: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
.separator {
|
||||
background-color: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.separator hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background-image: linear-gradient(to right, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background-image: linear-gradient(to right, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
|
||||
}
|
||||
.separator .chart-header {
|
||||
border: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
76
superset/assets/src/visualizations/Markup/Markup.jsx
Normal file
76
superset/assets/src/visualizations/Markup/Markup.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import './Markup.css';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
height: PropTypes.number.isRequired,
|
||||
isSeparator: PropTypes.bool,
|
||||
html: PropTypes.string,
|
||||
cssFiles: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
isSeparator: false,
|
||||
html: '',
|
||||
};
|
||||
|
||||
const CONTAINER_STYLE = {
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
class Markup extends React.PureComponent {
|
||||
render() {
|
||||
const { className, height, isSeparator, html, cssFiles } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={CONTAINER_STYLE}
|
||||
>
|
||||
<iframe
|
||||
title="superset-markup"
|
||||
frameBorder={0}
|
||||
height={isSeparator ? height - 20 : height}
|
||||
sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation"
|
||||
srcDoc={`
|
||||
<html>
|
||||
<head>
|
||||
${cssFiles.map(
|
||||
href => `<link rel="stylesheet" type="text/css" href="${href}" />`,
|
||||
)}
|
||||
</head>
|
||||
<body style="background-color: transparent;">
|
||||
${html}
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Markup.propTypes = propTypes;
|
||||
Markup.defaultProps = defaultProps;
|
||||
|
||||
export default Markup;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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/translation';
|
||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
name: t('Markup'),
|
||||
description: 'HTML Markup',
|
||||
thumbnail,
|
||||
});
|
||||
|
||||
export default class IframeChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
metadata,
|
||||
loadChart: () => import('./Markup.jsx'),
|
||||
transformProps,
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
superset/assets/src/visualizations/Markup/images/thumbnail.png
Normal file
BIN
superset/assets/src/visualizations/Markup/images/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
33
superset/assets/src/visualizations/Markup/transformProps.js
Normal file
33
superset/assets/src/visualizations/Markup/transformProps.js
Normal file
@@ -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.
|
||||
*/
|
||||
export default function transformProps(chartProps) {
|
||||
const { height, payload, formData } = chartProps;
|
||||
const { vizType } = formData;
|
||||
const {
|
||||
theme_css: cssFiles,
|
||||
html,
|
||||
} = payload.data;
|
||||
|
||||
return {
|
||||
height,
|
||||
cssFiles,
|
||||
html,
|
||||
isSeparator: vizType === 'separator',
|
||||
};
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.getInitialStateFromProps(props);
|
||||
this.state = this.getStateFromProps(props);
|
||||
|
||||
this.getLayers = this.getLayers.bind(this);
|
||||
this.onValuesChange = this.onValuesChange.bind(this);
|
||||
@@ -78,6 +78,11 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
this.toggleCategory = this.toggleCategory.bind(this);
|
||||
this.showSingleCategory = this.showSingleCategory.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.payload.form_data !== this.state.formData) {
|
||||
this.setState({ ...this.getStateFromProps(nextProps) });
|
||||
}
|
||||
}
|
||||
onValuesChange(values) {
|
||||
this.setState({
|
||||
values: Array.isArray(values)
|
||||
@@ -88,7 +93,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
getInitialStateFromProps(props, state) {
|
||||
getStateFromProps(props, state) {
|
||||
const features = props.payload.data.features || [];
|
||||
const timestamps = features.map(f => f.__timestamp);
|
||||
const categories = getCategories(props.formData, features);
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import srcdoc from 'srcdoc-polyfill';
|
||||
import './markup.css';
|
||||
|
||||
function markupWidget(slice, payload) {
|
||||
const { selector } = slice;
|
||||
const height = slice.height();
|
||||
const headerHeight = slice.headerHeight();
|
||||
const vizType = slice.props.vizType;
|
||||
const { data } = payload;
|
||||
|
||||
const container = document.querySelector(selector);
|
||||
container.style.overflow = 'auto';
|
||||
|
||||
// markup height is slice height - (marginTop + marginBottom)
|
||||
const iframeHeight = vizType === 'separator'
|
||||
? height - 20
|
||||
: height + headerHeight;
|
||||
|
||||
const html = `
|
||||
<html>
|
||||
<head>
|
||||
${data.theme_css.map(
|
||||
href => `<link rel="stylesheet" type="text/css" href="${href}" />`,
|
||||
)}
|
||||
</head>
|
||||
<body style="background-color: transparent;">
|
||||
${data.html}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('frameborder', 0);
|
||||
iframe.setAttribute('height', iframeHeight);
|
||||
iframe.setAttribute('sandbox', 'allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation');
|
||||
container.appendChild(iframe);
|
||||
|
||||
srcdoc.set(iframe, html);
|
||||
}
|
||||
|
||||
export default markupWidget;
|
||||
@@ -166,9 +166,9 @@ export function generateBubbleTooltipContent({
|
||||
}
|
||||
|
||||
export function hideTooltips() {
|
||||
const target = document.querySelector('.nvtooltip');
|
||||
if (target) {
|
||||
target.style.opacity = 0;
|
||||
const targets = document.querySelectorAll('.nvtooltip');
|
||||
if (targets.length > 0) {
|
||||
targets.forEach(t => t.remove());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ import EventFlowChartPlugin from '../EventFlow/EventFlowChartPlugin';
|
||||
import ForceDirectedChartPlugin from '../ForceDirected/ForceDirectedChartPlugin';
|
||||
import HeatmapChartPlugin from '../Heatmap/HeatmapChartPlugin';
|
||||
import HorizonChartPlugin from '../Horizon/HorizonChartPlugin';
|
||||
import IframeChartPlugin from '../Iframe/IframeChartPlugin';
|
||||
import LineMultiChartPlugin from '../nvd3/LineMulti/LineMultiChartPlugin';
|
||||
import MarkupChartPlugin from '../Markup/MarkupChartPlugin';
|
||||
import PairedTTestChartPlugin from '../PairedTTest/PairedTTestChartPlugin';
|
||||
import ParallelCoordinatesChartPlugin from '../ParallelCoordinates/ParallelCoordinatesChartPlugin';
|
||||
import RoseChartPlugin from '../Rose/RoseChartPlugin';
|
||||
@@ -57,7 +59,10 @@ export default class LegacyChartPreset extends Preset {
|
||||
new ForceDirectedChartPlugin().configure({ key: 'directed_force' }),
|
||||
new HeatmapChartPlugin().configure({ key: 'heatmap' }),
|
||||
new HorizonChartPlugin().configure({ key: 'horizon' }),
|
||||
new IframeChartPlugin().configure({ key: 'iframe' }),
|
||||
new LineMultiChartPlugin().configure({ key: 'line_multi' }),
|
||||
new MarkupChartPlugin().configure({ key: 'markup' }),
|
||||
new MarkupChartPlugin().configure({ key: 'separator' }),
|
||||
new PairedTTestChartPlugin().configure({ key: 'paired_ttest' }),
|
||||
new ParallelCoordinatesChartPlugin().configure({ key: 'para' }),
|
||||
new RoseChartPlugin().configure({ key: 'rose' }),
|
||||
|
||||
@@ -71,7 +71,7 @@ class DashboardTable extends React.PureComponent {
|
||||
{this.state.dashboards.map(o => (
|
||||
<Tr key={o.id}>
|
||||
<Td column="dashboard" value={o.dashboard_title}>
|
||||
{o.dashboard_title}
|
||||
<a href={o.url}>{o.dashboard_title}</a>
|
||||
</Td>
|
||||
<Td column="creator" value={o.changed_by_name}>
|
||||
{unsafe(o.creator)}
|
||||
|
||||
@@ -475,20 +475,17 @@ g.annotation-container {
|
||||
position:relative;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.reactable-header-sortable::before{
|
||||
.reactable-header-sortable::after {
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
content: "\f0dc";
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 5px;
|
||||
margin-left: 10px;
|
||||
color: @brand-primary;
|
||||
}
|
||||
.reactable-header-sort-asc::before{
|
||||
.reactable-header-sort-asc::after{
|
||||
content: "\f0de";
|
||||
color: @brand-primary;
|
||||
}
|
||||
.reactable-header-sort-desc::before{
|
||||
.reactable-header-sort-desc::after{
|
||||
content: "\f0dd";
|
||||
color: @brand-primary;
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ APP_NAME = 'Superset'
|
||||
|
||||
# Uncomment to setup an App icon
|
||||
APP_ICON = '/static/assets/images/superset-logo@2x.png'
|
||||
APP_ICON_WIDTH = 126
|
||||
|
||||
# Druid query timezone
|
||||
# tz.tzutc() : Using utc timezone
|
||||
@@ -180,14 +181,18 @@ LANGUAGES = {
|
||||
'pt': {'flag': 'pt', 'name': 'Portuguese'},
|
||||
'pt_BR': {'flag': 'br', 'name': 'Brazilian Portuguese'},
|
||||
'ru': {'flag': 'ru', 'name': 'Russian'},
|
||||
'ko': {'flag': 'kr', 'name': 'Korean'},
|
||||
}
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Feature flags
|
||||
# ---------------------------------------------------
|
||||
# Feature flags that are on by default go here. Their
|
||||
# values can be overridden by those in super_config.py
|
||||
FEATURE_FLAGS = {}
|
||||
# Feature flags that are set by default go here. Their values can be
|
||||
# overwritten by those specified under FEATURE_FLAGS in super_config.py
|
||||
# For example, DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False } here
|
||||
# and FEATURE_FLAGS = { 'BAR': True, 'BAZ': True } in superset_config.py
|
||||
# will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True }
|
||||
DEFAULT_FEATURE_FLAGS = {}
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Image and file configuration
|
||||
@@ -558,6 +563,9 @@ WEBDRIVER_CONFIGURATION = {}
|
||||
# The base URL to query for accessing the user interface
|
||||
WEBDRIVER_BASEURL = 'http://0.0.0.0:8080/'
|
||||
|
||||
# Send user to a link where they can report bugs
|
||||
BUG_REPORT_URL = None
|
||||
|
||||
|
||||
try:
|
||||
if CONFIG_PATH_ENV_VAR in os.environ:
|
||||
|
||||
@@ -41,7 +41,7 @@ from . import models
|
||||
class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): # noqa
|
||||
datamodel = SQLAInterface(models.DruidColumn)
|
||||
|
||||
list_title = _('List Druid Column')
|
||||
list_title = _('Columns')
|
||||
show_title = _('Show Druid Column')
|
||||
add_title = _('Add Druid Column')
|
||||
edit_title = _('Edit Druid Column')
|
||||
@@ -109,7 +109,7 @@ appbuilder.add_view_no_menu(DruidColumnInlineView)
|
||||
class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa
|
||||
datamodel = SQLAInterface(models.DruidMetric)
|
||||
|
||||
list_title = _('List Druid Metric')
|
||||
list_title = _('Metrics')
|
||||
show_title = _('Show Druid Metric')
|
||||
add_title = _('Add Druid Metric')
|
||||
edit_title = _('Edit Druid Metric')
|
||||
@@ -160,7 +160,7 @@ appbuilder.add_view_no_menu(DruidMetricInlineView)
|
||||
class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # noqa
|
||||
datamodel = SQLAInterface(models.DruidCluster)
|
||||
|
||||
list_title = _('List Druid Cluster')
|
||||
list_title = _('Druid Clusters')
|
||||
show_title = _('Show Druid Cluster')
|
||||
add_title = _('Add Druid Cluster')
|
||||
edit_title = _('Edit Druid Cluster')
|
||||
@@ -212,7 +212,7 @@ appbuilder.add_view(
|
||||
class DruidDatasourceModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
|
||||
datamodel = SQLAInterface(models.DruidDatasource)
|
||||
|
||||
list_title = _('List Druid Datasource')
|
||||
list_title = _('Druid Datasources')
|
||||
show_title = _('Show Druid Datasource')
|
||||
add_title = _('Add Druid Datasource')
|
||||
edit_title = _('Edit Druid Datasource')
|
||||
|
||||
@@ -115,7 +115,9 @@ class TableColumn(Model, BaseColumn):
|
||||
label = label if label else self.column_name
|
||||
label = self.table.get_label(label)
|
||||
if not self.expression:
|
||||
col = column(self.column_name).label(label)
|
||||
db_engine_spec = self.table.database.db_engine_spec
|
||||
type_ = db_engine_spec.get_sqla_column_type(self.type)
|
||||
col = column(self.column_name, type_=type_).label(label)
|
||||
else:
|
||||
col = literal_column(self.expression).label(label)
|
||||
return col
|
||||
@@ -856,13 +858,13 @@ class SqlaTable(Model, BaseDatasource):
|
||||
dbcol = dbcols.get(col.name, None)
|
||||
if not dbcol:
|
||||
dbcol = TableColumn(column_name=col.name, type=datatype)
|
||||
dbcol.groupby = dbcol.is_string
|
||||
dbcol.filterable = dbcol.is_string
|
||||
dbcol.sum = dbcol.is_num
|
||||
dbcol.avg = dbcol.is_num
|
||||
dbcol.is_dttm = dbcol.is_time
|
||||
else:
|
||||
dbcol.type = datatype
|
||||
dbcol.groupby = True
|
||||
dbcol.filterable = True
|
||||
self.columns.append(dbcol)
|
||||
if not any_date_col and dbcol.is_time:
|
||||
any_date_col = col.name
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
# under the License.
|
||||
# pylint: disable=C,R,W
|
||||
"""Views used by the SqlAlchemy connector"""
|
||||
import logging
|
||||
|
||||
from flask import flash, Markup, redirect
|
||||
from flask_appbuilder import CompactCRUDMixin, expose
|
||||
from flask_appbuilder.actions import action
|
||||
@@ -33,11 +35,13 @@ from superset.views.base import (
|
||||
)
|
||||
from . import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): # noqa
|
||||
datamodel = SQLAInterface(models.TableColumn)
|
||||
|
||||
list_title = _('List Columns')
|
||||
list_title = _('Columns')
|
||||
show_title = _('Show Column')
|
||||
add_title = _('Add Column')
|
||||
edit_title = _('Edit Column')
|
||||
@@ -110,7 +114,7 @@ appbuilder.add_view_no_menu(TableColumnInlineView)
|
||||
class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa
|
||||
datamodel = SQLAInterface(models.SqlMetric)
|
||||
|
||||
list_title = _('List Metrics')
|
||||
list_title = _('Metrics')
|
||||
show_title = _('Show Metric')
|
||||
add_title = _('Add Metric')
|
||||
edit_title = _('Edit Metric')
|
||||
@@ -164,7 +168,7 @@ appbuilder.add_view_no_menu(SqlMetricInlineView)
|
||||
class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
|
||||
datamodel = SQLAInterface(models.SqlaTable)
|
||||
|
||||
list_title = _('List Tables')
|
||||
list_title = _('Tables')
|
||||
show_title = _('Show Table')
|
||||
add_title = _('Import a table definition')
|
||||
edit_title = _('Edit Table')
|
||||
@@ -269,12 +273,13 @@ class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
|
||||
# Fail before adding if the table can't be found
|
||||
try:
|
||||
table.get_sqla_table_object()
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.exception(f'Got an error in pre_add for {table.name}')
|
||||
raise Exception(_(
|
||||
'Table [{}] could not be found, '
|
||||
'please double check your '
|
||||
'database connection, schema, and '
|
||||
'table name').format(table.name))
|
||||
'table name, error: {}').format(table.name, str(e)))
|
||||
|
||||
def post_add(self, table, flash_message=True):
|
||||
table.fetch_metadata()
|
||||
|
||||
@@ -46,6 +46,7 @@ from sqlalchemy.engine import create_engine
|
||||
from sqlalchemy.engine.url import make_url
|
||||
from sqlalchemy.sql import quoted_name, text
|
||||
from sqlalchemy.sql.expression import TextAsFrom
|
||||
from sqlalchemy.types import UnicodeText
|
||||
import sqlparse
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
@@ -400,6 +401,15 @@ class BaseEngineSpec(object):
|
||||
label = cls.mutate_label(label)
|
||||
return quoted_name(label, True) if cls.force_column_alias_quotes else label
|
||||
|
||||
@classmethod
|
||||
def get_sqla_column_type(cls, type_):
|
||||
"""
|
||||
Return a sqlalchemy native column type that corresponds to the column type
|
||||
defined in the data source (optional). Needs to be overridden if column requires
|
||||
special handling (see MSSQL for example of NCHAR/NVARCHAR handling).
|
||||
"""
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def mutate_label(label):
|
||||
"""
|
||||
@@ -1362,6 +1372,12 @@ class MssqlEngineSpec(BaseEngineSpec):
|
||||
data = [[elem for elem in r] for r in data]
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_sqla_column_type(cls, type_):
|
||||
if isinstance(type_, str) and re.match(r'^N(VAR){0-1}CHAR', type_):
|
||||
return UnicodeText()
|
||||
return None
|
||||
|
||||
|
||||
class AthenaEngineSpec(BaseEngineSpec):
|
||||
engine = 'awsathena'
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Add extra column to SavedQuery
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
|
||||
@@ -801,7 +801,9 @@ class Database(Model, AuditMixinNullable, ImportMixin):
|
||||
self.impersonate_user,
|
||||
effective_username))
|
||||
if configuration:
|
||||
params['connect_args'] = {'configuration': configuration}
|
||||
d = params.get('connect_args', {})
|
||||
d['configuration'] = configuration
|
||||
params['connect_args'] = d
|
||||
|
||||
DB_CONNECTION_MUTATOR = config.get('DB_CONNECTION_MUTATOR')
|
||||
if DB_CONNECTION_MUTATOR:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
{% set menu = appbuilder.menu %}
|
||||
{% set languages = appbuilder.languages %}
|
||||
{% set WARNING_MSG = appbuilder.app.config.get('WARNING_MSG') %}
|
||||
{% set app_icon_width = appbuilder.app.config.get('APP_ICON_WIDTH', 126) %}
|
||||
|
||||
<div class="navbar navbar-static-top {{menu.extra_classes}}" role="navigation">
|
||||
<div class="container-fluid">
|
||||
@@ -30,7 +31,7 @@
|
||||
</button>
|
||||
<a class="navbar-brand" href="/superset/profile/{{ current_user.username }}/">
|
||||
<img
|
||||
width="126"
|
||||
width="{{ app_icon_width }}"
|
||||
src="{{ appbuilder.app_icon }}"
|
||||
alt="{{ appbuilder.app_name }}"
|
||||
/>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
under the License.
|
||||
#}
|
||||
|
||||
{% set bug_report_url = appbuilder.app.config.get('BUG_REPORT_URL') %}
|
||||
{% set locale = session['locale'] %}
|
||||
{% if not locale %}
|
||||
{% set locale = 'en' %}
|
||||
@@ -34,6 +35,17 @@
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if bug_report_url %}
|
||||
<li>
|
||||
<a
|
||||
tabindex="-1"
|
||||
href="{{ bug_report_url }}"
|
||||
title="Report a bug"
|
||||
>
|
||||
<i class="fa fa-bug"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if languages.keys()|length > 1 %}
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
|
||||
|
||||
1
superset/translations/ko/LC_MESSAGES/messages.json
Normal file
1
superset/translations/ko/LC_MESSAGES/messages.json
Normal file
File diff suppressed because one or more lines are too long
BIN
superset/translations/ko/LC_MESSAGES/messages.mo
Normal file
BIN
superset/translations/ko/LC_MESSAGES/messages.mo
Normal file
Binary file not shown.
4947
superset/translations/ko/LC_MESSAGES/messages.po
Normal file
4947
superset/translations/ko/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ from flask_babel import lazy_gettext as _
|
||||
import simplejson as json
|
||||
import yaml
|
||||
|
||||
from superset import conf, db, security_manager
|
||||
from superset import conf, db, feature_flags, security_manager
|
||||
from superset.exceptions import SupersetException, SupersetSecurityException
|
||||
from superset.translations.utils import get_language_pack
|
||||
from superset.utils import core as utils
|
||||
@@ -157,7 +157,7 @@ class BaseSupersetView(BaseView):
|
||||
'conf': {k: conf.get(k) for k in FRONTEND_CONF_KEYS},
|
||||
'locale': locale,
|
||||
'language_pack': get_language_pack(locale),
|
||||
'feature_flags': conf.get('FEATURE_FLAGS'),
|
||||
'feature_flags': feature_flags,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ class DashboardFilter(SupersetFilter):
|
||||
class DatabaseView(SupersetModelView, DeleteMixin, YamlExportMixin): # noqa
|
||||
datamodel = SQLAInterface(models.Database)
|
||||
|
||||
list_title = _('List Databases')
|
||||
list_title = _('Databases')
|
||||
show_title = _('Show Database')
|
||||
add_title = _('Add Database')
|
||||
edit_title = _('Edit Database')
|
||||
@@ -446,7 +446,7 @@ class SliceModelView(SupersetModelView, DeleteMixin): # noqa
|
||||
route_base = '/chart'
|
||||
datamodel = SQLAInterface(models.Slice)
|
||||
|
||||
list_title = _('List Charts')
|
||||
list_title = _('Charts')
|
||||
show_title = _('Show Chart')
|
||||
add_title = _('Add Chart')
|
||||
edit_title = _('Edit Chart')
|
||||
@@ -562,7 +562,7 @@ class DashboardModelView(SupersetModelView, DeleteMixin): # noqa
|
||||
route_base = '/dashboard'
|
||||
datamodel = SQLAInterface(models.Dashboard)
|
||||
|
||||
list_title = _('List Dashboards')
|
||||
list_title = _('Dashboards')
|
||||
show_title = _('Show Dashboard')
|
||||
add_title = _('Add Dashboard')
|
||||
edit_title = _('Edit Dashboard')
|
||||
@@ -694,7 +694,7 @@ appbuilder.add_view_no_menu(DashboardAddView)
|
||||
class LogModelView(SupersetModelView):
|
||||
datamodel = SQLAInterface(models.Log)
|
||||
|
||||
list_title = _('List Log')
|
||||
list_title = _('Logs')
|
||||
show_title = _('Show Log')
|
||||
add_title = _('Add Log')
|
||||
edit_title = _('Edit Log')
|
||||
@@ -1768,7 +1768,7 @@ class Superset(BaseSupersetView):
|
||||
.get('engine_params', {}))
|
||||
connect_args = engine_params.get('connect_args')
|
||||
|
||||
if configuration:
|
||||
if configuration and connect_args is not None:
|
||||
connect_args['configuration'] = configuration
|
||||
|
||||
engine = create_engine(uri, **engine_params)
|
||||
@@ -2883,10 +2883,10 @@ appbuilder.add_view_no_menu(Superset)
|
||||
class CssTemplateModelView(SupersetModelView, DeleteMixin):
|
||||
datamodel = SQLAInterface(models.CssTemplate)
|
||||
|
||||
list_title = _('List Css Template')
|
||||
show_title = _('Show Css Template')
|
||||
add_title = _('Add Css Template')
|
||||
edit_title = _('Edit Css Template')
|
||||
list_title = _('CSS Templates')
|
||||
show_title = _('Show CSS Template')
|
||||
add_title = _('Add CSS Template')
|
||||
edit_title = _('Edit CSS Template')
|
||||
|
||||
list_columns = ['template_name']
|
||||
edit_columns = ['template_name', 'css']
|
||||
|
||||
@@ -1254,7 +1254,9 @@ class NVD3TimeSeriesViz(NVD3Viz):
|
||||
self.to_series(
|
||||
diff, classed='time-shift-{}'.format(i), title_suffix=label))
|
||||
|
||||
return sorted(chart_data, key=lambda x: tuple(x['key']))
|
||||
if not self.sort_series:
|
||||
chart_data = sorted(chart_data, key=lambda x: tuple(x['key']))
|
||||
return chart_data
|
||||
|
||||
|
||||
class MultiLineViz(NVD3Viz):
|
||||
|
||||
@@ -19,10 +19,10 @@ import json
|
||||
import unittest
|
||||
|
||||
from flask_appbuilder.security.sqla import models as ab_models
|
||||
from mock import Mock
|
||||
from mock import Mock, patch
|
||||
import pandas as pd
|
||||
|
||||
from superset import app, db, security_manager
|
||||
from superset import app, db, is_feature_enabled, security_manager
|
||||
from superset.connectors.druid.models import DruidCluster, DruidDatasource
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models import core as models
|
||||
@@ -185,3 +185,11 @@ class SupersetTestCase(unittest.TestCase):
|
||||
if raise_on_error and 'error' in resp:
|
||||
raise Exception('run_sql failed')
|
||||
return resp
|
||||
|
||||
@patch.dict('superset.feature_flags', {'FOO': True}, clear=True)
|
||||
def test_existing_feature_flags(self):
|
||||
self.assertTrue(is_feature_enabled('FOO'))
|
||||
|
||||
@patch.dict('superset.feature_flags', {}, clear=True)
|
||||
def test_nonexistent_feature_flags(self):
|
||||
self.assertFalse(is_feature_enabled('FOO'))
|
||||
|
||||
@@ -462,8 +462,8 @@ class CoreTests(SupersetTestCase):
|
||||
|
||||
def test_gamma(self):
|
||||
self.login(username='gamma')
|
||||
assert 'List Charts' in self.get_resp('/chart/list/')
|
||||
assert 'List Dashboard' in self.get_resp('/dashboard/list/')
|
||||
assert 'Charts' in self.get_resp('/chart/list/')
|
||||
assert 'Dashboards' in self.get_resp('/dashboard/list/')
|
||||
|
||||
def test_csv_endpoint(self):
|
||||
self.login('admin')
|
||||
|
||||
Reference in New Issue
Block a user