Merging lyftga into master
This commit is contained in:
commit
5f28027ce7
|
|
@ -40,6 +40,7 @@ dump.rdb
|
|||
env
|
||||
env_py3
|
||||
envpy3
|
||||
env36
|
||||
local_config.py
|
||||
superset_config.py
|
||||
superset.egg-info/
|
||||
|
|
|
|||
|
|
@ -517,6 +517,12 @@ Run Cypress tests:
|
|||
cd /superset/superset/assets
|
||||
npm run build
|
||||
npm run cypress run
|
||||
|
||||
# run tests from a specific file
|
||||
npm run cypress run -- --spec cypress/integration/explore/link.test.js
|
||||
|
||||
# run specific file with video capture
|
||||
npm run cypress run -- --spec cypress/integration/dashboard/index.test.js --config video=true
|
||||
```
|
||||
|
||||
## Translating
|
||||
|
|
|
|||
|
|
@ -479,6 +479,26 @@ into your global default defined in ``CACHE_CONFIG``.
|
|||
'CACHE_REDIS_URL': 'redis://localhost:6379/0',
|
||||
}
|
||||
|
||||
Superset has a Celery task that will periodically warm up the cache based on
|
||||
different strategies. To use it, add the following to the `CELERYBEAT_SCHEDULE`
|
||||
section in `config.py`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'cache-warmup-hourly': {
|
||||
'task': 'cache-warmup',
|
||||
'schedule': crontab(minute=0, hour='*'), # hourly
|
||||
'kwargs': {
|
||||
'strategy_name': 'top_n_dashboards',
|
||||
'top_n': 5,
|
||||
'since': '7 days ago',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
This will cache all the charts in the top 5 most popular dashboards every hour.
|
||||
For other strategies, check the `superset/tasks/cache.py` file.
|
||||
|
||||
|
||||
Deeper SQLAlchemy integration
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ pathlib2==2.3.0
|
|||
polyline==1.3.2
|
||||
py==1.7.0 # via retry
|
||||
pycparser==2.19 # via cffi
|
||||
pydruid==0.5.0
|
||||
pydruid==0.5.2
|
||||
pyjwt==1.7.1 # via flask-appbuilder
|
||||
python-dateutil==2.6.1
|
||||
python-editor==1.0.3 # via alembic
|
||||
|
|
|
|||
|
|
@ -1635,7 +1635,7 @@
|
|||
"freeForm": true,
|
||||
"label": "Number format",
|
||||
"renderTrigger": true,
|
||||
"default": ".3s",
|
||||
"default": "SMART_NUMBER",
|
||||
"choices": [
|
||||
[
|
||||
".1s",
|
||||
|
|
@ -2085,7 +2085,7 @@
|
|||
"freeForm": true,
|
||||
"label": "X Axis Format",
|
||||
"renderTrigger": true,
|
||||
"default": ".3s",
|
||||
"default": "SMART_NUMBER",
|
||||
"choices": [
|
||||
[
|
||||
".1s",
|
||||
|
|
@ -2165,7 +2165,7 @@
|
|||
"freeForm": true,
|
||||
"label": "Y Axis Format",
|
||||
"renderTrigger": true,
|
||||
"default": ".3s",
|
||||
"default": "SMART_NUMBER",
|
||||
"choices": [
|
||||
[
|
||||
".1s",
|
||||
|
|
@ -2206,7 +2206,7 @@
|
|||
"type": "SelectControl",
|
||||
"freeForm": true,
|
||||
"label": "Right Axis Format",
|
||||
"default": ".3s",
|
||||
"default": "SMART_NUMBER",
|
||||
"choices": [
|
||||
[
|
||||
".1s",
|
||||
|
|
@ -2497,6 +2497,64 @@
|
|||
"default": "150",
|
||||
"description": "Font size for the biggest value in the list"
|
||||
},
|
||||
"header_font_size": {
|
||||
"type": "SelectControl",
|
||||
"label": "Header Font Size",
|
||||
"renderTrigger": true,
|
||||
"clearable": false,
|
||||
"default": 0.3,
|
||||
"options": [
|
||||
{
|
||||
"label": "Tiny",
|
||||
"value": 0.125
|
||||
},
|
||||
{
|
||||
"label": "Small",
|
||||
"value": 0.2
|
||||
},
|
||||
{
|
||||
"label": "Normal",
|
||||
"value": 0.3
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": 0.4
|
||||
},
|
||||
{
|
||||
"label": "Huge",
|
||||
"value": 0.5
|
||||
}
|
||||
]
|
||||
},
|
||||
"subheader_font_size": {
|
||||
"type": "SelectControl",
|
||||
"label": "Subheader Font Size",
|
||||
"renderTrigger": true,
|
||||
"clearable": false,
|
||||
"default": 0.125,
|
||||
"options": [
|
||||
{
|
||||
"label": "Tiny",
|
||||
"value": 0.125
|
||||
},
|
||||
{
|
||||
"label": "Small",
|
||||
"value": 0.2
|
||||
},
|
||||
{
|
||||
"label": "Normal",
|
||||
"value": 0.3
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": 0.4
|
||||
},
|
||||
{
|
||||
"label": "Huge",
|
||||
"value": 0.5
|
||||
}
|
||||
]
|
||||
},
|
||||
"instant_filtering": {
|
||||
"type": "CheckboxControl",
|
||||
"label": "Instant Filtering",
|
||||
|
|
@ -3220,6 +3278,11 @@
|
|||
"renderTrigger": true,
|
||||
"description": "The color scheme for rendering chart"
|
||||
},
|
||||
"label_colors": {
|
||||
"type": "ColorMapControl",
|
||||
"label": "Color Map",
|
||||
"default": {}
|
||||
},
|
||||
"significance_level": {
|
||||
"type": "TextControl",
|
||||
"label": "Significance Level",
|
||||
|
|
|
|||
|
|
@ -32,18 +32,18 @@ export default () => describe('top-level controls', () => {
|
|||
cy.get('#app').then((data) => {
|
||||
const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
|
||||
const dashboard = bootstrapData.dashboard_data;
|
||||
const sliceIds = dashboard.slices.map(slice => (slice.slice_id));
|
||||
mapId = dashboard.slices.find(slice => (slice.form_data.viz_type === 'world_map')).slice_id;
|
||||
|
||||
sliceIds
|
||||
.forEach((id) => {
|
||||
const sliceRequest = `getJson_${id}`;
|
||||
dashboard.slices
|
||||
.forEach((slice) => {
|
||||
const sliceRequest = `getJson_${slice.slice_id}`;
|
||||
sliceRequests.push(`@${sliceRequest}`);
|
||||
cy.route('POST', `/superset/explore_json/?form_data={"slice_id":${id}}`).as(sliceRequest);
|
||||
const formData = `{"slice_id":${slice.slice_id},"viz_type":"${slice.form_data.viz_type}"}`;
|
||||
cy.route('GET', `/superset/explore_json/?form_data=${formData}`).as(sliceRequest);
|
||||
|
||||
const forceRefresh = `getJson_${id}_force`;
|
||||
const forceRefresh = `postJson_${slice.slice_id}_force`;
|
||||
forceRefreshRequests.push(`@${forceRefresh}`);
|
||||
cy.route('POST', `/superset/explore_json/?form_data={"slice_id":${id}}&force=true`).as(forceRefresh);
|
||||
cy.route('POST', `/superset/explore_json/?form_data={"slice_id":${slice.slice_id}}&force=true`).as(forceRefresh);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -69,7 +69,7 @@ export default () => describe('top-level controls', () => {
|
|||
.parent()
|
||||
.should('have.class', 'disabled');
|
||||
|
||||
cy.wait(`@getJson_${mapId}_force`);
|
||||
cy.wait(`@postJson_${mapId}_force`);
|
||||
cy.get('#save-dash-split-button').trigger('click');
|
||||
cy.contains('Force refresh dashboard').parent().not('have.class', 'disabled');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ export default () => describe('edit mode', () => {
|
|||
const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
|
||||
const dashboard = bootstrapData.dashboard_data;
|
||||
const boxplotChartId = dashboard.slices.find(slice => (slice.form_data.viz_type === 'box_plot')).slice_id;
|
||||
const boxplotRequest = `/superset/explore_json/?form_data={"slice_id":${boxplotChartId}}`;
|
||||
cy.route('POST', boxplotRequest).as('boxplotRequest');
|
||||
const formData = `{"slice_id":${boxplotChartId},"viz_type":"box_plot"}`;
|
||||
const boxplotRequest = `/superset/explore_json/?form_data=${formData}`;
|
||||
cy.route('GET', boxplotRequest).as('boxplotRequest');
|
||||
});
|
||||
|
||||
cy.get('.dashboard-header').contains('Edit dashboard').click();
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ export default () => describe('dashboard filter', () => {
|
|||
it('should apply filter', () => {
|
||||
const aliases = [];
|
||||
|
||||
const filterRoute = `/superset/explore_json/?form_data={"slice_id":${filterId}}`;
|
||||
cy.route('POST', filterRoute).as('fetchFilter');
|
||||
const formData = `{"slice_id":${filterId},"viz_type":"filter_box"}`;
|
||||
const filterRoute = `/superset/explore_json/?form_data=${formData}`;
|
||||
cy.route('GET', filterRoute).as('fetchFilter');
|
||||
cy.wait('@fetchFilter');
|
||||
sliceIds
|
||||
.filter(id => (parseInt(id, 10) !== filterId))
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ export default () => describe('load', () => {
|
|||
// then define routes and create alias for each requests
|
||||
slices.forEach((slice) => {
|
||||
const alias = `getJson_${slice.slice_id}`;
|
||||
cy.route('POST', `/superset/explore_json/?form_data={"slice_id":${slice.slice_id}}`).as(alias);
|
||||
const formData = `{"slice_id":${slice.slice_id},"viz_type":"${slice.form_data.viz_type}"}`;
|
||||
cy.route('GET', `/superset/explore_json/?form_data=${formData}`).as(alias);
|
||||
aliases.push(`@${alias}`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ export default () => describe('save', () => {
|
|||
cy.wait('@copyRequest');
|
||||
|
||||
// should have box_plot chart
|
||||
const boxplotRequest = `/superset/explore_json/?form_data={"slice_id":${boxplotChartId}}`;
|
||||
cy.route('POST', boxplotRequest).as('boxplotRequest');
|
||||
const formData = `{"slice_id":${boxplotChartId},"viz_type":"box_plot"}`;
|
||||
const boxplotRequest = `/superset/explore_json/?form_data=${formData}`;
|
||||
cy.route('GET', boxplotRequest).as('boxplotRequest');
|
||||
cy.wait('@boxplotRequest');
|
||||
cy.get('.grid-container .box_plot').should('be.exist');
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ describe('Groupby', () => {
|
|||
cy.server();
|
||||
cy.login();
|
||||
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=groupby]').within(() => {
|
||||
cy.get('.Select-control').click();
|
||||
|
|
@ -36,7 +37,7 @@ describe('Groupby', () => {
|
|||
cy.get('.VirtualizedSelectFocusedOption').click();
|
||||
});
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson', chartSelector: 'svg' });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -44,14 +45,15 @@ describe('AdhocMetrics', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Clear metric and set simple adhoc metric', () => {
|
||||
const metricName = 'Girl Births';
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=metrics]').within(() => {
|
||||
cy.get('.select-clear').click();
|
||||
|
|
@ -74,7 +76,7 @@ describe('AdhocMetrics', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
querySubstring: metricName,
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
|
|
@ -84,7 +86,7 @@ describe('AdhocMetrics', () => {
|
|||
const metric = 'SUM(num)/COUNT(DISTINCT name)';
|
||||
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=metrics]').within(() => {
|
||||
cy.get('.select-clear').click();
|
||||
|
|
@ -105,7 +107,7 @@ describe('AdhocMetrics', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
querySubstring: metric,
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
|
|
@ -113,7 +115,7 @@ describe('AdhocMetrics', () => {
|
|||
|
||||
it('Switch between simple and custom sql tabs', () => {
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=metrics]').within(() => {
|
||||
cy.get('.select-clear').click();
|
||||
|
|
@ -137,7 +139,7 @@ describe('AdhocMetrics', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
});
|
||||
|
|
@ -147,12 +149,13 @@ describe('AdhocFilters', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Set simple adhoc filter', () => {
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=adhoc_filters]').within(() => {
|
||||
cy.get('.Select-control').click({ force: true });
|
||||
|
|
@ -177,14 +180,14 @@ describe('AdhocFilters', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
});
|
||||
|
||||
it('Set custom adhoc filter', () => {
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=adhoc_filters]').within(() => {
|
||||
cy.get('.Select-control').click({ force: true });
|
||||
|
|
@ -206,7 +209,7 @@ describe('AdhocFilters', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
});
|
||||
|
|
@ -217,12 +220,13 @@ describe('Advanced analytics', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Create custom time compare', () => {
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('span')
|
||||
.contains('Advanced Analytics')
|
||||
|
|
@ -240,10 +244,10 @@ describe('Advanced analytics', () => {
|
|||
});
|
||||
|
||||
cy.get('button.query').click();
|
||||
cy.wait('@getJson');
|
||||
cy.wait('@postJson');
|
||||
cy.reload();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
|
||||
|
|
@ -257,12 +261,13 @@ describe('Annotations', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Create formula annotation y-axis goal line', () => {
|
||||
cy.visitChartByName('Num Births Trend');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=annotation_layers]').within(() => {
|
||||
cy.get('button').click();
|
||||
|
|
@ -280,7 +285,7 @@ describe('Annotations', () => {
|
|||
|
||||
cy.get('button.query').click();
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@postJson',
|
||||
chartSelector: 'svg',
|
||||
});
|
||||
|
||||
|
|
@ -292,7 +297,8 @@ describe('Time range filter', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Defaults to the correct tab for time_range params', () => {
|
||||
|
|
@ -304,7 +310,7 @@ describe('Time range filter', () => {
|
|||
};
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=time_range]').within(() => {
|
||||
cy.get('span.label').click();
|
||||
|
|
|
|||
|
|
@ -26,16 +26,17 @@ describe('Test explore links', () => {
|
|||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.server();
|
||||
cy.route('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('GET', '/superset/explore_json/**').as('getJson');
|
||||
cy.route('POST', '/superset/explore_json/**').as('postJson');
|
||||
});
|
||||
|
||||
it('Open and close view query modal', () => {
|
||||
cy.visitChartByName('Growth Rate');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('button#query').click();
|
||||
cy.get('span').contains('View query').parent().click();
|
||||
cy.wait('@getJson').then(() => {
|
||||
cy.wait('@postJson').then(() => {
|
||||
cy.get('code');
|
||||
});
|
||||
cy.get('.modal-header').within(() => {
|
||||
|
|
@ -47,7 +48,7 @@ describe('Test explore links', () => {
|
|||
cy.route('POST', 'r/shortner/').as('getShortUrl');
|
||||
|
||||
cy.visitChartByName('Growth Rate');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=short-link-button]').click();
|
||||
|
||||
|
|
@ -60,12 +61,12 @@ describe('Test explore links', () => {
|
|||
.then((text) => {
|
||||
cy.visit(text);
|
||||
});
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
});
|
||||
|
||||
it('Test iframe link', () => {
|
||||
cy.visitChartByName('Growth Rate');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=embed-code-button]').click();
|
||||
cy.get('#embed-code-popover').within(() => {
|
||||
|
|
@ -83,7 +84,7 @@ describe('Test explore links', () => {
|
|||
const newChartName = 'Test chart';
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
cy.url().then((url) => {
|
||||
cy.get('button[data-target="#save_modal"]').click();
|
||||
cy.get('.modal-content').within(() => {
|
||||
|
|
@ -93,14 +94,14 @@ describe('Test explore links', () => {
|
|||
cy.url().should('eq', url);
|
||||
|
||||
cy.visitChartByName(newChartName);
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
});
|
||||
});
|
||||
|
||||
xit('Test chart save', () => {
|
||||
const chartName = 'Test chart';
|
||||
cy.visitChartByName(chartName);
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
cy.get('[data-test=groupby]').within(() => {
|
||||
cy.get('span.select-clear-zone').click();
|
||||
|
|
@ -109,7 +110,7 @@ describe('Test explore links', () => {
|
|||
cy.get('.modal-content').within(() => {
|
||||
cy.get('button#btn_modal_save').click();
|
||||
});
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
cy.request(`/chart/api/read?_flt_3_slice_name=${chartName}`).then((response) => {
|
||||
cy.request('DELETE', `/chart/api/delete/${response.body.pks[0]}`);
|
||||
});
|
||||
|
|
@ -117,7 +118,7 @@ describe('Test explore links', () => {
|
|||
|
||||
it('Test chart save as and add to new dashboard', () => {
|
||||
cy.visitChartByName('Growth Rate');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
|
||||
const dashboardTitle = 'Test dashboard';
|
||||
cy.get('button[data-target="#save_modal"]').click();
|
||||
|
|
@ -127,7 +128,7 @@ describe('Test explore links', () => {
|
|||
cy.get('input[placeholder="[dashboard name]"]').type(dashboardTitle);
|
||||
cy.get('button#btn_modal_save').click();
|
||||
});
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
cy.request(`/dashboard/api/read?_flt_3_dashboard_title=${dashboardTitle}`).then((response) => {
|
||||
expect(response.body.pks[0]).not.equals(null);
|
||||
});
|
||||
|
|
@ -135,7 +136,7 @@ describe('Test explore links', () => {
|
|||
|
||||
it('Test chart save as and add to existing dashboard', () => {
|
||||
cy.visitChartByName('Most Populated Countries');
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
const chartName = 'New Most Populated Countries';
|
||||
const dashboardTitle = 'Test dashboard';
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ describe('Test explore links', () => {
|
|||
});
|
||||
cy.get('button#btn_modal_save').click();
|
||||
});
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@postJson' });
|
||||
cy.request(`/chart/api/read?_flt_3_slice_name=${chartName}`).then((response) => {
|
||||
cy.request('DELETE', `/chart/api/delete/${response.body.pks[0]}`);
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -81,7 +81,6 @@
|
|||
"@superset-ui/translation": "^0.11.0",
|
||||
"@vx/responsive": "0.0.172",
|
||||
"abortcontroller-polyfill": "^1.1.9",
|
||||
"bignumber.js": "^8.1.1",
|
||||
"bootstrap": "^3.3.6",
|
||||
"bootstrap-slider": "^10.0.0",
|
||||
"brace": "^0.11.1",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('chart actions', () => {
|
|||
});
|
||||
|
||||
it('should dispatch CHART_UPDATE_STARTED action before the query', () => {
|
||||
const actionThunk = actions.runQuery({});
|
||||
const actionThunk = actions.postChartFormData({});
|
||||
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, success
|
||||
|
|
@ -64,7 +64,7 @@ describe('chart actions', () => {
|
|||
});
|
||||
|
||||
it('should dispatch TRIGGER_QUERY action with the query', () => {
|
||||
const actionThunk = actions.runQuery({});
|
||||
const actionThunk = actions.postChartFormData({});
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, success
|
||||
expect(dispatch.callCount).toBe(5);
|
||||
|
|
@ -76,7 +76,7 @@ describe('chart actions', () => {
|
|||
});
|
||||
|
||||
it('should dispatch UPDATE_QUERY_FORM_DATA action with the query', () => {
|
||||
const actionThunk = actions.runQuery({});
|
||||
const actionThunk = actions.postChartFormData({});
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, success
|
||||
expect(dispatch.callCount).toBe(5);
|
||||
|
|
@ -88,7 +88,7 @@ describe('chart actions', () => {
|
|||
});
|
||||
|
||||
it('should dispatch logEvent async action', () => {
|
||||
const actionThunk = actions.runQuery({});
|
||||
const actionThunk = actions.postChartFormData({});
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, success
|
||||
expect(dispatch.callCount).toBe(5);
|
||||
|
|
@ -104,7 +104,7 @@ describe('chart actions', () => {
|
|||
});
|
||||
|
||||
it('should dispatch CHART_UPDATE_SUCCEEDED action upon success', () => {
|
||||
const actionThunk = actions.runQuery({});
|
||||
const actionThunk = actions.postChartFormData({});
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, success
|
||||
expect(dispatch.callCount).toBe(5);
|
||||
|
|
@ -120,7 +120,7 @@ describe('chart actions', () => {
|
|||
fetchMock.post(MOCK_URL, () => unresolvingPromise, { overwriteRoutes: true });
|
||||
|
||||
const timeoutInSec = 1 / 1000;
|
||||
const actionThunk = actions.runQuery({}, false, timeoutInSec);
|
||||
const actionThunk = actions.postChartFormData({}, false, timeoutInSec);
|
||||
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, fail
|
||||
|
|
@ -136,7 +136,7 @@ describe('chart actions', () => {
|
|||
fetchMock.post(MOCK_URL, { throws: { statusText: 'misc error' } }, { overwriteRoutes: true });
|
||||
|
||||
const timeoutInSec = 1 / 1000;
|
||||
const actionThunk = actions.runQuery({}, false, timeoutInSec);
|
||||
const actionThunk = actions.postChartFormData({}, false, timeoutInSec);
|
||||
|
||||
return actionThunk(dispatch).then(() => {
|
||||
// chart update, trigger query, update form data, fail
|
||||
|
|
|
|||
|
|
@ -1,53 +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 BigNumber from 'bignumber.js';
|
||||
import transform from 'src/chart/transformBigNumber';
|
||||
|
||||
describe('transformBigNumber', () => {
|
||||
it('should transform BigNumber on its own', () => {
|
||||
expect(transform(new BigNumber(123.456))).toBe(123.456);
|
||||
});
|
||||
|
||||
it('should transform BigNumber in objects', () => {
|
||||
expect(transform({
|
||||
foo: new BigNumber(123),
|
||||
bar: 456,
|
||||
baz: null,
|
||||
})).toEqual({ foo: 123, bar: 456, baz: null });
|
||||
});
|
||||
|
||||
it('should transform BigNumber in arrays', () => {
|
||||
expect(transform([
|
||||
{ foo: new BigNumber(123) },
|
||||
{ bar: 456 },
|
||||
])).toEqual([{ foo: 123 }, { bar: 456 }]);
|
||||
});
|
||||
|
||||
it('should transform BigNumber in nested structures', () => {
|
||||
expect(transform([{
|
||||
x: new BigNumber(123),
|
||||
y: [{ foo: new BigNumber(456) }, { bar: 'str' }],
|
||||
z: { some: [new BigNumber(789)] },
|
||||
}])).toEqual([{
|
||||
x: 123,
|
||||
y: [{ foo: 456 }, { bar: 'str' }],
|
||||
z: { some: [789] },
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
|
@ -31,6 +31,7 @@ import DashboardComponent from '../../../../src/dashboard/containers/DashboardCo
|
|||
import DashboardHeader from '../../../../src/dashboard/containers/DashboardHeader';
|
||||
import DashboardGrid from '../../../../src/dashboard/containers/DashboardGrid';
|
||||
import * as dashboardStateActions from '../../../../src/dashboard/actions/dashboardState';
|
||||
import { BUILDER_PANE_TYPE } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
import WithDragDropContext from '../helpers/WithDragDropContext';
|
||||
import {
|
||||
|
|
@ -61,7 +62,10 @@ describe('DashboardBuilder', () => {
|
|||
dashboardLayout,
|
||||
deleteTopLevelTabs() {},
|
||||
editMode: false,
|
||||
showBuilderPane: false,
|
||||
showBuilderPane() {},
|
||||
builderPaneType: BUILDER_PANE_TYPE.NONE,
|
||||
setColorSchemeAndUnsavedChanges() {},
|
||||
colorScheme: undefined,
|
||||
handleComponentDrop() {},
|
||||
toggleBuilderPane() {},
|
||||
};
|
||||
|
|
@ -143,11 +147,27 @@ describe('DashboardBuilder', () => {
|
|||
expect(parentSize.find(DashboardGrid)).toHaveLength(expectedCount);
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=showBuilderPane=true', () => {
|
||||
it('should render a BuilderComponentPane if editMode=true and user selects "Insert Components" pane', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BuilderComponentPane)).toHaveLength(0);
|
||||
|
||||
wrapper.setProps({ ...props, editMode: true, showBuilderPane: true });
|
||||
wrapper.setProps({
|
||||
...props,
|
||||
editMode: true,
|
||||
builderPaneType: BUILDER_PANE_TYPE.ADD_COMPONENTS,
|
||||
});
|
||||
expect(wrapper.find(BuilderComponentPane)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a BuilderComponentPane if editMode=true and user selects "Colors" pane', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(BuilderComponentPane)).toHaveLength(0);
|
||||
|
||||
wrapper.setProps({
|
||||
...props,
|
||||
editMode: true,
|
||||
builderPaneType: BUILDER_PANE_TYPE.COLORS,
|
||||
});
|
||||
expect(wrapper.find(BuilderComponentPane)).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe('Dashboard', () => {
|
|||
actions: {
|
||||
addSliceToDashboard() {},
|
||||
removeSliceFromDashboard() {},
|
||||
runQuery() {},
|
||||
postChartFormData() {},
|
||||
logEvent() {},
|
||||
},
|
||||
initMessages: [],
|
||||
|
|
@ -82,15 +82,15 @@ describe('Dashboard', () => {
|
|||
},
|
||||
};
|
||||
|
||||
it('should call runQuery for all non-exempt slices', () => {
|
||||
it('should call postChartFormData for all non-exempt slices', () => {
|
||||
const wrapper = setup({ charts: overrideCharts, slices: overrideSlices });
|
||||
const spy = sinon.spy(props.actions, 'runQuery');
|
||||
const spy = sinon.spy(props.actions, 'postChartFormData');
|
||||
wrapper.instance().refreshExcept('1001');
|
||||
spy.restore();
|
||||
expect(spy.callCount).toBe(Object.keys(overrideCharts).length - 1);
|
||||
});
|
||||
|
||||
it('should not call runQuery for filter_immune_slices', () => {
|
||||
it('should not call postChartFormData for filter_immune_slices', () => {
|
||||
const wrapper = setup({
|
||||
charts: overrideCharts,
|
||||
dashboardInfo: {
|
||||
|
|
@ -103,7 +103,7 @@ describe('Dashboard', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const spy = sinon.spy(props.actions, 'runQuery');
|
||||
const spy = sinon.spy(props.actions, 'postChartFormData');
|
||||
wrapper.instance().refreshExcept();
|
||||
spy.restore();
|
||||
expect(spy.callCount).toBe(0);
|
||||
|
|
|
|||
|
|
@ -71,9 +71,9 @@ describe('HeaderActionsDropdown', () => {
|
|||
expect(wrapper.find(MenuItem)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
it('should not render the RefreshIntervalModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toHaveLength(1);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should render the URLShortLinkModal', () => {
|
||||
|
|
@ -105,9 +105,9 @@ describe('HeaderActionsDropdown', () => {
|
|||
expect(wrapper.find(MenuItem)).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should render the RefreshIntervalModal', () => {
|
||||
it('should not render the RefreshIntervalModal', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toHaveLength(1);
|
||||
expect(wrapper.find(RefreshIntervalModal)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should render the URLShortLinkModal', () => {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import FaveStar from '../../../../src/components/FaveStar';
|
|||
import HeaderActionsDropdown from '../../../../src/dashboard/components/HeaderActionsDropdown';
|
||||
import Button from '../../../../src/components/Button';
|
||||
import UndoRedoKeylisteners from '../../../../src/dashboard/components/UndoRedoKeylisteners';
|
||||
import { BUILDER_PANE_TYPE } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
describe('Header', () => {
|
||||
const props = {
|
||||
|
|
@ -46,7 +47,8 @@ describe('Header', () => {
|
|||
updateDashboardTitle: () => {},
|
||||
editMode: false,
|
||||
setEditMode: () => {},
|
||||
showBuilderPane: false,
|
||||
showBuilderPane: () => {},
|
||||
builderPaneType: BUILDER_PANE_TYPE.NONE,
|
||||
toggleBuilderPane: () => {},
|
||||
updateCss: () => {},
|
||||
hasUnsavedChanges: false,
|
||||
|
|
@ -150,9 +152,9 @@ describe('Header', () => {
|
|||
expect(wrapper.find(HeaderActionsDropdown)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render four Buttons', () => {
|
||||
it('should render five Buttons', () => {
|
||||
const wrapper = setup(overrideProps);
|
||||
expect(wrapper.find(Button)).toHaveLength(4);
|
||||
expect(wrapper.find(Button)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should set up undo/redo', () => {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { id as sliceId } from './mockChartQueries';
|
||||
import { BUILDER_PANE_TYPE } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
export default {
|
||||
sliceIds: [sliceId],
|
||||
|
|
@ -24,7 +25,7 @@ export default {
|
|||
filters: {},
|
||||
expandedSlices: {},
|
||||
editMode: false,
|
||||
showBuilderPane: false,
|
||||
builderPaneType: BUILDER_PANE_TYPE.NONE,
|
||||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
isStarred: true,
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ import {
|
|||
SET_EDIT_MODE,
|
||||
SET_MAX_UNDO_HISTORY_EXCEEDED,
|
||||
SET_UNSAVED_CHANGES,
|
||||
TOGGLE_BUILDER_PANE,
|
||||
TOGGLE_EXPAND_SLICE,
|
||||
TOGGLE_FAVE_STAR,
|
||||
} from '../../../../src/dashboard/actions/dashboardState';
|
||||
|
||||
import dashboardStateReducer from '../../../../src/dashboard/reducers/dashboardState';
|
||||
import { BUILDER_PANE_TYPE } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
describe('dashboardState reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
|
|
@ -79,23 +79,10 @@ describe('dashboardState reducer', () => {
|
|||
{ editMode: false },
|
||||
{ type: SET_EDIT_MODE, editMode: true },
|
||||
),
|
||||
).toEqual({ editMode: true, showBuilderPane: true });
|
||||
});
|
||||
|
||||
it('should toggle builder pane', () => {
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ showBuilderPane: false },
|
||||
{ type: TOGGLE_BUILDER_PANE },
|
||||
),
|
||||
).toEqual({ showBuilderPane: true });
|
||||
|
||||
expect(
|
||||
dashboardStateReducer(
|
||||
{ showBuilderPane: true },
|
||||
{ type: TOGGLE_BUILDER_PANE },
|
||||
),
|
||||
).toEqual({ showBuilderPane: false });
|
||||
).toEqual({
|
||||
editMode: true,
|
||||
builderPaneType: BUILDER_PANE_TYPE.ADD_COMPONENTS,
|
||||
});
|
||||
});
|
||||
|
||||
it('should toggle expanded slices', () => {
|
||||
|
|
@ -150,6 +137,8 @@ describe('dashboardState reducer', () => {
|
|||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
editMode: false,
|
||||
builderPaneType: BUILDER_PANE_TYPE.NONE,
|
||||
updatedColorScheme: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { max } from 'd3-array';
|
||||
import { getAggFunc } from '../../../../../src/visualizations/deckgl/layers/common';
|
||||
import { getAggFunc, getBounds } from '../../../../../src/visualizations/deckgl/layers/common';
|
||||
|
||||
describe('deckgl layers common', () => {
|
||||
it('getAggFunc', () => {
|
||||
|
|
@ -46,4 +46,65 @@ describe('deckgl layers common', () => {
|
|||
expect(getAggFunc('p95', accessor)(arr)).toEqual(2.9);
|
||||
expect(getAggFunc('p99', accessor)(arr)).toEqual(2.98);
|
||||
});
|
||||
|
||||
describe('getBounds', () => {
|
||||
it('should return valid bounds for multiple points', () => {
|
||||
const points = [
|
||||
[0, 20],
|
||||
[5, 25],
|
||||
[10, 15],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[0, 15],
|
||||
[10, 25],
|
||||
]);
|
||||
});
|
||||
it('should return valid bounds for single latitude point', () => {
|
||||
const points = [
|
||||
[0, 0],
|
||||
[5, 0],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[0, -0.25],
|
||||
[5, 0.25],
|
||||
]);
|
||||
});
|
||||
it('should return valid bounds for single longitude point', () => {
|
||||
const points = [
|
||||
[0, 0],
|
||||
[0, 5],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[-0.25, 0],
|
||||
[0.25, 5],
|
||||
]);
|
||||
});
|
||||
it('should return valid bounds for single point', () => {
|
||||
const points = [
|
||||
[0, 0],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[-0.25, -0.25],
|
||||
[0.25, 0.25],
|
||||
]);
|
||||
});
|
||||
it('should return valid bounds for point 90, 180', () => {
|
||||
const points = [
|
||||
[180, 90],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[179.75, 89.75],
|
||||
[180, 90],
|
||||
]);
|
||||
});
|
||||
it('should return valid bounds for point -90, -180', () => {
|
||||
const points = [
|
||||
[-180, -90],
|
||||
];
|
||||
expect(getBounds(points)).toEqual([
|
||||
[-180, -90],
|
||||
[-179.75, -89.75],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ describe('getBreakPoints', () => {
|
|||
});
|
||||
|
||||
it('returns sorted break points', () => {
|
||||
const fd = { breakPoints: ['0', '10', '100', '50', '1000'] };
|
||||
const fd = { break_points: ['0', '10', '100', '50', '1000'] };
|
||||
const result = getBreakPoints(fd, [], metricAccessor);
|
||||
const expected = ['0', '10', '50', '100', '1000'];
|
||||
expect(result).toEqual(expected);
|
||||
|
|
@ -45,7 +45,7 @@ describe('getBreakPoints', () => {
|
|||
});
|
||||
|
||||
it('formats number with proper precision', () => {
|
||||
const fd = { metric: 'count', numBuckets: 2 };
|
||||
const fd = { metric: 'count', num_buckets: 2 };
|
||||
const features = [0, 1 / 3, 2 / 3, 1].map(count => ({ count }));
|
||||
const result = getBreakPoints(fd, features, metricAccessor);
|
||||
const expected = ['0.0', '0.5', '1.0'];
|
||||
|
|
@ -53,7 +53,7 @@ describe('getBreakPoints', () => {
|
|||
});
|
||||
|
||||
it('works with a zero range', () => {
|
||||
const fd = { metric: 'count', numBuckets: 1 };
|
||||
const fd = { metric: 'count', num_buckets: 1 };
|
||||
const features = [1, 1, 1].map(count => ({ count }));
|
||||
const result = getBreakPoints(fd, features, metricAccessor);
|
||||
const expected = ['1', '1'];
|
||||
|
|
@ -69,7 +69,7 @@ describe('getBreakPointColorScaler', () => {
|
|||
it('returns linear color scaler if there are no break points', () => {
|
||||
const fd = {
|
||||
metric: 'count',
|
||||
linearColorScheme: ['#000000', '#ffffff'],
|
||||
linear_color_scheme: ['#000000', '#ffffff'],
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [10, 20, 30].map(count => ({ count }));
|
||||
|
|
@ -82,8 +82,8 @@ describe('getBreakPointColorScaler', () => {
|
|||
it('returns bucketing scaler if there are break points', () => {
|
||||
const fd = {
|
||||
metric: 'count',
|
||||
linearColorScheme: ['#000000', '#ffffff'],
|
||||
breakPoints: ['0', '1', '10'],
|
||||
linear_color_scheme: ['#000000', '#ffffff'],
|
||||
break_points: ['0', '1', '10'],
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
|
|
@ -97,8 +97,8 @@ describe('getBreakPointColorScaler', () => {
|
|||
it('mask values outside the break points', () => {
|
||||
const fd = {
|
||||
metric: 'count',
|
||||
linearColorScheme: ['#000000', '#ffffff'],
|
||||
breakPoints: ['0', '1', '10'],
|
||||
linear_color_scheme: ['#000000', '#ffffff'],
|
||||
break_points: ['0', '1', '10'],
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
|
|
@ -116,8 +116,8 @@ describe('getBuckets', () => {
|
|||
it('computes buckets for break points', () => {
|
||||
const fd = {
|
||||
metric: 'count',
|
||||
linearColorScheme: ['#000000', '#ffffff'],
|
||||
breakPoints: ['0', '1', '10'],
|
||||
linear_color_scheme: ['#000000', '#ffffff'],
|
||||
break_points: ['0', '1', '10'],
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import shortid from 'shortid';
|
||||
import JSONbig from 'json-bigint';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { SupersetClient } from '@superset-ui/connection';
|
||||
|
||||
|
|
@ -128,9 +129,11 @@ export function fetchQueryResults(query) {
|
|||
|
||||
return SupersetClient.get({
|
||||
endpoint: `/superset/results/${query.resultsKey}/`,
|
||||
parseMethod: 'text',
|
||||
})
|
||||
.then(({ json = {} }) => {
|
||||
dispatch(querySuccess(query, json));
|
||||
.then(({ text = '{}' }) => {
|
||||
const bigIntJson = JSONbig.parse(text);
|
||||
dispatch(querySuccess(query, bigIntJson));
|
||||
})
|
||||
.catch(response =>
|
||||
getClientErrorObject(response).then((error) => {
|
||||
|
|
@ -164,10 +167,12 @@ export function runQuery(query) {
|
|||
endpoint: `/superset/sql_json/${window.location.search}`,
|
||||
postPayload,
|
||||
stringify: false,
|
||||
parseMethod: 'text',
|
||||
})
|
||||
.then(({ json }) => {
|
||||
.then(({ text = '{}' }) => {
|
||||
if (!query.runAsync) {
|
||||
dispatch(querySuccess(query, json));
|
||||
const bigIntJson = JSONbig.parse(text);
|
||||
dispatch(querySuccess(query, bigIntJson));
|
||||
}
|
||||
})
|
||||
.catch(response =>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
|
||||
import { Logger, LOG_ACTIONS_RENDER_CHART_CONTAINER } from '../logger/LogUtils';
|
||||
import Loading from '../components/Loading';
|
||||
import RefreshChartOverlay from '../components/RefreshChartOverlay';
|
||||
|
|
@ -68,12 +70,23 @@ class Chart extends React.PureComponent {
|
|||
}
|
||||
componentDidMount() {
|
||||
if (this.props.triggerQuery) {
|
||||
this.props.actions.runQuery(
|
||||
this.props.formData,
|
||||
false,
|
||||
this.props.timeout,
|
||||
this.props.chartId,
|
||||
);
|
||||
if (this.props.chartId > 0) {
|
||||
// Load saved chart with a GET request
|
||||
this.props.actions.getSavedChart(
|
||||
this.props.formData,
|
||||
false,
|
||||
this.props.timeout,
|
||||
this.props.chartId,
|
||||
);
|
||||
} else {
|
||||
// Create chart with POST request
|
||||
this.props.actions.postChartFormData(
|
||||
this.props.formData,
|
||||
false,
|
||||
this.props.timeout,
|
||||
this.props.chartId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +135,9 @@ class Chart extends React.PureComponent {
|
|||
if (chartStatus === 'failed') {
|
||||
return this.renderStackTraceMessage();
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
return <Alert bsStyle="warning">{errorMessage}</Alert>;
|
||||
}
|
||||
return (
|
||||
<ErrorBoundary onError={this.handleRenderContainerFailure} showMessage={false}>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import React from 'react';
|
|||
import { ChartProps, SuperChart } from '@superset-ui/chart';
|
||||
import { Tooltip } from 'react-bootstrap';
|
||||
import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger/LogUtils';
|
||||
import transformBigNumber from './transformBigNumber';
|
||||
|
||||
const propTypes = {
|
||||
annotationData: PropTypes.object,
|
||||
|
|
@ -68,7 +67,6 @@ class ChartRenderer extends React.Component {
|
|||
this.handleAddFilter = this.handleAddFilter.bind(this);
|
||||
this.handleRenderSuccess = this.handleRenderSuccess.bind(this);
|
||||
this.handleRenderFailure = this.handleRenderFailure.bind(this);
|
||||
this.preTransformProps = this.preTransformProps.bind(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
|
@ -87,7 +85,8 @@ class ChartRenderer extends React.Component {
|
|||
nextProps.height !== this.props.height ||
|
||||
nextProps.width !== this.props.width ||
|
||||
nextState.tooltip !== this.state.tooltip ||
|
||||
nextProps.triggerRender) {
|
||||
nextProps.triggerRender ||
|
||||
nextProps.formData.color_scheme !== this.props.formData.color_scheme) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -166,18 +165,6 @@ class ChartRenderer extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
preTransformProps(chartProps) {
|
||||
const payload = chartProps.payload;
|
||||
const data = transformBigNumber(payload.data);
|
||||
return new ChartProps({
|
||||
...chartProps,
|
||||
payload: {
|
||||
...payload,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
renderTooltip() {
|
||||
const { tooltip } = this.state;
|
||||
if (tooltip && tooltip.content) {
|
||||
|
|
@ -212,9 +199,8 @@ class ChartRenderer extends React.Component {
|
|||
|
||||
const isLoading = chartStatus === 'loading';
|
||||
|
||||
const skipChartRendering = isLoading || !!chartAlert;
|
||||
const skipChartRendering = isLoading || !!chartAlert || chartStatus === null;
|
||||
this.renderStartTime = Logger.getTimestamp();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.renderTooltip()}
|
||||
|
|
@ -223,7 +209,6 @@ class ChartRenderer extends React.Component {
|
|||
className={`${snakeCase(vizType)}`}
|
||||
chartType={vizType}
|
||||
chartProps={skipChartRendering ? null : this.prepareChartProps()}
|
||||
preTransformProps={this.preTransformProps}
|
||||
onRenderSuccess={this.handleRenderSuccess}
|
||||
onRenderFailure={this.handleRenderFailure}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -165,14 +165,14 @@ export function addChart(chart, key) {
|
|||
return { type: ADD_CHART, chart, key };
|
||||
}
|
||||
|
||||
export const RUN_QUERY = 'RUN_QUERY';
|
||||
export function runQuery(formData, force = false, timeout = 60, key) {
|
||||
export function exploreJSON(formData, force = false, timeout = 60, key, method) {
|
||||
return (dispatch) => {
|
||||
const { url, payload } = getExploreUrlAndPayload({
|
||||
formData,
|
||||
endpointType: 'json',
|
||||
force,
|
||||
allowDomainSharding: true,
|
||||
method,
|
||||
});
|
||||
const logStart = Logger.getTimestamp();
|
||||
const controller = new AbortController();
|
||||
|
|
@ -193,7 +193,9 @@ export function runQuery(formData, force = false, timeout = 60, key) {
|
|||
credentials: 'include',
|
||||
};
|
||||
}
|
||||
const queryPromise = SupersetClient.post(querySettings)
|
||||
|
||||
const clientMethod = method === 'GET' ? SupersetClient.get : SupersetClient.post;
|
||||
const queryPromise = clientMethod(querySettings)
|
||||
.then(({ json }) => {
|
||||
dispatch(logEvent(LOG_ACTIONS_LOAD_CHART, {
|
||||
slice_id: key,
|
||||
|
|
@ -246,6 +248,32 @@ export function runQuery(formData, force = false, timeout = 60, key) {
|
|||
};
|
||||
}
|
||||
|
||||
export const GET_SAVED_CHART = 'GET_SAVED_CHART';
|
||||
export function getSavedChart(formData, force = false, timeout = 60, key) {
|
||||
/*
|
||||
* Perform a GET request to `/explore_json`.
|
||||
*
|
||||
* This will return the payload of a saved chart, optionally filtered by
|
||||
* ad-hoc or extra filters from dashboards. Eg:
|
||||
*
|
||||
* GET /explore_json?{"chart_id":1}
|
||||
* GET /explore_json?{"chart_id":1,"extra_filters":"..."}
|
||||
*
|
||||
*/
|
||||
return exploreJSON(formData, force, timeout, key, 'GET');
|
||||
}
|
||||
|
||||
export const POST_CHART_FORM_DATA = 'POST_CHART_FORM_DATA';
|
||||
export function postChartFormData(formData, force = false, timeout = 60, key) {
|
||||
/*
|
||||
* Perform a POST request to `/explore_json`.
|
||||
*
|
||||
* This will post the form data to the endpoint, returning a new chart.
|
||||
*
|
||||
*/
|
||||
return exploreJSON(formData, force, timeout, key, 'POST');
|
||||
}
|
||||
|
||||
export function redirectSQLLab(formData) {
|
||||
return (dispatch) => {
|
||||
const { url } = getExploreUrlAndPayload({ formData, endpointType: 'query' });
|
||||
|
|
@ -272,6 +300,6 @@ export function refreshChart(chart, force, timeout) {
|
|||
if (!chart.latestQueryFormData || Object.keys(chart.latestQueryFormData).length === 0) {
|
||||
return;
|
||||
}
|
||||
dispatch(runQuery(chart.latestQueryFormData, force, timeout, chart.id));
|
||||
dispatch(postChartFormData(chart.latestQueryFormData, force, timeout, chart.id));
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ export default function chartReducer(charts = {}, action) {
|
|||
return { ...state,
|
||||
chartStatus: 'success',
|
||||
queryResponse: action.queryResponse,
|
||||
chartUpdateEndTime: now(),
|
||||
chartAlert: null,
|
||||
};
|
||||
},
|
||||
|
|
@ -72,6 +71,7 @@ export default function chartReducer(charts = {}, action) {
|
|||
[actions.CHART_RENDERING_SUCCEEDED](state) {
|
||||
return { ...state,
|
||||
chartStatus: 'rendered',
|
||||
chartUpdateEndTime: now(),
|
||||
};
|
||||
},
|
||||
[actions.CHART_RENDERING_FAILED](state) {
|
||||
|
|
|
|||
|
|
@ -1,45 +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.
|
||||
*/
|
||||
// This method transforms any BigNumber object in the given payload to its
|
||||
// 64-bit float representation. It is a temporary fix so charts receive
|
||||
// floats instead of BigNumber instances in their props to properly render.
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export default function transform(payload) {
|
||||
if (!payload) {
|
||||
return payload;
|
||||
} else if (BigNumber.isBigNumber(payload)) {
|
||||
return payload.toNumber();
|
||||
} else if (payload.constructor === Object) {
|
||||
for (const key in payload) {
|
||||
if (payload.hasOwnProperty(key)) {
|
||||
// Modify in place to prevent creating large payloads
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
payload[key] = transform(payload[key]);
|
||||
}
|
||||
}
|
||||
} else if (payload.constructor === Array) {
|
||||
payload.forEach((elem, idx) => {
|
||||
// Modify in place to prevent creating large payloads
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
payload[idx] = transform(elem);
|
||||
});
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
|
@ -209,7 +209,8 @@ export function undoLayoutAction() {
|
|||
|
||||
if (
|
||||
dashboardLayout.past.length === 0 &&
|
||||
!dashboardState.maxUndoHistoryExceeded
|
||||
!dashboardState.maxUndoHistoryExceeded &&
|
||||
!dashboardState.updatedColorScheme
|
||||
) {
|
||||
dispatch(setUnsavedChanges(false));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,9 +226,9 @@ export function startPeriodicRender(interval) {
|
|||
};
|
||||
}
|
||||
|
||||
export const TOGGLE_BUILDER_PANE = 'TOGGLE_BUILDER_PANE';
|
||||
export function toggleBuilderPane() {
|
||||
return { type: TOGGLE_BUILDER_PANE };
|
||||
export const SHOW_BUILDER_PANE = 'SHOW_BUILDER_PANE';
|
||||
export function showBuilderPane(builderPaneType) {
|
||||
return { type: SHOW_BUILDER_PANE, builderPaneType };
|
||||
}
|
||||
|
||||
export function addSliceToDashboard(id) {
|
||||
|
|
@ -267,6 +267,18 @@ export function removeSliceFromDashboard(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export const SET_COLOR_SCHEME = 'SET_COLOR_SCHEME';
|
||||
export function setColorScheme(colorScheme) {
|
||||
return { type: SET_COLOR_SCHEME, colorScheme };
|
||||
}
|
||||
|
||||
export function setColorSchemeAndUnsavedChanges(colorScheme) {
|
||||
return dispatch => {
|
||||
dispatch(setColorScheme(colorScheme));
|
||||
dispatch(setUnsavedChanges(true));
|
||||
};
|
||||
}
|
||||
|
||||
// Undo history ---------------------------------------------------------------
|
||||
export const SET_MAX_UNDO_HISTORY_EXCEEDED = 'SET_MAX_UNDO_HISTORY_EXCEEDED';
|
||||
export function setMaxUndoHistoryExceeded(maxUndoHistoryExceeded = true) {
|
||||
|
|
|
|||
|
|
@ -19,49 +19,37 @@
|
|||
/* eslint-env browser */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import { StickyContainer, Sticky } from 'react-sticky';
|
||||
import { ParentSize } from '@vx/responsive';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import NewColumn from './gridComponents/new/NewColumn';
|
||||
import NewDivider from './gridComponents/new/NewDivider';
|
||||
import NewHeader from './gridComponents/new/NewHeader';
|
||||
import NewRow from './gridComponents/new/NewRow';
|
||||
import NewTabs from './gridComponents/new/NewTabs';
|
||||
import NewMarkdown from './gridComponents/new/NewMarkdown';
|
||||
import SliceAdder from '../containers/SliceAdder';
|
||||
|
||||
const SUPERSET_HEADER_HEIGHT = 59;
|
||||
import InsertComponentPane, {
|
||||
SUPERSET_HEADER_HEIGHT,
|
||||
} from './InsertComponentPane';
|
||||
import ColorComponentPane from './ColorComponentPane';
|
||||
import { BUILDER_PANE_TYPE } from '../util/constants';
|
||||
|
||||
const propTypes = {
|
||||
topOffset: PropTypes.number,
|
||||
toggleBuilderPane: PropTypes.func.isRequired,
|
||||
showBuilderPane: PropTypes.func.isRequired,
|
||||
builderPaneType: PropTypes.string.isRequired,
|
||||
setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired,
|
||||
colorScheme: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
topOffset: 0,
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class BuilderComponentPane extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
slideDirection: 'slide-out',
|
||||
};
|
||||
|
||||
this.openSlicesPane = this.slide.bind(this, 'slide-in');
|
||||
this.closeSlicesPane = this.slide.bind(this, 'slide-out');
|
||||
}
|
||||
|
||||
slide(direction) {
|
||||
this.setState({
|
||||
slideDirection: direction,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { topOffset } = this.props;
|
||||
const {
|
||||
topOffset,
|
||||
builderPaneType,
|
||||
showBuilderPane,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
colorScheme,
|
||||
} = this.props;
|
||||
return (
|
||||
<div
|
||||
className="dashboard-builder-sidepane"
|
||||
|
|
@ -78,56 +66,22 @@ class BuilderComponentPane extends React.PureComponent {
|
|||
className="viewport"
|
||||
style={isSticky ? { ...style, top: topOffset } : null}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'slider-container',
|
||||
this.state.slideDirection,
|
||||
)}
|
||||
>
|
||||
<div className="component-layer slide-content">
|
||||
<div className="dashboard-builder-sidepane-header">
|
||||
<span>{t('Insert components')}</span>
|
||||
<i
|
||||
className="fa fa-times trigger"
|
||||
onClick={this.props.toggleBuilderPane}
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="new-component static"
|
||||
role="none"
|
||||
onClick={this.openSlicesPane}
|
||||
>
|
||||
<div className="new-component-placeholder fa fa-area-chart" />
|
||||
<div className="new-component-label">
|
||||
{t('Your charts & filters')}
|
||||
</div>
|
||||
|
||||
<i className="fa fa-arrow-right trigger" />
|
||||
</div>
|
||||
<NewTabs />
|
||||
<NewRow />
|
||||
<NewColumn />
|
||||
<NewHeader />
|
||||
<NewMarkdown />
|
||||
<NewDivider />
|
||||
</div>
|
||||
<div className="slices-layer slide-content">
|
||||
<div
|
||||
className="dashboard-builder-sidepane-header"
|
||||
onClick={this.closeSlicesPane}
|
||||
role="none"
|
||||
>
|
||||
<i className="fa fa-arrow-left trigger" />
|
||||
<span>{t('Your charts and filters')}</span>
|
||||
</div>
|
||||
<SliceAdder
|
||||
height={
|
||||
height + (isSticky ? SUPERSET_HEADER_HEIGHT : 0)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{builderPaneType === BUILDER_PANE_TYPE.ADD_COMPONENTS && (
|
||||
<InsertComponentPane
|
||||
height={height}
|
||||
isSticky={isSticky}
|
||||
showBuilderPane={showBuilderPane}
|
||||
/>
|
||||
)}
|
||||
{builderPaneType === BUILDER_PANE_TYPE.COLORS && (
|
||||
<ColorComponentPane
|
||||
showBuilderPane={showBuilderPane}
|
||||
setColorSchemeAndUnsavedChanges={
|
||||
setColorSchemeAndUnsavedChanges
|
||||
}
|
||||
colorScheme={colorScheme}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Sticky>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint-env browser */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { getCategoricalSchemeRegistry } from '@superset-ui/color';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import ColorSchemeControl from '../../explore/components/controls/ColorSchemeControl';
|
||||
import { BUILDER_PANE_TYPE } from '../util/constants';
|
||||
|
||||
const propTypes = {
|
||||
showBuilderPane: PropTypes.func.isRequired,
|
||||
setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired,
|
||||
colorScheme: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class ColorComponentPane extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hovered: false };
|
||||
this.categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
||||
this.getChoices = this.getChoices.bind(this);
|
||||
this.getSchemes = this.getSchemes.bind(this);
|
||||
this.onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.onMouseEnter = this.setHover.bind(this, true);
|
||||
this.onMouseLeave = this.setHover.bind(this, false);
|
||||
}
|
||||
|
||||
onCloseButtonClick() {
|
||||
this.props.showBuilderPane(BUILDER_PANE_TYPE.NONE);
|
||||
}
|
||||
|
||||
getChoices() {
|
||||
return this.categoricalSchemeRegistry.keys().map(s => [s, s]);
|
||||
}
|
||||
|
||||
getSchemes() {
|
||||
return this.categoricalSchemeRegistry.getMap();
|
||||
}
|
||||
|
||||
setHover(hovered) {
|
||||
this.setState({ hovered });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { setColorSchemeAndUnsavedChanges, colorScheme } = this.props;
|
||||
|
||||
return (
|
||||
<div className="slider-container">
|
||||
<div className="component-layer slide-content">
|
||||
<div className="dashboard-builder-sidepane-header">
|
||||
<span>{'Color Settings'}</span>
|
||||
<i
|
||||
className="fa fa-times trigger"
|
||||
onClick={this.onCloseButtonClick}
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="panel-body"
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
>
|
||||
<ColorSchemeControl
|
||||
description={t(
|
||||
"Any color palette selected here will override the colors applied to this dashboard's individual charts",
|
||||
)}
|
||||
label={t('Color Scheme')}
|
||||
name="color_scheme"
|
||||
onChange={setColorSchemeAndUnsavedChanges}
|
||||
value={colorScheme}
|
||||
choices={this.getChoices}
|
||||
schemes={this.getSchemes}
|
||||
hovered={this.state.hovered}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ColorComponentPane.propTypes = propTypes;
|
||||
ColorComponentPane.defaultProps = defaultProps;
|
||||
|
||||
export default ColorComponentPane;
|
||||
|
|
@ -40,7 +40,7 @@ const propTypes = {
|
|||
actions: PropTypes.shape({
|
||||
addSliceToDashboard: PropTypes.func.isRequired,
|
||||
removeSliceFromDashboard: PropTypes.func.isRequired,
|
||||
runQuery: PropTypes.func.isRequired,
|
||||
postChartFormData: PropTypes.func.isRequired,
|
||||
logEvent: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
dashboardInfo: dashboardInfoPropShape.isRequired,
|
||||
|
|
@ -153,10 +153,12 @@ class Dashboard extends React.PureComponent {
|
|||
chart,
|
||||
dashboardMetadata: this.props.dashboardInfo.metadata,
|
||||
filters: this.props.dashboardState.filters,
|
||||
colorScheme: this.props.dashboardState.colorScheme,
|
||||
colorNamespace: this.props.dashboardState.colorNamespace,
|
||||
sliceId: chart.id,
|
||||
});
|
||||
|
||||
this.props.actions.runQuery(
|
||||
this.props.actions.postChartFormData(
|
||||
updatedFormData,
|
||||
false,
|
||||
this.props.timeout,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import getDragDropManager from '../util/getDragDropManager';
|
|||
import findTabIndexByComponentId from '../util/findTabIndexByComponentId';
|
||||
|
||||
import {
|
||||
BUILDER_PANE_TYPE,
|
||||
DASHBOARD_GRID_ID,
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_ROOT_DEPTH,
|
||||
|
|
@ -52,7 +53,10 @@ const propTypes = {
|
|||
dashboardLayout: PropTypes.object.isRequired,
|
||||
deleteTopLevelTabs: PropTypes.func.isRequired,
|
||||
editMode: PropTypes.bool.isRequired,
|
||||
showBuilderPane: PropTypes.bool,
|
||||
showBuilderPane: PropTypes.func.isRequired,
|
||||
builderPaneType: PropTypes.string.isRequired,
|
||||
setColorSchemeAndUnsavedChanges: PropTypes.func.isRequired,
|
||||
colorScheme: PropTypes.string,
|
||||
handleComponentDrop: PropTypes.func.isRequired,
|
||||
toggleBuilderPane: PropTypes.func.isRequired,
|
||||
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
||||
|
|
@ -61,6 +65,7 @@ const propTypes = {
|
|||
const defaultProps = {
|
||||
showBuilderPane: false,
|
||||
directPathToChild: [],
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class DashboardBuilder extends React.Component {
|
||||
|
|
@ -116,7 +121,15 @@ class DashboardBuilder extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { handleComponentDrop, dashboardLayout, editMode } = this.props;
|
||||
const {
|
||||
handleComponentDrop,
|
||||
dashboardLayout,
|
||||
editMode,
|
||||
showBuilderPane,
|
||||
builderPaneType,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
colorScheme,
|
||||
} = this.props;
|
||||
const { tabIndex } = this.state;
|
||||
const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
|
||||
const rootChildId = dashboardRoot.children[0];
|
||||
|
|
@ -216,10 +229,13 @@ class DashboardBuilder extends React.Component {
|
|||
)}
|
||||
</ParentSize>
|
||||
</div>
|
||||
{this.props.editMode && this.props.showBuilderPane && (
|
||||
{editMode && builderPaneType !== BUILDER_PANE_TYPE.NONE && (
|
||||
<BuilderComponentPane
|
||||
topOffset={HEADER_HEIGHT + (topLevelTabs ? TABS_HEIGHT : 0)}
|
||||
toggleBuilderPane={this.props.toggleBuilderPane}
|
||||
showBuilderPane={showBuilderPane}
|
||||
builderPaneType={builderPaneType}
|
||||
setColorSchemeAndUnsavedChanges={setColorSchemeAndUnsavedChanges}
|
||||
colorScheme={colorScheme}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
/* eslint-env browser */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CategoricalColorNamespace } from '@superset-ui/color';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import HeaderActionsDropdown from './HeaderActionsDropdown';
|
||||
|
|
@ -29,6 +30,7 @@ import UndoRedoKeylisteners from './UndoRedoKeylisteners';
|
|||
|
||||
import { chartPropShape } from '../util/propShapes';
|
||||
import {
|
||||
BUILDER_PANE_TYPE,
|
||||
UNDO_LIMIT,
|
||||
SAVE_TYPE_OVERWRITE,
|
||||
DASHBOARD_POSITION_DATA_LIMIT,
|
||||
|
|
@ -52,6 +54,8 @@ const propTypes = {
|
|||
filters: PropTypes.object.isRequired,
|
||||
expandedSlices: PropTypes.object.isRequired,
|
||||
css: PropTypes.string.isRequired,
|
||||
colorNamespace: PropTypes.string,
|
||||
colorScheme: PropTypes.string,
|
||||
isStarred: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
|
|
@ -63,8 +67,8 @@ const propTypes = {
|
|||
updateDashboardTitle: PropTypes.func.isRequired,
|
||||
editMode: PropTypes.bool.isRequired,
|
||||
setEditMode: PropTypes.func.isRequired,
|
||||
showBuilderPane: PropTypes.bool.isRequired,
|
||||
toggleBuilderPane: PropTypes.func.isRequired,
|
||||
showBuilderPane: PropTypes.func.isRequired,
|
||||
builderPaneType: PropTypes.string.isRequired,
|
||||
updateCss: PropTypes.func.isRequired,
|
||||
logEvent: PropTypes.func.isRequired,
|
||||
hasUnsavedChanges: PropTypes.bool.isRequired,
|
||||
|
|
@ -81,6 +85,11 @@ const propTypes = {
|
|||
setRefreshFrequency: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
colorNamespace: undefined,
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class Header extends React.PureComponent {
|
||||
static discardChanges() {
|
||||
window.location.reload();
|
||||
|
|
@ -96,6 +105,10 @@ class Header extends React.PureComponent {
|
|||
this.handleChangeText = this.handleChangeText.bind(this);
|
||||
this.handleCtrlZ = this.handleCtrlZ.bind(this);
|
||||
this.handleCtrlY = this.handleCtrlY.bind(this);
|
||||
this.onInsertComponentsButtonClick = this.onInsertComponentsButtonClick.bind(
|
||||
this,
|
||||
);
|
||||
this.onColorsButtonClick = this.onColorsButtonClick.bind(this);
|
||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||
this.forceRefresh = this.forceRefresh.bind(this);
|
||||
this.startPeriodicRender = this.startPeriodicRender.bind(this);
|
||||
|
|
@ -128,25 +141,12 @@ class Header extends React.PureComponent {
|
|||
clearTimeout(this.ctrlZTimeout);
|
||||
}
|
||||
|
||||
forceRefresh() {
|
||||
if (!this.props.isLoading) {
|
||||
const chartList = Object.values(this.props.charts);
|
||||
this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
|
||||
force: true,
|
||||
interval: 0,
|
||||
chartCount: chartList.length,
|
||||
});
|
||||
return this.props.fetchCharts(chartList, true);
|
||||
}
|
||||
return false;
|
||||
onInsertComponentsButtonClick() {
|
||||
this.props.showBuilderPane(BUILDER_PANE_TYPE.ADD_COMPONENTS);
|
||||
}
|
||||
|
||||
startPeriodicRender(interval) {
|
||||
this.props.logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, {
|
||||
force: true,
|
||||
interval,
|
||||
});
|
||||
return this.props.startPeriodicRender(interval);
|
||||
onColorsButtonClick() {
|
||||
this.props.showBuilderPane(BUILDER_PANE_TYPE.COLORS);
|
||||
}
|
||||
|
||||
handleChangeText(nextText) {
|
||||
|
|
@ -177,6 +177,27 @@ class Header extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
forceRefresh() {
|
||||
if (!this.props.isLoading) {
|
||||
const chartList = Object.values(this.props.charts);
|
||||
this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
|
||||
force: true,
|
||||
interval: 0,
|
||||
chartCount: chartList.length,
|
||||
});
|
||||
return this.props.fetchCharts(chartList, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
startPeriodicRender(interval) {
|
||||
this.props.logEvent(LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD, {
|
||||
force: true,
|
||||
interval,
|
||||
});
|
||||
return this.props.startPeriodicRender(interval);
|
||||
}
|
||||
|
||||
toggleEditMode() {
|
||||
this.props.logEvent(LOG_ACTIONS_TOGGLE_EDIT_DASHBOARD, {
|
||||
edit_mode: !this.props.editMode,
|
||||
|
|
@ -190,16 +211,28 @@ class Header extends React.PureComponent {
|
|||
layout: positions,
|
||||
expandedSlices,
|
||||
css,
|
||||
colorNamespace,
|
||||
colorScheme,
|
||||
filters,
|
||||
dashboardInfo,
|
||||
refreshFrequency,
|
||||
} = this.props;
|
||||
|
||||
const scale = CategoricalColorNamespace.getScale(
|
||||
colorScheme,
|
||||
colorNamespace,
|
||||
);
|
||||
const labelColors = scale.getColorMap();
|
||||
const data = {
|
||||
positions,
|
||||
expanded_slices: expandedSlices,
|
||||
css,
|
||||
color_namespace: colorNamespace,
|
||||
color_scheme: colorScheme,
|
||||
label_colors: labelColors,
|
||||
dashboard_title: dashboardTitle,
|
||||
default_filters: safeStringify(filters),
|
||||
refresh_frequency: refreshFrequency,
|
||||
};
|
||||
|
||||
// make sure positions data less than DB storage limitation:
|
||||
|
|
@ -229,6 +262,8 @@ class Header extends React.PureComponent {
|
|||
filters,
|
||||
expandedSlices,
|
||||
css,
|
||||
colorNamespace,
|
||||
colorScheme,
|
||||
onUndo,
|
||||
onRedo,
|
||||
undoLength,
|
||||
|
|
@ -237,7 +272,7 @@ class Header extends React.PureComponent {
|
|||
onSave,
|
||||
updateCss,
|
||||
editMode,
|
||||
showBuilderPane,
|
||||
builderPaneType,
|
||||
dashboardInfo,
|
||||
hasUnsavedChanges,
|
||||
isLoading,
|
||||
|
|
@ -294,10 +329,22 @@ class Header extends React.PureComponent {
|
|||
)}
|
||||
|
||||
{editMode && (
|
||||
<Button bsSize="small" onClick={this.props.toggleBuilderPane}>
|
||||
{showBuilderPane
|
||||
? t('Hide components')
|
||||
: t('Insert components')}
|
||||
<Button
|
||||
active={builderPaneType === BUILDER_PANE_TYPE.ADD_COMPONENTS}
|
||||
bsSize="small"
|
||||
onClick={this.onInsertComponentsButtonClick}
|
||||
>
|
||||
{t('Insert components')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{editMode && (
|
||||
<Button
|
||||
active={builderPaneType === BUILDER_PANE_TYPE.COLORS}
|
||||
bsSize="small"
|
||||
onClick={this.onColorsButtonClick}
|
||||
>
|
||||
{t('Colors')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
|
@ -351,6 +398,8 @@ class Header extends React.PureComponent {
|
|||
filters={filters}
|
||||
expandedSlices={expandedSlices}
|
||||
css={css}
|
||||
colorNamespace={colorNamespace}
|
||||
colorScheme={colorScheme}
|
||||
onSave={onSave}
|
||||
onChange={onChange}
|
||||
forceRefreshAllCharts={this.forceRefresh}
|
||||
|
|
@ -371,5 +420,6 @@ class Header extends React.PureComponent {
|
|||
}
|
||||
|
||||
Header.propTypes = propTypes;
|
||||
Header.defaultProps = defaultProps;
|
||||
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ const propTypes = {
|
|||
dashboardTitle: PropTypes.string.isRequired,
|
||||
hasUnsavedChanges: PropTypes.bool.isRequired,
|
||||
css: PropTypes.string.isRequired,
|
||||
colorNamespace: PropTypes.string,
|
||||
colorScheme: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
updateCss: PropTypes.func.isRequired,
|
||||
forceRefreshAllCharts: PropTypes.func.isRequired,
|
||||
|
|
@ -53,7 +55,10 @@ const propTypes = {
|
|||
onSave: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {};
|
||||
const defaultProps = {
|
||||
colorNamespace: undefined,
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class HeaderActionsDropdown extends React.PureComponent {
|
||||
static discardChanges() {
|
||||
|
|
@ -111,6 +116,8 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
refreshFrequency,
|
||||
editMode,
|
||||
css,
|
||||
colorNamespace,
|
||||
colorScheme,
|
||||
hasUnsavedChanges,
|
||||
layout,
|
||||
filters,
|
||||
|
|
@ -145,6 +152,8 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
expandedSlices={expandedSlices}
|
||||
refreshFrequency={refreshFrequency}
|
||||
css={css}
|
||||
colorNamespace={colorNamespace}
|
||||
colorScheme={colorScheme}
|
||||
onSave={onSave}
|
||||
isMenuItem
|
||||
triggerNode={<span>{t('Save as')}</span>}
|
||||
|
|
@ -168,11 +177,13 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
<MenuItem onClick={forceRefreshAllCharts} disabled={isLoading}>
|
||||
{t('Force refresh dashboard')}
|
||||
</MenuItem>
|
||||
<RefreshIntervalModal
|
||||
refreshFrequency={refreshFrequency}
|
||||
onChange={this.changeRefreshInterval}
|
||||
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
|
||||
/>
|
||||
{editMode && (
|
||||
<RefreshIntervalModal
|
||||
refreshFrequency={refreshFrequency}
|
||||
onChange={this.changeRefreshInterval}
|
||||
triggerNode={<span>{t('Set auto-refresh interval')}</span>}
|
||||
/>
|
||||
)}
|
||||
{editMode && (
|
||||
<MenuItem target="_blank" href={`/dashboard/edit/${dashboardId}`}>
|
||||
{t('Edit dashboard metadata')}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
/* eslint-env browser */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import NewColumn from './gridComponents/new/NewColumn';
|
||||
import NewDivider from './gridComponents/new/NewDivider';
|
||||
import NewHeader from './gridComponents/new/NewHeader';
|
||||
import NewRow from './gridComponents/new/NewRow';
|
||||
import NewTabs from './gridComponents/new/NewTabs';
|
||||
import NewMarkdown from './gridComponents/new/NewMarkdown';
|
||||
import SliceAdder from '../containers/SliceAdder';
|
||||
import { BUILDER_PANE_TYPE } from '../util/constants';
|
||||
|
||||
export const SUPERSET_HEADER_HEIGHT = 59;
|
||||
|
||||
const propTypes = {
|
||||
height: PropTypes.number.isRequired,
|
||||
isSticky: PropTypes.bool.isRequired,
|
||||
showBuilderPane: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class InsertComponentPane extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
slideDirection: 'slide-out',
|
||||
};
|
||||
|
||||
this.onCloseButtonClick = this.onCloseButtonClick.bind(this);
|
||||
this.openSlicesPane = this.slide.bind(this, 'slide-in');
|
||||
this.closeSlicesPane = this.slide.bind(this, 'slide-out');
|
||||
}
|
||||
|
||||
onCloseButtonClick() {
|
||||
this.props.showBuilderPane(BUILDER_PANE_TYPE.NONE);
|
||||
}
|
||||
|
||||
slide(direction) {
|
||||
this.setState({
|
||||
slideDirection: direction,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={cx('slider-container', this.state.slideDirection)}>
|
||||
<div className="component-layer slide-content">
|
||||
<div className="dashboard-builder-sidepane-header">
|
||||
<span>{t('Insert components')}</span>
|
||||
<i
|
||||
className="fa fa-times trigger"
|
||||
onClick={this.onCloseButtonClick}
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="new-component static"
|
||||
role="none"
|
||||
onClick={this.openSlicesPane}
|
||||
>
|
||||
<div className="new-component-placeholder fa fa-area-chart" />
|
||||
<div className="new-component-label">
|
||||
{t('Your charts & filters')}
|
||||
</div>
|
||||
|
||||
<i className="fa fa-arrow-right trigger" />
|
||||
</div>
|
||||
<NewTabs />
|
||||
<NewRow />
|
||||
<NewColumn />
|
||||
<NewHeader />
|
||||
<NewMarkdown />
|
||||
<NewDivider />
|
||||
</div>
|
||||
<div className="slices-layer slide-content">
|
||||
<div
|
||||
className="dashboard-builder-sidepane-header"
|
||||
onClick={this.closeSlicesPane}
|
||||
role="none"
|
||||
>
|
||||
<i className="fa fa-arrow-left trigger" />
|
||||
<span>{t('Your charts and filters')}</span>
|
||||
</div>
|
||||
<SliceAdder
|
||||
height={
|
||||
this.props.height +
|
||||
(this.props.isSticky ? SUPERSET_HEADER_HEIGHT : 0)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InsertComponentPane.propTypes = propTypes;
|
||||
|
||||
export default InsertComponentPane;
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
|
||||
import { CategoricalColorNamespace } from '@superset-ui/color';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
|
|
@ -38,6 +39,8 @@ const propTypes = {
|
|||
triggerNode: PropTypes.node.isRequired,
|
||||
filters: PropTypes.object.isRequired,
|
||||
css: PropTypes.string.isRequired,
|
||||
colorNamespace: PropTypes.string,
|
||||
colorScheme: PropTypes.string,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
isMenuItem: PropTypes.bool,
|
||||
canOverwrite: PropTypes.bool.isRequired,
|
||||
|
|
@ -47,6 +50,8 @@ const propTypes = {
|
|||
const defaultProps = {
|
||||
isMenuItem: false,
|
||||
saveType: SAVE_TYPE_OVERWRITE,
|
||||
colorNamespace: undefined,
|
||||
colorScheme: undefined,
|
||||
};
|
||||
|
||||
class SaveModal extends React.PureComponent {
|
||||
|
|
@ -93,15 +98,25 @@ class SaveModal extends React.PureComponent {
|
|||
dashboardTitle,
|
||||
layout: positions,
|
||||
css,
|
||||
colorNamespace,
|
||||
colorScheme,
|
||||
expandedSlices,
|
||||
filters,
|
||||
dashboardId,
|
||||
refreshFrequency,
|
||||
} = this.props;
|
||||
|
||||
const scale = CategoricalColorNamespace.getScale(
|
||||
colorScheme,
|
||||
colorNamespace,
|
||||
);
|
||||
const labelColors = scale.getColorMap();
|
||||
const data = {
|
||||
positions,
|
||||
css,
|
||||
color_namespace: colorNamespace,
|
||||
color_scheme: colorScheme,
|
||||
label_colors: labelColors,
|
||||
expanded_slices: expandedSlices,
|
||||
dashboard_title:
|
||||
saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function mapStateToProps(
|
|||
) {
|
||||
const { id } = ownProps;
|
||||
const chart = chartQueries[id] || {};
|
||||
const { filters } = dashboardState;
|
||||
const { filters, colorScheme, colorNamespace } = dashboardState;
|
||||
|
||||
return {
|
||||
chart,
|
||||
|
|
@ -59,6 +59,8 @@ function mapStateToProps(
|
|||
chart,
|
||||
dashboardMetadata: dashboardInfo.metadata,
|
||||
filters,
|
||||
colorScheme,
|
||||
colorNamespace,
|
||||
sliceId: id,
|
||||
}),
|
||||
editMode: dashboardState.editMode,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
addSliceToDashboard,
|
||||
removeSliceFromDashboard,
|
||||
} from '../actions/dashboardState';
|
||||
import { runQuery } from '../../chart/chartAction';
|
||||
import { postChartFormData } from '../../chart/chartAction';
|
||||
import { logEvent } from '../../logger/actions';
|
||||
import getLoadStatsPerTopLevelComponent from '../util/logging/getLoadStatsPerTopLevelComponent';
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ function mapDispatchToProps(dispatch) {
|
|||
{
|
||||
addSliceToDashboard,
|
||||
removeSliceFromDashboard,
|
||||
runQuery,
|
||||
postChartFormData,
|
||||
logEvent,
|
||||
},
|
||||
dispatch,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import { bindActionCreators } from 'redux';
|
|||
import { connect } from 'react-redux';
|
||||
import DashboardBuilder from '../components/DashboardBuilder';
|
||||
|
||||
import { toggleBuilderPane } from '../actions/dashboardState';
|
||||
import {
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
showBuilderPane,
|
||||
} from '../actions/dashboardState';
|
||||
import {
|
||||
deleteTopLevelTabs,
|
||||
handleComponentDrop,
|
||||
|
|
@ -32,6 +35,8 @@ function mapStateToProps({ dashboardLayout: undoableLayout, dashboardState }) {
|
|||
editMode: dashboardState.editMode,
|
||||
showBuilderPane: dashboardState.showBuilderPane,
|
||||
directPathToChild: dashboardState.directPathToChild,
|
||||
builderPaneType: dashboardState.builderPaneType,
|
||||
colorScheme: dashboardState.colorScheme,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +45,8 @@ function mapDispatchToProps(dispatch) {
|
|||
{
|
||||
deleteTopLevelTabs,
|
||||
handleComponentDrop,
|
||||
toggleBuilderPane,
|
||||
showBuilderPane,
|
||||
setColorSchemeAndUnsavedChanges,
|
||||
},
|
||||
dispatch,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import isDashboardLoading from '../util/isDashboardLoading';
|
|||
|
||||
import {
|
||||
setEditMode,
|
||||
toggleBuilderPane,
|
||||
showBuilderPane,
|
||||
fetchFaveStar,
|
||||
saveFaveStar,
|
||||
fetchCharts,
|
||||
|
|
@ -71,6 +71,8 @@ function mapStateToProps({
|
|||
expandedSlices: dashboardState.expandedSlices,
|
||||
refreshFrequency: dashboardState.refreshFrequency,
|
||||
css: dashboardState.css,
|
||||
colorNamespace: dashboardState.colorNamespace,
|
||||
colorScheme: dashboardState.colorScheme,
|
||||
charts,
|
||||
userId: dashboardInfo.userId,
|
||||
isStarred: !!dashboardState.isStarred,
|
||||
|
|
@ -78,7 +80,7 @@ function mapStateToProps({
|
|||
hasUnsavedChanges: !!dashboardState.hasUnsavedChanges,
|
||||
maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded,
|
||||
editMode: !!dashboardState.editMode,
|
||||
showBuilderPane: !!dashboardState.showBuilderPane,
|
||||
builderPaneType: dashboardState.builderPaneType,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +93,7 @@ function mapDispatchToProps(dispatch) {
|
|||
onUndo: undoLayoutAction,
|
||||
onRedo: redoLayoutAction,
|
||||
setEditMode,
|
||||
toggleBuilderPane,
|
||||
showBuilderPane,
|
||||
fetchFaveStar,
|
||||
saveFaveStar,
|
||||
fetchCharts,
|
||||
|
|
|
|||
|
|
@ -23,15 +23,17 @@ import {
|
|||
ON_CHANGE,
|
||||
ON_SAVE,
|
||||
REMOVE_SLICE,
|
||||
SET_COLOR_SCHEME,
|
||||
SET_EDIT_MODE,
|
||||
SET_MAX_UNDO_HISTORY_EXCEEDED,
|
||||
SET_UNSAVED_CHANGES,
|
||||
TOGGLE_BUILDER_PANE,
|
||||
SHOW_BUILDER_PANE,
|
||||
TOGGLE_EXPAND_SLICE,
|
||||
TOGGLE_FAVE_STAR,
|
||||
UPDATE_CSS,
|
||||
SET_REFRESH_FREQUENCY,
|
||||
} from '../actions/dashboardState';
|
||||
import { BUILDER_PANE_TYPE } from '../util/constants';
|
||||
|
||||
export default function dashboardStateReducer(state = {}, action) {
|
||||
const actionHandlers = {
|
||||
|
|
@ -73,15 +75,24 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
return {
|
||||
...state,
|
||||
editMode: action.editMode,
|
||||
showBuilderPane: !!action.editMode,
|
||||
builderPaneType: action.editMode
|
||||
? BUILDER_PANE_TYPE.ADD_COMPONENTS
|
||||
: BUILDER_PANE_TYPE.NONE,
|
||||
};
|
||||
},
|
||||
[SET_MAX_UNDO_HISTORY_EXCEEDED]() {
|
||||
const { maxUndoHistoryExceeded = true } = action.payload;
|
||||
return { ...state, maxUndoHistoryExceeded };
|
||||
},
|
||||
[TOGGLE_BUILDER_PANE]() {
|
||||
return { ...state, showBuilderPane: !state.showBuilderPane };
|
||||
[SHOW_BUILDER_PANE]() {
|
||||
return { ...state, builderPaneType: action.builderPaneType };
|
||||
},
|
||||
[SET_COLOR_SCHEME]() {
|
||||
return {
|
||||
...state,
|
||||
colorScheme: action.colorScheme,
|
||||
updatedColorScheme: true,
|
||||
};
|
||||
},
|
||||
[TOGGLE_EXPAND_SLICE]() {
|
||||
const updatedExpandedSlices = { ...state.expandedSlices };
|
||||
|
|
@ -102,6 +113,8 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
editMode: false,
|
||||
builderPaneType: BUILDER_PANE_TYPE.NONE,
|
||||
updatedColorScheme: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -149,7 +162,11 @@ export default function dashboardStateReducer(state = {}, action) {
|
|||
return { ...state, hasUnsavedChanges };
|
||||
},
|
||||
[SET_REFRESH_FREQUENCY]() {
|
||||
return { ...state, refreshFrequency: action.refreshFrequency };
|
||||
return {
|
||||
...state,
|
||||
refreshFrequency: action.refreshFrequency,
|
||||
hasUnsavedChanges: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
/* eslint-disable camelcase */
|
||||
import { isString } from 'lodash';
|
||||
import shortid from 'shortid';
|
||||
import { CategoricalColorNamespace } from '@superset-ui/color';
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ import findFirstParentContainerId from '../util/findFirstParentContainer';
|
|||
import getEmptyLayout from '../util/getEmptyLayout';
|
||||
import newComponentFactory from '../util/newComponentFactory';
|
||||
import {
|
||||
BUILDER_PANE_TYPE,
|
||||
DASHBOARD_HEADER_ID,
|
||||
GRID_DEFAULT_CHART_WIDTH,
|
||||
GRID_COLUMN_COUNT,
|
||||
|
|
@ -55,9 +57,16 @@ export default function(bootstrapData) {
|
|||
// Priming the color palette with user's label-color mapping provided in
|
||||
// the dashboard's JSON metadata
|
||||
if (dashboard.metadata && dashboard.metadata.label_colors) {
|
||||
const colorMap = dashboard.metadata.label_colors;
|
||||
const scheme = dashboard.metadata.color_scheme;
|
||||
const namespace = dashboard.metadata.color_namespace;
|
||||
const colorMap = isString(dashboard.metadata.label_colors)
|
||||
? JSON.parse(dashboard.metadata.label_colors)
|
||||
: dashboard.metadata.label_colors;
|
||||
Object.keys(colorMap).forEach(label => {
|
||||
CategoricalColorNamespace.getScale().setColor(label, colorMap[label]);
|
||||
CategoricalColorNamespace.getScale(scheme, namespace).setColor(
|
||||
label,
|
||||
colorMap[label],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -201,8 +210,13 @@ export default function(bootstrapData) {
|
|||
expandedSlices: dashboard.metadata.expanded_slices || {},
|
||||
refreshFrequency: dashboard.metadata.refresh_frequency || 0,
|
||||
css: dashboard.css || '',
|
||||
colorNamespace: dashboard.metadata.color_namespace,
|
||||
colorScheme: dashboard.metadata.color_scheme,
|
||||
editMode: dashboard.dash_edit_perm && editMode,
|
||||
showBuilderPane: dashboard.dash_edit_perm && editMode,
|
||||
builderPaneType:
|
||||
dashboard.dash_edit_perm && editMode
|
||||
? BUILDER_PANE_TYPE.ADD_COMPONENTS
|
||||
: BUILDER_PANE_TYPE.NONE,
|
||||
hasUnsavedChanges: false,
|
||||
maxUndoHistoryExceeded: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -185,4 +185,18 @@
|
|||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.color-scheme-container {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.color-scheme-container li {
|
||||
flex-basis: 9px;
|
||||
height: 10px;
|
||||
margin: 9px 1px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,14 @@ body {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
& > :not(:last-child) {
|
||||
& > :nth-child(3) {
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border-right: none;
|
||||
}
|
||||
& > :nth-child(4) {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
}
|
||||
& > :not(:nth-child(3)):not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { isEqual } from 'lodash';
|
||||
import { CategoricalColorNamespace } from '@superset-ui/color';
|
||||
import getEffectiveExtraFilters from './getEffectiveExtraFilters';
|
||||
|
||||
// We cache formData objects so that our connected container components don't always trigger
|
||||
|
|
@ -28,12 +30,22 @@ export default function getFormDataWithExtraFilters({
|
|||
chart = {},
|
||||
dashboardMetadata,
|
||||
filters,
|
||||
colorScheme,
|
||||
colorNamespace,
|
||||
sliceId,
|
||||
}) {
|
||||
// Propagate color mapping to chart
|
||||
const scale = CategoricalColorNamespace.getScale(colorScheme, colorNamespace);
|
||||
const labelColors = scale.getColorMap();
|
||||
|
||||
// if dashboard metadata + filters have not changed, use cache if possible
|
||||
if (
|
||||
(cachedDashboardMetadataByChart[sliceId] || {}) === dashboardMetadata &&
|
||||
(cachedFiltersByChart[sliceId] || {}) === filters &&
|
||||
(colorScheme == null ||
|
||||
cachedFormdataByChart[sliceId].color_scheme === colorScheme) &&
|
||||
cachedFormdataByChart[sliceId].color_namespace === colorNamespace &&
|
||||
isEqual(cachedFormdataByChart[sliceId].label_colors, labelColors) &&
|
||||
!!cachedFormdataByChart[sliceId]
|
||||
) {
|
||||
return cachedFormdataByChart[sliceId];
|
||||
|
|
@ -41,6 +53,8 @@ export default function getFormDataWithExtraFilters({
|
|||
|
||||
const formData = {
|
||||
...chart.formData,
|
||||
...(colorScheme && { color_scheme: colorScheme }),
|
||||
label_colors: labelColors,
|
||||
extra_filters: getEffectiveExtraFilters({
|
||||
dashboardMetadata,
|
||||
filters,
|
||||
|
|
|
|||
|
|
@ -62,3 +62,10 @@ export const SAVE_TYPE_NEWDASHBOARD = 'newDashboard';
|
|||
// default dashboard layout data size limit
|
||||
// could be overwritten by server-side config
|
||||
export const DASHBOARD_POSITION_DATA_LIMIT = 65535;
|
||||
|
||||
// Dashboard pane types
|
||||
export const BUILDER_PANE_TYPE = {
|
||||
NONE: 'NONE',
|
||||
ADD_COMPONENTS: 'ADD_COMPONENTS',
|
||||
COLORS: 'COLORS',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,10 @@ export const dashboardStatePropShape = PropTypes.shape({
|
|||
filters: PropTypes.object.isRequired,
|
||||
expandedSlices: PropTypes.object,
|
||||
editMode: PropTypes.bool,
|
||||
showBuilderPane: PropTypes.bool,
|
||||
builderPaneType: PropTypes.string.isRequired,
|
||||
colorNamespace: PropTypes.string,
|
||||
colorScheme: PropTypes.string,
|
||||
updatedColorScheme: PropTypes.bool,
|
||||
hasUnsavedChanges: PropTypes.bool,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ const propTypes = {
|
|||
description: PropTypes.string,
|
||||
tooltipOnClick: PropTypes.func,
|
||||
places: PropTypes.number,
|
||||
validators: PropTypes.array,
|
||||
validationErrors: PropTypes.array,
|
||||
renderTrigger: PropTypes.bool,
|
||||
rightNode: PropTypes.node,
|
||||
|
|
@ -54,7 +53,6 @@ const propTypes = {
|
|||
|
||||
const defaultProps = {
|
||||
renderTrigger: false,
|
||||
validators: [],
|
||||
hidden: false,
|
||||
validationErrors: [],
|
||||
};
|
||||
|
|
@ -63,45 +61,14 @@ export default class Control extends React.PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hovered: false };
|
||||
this.validate = this.validate.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
componentDidMount() {
|
||||
this.validateAndSetValue(this.props.value, []);
|
||||
}
|
||||
onChange(value, errors) {
|
||||
this.validateAndSetValue(value, errors);
|
||||
this.props.actions.setControlValue(this.props.name, value, errors);
|
||||
}
|
||||
setHover(hovered) {
|
||||
this.setState({ hovered });
|
||||
}
|
||||
validateAndSetValue(value, errors) {
|
||||
let validationErrors = this.props.validationErrors;
|
||||
let currentErrors = this.validate(value);
|
||||
if (errors && errors.length > 0) {
|
||||
currentErrors = validationErrors.concat(errors);
|
||||
}
|
||||
if (validationErrors.length + currentErrors.length > 0) {
|
||||
validationErrors = currentErrors;
|
||||
}
|
||||
|
||||
if (value !== this.props.value || validationErrors !== this.props.validationErrors) {
|
||||
this.props.actions.setControlValue(this.props.name, value, validationErrors);
|
||||
}
|
||||
}
|
||||
validate(value) {
|
||||
const validators = this.props.validators;
|
||||
const validationErrors = [];
|
||||
if (validators && validators.length > 0) {
|
||||
validators.forEach((f) => {
|
||||
const v = f(value);
|
||||
if (v) {
|
||||
validationErrors.push(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
return validationErrors;
|
||||
}
|
||||
render() {
|
||||
const ControlType = controlMap[this.props.type];
|
||||
const divStyle = this.props.hidden ? { display: 'none' } : null;
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ const propTypes = {
|
|||
};
|
||||
|
||||
class ExploreChartHeader extends React.PureComponent {
|
||||
runQuery() {
|
||||
this.props.actions.runQuery(this.props.form_data, true,
|
||||
postChartFormData() {
|
||||
this.props.actions.postChartFormData(this.props.form_data, true,
|
||||
this.props.timeout, this.props.chart.id);
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ class ExploreChartHeader extends React.PureComponent {
|
|||
/>}
|
||||
{chartFinished && queryResponse && queryResponse.is_cached &&
|
||||
<CachedLabel
|
||||
onClick={this.runQuery.bind(this)}
|
||||
onClick={this.postChartFormData.bind(this)}
|
||||
cachedTimestamp={queryResponse.cached_dttm}
|
||||
/>}
|
||||
<Timer
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import ExploreChartPanel from './ExploreChartPanel';
|
||||
import ControlPanelsContainer from './ControlPanelsContainer';
|
||||
|
|
@ -94,6 +95,12 @@ class ExploreViewContainer extends React.Component {
|
|||
document.addEventListener('keydown', this.handleKeydown);
|
||||
this.addHistory({ isReplace: true });
|
||||
this.props.actions.logEvent(LOG_ACTIONS_MOUNT_EXPLORER);
|
||||
|
||||
// Trigger the chart if there are no errors
|
||||
const { chart } = this.props;
|
||||
if (!this.hasErrors()) {
|
||||
this.props.actions.triggerQuery(true, this.props.chart.id);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -209,7 +216,7 @@ class ExploreViewContainer extends React.Component {
|
|||
|
||||
triggerQueryIfNeeded() {
|
||||
if (this.props.chart.triggerQuery && !this.hasErrors()) {
|
||||
this.props.actions.runQuery(
|
||||
this.props.actions.postChartFormData(
|
||||
this.props.form_data,
|
||||
false,
|
||||
this.props.timeout,
|
||||
|
|
@ -249,7 +256,8 @@ class ExploreViewContainer extends React.Component {
|
|||
const formData = history.state;
|
||||
if (formData && Object.keys(formData).length) {
|
||||
this.props.actions.setExploreControls(formData);
|
||||
this.props.actions.runQuery(formData, false, this.props.timeout, this.props.chart.id);
|
||||
this.props.actions.postChartFormData(
|
||||
formData, false, this.props.timeout, this.props.chart.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,12 +273,13 @@ class ExploreViewContainer extends React.Component {
|
|||
renderErrorMessage() {
|
||||
// Returns an error message as a node if any errors are in the store
|
||||
const errors = [];
|
||||
const ctrls = this.props.controls;
|
||||
for (const controlName in this.props.controls) {
|
||||
const control = this.props.controls[controlName];
|
||||
if (control.validationErrors && control.validationErrors.length > 0) {
|
||||
errors.push(
|
||||
<div key={controlName}>
|
||||
<strong>{`[ ${control.label} ] `}</strong>
|
||||
{t('Control labeled ')}<strong>{` "${control.label}" `}</strong>
|
||||
{control.validationErrors.join('. ')}
|
||||
</div>,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { CategoricalColorNamespace } from '@superset-ui/color';
|
||||
|
||||
const propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.object,
|
||||
colorScheme: PropTypes.string,
|
||||
colorNamespace: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
value: {},
|
||||
colorScheme: undefined,
|
||||
colorNamespace: undefined,
|
||||
};
|
||||
|
||||
export default class ColorMapControl extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
Object.keys(this.props.value).forEach((label) => {
|
||||
CategoricalColorNamespace.getScale(
|
||||
this.props.colorScheme,
|
||||
this.props.colorNamespace,
|
||||
).setColor(label, this.props.value[label]);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ColorMapControl.propTypes = propTypes;
|
||||
ColorMapControl.defaultProps = defaultProps;
|
||||
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import { isFunction } from 'lodash';
|
||||
import { Creatable } from 'react-select';
|
||||
import ControlHeader from '../ControlHeader';
|
||||
import TooltipWrapper from '../../../components/TooltipWrapper';
|
||||
|
||||
const propTypes = {
|
||||
description: PropTypes.string,
|
||||
|
|
@ -77,17 +78,22 @@ export default class ColorSchemeControl extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<ul className="color-scheme-container">
|
||||
{colors.map((color, i) => (
|
||||
<li
|
||||
key={`${currentScheme.name}-${i}`}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
border: `1px solid ${color === 'white' ? 'black' : color}`,
|
||||
}}
|
||||
> </li>
|
||||
))}
|
||||
</ul>
|
||||
<TooltipWrapper
|
||||
label={`${currentScheme.id}-tooltip`}
|
||||
tooltip={currentScheme.label}
|
||||
>
|
||||
<ul className="color-scheme-container">
|
||||
{colors.map((color, i) => (
|
||||
<li
|
||||
key={`${currentScheme.id}-${i}`}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
border: `1px solid ${color === 'white' ? 'black' : color}`,
|
||||
}}
|
||||
> </li>
|
||||
))}
|
||||
</ul>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
Radio,
|
||||
Tab,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
} from 'react-bootstrap';
|
||||
import Datetime from 'react-datetime';
|
||||
import 'react-datetime/css/react-datetime.css';
|
||||
|
|
@ -311,15 +312,30 @@ export default class DateFilterControl extends React.Component {
|
|||
{grain}
|
||||
</MenuItem>
|
||||
));
|
||||
const timeFrames = COMMON_TIME_FRAMES.map(timeFrame => (
|
||||
<Radio
|
||||
key={timeFrame.replace(' ', '').toLowerCase()}
|
||||
checked={this.state.common === timeFrame}
|
||||
onChange={() => this.setState(getStateFromCommonTimeFrame(timeFrame))}
|
||||
>
|
||||
{timeFrame}
|
||||
</Radio>
|
||||
));
|
||||
const timeFrames = COMMON_TIME_FRAMES.map((timeFrame) => {
|
||||
const nextState = getStateFromCommonTimeFrame(timeFrame);
|
||||
return (
|
||||
<OverlayTrigger
|
||||
key={timeFrame}
|
||||
placement="left"
|
||||
overlay={
|
||||
<Tooltip id={`tooltip-${timeFrame}`}>
|
||||
{nextState.since}<br />{nextState.until}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Radio
|
||||
key={timeFrame.replace(' ', '').toLowerCase()}
|
||||
checked={this.state.common === timeFrame}
|
||||
onChange={() => this.setState(nextState)}
|
||||
>
|
||||
{timeFrame}
|
||||
</Radio>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Popover id="filter-popover" placement="top" positionTop={0}>
|
||||
<div style={{ width: '250px' }}>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import AnnotationLayerControl from './AnnotationLayerControl';
|
|||
import BoundsControl from './BoundsControl';
|
||||
import CheckboxControl from './CheckboxControl';
|
||||
import CollectionControl from './CollectionControl';
|
||||
import ColorMapControl from './ColorMapControl';
|
||||
import ColorPickerControl from './ColorPickerControl';
|
||||
import ColorSchemeControl from './ColorSchemeControl';
|
||||
import DatasourceControl from './DatasourceControl';
|
||||
|
|
@ -45,6 +46,7 @@ const controlMap = {
|
|||
BoundsControl,
|
||||
CheckboxControl,
|
||||
CollectionControl,
|
||||
ColorMapControl,
|
||||
ColorPickerControl,
|
||||
ColorSchemeControl,
|
||||
DatasourceControl,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
controlSetRows: [
|
||||
['show_brush', 'show_legend'],
|
||||
['line_interpolation', 'stacked_style'],
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['rich_tooltip', 'show_controls'],
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['show_brush', 'show_legend', 'show_bar_value'],
|
||||
['rich_tooltip', 'bar_stacked'],
|
||||
['line_interpolation', 'show_controls'],
|
||||
|
|
|
|||
|
|
@ -29,13 +29,21 @@ export default {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
label: t('Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['compare_lag', 'compare_suffix'],
|
||||
['y_axis_format', null],
|
||||
['show_trend_line', 'start_y_axis_at_zero'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_picker', null],
|
||||
['header_font_size'],
|
||||
['subheader_font_size'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -43,5 +51,8 @@ export default {
|
|||
y_axis_format: {
|
||||
label: t('Number format'),
|
||||
},
|
||||
header_font_size: {
|
||||
label: t('Big Number Font Size'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,17 +29,28 @@ export default {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
label: t('Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['subheader'],
|
||||
['y_axis_format'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['header_font_size'],
|
||||
['subheader_font_size'],
|
||||
],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
y_axis_format: {
|
||||
label: t('Number format'),
|
||||
},
|
||||
header_font_size: {
|
||||
label: t('Big Number Font Size'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['whisker_options', 'x_ticks_layout'],
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['show_legend', null],
|
||||
],
|
||||
},
|
||||
|
|
@ -62,9 +62,6 @@ export default {
|
|||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
x_axis_format: {
|
||||
default: '.3s',
|
||||
},
|
||||
color_scheme: {
|
||||
renderTrigger: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
expanded: true,
|
||||
controlSetRows: [
|
||||
['y_axis_format', null],
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export default {
|
|||
label: t('Arc'),
|
||||
controlSetRows: [
|
||||
['color_picker', 'target_color_picker'],
|
||||
['dimension', 'color_scheme'],
|
||||
['dimension', 'color_scheme', 'label_colors'],
|
||||
['stroke_width', 'legend_position'],
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default {
|
|||
label: t('Point Color'),
|
||||
controlSetRows: [
|
||||
['color_picker', 'legend_position'],
|
||||
['dimension', 'color_scheme'],
|
||||
['dimension', 'color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['show_legend', 'show_bar_value'],
|
||||
['bar_stacked', 'order_bars'],
|
||||
['y_axis_format', 'y_axis_label'],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['x_axis_format'],
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['link_length'],
|
||||
['x_axis_label', 'y_axis_label'],
|
||||
['global_opacity'],
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['show_brush', 'send_time_range', 'show_legend'],
|
||||
['rich_tooltip', 'show_markers'],
|
||||
['line_interpolation'],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['prefix_metric_with_slice_name', null],
|
||||
['show_legend', 'show_markers'],
|
||||
['line_interpolation', null],
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['number_format', 'date_time_format'],
|
||||
['partition_limit', 'partition_threshold'],
|
||||
['log_scale', 'equal_date_size'],
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
['pie_label_type', 'number_format'],
|
||||
['donut', 'show_legend'],
|
||||
['show_labels', 'labels_outside'],
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['number_format', 'date_time_format'],
|
||||
['rich_tooltip', 'rose_area_proportion'],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
['treemap_ratio'],
|
||||
['number_format'],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
controlSetRows: [
|
||||
['size_from', 'size_to'],
|
||||
['rotation'],
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const datasourceAndVizType = {
|
|||
export const colorScheme = {
|
||||
label: t('Color Scheme'),
|
||||
controlSetRows: [
|
||||
['color_scheme'],
|
||||
['color_scheme', 'label_colors'],
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
|
|||
|
||||
// input choices & options
|
||||
const D3_FORMAT_OPTIONS = [
|
||||
['SMART_NUMBER', 'Adaptative formating'],
|
||||
['.1s', '.1s (12345.432 => 10k)'],
|
||||
['.3s', '.3s (12345.432 => 12.3k)'],
|
||||
[',.1%', ',.1% (12345.432 => 1,234,543.2%)'],
|
||||
|
|
@ -989,7 +990,7 @@ export const controls = {
|
|||
freeForm: true,
|
||||
label: t('Number format'),
|
||||
renderTrigger: true,
|
||||
default: '.3s',
|
||||
default: 'SMART_NUMBER',
|
||||
choices: D3_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
},
|
||||
|
|
@ -1232,7 +1233,7 @@ export const controls = {
|
|||
freeForm: true,
|
||||
label: t('X Axis Format'),
|
||||
renderTrigger: true,
|
||||
default: '.3s',
|
||||
default: 'SMART_NUMBER',
|
||||
choices: D3_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
},
|
||||
|
|
@ -1252,7 +1253,7 @@ export const controls = {
|
|||
freeForm: true,
|
||||
label: t('Y Axis Format'),
|
||||
renderTrigger: true,
|
||||
default: '.3s',
|
||||
default: 'SMART_NUMBER',
|
||||
choices: D3_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
mapStateToProps: (state) => {
|
||||
|
|
@ -1273,7 +1274,7 @@ export const controls = {
|
|||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
label: t('Right Axis Format'),
|
||||
default: '.3s',
|
||||
default: 'SMART_NUMBER',
|
||||
choices: D3_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
},
|
||||
|
|
@ -1401,6 +1402,68 @@ export const controls = {
|
|||
description: t('Font size for the biggest value in the list'),
|
||||
},
|
||||
|
||||
header_font_size: {
|
||||
type: 'SelectControl',
|
||||
label: t('Header Font Size'),
|
||||
renderTrigger: true,
|
||||
clearable: false,
|
||||
default: 0.3,
|
||||
// Values represent the percentage of space a header should take
|
||||
options: [
|
||||
{
|
||||
label: t('Tiny'),
|
||||
value: 0.125,
|
||||
},
|
||||
{
|
||||
label: t('Small'),
|
||||
value: 0.2,
|
||||
},
|
||||
{
|
||||
label: t('Normal'),
|
||||
value: 0.3,
|
||||
},
|
||||
{
|
||||
label: t('Large'),
|
||||
value: 0.4,
|
||||
},
|
||||
{
|
||||
label: t('Huge'),
|
||||
value: 0.5,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
subheader_font_size: {
|
||||
type: 'SelectControl',
|
||||
label: t('Subheader Font Size'),
|
||||
renderTrigger: true,
|
||||
clearable: false,
|
||||
default: 0.125,
|
||||
// Values represent the percentage of space a subheader should take
|
||||
options: [
|
||||
{
|
||||
label: t('Tiny'),
|
||||
value: 0.125,
|
||||
},
|
||||
{
|
||||
label: t('Small'),
|
||||
value: 0.2,
|
||||
},
|
||||
{
|
||||
label: t('Normal'),
|
||||
value: 0.3,
|
||||
},
|
||||
{
|
||||
label: t('Large'),
|
||||
value: 0.4,
|
||||
},
|
||||
{
|
||||
label: t('Huge'),
|
||||
value: 0.5,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
instant_filtering: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Instant Filtering'),
|
||||
|
|
@ -2028,6 +2091,16 @@ export const controls = {
|
|||
schemes: () => categoricalSchemeRegistry.getMap(),
|
||||
},
|
||||
|
||||
label_colors: {
|
||||
type: 'ColorMapControl',
|
||||
label: t('Color Map'),
|
||||
default: {},
|
||||
mapStateToProps: state => ({
|
||||
colorNamespace: state.form_data.color_namespace,
|
||||
colorScheme: state.form_data.color_scheme,
|
||||
}),
|
||||
},
|
||||
|
||||
significance_level: {
|
||||
type: 'TextControl',
|
||||
label: t('Significance Level'),
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ export function getExploreUrlAndPayload({
|
|||
curUrl = null,
|
||||
requestParams = {},
|
||||
allowDomainSharding = false,
|
||||
method = 'POST',
|
||||
}) {
|
||||
if (!formData.datasource) {
|
||||
return null;
|
||||
|
|
@ -118,8 +119,19 @@ export function getExploreUrlAndPayload({
|
|||
|
||||
// Building the querystring (search) part of the URI
|
||||
const search = uri.search(true);
|
||||
if (formData.slice_id) {
|
||||
search.form_data = safeStringify({ slice_id: formData.slice_id });
|
||||
const { slice_id, extra_filters, adhoc_filters, viz_type } = formData;
|
||||
if (slice_id) {
|
||||
const form_data = { slice_id };
|
||||
if (method === 'GET') {
|
||||
form_data.viz_type = viz_type;
|
||||
if (extra_filters && extra_filters.length) {
|
||||
form_data.extra_filters = extra_filters;
|
||||
}
|
||||
if (adhoc_filters && adhoc_filters.length) {
|
||||
form_data.adhoc_filters = adhoc_filters;
|
||||
}
|
||||
}
|
||||
search.form_data = safeStringify(form_data);
|
||||
}
|
||||
if (force) {
|
||||
search.force = 'true';
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
/* eslint camelcase: 0 */
|
||||
import { getControlsState, getFormDataFromControls } from '../store';
|
||||
import { validateControl, getControlsState, getFormDataFromControls } from '../store';
|
||||
import controls from '../controls';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
|
||||
export default function exploreReducer(state = {}, action) {
|
||||
|
|
@ -75,24 +76,28 @@ export default function exploreReducer(state = {}, action) {
|
|||
};
|
||||
},
|
||||
[actions.SET_FIELD_VALUE]() {
|
||||
const controls = Object.assign({}, state.controls);
|
||||
const control = Object.assign({}, controls[action.controlName]);
|
||||
control.value = action.value;
|
||||
control.validationErrors = action.validationErrors;
|
||||
controls[action.controlName] = control;
|
||||
const changes = {
|
||||
controls,
|
||||
// These errors are reported from the Control components
|
||||
let errors = action.validationErrors || [];
|
||||
let control = {
|
||||
...controls[action.controlName],
|
||||
value: action.value,
|
||||
};
|
||||
if (control.renderTrigger) {
|
||||
changes.triggerRender = true;
|
||||
} else {
|
||||
changes.triggerRender = false;
|
||||
}
|
||||
const newState = {
|
||||
control = validateControl(control);
|
||||
|
||||
// These errors are based on control config `validators`
|
||||
errors = errors.concat(control.validationErrors || []);
|
||||
const hasErrors = errors && errors.length > 0;
|
||||
return {
|
||||
...state,
|
||||
...changes,
|
||||
triggerRender: control.renderTrigger && !hasErrors,
|
||||
controls: {
|
||||
...state.controls,
|
||||
[action.controlName]: {
|
||||
...control,
|
||||
validationErrors: errors,
|
||||
},
|
||||
},
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
[actions.SET_EXPLORE_CONTROLS]() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ export default function getInitialState(bootstrapData) {
|
|||
[chartKey]: {
|
||||
id: chartKey,
|
||||
chartAlert: null,
|
||||
chartStatus: 'loading',
|
||||
chartStatus: null,
|
||||
chartUpdateEndTime: null,
|
||||
chartUpdateStartTime: 0,
|
||||
latestQueryFormData: getFormDataFromControls(controls),
|
||||
sliceFormData,
|
||||
queryController: null,
|
||||
queryResponse: null,
|
||||
triggerQuery: true,
|
||||
triggerQuery: false,
|
||||
lastRendered: 0,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,24 @@ export function getFormDataFromControls(controlsState) {
|
|||
return formData;
|
||||
}
|
||||
|
||||
export function validateControl(control) {
|
||||
const validators = control.validators;
|
||||
const validationErrors = [];
|
||||
if (validators && validators.length > 0) {
|
||||
validators.forEach((f) => {
|
||||
const v = f(control.value);
|
||||
if (v) {
|
||||
validationErrors.push(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (validationErrors.length > 0) {
|
||||
return { ...control, validationErrors };
|
||||
}
|
||||
return control;
|
||||
}
|
||||
|
||||
|
||||
export function getControlNames(vizType, datasourceType) {
|
||||
const controlNames = [];
|
||||
sectionsToRender(vizType, datasourceType).forEach(
|
||||
|
|
@ -109,7 +127,7 @@ export function getControlsState(state, form_data) {
|
|||
) {
|
||||
control.value = formData[k];
|
||||
}
|
||||
controlsState[k] = control;
|
||||
controlsState[k] = validateControl(control);
|
||||
});
|
||||
if (viz.onInit) {
|
||||
return viz.onInit(controlsState);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ const propTypes = {
|
|||
disabled: PropTypes.bool,
|
||||
viewport: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
mapStyle: PropTypes.string,
|
||||
mapboxApiAccessToken: PropTypes.string.isRequired,
|
||||
setControlValue: PropTypes.func,
|
||||
onViewportChange: PropTypes.func,
|
||||
onValuesChange: PropTypes.func,
|
||||
};
|
||||
|
|
@ -41,6 +44,8 @@ const propTypes = {
|
|||
const defaultProps = {
|
||||
aggregation: false,
|
||||
disabled: false,
|
||||
mapStyle: 'light',
|
||||
setControlValue: () => {},
|
||||
onViewportChange: () => {},
|
||||
onValuesChange: () => {},
|
||||
};
|
||||
|
|
@ -48,9 +53,6 @@ const defaultProps = {
|
|||
export default class AnimatableDeckGLContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { getLayers, start, end, getStep, values, disabled, viewport, ...other } = props;
|
||||
this.other = other;
|
||||
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
|
|
@ -71,6 +73,9 @@ export default class AnimatableDeckGLContainer extends React.Component {
|
|||
values,
|
||||
onValuesChange,
|
||||
viewport,
|
||||
setControlValue,
|
||||
mapStyle,
|
||||
mapboxApiAccessToken,
|
||||
} = this.props;
|
||||
const layers = getLayers(values);
|
||||
|
||||
|
|
@ -83,9 +88,11 @@ export default class AnimatableDeckGLContainer extends React.Component {
|
|||
return (
|
||||
<div>
|
||||
<DeckGLContainer
|
||||
{...this.other}
|
||||
viewport={modifiedViewport}
|
||||
layers={layers}
|
||||
setControlValue={setControlValue}
|
||||
mapStyle={mapStyle}
|
||||
mapboxApiAccessToken={mapboxApiAccessToken}
|
||||
onViewportChange={this.onViewportChange}
|
||||
/>
|
||||
{!disabled &&
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ import { fitViewport } from './layers/common';
|
|||
const { getScale } = CategoricalColorNamespace;
|
||||
|
||||
function getCategories(fd, data) {
|
||||
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
|
||||
const colorFn = getScale(fd.colorScheme);
|
||||
const colorFn = getScale(fd.color_scheme);
|
||||
const categories = {};
|
||||
data.forEach((d) => {
|
||||
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
||||
|
|
@ -108,7 +108,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
|||
// the granularity has to be read from the payload form_data, not the
|
||||
// props formData which comes from the instantaneous controls state
|
||||
const granularity = (
|
||||
props.payload.form_data.timeGrainSqla ||
|
||||
props.payload.form_data.time_grain_sqla ||
|
||||
props.payload.form_data.granularity ||
|
||||
'P1D'
|
||||
);
|
||||
|
|
@ -154,8 +154,8 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
|||
features = this.addColor(features, fd);
|
||||
|
||||
// Apply user defined data mutator if defined
|
||||
if (fd.jsDataMutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
if (fd.js_data_mutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
features = jsFnMutator(features);
|
||||
}
|
||||
|
||||
|
|
@ -180,8 +180,8 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
|||
return [getLayer(fd, filteredPayload, onAddFilter, setTooltip)];
|
||||
}
|
||||
addColor(data, fd) {
|
||||
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const colorFn = getScale(fd.colorScheme);
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const colorFn = getScale(fd.color_scheme);
|
||||
return data.map((d) => {
|
||||
let color;
|
||||
if (fd.dimension) {
|
||||
|
|
@ -229,14 +229,14 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
|||
viewport={this.state.viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={this.props.mapboxApiKey}
|
||||
mapStyle={this.props.formData.mapboxStyle}
|
||||
mapStyle={this.props.formData.mapbox_style}
|
||||
setControlValue={this.props.setControlValue}
|
||||
>
|
||||
<Legend
|
||||
categories={this.state.categories}
|
||||
toggleCategory={this.toggleCategory}
|
||||
showSingleCategory={this.showSingleCategory}
|
||||
position={this.props.formData.legendPosition}
|
||||
position={this.props.formData.legend_position}
|
||||
/>
|
||||
</AnimatableDeckGLContainer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import MapGL from 'react-map-gl';
|
|||
import DeckGL from 'deck.gl';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { isEqual } from 'lodash';
|
||||
import '../stylesheets/deckgl.css';
|
||||
|
||||
const TICK = 2000; // milliseconds
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class DeckMulti extends React.PureComponent {
|
|||
const filters = [
|
||||
...(subslice.form_data.filters || []),
|
||||
...(formData.filters || []),
|
||||
...(formData.extraFilters || []),
|
||||
...(formData.extra_filters || []),
|
||||
];
|
||||
const subsliceCopy = {
|
||||
...subslice,
|
||||
|
|
@ -70,7 +70,7 @@ class DeckMulti extends React.PureComponent {
|
|||
endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
|
||||
})
|
||||
.then(({ json }) => {
|
||||
const layer = layerGenerators[subsliceCopy.form_data.vizType](
|
||||
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
|
||||
subsliceCopy.form_data,
|
||||
json,
|
||||
);
|
||||
|
|
@ -96,7 +96,7 @@ class DeckMulti extends React.PureComponent {
|
|||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={layers}
|
||||
mapStyle={formData.mapboxStyle}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
const propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default class TooltipRow extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div>{this.props.label}<strong>{this.props.value}</strong></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TooltipRow.propTypes = propTypes;
|
||||
|
|
@ -90,7 +90,7 @@ export function createDeckGLComponent(getLayer, getPoints) {
|
|||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapboxStyle}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
onViewportChange={this.onViewportChange}
|
||||
/>);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ArcLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { commonLayerProps } from '../common';
|
||||
import { createCategoricalDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function getPoints(data) {
|
||||
const points = [];
|
||||
|
|
@ -29,17 +32,29 @@ function getPoints(data) {
|
|||
return points;
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return o => (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Start (Longitude, Latitude)')}: `} value={`${o.object.sourcePosition[0]}, ${o.object.sourcePosition[1]}`} />
|
||||
<TooltipRow label={`${t('End (Longitude, Latitude)')}: `} value={`${o.object.targetPosition[0]}, ${o.object.targetPosition[1]}`} />
|
||||
{
|
||||
formData.dimension && <TooltipRow label={`${formData.dimension}: `} value={`${o.object.cat_color}`} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(fd, payload, onAddFilter, setTooltip) {
|
||||
const data = payload.data.features;
|
||||
const sc = fd.colorPicker;
|
||||
const tc = fd.targetColorPicker;
|
||||
const sc = fd.color_picker;
|
||||
const tc = fd.target_color_picker;
|
||||
return new ArcLayer({
|
||||
id: `path-layer-${fd.sliceId}`,
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
getSourceColor: d => d.sourceColor || d.color || [sc.r, sc.g, sc.b, 255 * sc.a],
|
||||
getTargetColor: d => d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a],
|
||||
strokeWidth: (fd.strokeWidth) ? fd.strokeWidth : 3,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
strokeWidth: (fd.stroke_width) ? fd.stroke_width : 3,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent(fd)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import DeckGLContainer from '../../DeckGLContainer';
|
|||
import { hexToRGB } from '../../../../modules/colors';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { commonLayerProps } from '../common';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
const propertyMap = {
|
||||
fillColor: 'fillColor',
|
||||
|
|
@ -75,10 +76,23 @@ const recurseGeoJson = (node, propOverrides, extraProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
o.object.extraProps &&
|
||||
<div className="deckgl-tooltip">
|
||||
{
|
||||
Object.keys(o.object.extraProps).map((prop, index) =>
|
||||
<TooltipRow key={`prop-${index}`} label={`${prop}: `} value={`${o.object.extraProps[prop]}`} />,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const fc = fd.fillColorPicker;
|
||||
const sc = fd.strokeColorPicker;
|
||||
const fc = fd.fill_color_picker;
|
||||
const sc = fd.stroke_color_picker;
|
||||
const fillColor = [fc.r, fc.g, fc.b, 255 * fc.a];
|
||||
const strokeColor = [sc.r, sc.g, sc.b, 255 * sc.a];
|
||||
const propOverrides = {};
|
||||
|
|
@ -93,20 +107,20 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
|||
recurseGeoJson(payload.data, propOverrides);
|
||||
|
||||
let jsFnMutator;
|
||||
if (fd.jsDataMutator) {
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
features = jsFnMutator(features);
|
||||
}
|
||||
|
||||
return new GeoJsonLayer({
|
||||
id: `geojson-layer-${fd.sliceId}`,
|
||||
id: `geojson-layer-${fd.slice_id}`,
|
||||
filled: fd.filled,
|
||||
data: features,
|
||||
stroked: fd.stroked,
|
||||
extruded: fd.extruded,
|
||||
pointRadiusScale: fd.pointRadiusScale,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
pointRadiusScale: fd.point_radius_scale,
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +159,7 @@ function deckGeoJson(props) {
|
|||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapboxStyle}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,38 +17,50 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { GridLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import { commonLayerProps, getAggFunc } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
<TooltipRow label={`${t('Height')}: `} value={`${o.object.elevationValue}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.colorPicker;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.jsDataMutator) {
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
const aggFunc = getAggFunc(fd.jsAggFunction, p => p.weight);
|
||||
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
|
||||
return new GridLayer({
|
||||
id: `grid-layer-${fd.sliceId}`,
|
||||
id: `grid-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
cellSize: fd.gridSize,
|
||||
cellSize: fd.grid_size,
|
||||
minColor: [0, 0, 0, 0],
|
||||
extruded: fd.extruded,
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getElevationValue: aggFunc,
|
||||
getColorValue: aggFunc,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,37 +17,49 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { HexagonLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import { commonLayerProps, getAggFunc } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Centroid (Longitude and Latitude)')}: `} value={`(${o.object.centroid[0]}, ${o.object.centroid[1]})`} />
|
||||
<TooltipRow label={`${t('Height')}: `} value={`${o.object.elevationValue}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.colorPicker;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.jsDataMutator) {
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
const aggFunc = getAggFunc(fd.jsAggFunction, p => p.weight);
|
||||
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
|
||||
return new HexagonLayer({
|
||||
id: `hex-layer-${fd.sliceId}`,
|
||||
id: `hex-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
radius: fd.gridSize,
|
||||
radius: fd.grid_size,
|
||||
minColor: [0, 0, 0, 0],
|
||||
extruded: fd.extruded,
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getElevationValue: aggFunc,
|
||||
getColorValue: aggFunc,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,32 +17,47 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { PathLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { commonLayerProps } from '../common';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { createDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
o.object.extraProps &&
|
||||
<div className="deckgl-tooltip">
|
||||
{
|
||||
Object.keys(o.object.extraProps).map((prop, index) =>
|
||||
<TooltipRow key={`prop-${index}`} label={`${prop}: `} value={`${o.object.extraProps[prop]}`} />,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip) {
|
||||
const fd = formData;
|
||||
const c = fd.colorPicker;
|
||||
const c = fd.color_picker;
|
||||
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
|
||||
let data = payload.data.features.map(feature => ({
|
||||
...feature,
|
||||
path: feature.path,
|
||||
width: fd.lineWidth,
|
||||
width: fd.line_width,
|
||||
color: fixedColor,
|
||||
}));
|
||||
|
||||
if (fd.jsDataMutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
if (fd.js_data_mutator) {
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
return new PathLayer({
|
||||
id: `path-layer-${fd.sliceId}`,
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
rounded: true,
|
||||
widthScale: 1,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { PolygonLayer } from 'deck.gl';
|
|||
|
||||
import AnimatableDeckGLContainer from '../../AnimatableDeckGLContainer';
|
||||
import Legend from '../../../Legend';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
import { getBuckets, getBreakPointColorScaler } from '../../utils';
|
||||
|
||||
import { commonLayerProps, fitViewport } from '../common';
|
||||
|
|
@ -48,10 +49,22 @@ function getElevation(d, colorScaler) {
|
|||
: d.elevation;
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return (o) => {
|
||||
const metricLabel = formData.metric.label || formData.metric;
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${formData.line_column}: `} value={`${o.object[formData.line_column]}`} />
|
||||
{formData.metric && <TooltipRow label={`${metricLabel}: `} value={`${o.object[metricLabel]}`} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, setTooltip, selected, onSelect, filters) {
|
||||
const fd = formData;
|
||||
const fc = fd.fillColorPicker;
|
||||
const sc = fd.strokeColorPicker;
|
||||
const fc = fd.fill_color_picker;
|
||||
const sc = fd.stroke_color_picker;
|
||||
let data = [...payload.data.features];
|
||||
|
||||
if (filters != null) {
|
||||
|
|
@ -60,9 +73,9 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
|
|||
});
|
||||
}
|
||||
|
||||
if (fd.jsDataMutator) {
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
|
|
@ -76,13 +89,16 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
|
|||
// when polygons are selected, reduce the opacity of non-selected polygons
|
||||
const colorScaler = (d) => {
|
||||
const baseColor = baseColorScaler(d);
|
||||
if (selected.length > 0 && selected.indexOf(d[fd.lineColumn]) === -1) {
|
||||
if (selected.length > 0 && selected.indexOf(d[fd.line_column]) === -1) {
|
||||
baseColor[3] /= 2;
|
||||
}
|
||||
return baseColor;
|
||||
};
|
||||
const tooltipContentGenerator = (fd.line_column && fd.metric && ['geohash', 'zipcode'].indexOf(fd.line_type) >= 0)
|
||||
? setTooltipContent(fd)
|
||||
: undefined;
|
||||
return new PolygonLayer({
|
||||
id: `path-layer-${fd.sliceId}`,
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
filled: fd.filled,
|
||||
|
|
@ -90,12 +106,12 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
|
|||
getPolygon: d => d.polygon,
|
||||
getFillColor: colorScaler,
|
||||
getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a],
|
||||
getLineWidth: fd.lineWidth,
|
||||
getLineWidth: fd.line_width,
|
||||
extruded: fd.extruded,
|
||||
getElevation: d => getElevation(d, colorScaler),
|
||||
elevationScale: fd.multiplier,
|
||||
fp64: true,
|
||||
...commonLayerProps(fd, setTooltip, onSelect),
|
||||
...commonLayerProps(fd, setTooltip, tooltipContentGenerator, onSelect),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +154,7 @@ class DeckGLPolygon extends React.Component {
|
|||
// the granularity has to be read from the payload form_data, not the
|
||||
// props formData which comes from the instantaneous controls state
|
||||
const granularity = (
|
||||
props.payload.form_data.timeGrainSqla ||
|
||||
props.payload.form_data.time_grain_sqla ||
|
||||
props.payload.form_data.granularity ||
|
||||
'P1D'
|
||||
);
|
||||
|
|
@ -177,7 +193,7 @@ class DeckGLPolygon extends React.Component {
|
|||
const selected = [...this.state.selected];
|
||||
if (doubleClick) {
|
||||
selected.splice(0, selected.length, polygon);
|
||||
} else if (formData.togglePolygons) {
|
||||
} else if (formData.toggle_polygons) {
|
||||
const i = selected.indexOf(polygon);
|
||||
if (i === -1) {
|
||||
selected.push(polygon);
|
||||
|
|
@ -189,8 +205,8 @@ class DeckGLPolygon extends React.Component {
|
|||
}
|
||||
|
||||
this.setState({ selected, lastClick: now });
|
||||
if (formData.tableFilter) {
|
||||
onAddFilter(formData.lineColumn, selected, false, true);
|
||||
if (formData.table_filter) {
|
||||
onAddFilter(formData.line_column, selected, false, true);
|
||||
}
|
||||
}
|
||||
onValuesChange(values) {
|
||||
|
|
@ -249,14 +265,14 @@ class DeckGLPolygon extends React.Component {
|
|||
viewport={viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
mapStyle={formData.mapboxStyle}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
aggregation
|
||||
>
|
||||
{formData.metric !== null &&
|
||||
<Legend
|
||||
categories={buckets}
|
||||
position={formData.legendPosition}
|
||||
position={formData.legend_position}
|
||||
/>}
|
||||
</AnimatableDeckGLContainer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,36 +17,53 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ScatterplotLayer } from 'deck.gl';
|
||||
import React from 'react';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { commonLayerProps } from '../common';
|
||||
import { createCategoricalDeckGLComponent } from '../../factory';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
import { unitToRadius } from '../../../../modules/geo';
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
function setTooltipContent(formData) {
|
||||
return o => (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
{
|
||||
o.object.cat_color && <TooltipRow label={`${t('Category')}: `} value={`${o.object.cat_color}`} />
|
||||
}
|
||||
{
|
||||
o.object.metric && <TooltipRow label={`${formData.point_radius_fixed.value}: `} value={`${o.object.metric}`} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(fd, payload, onAddFilter, setTooltip) {
|
||||
const dataWithRadius = payload.data.features.map((d) => {
|
||||
let radius = unitToRadius(fd.pointUnit, d.radius) || 10;
|
||||
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
|
||||
if (fd.multiplier) {
|
||||
radius *= fd.multiplier;
|
||||
}
|
||||
if (d.color) {
|
||||
return { ...d, radius };
|
||||
}
|
||||
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const color = [c.r, c.g, c.b, c.a * 255];
|
||||
return { ...d, radius, color };
|
||||
});
|
||||
|
||||
return new ScatterplotLayer({
|
||||
id: `scatter-layer-${fd.sliceId}`,
|
||||
id: `scatter-layer-${fd.slice_id}`,
|
||||
data: dataWithRadius,
|
||||
fp64: true,
|
||||
radiusMinPixels: fd.minRadius || null,
|
||||
radiusMaxPixels: fd.maxRadius || null,
|
||||
radiusMinPixels: fd.min_radius || null,
|
||||
radiusMaxPixels: fd.max_radius || null,
|
||||
outline: false,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent(fd)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,26 +21,37 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScreenGridLayer } from 'deck.gl';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import AnimatableDeckGLContainer from '../../AnimatableDeckGLContainer';
|
||||
import { getPlaySliderParams } from '../../../../modules/time';
|
||||
import sandboxedEval from '../../../../modules/sandbox';
|
||||
import { commonLayerProps, fitViewport } from '../common';
|
||||
import TooltipRow from '../../TooltipRow';
|
||||
|
||||
function getPoints(data) {
|
||||
return data.map(d => d.position);
|
||||
}
|
||||
|
||||
function setTooltipContent(o) {
|
||||
return (
|
||||
<div className="deckgl-tooltip">
|
||||
<TooltipRow label={`${t('Longitude and Latitude')}: `} value={`${o.object.position[0]}, ${o.object.position[1]}`} />
|
||||
<TooltipRow label={`${t('Weight')}: `} value={`${o.object.weight}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLayer(formData, payload, onAddFilter, setTooltip, filters) {
|
||||
const fd = formData;
|
||||
const c = fd.colorPicker;
|
||||
const c = fd.color_picker;
|
||||
let data = payload.data.features.map(d => ({
|
||||
...d,
|
||||
color: [c.r, c.g, c.b, 255 * c.a],
|
||||
}));
|
||||
|
||||
if (fd.jsDataMutator) {
|
||||
if (fd.js_data_mutator) {
|
||||
// Applying user defined data mutator if defined
|
||||
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
|
||||
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
|
|
@ -53,15 +64,15 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, filters) {
|
|||
// Passing a layer creator function instead of a layer since the
|
||||
// layer needs to be regenerated at each render
|
||||
return new ScreenGridLayer({
|
||||
id: `screengrid-layer-${fd.sliceId}`,
|
||||
id: `screengrid-layer-${fd.slice_id}`,
|
||||
data,
|
||||
pickable: true,
|
||||
cellSizePixels: fd.gridSize,
|
||||
cellSizePixels: fd.grid_size,
|
||||
minColor: [c.r, c.g, c.b, 0],
|
||||
maxColor: [c.r, c.g, c.b, 255 * c.a],
|
||||
outline: false,
|
||||
getWeight: d => d.weight || 0,
|
||||
...commonLayerProps(fd, setTooltip),
|
||||
...commonLayerProps(fd, setTooltip, setTooltipContent),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +113,7 @@ class DeckGLScreenGrid extends React.PureComponent {
|
|||
// the granularity has to be read from the payload form_data, not the
|
||||
// props formData which comes from the instantaneous controls state
|
||||
const granularity = (
|
||||
props.payload.form_data.timeGrainSqla ||
|
||||
props.payload.form_data.time_grain_sqla ||
|
||||
props.payload.form_data.granularity ||
|
||||
'P1D'
|
||||
);
|
||||
|
|
@ -176,7 +187,7 @@ class DeckGLScreenGrid extends React.PureComponent {
|
|||
viewport={this.state.viewport}
|
||||
onViewportChange={this.onViewportChange}
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
mapStyle={formData.mapboxStyle}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
aggregation
|
||||
/>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue