You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/12/04 21:05:45 UTC
(superset) branch 3.1 updated (c8844bdd5e -> 5bcd3ef17e)
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a change to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
from c8844bdd5e chore: Adds 3.1.0 data to CHANGELOG.md and UPDATING.md
new f4fd0e19e2 fix: alias column when fetching values (#26120)
new e382d0dd28 chore(deps): bump pillow deps (#25931)
new 93319696de fix: remove default secret key from helm (#23916)
new 2c3bf2895f chore(tags): Allow for lookup via ids vs. name in the API (#25996)
new fad4616d2f chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)
new 26e59662fb fix(annotations): time grain column (#26140)
new 4a4f9983df feat(helm): Add option to deploy extra containers to remaining deployments (#26123)
new 79d5975028 feat: Adds legacy time support for Waterfall chart (#26136)
new ceac19fa2f fix: set label on adhoc column should persist (#26154)
new 77332bfb38 fix(database-import): Support importing a DB connection with a version set (#26116)
new d0aa34bf79 fix(sqllab): table preview has gone (#25977)
new 880086c750 fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)
new aaa50c4b4a fix: Migration order due to cherry which went astray (#26160)
new 5ec1edc876 chore: Clean up the examples dashboards (#26158)
new 5bcd3ef17e chore: harmonize and clean up list views (#25961)
The 15 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
helm/superset/Chart.yaml | 2 +-
helm/superset/README.md | 11 +-
helm/superset/README.md.gotmpl | 6 +
helm/superset/templates/_helpers.tpl | 1 -
helm/superset/templates/deployment-beat.yaml | 3 +
helm/superset/templates/deployment-flower.yaml | 3 +
helm/superset/templates/deployment-ws.yaml | 3 +
helm/superset/values.yaml | 8 +
setup.py | 2 +-
.../cypress/e2e/alerts_and_reports/alerts.test.ts | 7 +-
.../cypress/e2e/alerts_and_reports/reports.test.ts | 7 +-
.../cypress/e2e/chart_list/filter.test.ts | 16 +-
.../cypress/e2e/chart_list/list.test.ts | 10 +-
.../cypress/e2e/dashboard/nativeFilters.test.ts | 6 +-
.../cypress/e2e/dashboard/tabs.test.ts | 3 -
.../cypress-base/cypress/e2e/dashboard/utils.ts | 1 -
.../cypress/e2e/dashboard_list/filter.test.ts | 6 +-
.../cypress/e2e/dashboard_list/list.test.ts | 12 +-
.../cypress-base/cypress/support/e2e.ts | 2 +-
.../src/Waterfall/buildQuery.ts | 7 +-
.../src/Waterfall/controlPanel.tsx | 8 +-
.../plugin-chart-echarts/src/Waterfall/index.ts | 2 +-
.../src/Waterfall/transformProps.ts | 6 +-
.../src/components/AuditInfo/ModifiedInfo.test.tsx | 42 ++
.../src/components/AuditInfo/index.tsx | 30 +
.../src/components/Chart/chartAction.js | 9 +-
.../src/components/Chart/chartActions.test.js | 67 +++
.../DatabaseSelector/DatabaseSelector.test.tsx | 9 +-
.../src/components/DatabaseSelector/index.tsx | 10 +-
.../src/components/Datasource/DatasourceEditor.jsx | 2 +-
.../src/dashboard/actions/dashboardState.js | 9 +-
.../DashboardBuilder/DashboardBuilder.test.tsx | 6 +-
.../dashboard/components/PropertiesModal/index.tsx | 2 +-
.../dashboard/components/gridComponents/Tabs.jsx | 8 +-
.../dashboard/containers/DashboardComponent.jsx | 4 +-
.../src/dashboard/reducers/dashboardState.js | 9 +-
.../src/dashboard/reducers/dashboardState.test.ts | 22 +-
.../ColumnSelectPopover.test.tsx | 77 +++
.../DndColumnSelectControl/ColumnSelectPopover.tsx | 39 +-
.../ColumnSelectPopoverTrigger.tsx | 13 +-
.../src/features/annotations/AnnotationModal.tsx | 2 +-
.../src/features/cssTemplates/CssTemplateModal.tsx | 5 +-
.../src/features/cssTemplates/types.ts | 11 +-
.../src/features/tags/TagModal.test.tsx | 2 +
superset-frontend/src/features/tags/TagModal.tsx | 6 +-
superset-frontend/src/features/tags/tags.ts | 17 +
.../src/pages/AlertReportList/index.tsx | 74 +--
superset-frontend/src/pages/AllEntities/index.tsx | 15 +-
.../src/pages/AnnotationLayerList/index.tsx | 89 +--
.../src/pages/AnnotationList/index.tsx | 2 +-
superset-frontend/src/pages/ChartList/index.tsx | 168 +++---
.../src/pages/CssTemplateList/index.tsx | 88 +--
.../src/pages/DashboardList/index.tsx | 187 +++----
.../src/pages/DatabaseList/DatabaseList.test.jsx | 2 +-
superset-frontend/src/pages/DatabaseList/index.tsx | 80 ++-
.../src/pages/DatasetList/DatasetList.test.tsx | 61 +--
superset-frontend/src/pages/DatasetList/index.tsx | 109 ++--
.../src/pages/QueryHistoryList/index.tsx | 3 +-
.../RowLevelSecurityList.test.tsx | 6 +-
.../src/pages/RowLevelSecurityList/index.tsx | 39 +-
.../src/pages/SavedQueryList/index.tsx | 136 ++---
superset-frontend/src/pages/Tags/index.tsx | 68 +--
.../utils/{parseCookie.ts => getOwnerName.test.ts} | 18 +-
.../utils/getOwnerName.ts} | 17 +-
superset-frontend/src/views/CRUD/types.ts | 11 +-
superset/annotation_layers/api.py | 2 +-
superset/charts/api.py | 2 +-
superset/css_templates/api.py | 6 +-
superset/daos/tag.py | 8 +
superset/dashboards/api.py | 2 +-
superset/databases/api.py | 13 +
superset/databases/schemas.py | 1 +
superset/datasets/api.py | 13 +-
.../examples/configs/charts/Filter_Segments.yaml | 68 ---
.../configs/charts/Filtering_Vaccines.yaml | 53 --
.../configs/charts/Vehicle_Sales_Filter.yaml | 47 --
.../configs/charts/Video_Game_Sales_Filter.yaml | 55 --
.../dashboards/COVID_Vaccine_Dashboard.yaml | 128 ++---
.../dashboards/FCC_New_Coder_Survey_2018.yaml | 608 ++++++++++-----------
.../configs/dashboards/Sales_Dashboard.yaml | 300 ++++------
.../configs/dashboards/Video_Game_Sales.yaml | 389 ++++++-------
superset/examples/misc_dashboard.py | 148 ++---
superset/examples/world_bank.py | 39 +-
superset/migrations/shared/utils.py | 8 +-
...317970b4400c_added_time_secondary_column_to_.py | 34 +-
...2-01_12-03_b7851ee5522f_replay_317970b4400c.py} | 24 +-
superset/models/helpers.py | 10 +-
superset/queries/saved_queries/api.py | 12 +-
superset/reports/api.py | 10 +-
superset/reports/notifications/slack.py | 11 +-
superset/row_level_security/api.py | 7 +-
superset/row_level_security/schemas.py | 2 +
superset/tags/api.py | 17 +-
superset/utils/screenshots.py | 2 +-
tests/integration_tests/charts/api_tests.py | 6 +-
tests/integration_tests/css_templates/api_tests.py | 25 +-
.../integration_tests/dashboards/commands_tests.py | 20 +-
tests/integration_tests/dashboards/dao_tests.py | 54 --
tests/integration_tests/databases/api_tests.py | 3 +-
.../queries/saved_queries/api_tests.py | 14 +-
tests/integration_tests/tags/dao_tests.py | 33 ++
tests/integration_tests/utils_tests.py | 44 --
.../databases/commands/importers/v1/import_test.py | 21 +
.../notifications/slack_tests.py} | 47 +-
104 files changed, 1881 insertions(+), 2111 deletions(-)
create mode 100644 superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
create mode 100644 superset-frontend/src/components/AuditInfo/index.tsx
create mode 100644 superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
copy superset-frontend/src/utils/{parseCookie.ts => getOwnerName.test.ts} (73%)
copy superset-frontend/{spec/helpers/ResizeObserver.ts => src/utils/getOwnerName.ts} (81%)
delete mode 100644 superset/examples/configs/charts/Filter_Segments.yaml
delete mode 100644 superset/examples/configs/charts/Filtering_Vaccines.yaml
delete mode 100644 superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
delete mode 100644 superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
copy superset/migrations/versions/{2015-11-21_11-18_289ce07647b_add_encrypted_password_field.py => 2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py} (71%)
copy tests/unit_tests/{notifications/email_tests.py => reports/notifications/slack_tests.py} (71%)
(superset) 15/15: chore: harmonize and clean up list views (#25961)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5bcd3ef17eebe67511e2fbd057a522338d3d0851
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Mon Dec 4 11:51:18 2023 -0800
chore: harmonize and clean up list views (#25961)
(cherry picked from commit 0b477e3f7c08fabdcdb0cb1bca48335685a699bf)
---
.../cypress/e2e/alerts_and_reports/alerts.test.ts | 7 +-
.../cypress/e2e/alerts_and_reports/reports.test.ts | 7 +-
.../cypress/e2e/chart_list/filter.test.ts | 16 +-
.../cypress/e2e/chart_list/list.test.ts | 10 +-
.../cypress/e2e/dashboard_list/filter.test.ts | 6 +-
.../cypress/e2e/dashboard_list/list.test.ts | 12 +-
.../src/components/AuditInfo/ModifiedInfo.test.tsx | 42 +++++
.../src/components/AuditInfo/index.tsx | 30 ++++
.../src/components/Datasource/DatasourceEditor.jsx | 2 +-
.../dashboard/components/PropertiesModal/index.tsx | 2 +-
.../src/features/annotations/AnnotationModal.tsx | 2 +-
.../src/features/cssTemplates/CssTemplateModal.tsx | 5 +-
.../src/features/cssTemplates/types.ts | 11 +-
.../src/features/tags/TagModal.test.tsx | 2 +
.../src/pages/AlertReportList/index.tsx | 74 ++++----
superset-frontend/src/pages/AllEntities/index.tsx | 5 +-
.../src/pages/AnnotationLayerList/index.tsx | 89 +++-------
.../src/pages/AnnotationList/index.tsx | 2 +-
superset-frontend/src/pages/ChartList/index.tsx | 168 +++++++++---------
.../src/pages/CssTemplateList/index.tsx | 88 +++-------
.../src/pages/DashboardList/index.tsx | 187 ++++++++++-----------
.../src/pages/DatabaseList/DatabaseList.test.jsx | 2 +-
superset-frontend/src/pages/DatabaseList/index.tsx | 80 ++++++---
.../src/pages/DatasetList/DatasetList.test.tsx | 61 +++----
superset-frontend/src/pages/DatasetList/index.tsx | 109 +++++++-----
.../src/pages/QueryHistoryList/index.tsx | 3 +-
.../RowLevelSecurityList.test.tsx | 6 +-
.../src/pages/RowLevelSecurityList/index.tsx | 39 ++++-
.../src/pages/SavedQueryList/index.tsx | 136 ++++++++-------
superset-frontend/src/pages/Tags/index.tsx | 68 ++++----
.../types.ts => utils/getOwnerName.test.ts} | 23 ++-
.../types.ts => utils/getOwnerName.ts} | 20 +--
superset-frontend/src/views/CRUD/types.ts | 11 +-
superset/annotation_layers/api.py | 2 +-
superset/charts/api.py | 2 +-
superset/css_templates/api.py | 6 +-
superset/dashboards/api.py | 2 +-
superset/databases/api.py | 13 ++
superset/datasets/api.py | 13 +-
superset/queries/saved_queries/api.py | 12 +-
superset/reports/api.py | 10 +-
superset/row_level_security/api.py | 7 +-
superset/row_level_security/schemas.py | 2 +
superset/tags/api.py | 2 +-
tests/integration_tests/css_templates/api_tests.py | 25 ++-
tests/integration_tests/databases/api_tests.py | 1 +
.../queries/saved_queries/api_tests.py | 14 +-
47 files changed, 745 insertions(+), 691 deletions(-)
diff --git a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
index a695541cee..b677507a46 100644
--- a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/alerts.test.ts
@@ -29,10 +29,9 @@ describe('Alert list view', () => {
cy.getBySel('sort-header').eq(2).contains('Name');
cy.getBySel('sort-header').eq(3).contains('Schedule');
cy.getBySel('sort-header').eq(4).contains('Notification method');
- cy.getBySel('sort-header').eq(5).contains('Created by');
- cy.getBySel('sort-header').eq(6).contains('Owners');
- cy.getBySel('sort-header').eq(7).contains('Modified');
- cy.getBySel('sort-header').eq(8).contains('Active');
+ cy.getBySel('sort-header').eq(5).contains('Owners');
+ cy.getBySel('sort-header').eq(6).contains('Last modified');
+ cy.getBySel('sort-header').eq(7).contains('Active');
// TODO Cypress won't recognize the Actions column
// cy.getBySel('sort-header').eq(9).contains('Actions');
});
diff --git a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
index e267d76f6f..a227fa03d7 100644
--- a/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/alerts_and_reports/reports.test.ts
@@ -29,10 +29,9 @@ describe('Report list view', () => {
cy.getBySel('sort-header').eq(2).contains('Name');
cy.getBySel('sort-header').eq(3).contains('Schedule');
cy.getBySel('sort-header').eq(4).contains('Notification method');
- cy.getBySel('sort-header').eq(5).contains('Created by');
- cy.getBySel('sort-header').eq(6).contains('Owners');
- cy.getBySel('sort-header').eq(7).contains('Modified');
- cy.getBySel('sort-header').eq(8).contains('Active');
+ cy.getBySel('sort-header').eq(5).contains('Owners');
+ cy.getBySel('sort-header').eq(6).contains('Last modified');
+ cy.getBySel('sort-header').eq(7).contains('Active');
// TODO Cypress won't recognize the Actions column
// cy.getBySel('sort-header').eq(9).contains('Actions');
});
diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
index acd11669be..00b09e2fb8 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/filter.test.ts
@@ -35,14 +35,14 @@ describe('Charts filters', () => {
setFilter('Owner', 'admin user');
});
- it('should allow filtering by "Created by" correctly', () => {
- setFilter('Created by', 'alpha user');
- setFilter('Created by', 'admin user');
+ it('should allow filtering by "Modified by" correctly', () => {
+ setFilter('Modified by', 'alpha user');
+ setFilter('Modified by', 'admin user');
});
- it('should allow filtering by "Chart type" correctly', () => {
- setFilter('Chart type', 'Area Chart (legacy)');
- setFilter('Chart type', 'Bubble Chart');
+ it('should allow filtering by "Type" correctly', () => {
+ setFilter('Type', 'Area Chart (legacy)');
+ setFilter('Type', 'Bubble Chart');
});
it('should allow filtering by "Dataset" correctly', () => {
@@ -51,7 +51,7 @@ describe('Charts filters', () => {
});
it('should allow filtering by "Dashboards" correctly', () => {
- setFilter('Dashboards', 'Unicode Test');
- setFilter('Dashboards', 'Tabbed Dashboard');
+ setFilter('Dashboard', 'Unicode Test');
+ setFilter('Dashboard', 'Tabbed Dashboard');
});
});
diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
index 6664281abe..44f348edc5 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
@@ -109,14 +109,12 @@ describe('Charts list', () => {
it('should load rows in list mode', () => {
cy.getBySel('listview-table').should('be.visible');
- cy.getBySel('sort-header').eq(1).contains('Chart');
- cy.getBySel('sort-header').eq(2).contains('Visualization type');
+ cy.getBySel('sort-header').eq(1).contains('Name');
+ cy.getBySel('sort-header').eq(2).contains('Type');
cy.getBySel('sort-header').eq(3).contains('Dataset');
- // cy.getBySel('sort-header').eq(4).contains('Dashboards added to');
- cy.getBySel('sort-header').eq(4).contains('Modified by');
+ cy.getBySel('sort-header').eq(4).contains('Owners');
cy.getBySel('sort-header').eq(5).contains('Last modified');
- cy.getBySel('sort-header').eq(6).contains('Created by');
- cy.getBySel('sort-header').eq(7).contains('Actions');
+ cy.getBySel('sort-header').eq(6).contains('Actions');
});
it('should sort correctly in list mode', () => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
index 4654b3b5c2..854ea541c7 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/filter.test.ts
@@ -35,9 +35,9 @@ describe('Dashboards filters', () => {
setFilter('Owner', 'admin user');
});
- it('should allow filtering by "Created by" correctly', () => {
- setFilter('Created by', 'alpha user');
- setFilter('Created by', 'admin user');
+ it('should allow filtering by "Modified by" correctly', () => {
+ setFilter('Modified by', 'alpha user');
+ setFilter('Modified by', 'admin user');
});
it('should allow filtering by "Status" correctly', () => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
index 9bc6eed224..7dfb7cd673 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts
@@ -54,13 +54,11 @@ describe('Dashboards list', () => {
it('should load rows in list mode', () => {
cy.getBySel('listview-table').should('be.visible');
- cy.getBySel('sort-header').eq(1).contains('Title');
- cy.getBySel('sort-header').eq(2).contains('Modified by');
- cy.getBySel('sort-header').eq(3).contains('Status');
- cy.getBySel('sort-header').eq(4).contains('Modified');
- cy.getBySel('sort-header').eq(5).contains('Created by');
- cy.getBySel('sort-header').eq(6).contains('Owners');
- cy.getBySel('sort-header').eq(7).contains('Actions');
+ cy.getBySel('sort-header').eq(1).contains('Name');
+ cy.getBySel('sort-header').eq(2).contains('Status');
+ cy.getBySel('sort-header').eq(3).contains('Owners');
+ cy.getBySel('sort-header').eq(4).contains('Last modified');
+ cy.getBySel('sort-header').eq(5).contains('Actions');
});
it('should sort correctly in list mode', () => {
diff --git a/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx b/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
new file mode 100644
index 0000000000..af9d6913d8
--- /dev/null
+++ b/superset-frontend/src/components/AuditInfo/ModifiedInfo.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
+
+import { ModifiedInfo } from '.';
+
+const TEST_DATE = '2023-11-20';
+const USER = {
+ id: 1,
+ first_name: 'Foo',
+ last_name: 'Bar',
+};
+
+test('should render a tooltip when user is provided', async () => {
+ render(<ModifiedInfo user={USER} date={TEST_DATE} />);
+
+ const dateElement = screen.getByTestId('audit-info-date');
+ expect(dateElement).toBeInTheDocument();
+ expect(screen.getByText(TEST_DATE)).toBeInTheDocument();
+ expect(screen.queryByText('Modified by: Foo Bar')).not.toBeInTheDocument();
+ userEvent.hover(dateElement);
+ const tooltip = await screen.findByRole('tooltip');
+ expect(tooltip).toBeInTheDocument();
+ expect(screen.getByText('Modified by: Foo Bar')).toBeInTheDocument();
+});
+
+test('should render only the date if username is not provided', async () => {
+ render(<ModifiedInfo date={TEST_DATE} />);
+
+ const dateElement = screen.getByTestId('audit-info-date');
+ expect(dateElement).toBeInTheDocument();
+ expect(screen.getByText(TEST_DATE)).toBeInTheDocument();
+ userEvent.hover(dateElement);
+ await waitFor(
+ () => {
+ const tooltip = screen.queryByRole('tooltip');
+ expect(tooltip).not.toBeInTheDocument();
+ },
+ { timeout: 1000 },
+ );
+});
diff --git a/superset-frontend/src/components/AuditInfo/index.tsx b/superset-frontend/src/components/AuditInfo/index.tsx
new file mode 100644
index 0000000000..24223a1554
--- /dev/null
+++ b/superset-frontend/src/components/AuditInfo/index.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+
+import Owner from 'src/types/Owner';
+import { Tooltip } from 'src/components/Tooltip';
+import getOwnerName from 'src/utils/getOwnerName';
+import { t } from '@superset-ui/core';
+
+export type ModifiedInfoProps = {
+ user?: Owner;
+ date: string;
+};
+
+export const ModifiedInfo = ({ user, date }: ModifiedInfoProps) => {
+ const dateSpan = (
+ <span className="no-wrap" data-test="audit-info-date">
+ {date}
+ </span>
+ );
+
+ if (user) {
+ const userName = getOwnerName(user);
+ const title = t('Modified by: %s', userName);
+ return (
+ <Tooltip title={title} placement="bottom">
+ {dateSpan}
+ </Tooltip>
+ );
+ }
+ return dateSpan;
+};
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index 86b5c22777..751001297a 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -1114,7 +1114,7 @@ class DatasourceEditor extends React.PureComponent {
<div css={{ width: 'calc(100% - 34px)', marginTop: -16 }}>
<Field
fieldKey="table_name"
- label={t('Dataset name')}
+ label={t('Name')}
control={
<TextControl
controlId="table_name"
diff --git a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index 92d34a4faa..3a1421e380 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -681,7 +681,7 @@ const PropertiesModal = ({
</Row>
<Row gutter={16}>
<Col xs={24} md={12}>
- <FormItem label={t('Title')} name="title">
+ <FormItem label={t('Name')} name="title">
<Input
data-test="dashboard-title-input"
type="text"
diff --git a/superset-frontend/src/features/annotations/AnnotationModal.tsx b/superset-frontend/src/features/annotations/AnnotationModal.tsx
index a5c5aa9c31..dd1107dfba 100644
--- a/superset-frontend/src/features/annotations/AnnotationModal.tsx
+++ b/superset-frontend/src/features/annotations/AnnotationModal.tsx
@@ -287,7 +287,7 @@ const AnnotationModal: FunctionComponent<AnnotationModalProps> = ({
</StyledAnnotationTitle>
<AnnotationContainer>
<div className="control-label">
- {t('Annotation name')}
+ {t('Name')}
<span className="required">*</span>
</div>
<input
diff --git a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
index 73bbfe7555..bd3c5b13a6 100644
--- a/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
+++ b/superset-frontend/src/features/cssTemplates/CssTemplateModal.tsx
@@ -105,6 +105,9 @@ const CssTemplateModal: FunctionComponent<CssTemplateModalProps> = ({
const update_id = currentCssTemplate.id;
delete currentCssTemplate.id;
delete currentCssTemplate.created_by;
+ delete currentCssTemplate.changed_by;
+ delete currentCssTemplate.changed_on_delta_humanized;
+
updateResource(update_id, currentCssTemplate).then(response => {
if (!response) {
return;
@@ -235,7 +238,7 @@ const CssTemplateModal: FunctionComponent<CssTemplateModalProps> = ({
</StyledCssTemplateTitle>
<TemplateContainer>
<div className="control-label">
- {t('CSS template name')}
+ {t('Name')}
<span className="required">*</span>
</div>
<input
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/features/cssTemplates/types.ts
index 1bb5b2e659..5e7e1af97a 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/features/cssTemplates/types.ts
@@ -1,3 +1,5 @@
+import Owner from 'src/types/Owner';
+
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -16,17 +18,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-type CreatedByUser = {
- id: number;
- first_name: string;
- last_name: string;
-};
-
export type TemplateObject = {
id?: number;
changed_on_delta_humanized?: string;
created_on?: string;
- created_by?: CreatedByUser;
+ changed_by?: Owner;
+ created_by?: Owner;
css?: string;
template_name: string;
};
diff --git a/superset-frontend/src/features/tags/TagModal.test.tsx b/superset-frontend/src/features/tags/TagModal.test.tsx
index 5f4fd4e2b9..99b7a3365e 100644
--- a/superset-frontend/src/features/tags/TagModal.test.tsx
+++ b/superset-frontend/src/features/tags/TagModal.test.tsx
@@ -56,10 +56,12 @@ test('renders correctly in edit mode', () => {
changed_on_delta_humanized: '',
created_on_delta_humanized: '',
created_by: {
+ id: 1,
first_name: 'joe',
last_name: 'smith',
},
changed_by: {
+ id: 2,
first_name: 'tom',
last_name: 'brown',
},
diff --git a/superset-frontend/src/pages/AlertReportList/index.tsx b/superset-frontend/src/pages/AlertReportList/index.tsx
index b0cd0a4622..c6d14d186f 100644
--- a/superset-frontend/src/pages/AlertReportList/index.tsx
+++ b/superset-frontend/src/pages/AlertReportList/index.tsx
@@ -53,6 +53,8 @@ import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
import Owner from 'src/types/Owner';
import AlertReportModal from 'src/features/alerts/AlertReportModal';
import { AlertObject, AlertState } from 'src/features/alerts/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const extensionsRegistry = getExtensionsRegistry();
@@ -303,18 +305,6 @@ function AlertList({
disableSortBy: true,
size: 'xl',
},
- {
- Cell: ({
- row: {
- original: { created_by },
- },
- }: any) =>
- created_by ? `${created_by.first_name} ${created_by.last_name}` : '',
- Header: t('Created by'),
- id: 'created_by',
- disableSortBy: true,
- size: 'xl',
- },
{
Cell: ({
row: {
@@ -329,10 +319,13 @@ function AlertList({
{
Cell: ({
row: {
- original: { changed_on_delta_humanized: changedOn },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => <span className="no-wrap">{changedOn}</span>,
- Header: t('Modified'),
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+ Header: t('Last modified'),
accessor: 'changed_on_delta_humanized',
size: 'xl',
},
@@ -407,6 +400,10 @@ function AlertList({
disableSortBy: true,
size: 'xl',
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canDelete, canEdit, isReportEnabled, toggleActive],
);
@@ -448,6 +445,13 @@ function AlertList({
const filters: Filters = useMemo(
() => [
+ {
+ Header: t('Name'),
+ key: 'search',
+ id: 'name',
+ input: 'search',
+ operator: FilterOperator.contains,
+ },
{
Header: t('Owner'),
key: 'owner',
@@ -465,23 +469,6 @@ function AlertList({
),
paginate: true,
},
- {
- Header: t('Created by'),
- key: 'created_by',
- id: 'created_by',
- input: 'select',
- operator: FilterOperator.relationOneMany,
- unfilteredLabel: 'All',
- fetchSelects: createFetchRelated(
- 'report',
- 'created_by',
- createErrorHandler(errMsg =>
- t('An error occurred while fetching created by values: %s', errMsg),
- ),
- user,
- ),
- paginate: true,
- },
{
Header: t('Status'),
key: 'status',
@@ -504,11 +491,24 @@ function AlertList({
],
},
{
- Header: t('Search'),
- key: 'search',
- id: 'name',
- input: 'search',
- operator: FilterOperator.contains,
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
+ input: 'select',
+ operator: FilterOperator.relationOneMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'report',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
},
],
[],
diff --git a/superset-frontend/src/pages/AllEntities/index.tsx b/superset-frontend/src/pages/AllEntities/index.tsx
index a1e2c52fe4..b94cab846d 100644
--- a/superset-frontend/src/pages/AllEntities/index.tsx
+++ b/superset-frontend/src/pages/AllEntities/index.tsx
@@ -35,6 +35,7 @@ import TagModal from 'src/features/tags/TagModal';
import withToasts, { useToasts } from 'src/components/MessageToasts/withToasts';
import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
import Loading from 'src/components/Loading';
+import getOwnerName from 'src/utils/getOwnerName';
interface TaggedObject {
id: number;
@@ -132,7 +133,7 @@ function AllEntities() {
const owner: Owner = {
type: MetadataType.OWNER,
- createdBy: `${tag?.created_by.first_name} ${tag?.created_by.last_name}`,
+ createdBy: getOwnerName(tag?.created_by),
createdOn: tag?.created_on_delta_humanized || '',
};
items.push(owner);
@@ -140,7 +141,7 @@ function AllEntities() {
const lastModified: LastModified = {
type: MetadataType.LAST_MODIFIED,
value: tag?.changed_on_delta_humanized || '',
- modifiedBy: `${tag?.changed_by.first_name} ${tag?.changed_by.last_name}`,
+ modifiedBy: getOwnerName(tag?.changed_by),
};
items.push(lastModified);
diff --git a/superset-frontend/src/pages/AnnotationLayerList/index.tsx b/superset-frontend/src/pages/AnnotationLayerList/index.tsx
index fc909538c0..fff5743b5a 100644
--- a/superset-frontend/src/pages/AnnotationLayerList/index.tsx
+++ b/superset-frontend/src/pages/AnnotationLayerList/index.tsx
@@ -21,7 +21,6 @@ import React, { useMemo, useState } from 'react';
import rison from 'rison';
import { t, SupersetClient } from '@superset-ui/core';
import { Link, useHistory } from 'react-router-dom';
-import moment from 'moment';
import { useListViewResource } from 'src/views/CRUD/hooks';
import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
import withToasts from 'src/components/MessageToasts/withToasts';
@@ -36,9 +35,10 @@ import DeleteModal from 'src/components/DeleteModal';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import AnnotationLayerModal from 'src/features/annotationLayers/AnnotationLayerModal';
import { AnnotationLayerObject } from 'src/features/annotationLayers/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const PAGE_SIZE = 25;
-const MOMENT_FORMAT = 'MMM DD, YYYY';
interface AnnotationLayersListProps {
addDangerToast: (msg: string) => void;
@@ -156,65 +156,16 @@ function AnnotationLayersList({
{
Cell: ({
row: {
- original: { changed_on: changedOn },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => {
- const date = new Date(changedOn);
- const utc = new Date(
- Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds(),
- ),
- );
-
- return moment(utc).format(MOMENT_FORMAT);
- },
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
Header: t('Last modified'),
accessor: 'changed_on',
size: 'xl',
},
- {
- Cell: ({
- row: {
- original: { created_on: createdOn },
- },
- }: any) => {
- const date = new Date(createdOn);
- const utc = new Date(
- Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds(),
- ),
- );
-
- return moment(utc).format(MOMENT_FORMAT);
- },
- Header: t('Created on'),
- accessor: 'created_on',
- size: 'xl',
- },
- {
- accessor: 'created_by',
- disableSortBy: true,
- Header: t('Created by'),
- Cell: ({
- row: {
- original: { created_by: createdBy },
- },
- }: any) =>
- createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
- size: 'xl',
- },
{
Cell: ({ row: { original } }: any) => {
const handleEdit = () => handleAnnotationLayerEdit(original);
@@ -249,6 +200,10 @@ function AnnotationLayersList({
hidden: !canEdit && !canDelete,
size: 'xl',
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canDelete, canCreate],
);
@@ -280,15 +235,22 @@ function AnnotationLayersList({
const filters: Filters = useMemo(
() => [
{
- Header: t('Created by'),
- key: 'created_by',
- id: 'created_by',
+ Header: t('Name'),
+ key: 'search',
+ id: 'name',
+ input: 'search',
+ operator: FilterOperator.contains,
+ },
+ {
+ Header: t('Changed by'),
+ key: 'changed_by',
+ id: 'changed_by',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'annotation_layer',
- 'created_by',
+ 'changed_by',
createErrorHandler(errMsg =>
t(
'An error occurred while fetching dataset datasource values: %s',
@@ -299,13 +261,6 @@ function AnnotationLayersList({
),
paginate: true,
},
- {
- Header: t('Search'),
- key: 'search',
- id: 'name',
- input: 'search',
- operator: FilterOperator.contains,
- },
],
[],
);
diff --git a/superset-frontend/src/pages/AnnotationList/index.tsx b/superset-frontend/src/pages/AnnotationList/index.tsx
index 980a18ba72..e04b48080f 100644
--- a/superset-frontend/src/pages/AnnotationList/index.tsx
+++ b/superset-frontend/src/pages/AnnotationList/index.tsx
@@ -154,7 +154,7 @@ function AnnotationList({
() => [
{
accessor: 'short_descr',
- Header: t('Label'),
+ Header: t('Name'),
},
{
accessor: 'long_descr',
diff --git a/superset-frontend/src/pages/ChartList/index.tsx b/superset-frontend/src/pages/ChartList/index.tsx
index d13113158e..5ed967d7c1 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -29,7 +29,6 @@ import {
import React, { useState, useMemo, useCallback } from 'react';
import rison from 'rison';
import { uniqBy } from 'lodash';
-import moment from 'moment';
import { useSelector } from 'react-redux';
import {
createErrorHandler,
@@ -69,11 +68,13 @@ import setupPlugins from 'src/setup/setupPlugins';
import InfoTooltip from 'src/components/InfoTooltip';
import CertifiedBadge from 'src/components/CertifiedBadge';
import { GenericLink } from 'src/components/GenericLink/GenericLink';
-import Owner from 'src/types/Owner';
import { loadTags } from 'src/components/Tags/utils';
+import FacePile from 'src/components/FacePile';
import ChartCard from 'src/features/charts/ChartCard';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { findPermission } from 'src/utils/findPermission';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const FlexRowContainer = styled.div`
align-items: center;
@@ -245,10 +246,6 @@ function ChartList(props: ChartListProps) {
});
setPreparingExport(true);
};
- const changedByName = (lastSavedBy: Owner) =>
- lastSavedBy?.first_name
- ? `${lastSavedBy?.first_name} ${lastSavedBy?.last_name}`
- : null;
function handleBulkChartDelete(chartsToDelete: Chart[]) {
SupersetClient.delete({
@@ -366,7 +363,7 @@ function ChartList(props: ChartListProps) {
)}
</FlexRowContainer>
),
- Header: t('Chart'),
+ Header: t('Name'),
accessor: 'slice_name',
},
{
@@ -375,7 +372,7 @@ function ChartList(props: ChartListProps) {
original: { viz_type: vizType },
},
}: any) => registry.get(vizType)?.name || vizType,
- Header: t('Visualization type'),
+ Header: t('Type'),
accessor: 'viz_type',
size: 'xxl',
},
@@ -438,44 +435,27 @@ function ChartList(props: ChartListProps) {
{
Cell: ({
row: {
- original: { last_saved_by: lastSavedBy },
+ original: { owners = [] },
},
- }: any) => <>{changedByName(lastSavedBy)}</>,
- Header: t('Modified by'),
- accessor: 'last_saved_by.first_name',
+ }: any) => <FacePile users={owners} />,
+ Header: t('Owners'),
+ accessor: 'owners',
+ disableSortBy: true,
size: 'xl',
},
{
Cell: ({
row: {
- original: { last_saved_at: lastSavedAt },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => (
- <span className="no-wrap">
- {lastSavedAt ? moment.utc(lastSavedAt).fromNow() : null}
- </span>
- ),
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
Header: t('Last modified'),
accessor: 'last_saved_at',
size: 'xl',
},
- {
- accessor: 'owners',
- hidden: true,
- disableSortBy: true,
- },
- {
- Cell: ({
- row: {
- original: { created_by: createdBy },
- },
- }: any) =>
- createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
- Header: t('Created by'),
- accessor: 'created_by',
- disableSortBy: true,
- size: 'xl',
- },
{
Cell: ({ row: { original } }: any) => {
const handleDelete = () =>
@@ -563,6 +543,10 @@ function ChartList(props: ChartListProps) {
disableSortBy: true,
hidden: !canEdit && !canDelete,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[
userId,
@@ -597,58 +581,14 @@ function ChartList(props: ChartListProps) {
const filters: Filters = useMemo(() => {
const filters_list = [
{
- Header: t('Search'),
+ Header: t('Name'),
key: 'search',
id: 'slice_name',
input: 'search',
operator: FilterOperator.chartAllText,
},
{
- Header: t('Owner'),
- key: 'owner',
- id: 'owners',
- input: 'select',
- operator: FilterOperator.relationManyMany,
- unfilteredLabel: t('All'),
- fetchSelects: createFetchRelated(
- 'chart',
- 'owners',
- createErrorHandler(errMsg =>
- addDangerToast(
- t(
- 'An error occurred while fetching chart owners values: %s',
- errMsg,
- ),
- ),
- ),
- props.user,
- ),
- paginate: true,
- },
- {
- Header: t('Created by'),
- key: 'created_by',
- id: 'created_by',
- input: 'select',
- operator: FilterOperator.relationOneMany,
- unfilteredLabel: t('All'),
- fetchSelects: createFetchRelated(
- 'chart',
- 'created_by',
- createErrorHandler(errMsg =>
- addDangerToast(
- t(
- 'An error occurred while fetching chart created by values: %s',
- errMsg,
- ),
- ),
- ),
- props.user,
- ),
- paginate: true,
- },
- {
- Header: t('Chart type'),
+ Header: t('Type'),
key: 'viz_type',
id: 'viz_type',
input: 'select',
@@ -683,8 +623,43 @@ function ChartList(props: ChartListProps) {
fetchSelects: createFetchDatasets,
paginate: true,
},
+ ...(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+ ? [
+ {
+ Header: t('Tag'),
+ key: 'tags',
+ id: 'tags',
+ input: 'select',
+ operator: FilterOperator.chartTags,
+ unfilteredLabel: t('All'),
+ fetchSelects: loadTags,
+ },
+ ]
+ : []),
{
- Header: t('Dashboards'),
+ Header: t('Owner'),
+ key: 'owner',
+ id: 'owners',
+ input: 'select',
+ operator: FilterOperator.relationManyMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'chart',
+ 'owners',
+ createErrorHandler(errMsg =>
+ addDangerToast(
+ t(
+ 'An error occurred while fetching chart owners values: %s',
+ errMsg,
+ ),
+ ),
+ ),
+ props.user,
+ ),
+ paginate: true,
+ },
+ {
+ Header: t('Dashboard'),
key: 'dashboards',
id: 'dashboards',
input: 'select',
@@ -707,18 +682,27 @@ function ChartList(props: ChartListProps) {
{ label: t('No'), value: false },
],
},
- ] as Filters;
- if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
- filters_list.push({
- Header: t('Tags'),
- key: 'tags',
- id: 'tags',
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
input: 'select',
- operator: FilterOperator.chartTags,
+ operator: FilterOperator.relationOneMany,
unfilteredLabel: t('All'),
- fetchSelects: loadTags,
- });
- }
+ fetchSelects: createFetchRelated(
+ 'chart',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ props.user,
+ ),
+ paginate: true,
+ },
+ ] as Filters;
return filters_list;
}, [addDangerToast, favoritesFilter, props.user]);
diff --git a/superset-frontend/src/pages/CssTemplateList/index.tsx b/superset-frontend/src/pages/CssTemplateList/index.tsx
index f777f8e743..b77217b22f 100644
--- a/superset-frontend/src/pages/CssTemplateList/index.tsx
+++ b/superset-frontend/src/pages/CssTemplateList/index.tsx
@@ -21,13 +21,11 @@ import React, { useMemo, useState } from 'react';
import { t, SupersetClient } from '@superset-ui/core';
import rison from 'rison';
-import moment from 'moment';
import { useListViewResource } from 'src/views/CRUD/hooks';
-import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils';
+import { createErrorHandler, createFetchRelated } from 'src/views/CRUD/utils';
import withToasts from 'src/components/MessageToasts/withToasts';
import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
import DeleteModal from 'src/components/DeleteModal';
-import { Tooltip } from 'src/components/Tooltip';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
import ListView, {
@@ -37,6 +35,8 @@ import ListView, {
} from 'src/components/ListView';
import CssTemplateModal from 'src/features/cssTemplates/CssTemplateModal';
import { TemplateObject } from 'src/features/cssTemplates/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const PAGE_SIZE = 25;
@@ -138,66 +138,12 @@ function CssTemplatesList({
changed_by: changedBy,
},
},
- }: any) => {
- let name = 'null';
-
- if (changedBy) {
- name = `${changedBy.first_name} ${changedBy.last_name}`;
- }
-
- return (
- <Tooltip
- id="allow-run-async-header-tooltip"
- title={t('Last modified by %s', name)}
- placement="right"
- >
- <span>{changedOn}</span>
- </Tooltip>
- );
- },
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
Header: t('Last modified'),
accessor: 'changed_on_delta_humanized',
size: 'xl',
disableSortBy: true,
},
- {
- Cell: ({
- row: {
- original: { created_on: createdOn },
- },
- }: any) => {
- const date = new Date(createdOn);
- const utc = new Date(
- Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds(),
- ),
- );
-
- return moment(utc).fromNow();
- },
- Header: t('Created on'),
- accessor: 'created_on',
- size: 'xl',
- disableSortBy: true,
- },
- {
- accessor: 'created_by',
- disableSortBy: true,
- Header: t('Created by'),
- Cell: ({
- row: {
- original: { created_by: createdBy },
- },
- }: any) =>
- createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
- size: 'xl',
- },
{
Cell: ({ row: { original } }: any) => {
const handleEdit = () => handleCssTemplateEdit(original);
@@ -232,6 +178,10 @@ function CssTemplatesList({
hidden: !canEdit && !canDelete,
size: 'xl',
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canDelete, canCreate],
);
@@ -270,15 +220,22 @@ function CssTemplatesList({
const filters: Filters = useMemo(
() => [
{
- Header: t('Created by'),
- key: 'created_by',
- id: 'created_by',
+ Header: t('Name'),
+ key: 'search',
+ id: 'template_name',
+ input: 'search',
+ operator: FilterOperator.contains,
+ },
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'css_template',
- 'created_by',
+ 'changed_by',
createErrorHandler(errMsg =>
t(
'An error occurred while fetching dataset datasource values: %s',
@@ -289,13 +246,6 @@ function CssTemplatesList({
),
paginate: true,
},
- {
- Header: t('Search'),
- key: 'search',
- id: 'template_name',
- input: 'search',
- operator: FilterOperator.contains,
- },
],
[],
);
diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx
index 6542d85129..e82b701859 100644
--- a/superset-frontend/src/pages/DashboardList/index.tsx
+++ b/superset-frontend/src/pages/DashboardList/index.tsx
@@ -57,13 +57,17 @@ import { Tooltip } from 'src/components/Tooltip';
import ImportModelsModal from 'src/components/ImportModal/index';
import Dashboard from 'src/dashboard/containers/Dashboard';
-import { Dashboard as CRUDDashboard } from 'src/views/CRUD/types';
+import {
+ Dashboard as CRUDDashboard,
+ QueryObjectColumns,
+} from 'src/views/CRUD/types';
import CertifiedBadge from 'src/components/CertifiedBadge';
import { loadTags } from 'src/components/Tags/utils';
import DashboardCard from 'src/features/dashboards/DashboardCard';
import { DashboardStatus } from 'src/features/dashboards/types';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { findPermission } from 'src/utils/findPermission';
+import { ModifiedInfo } from 'src/components/AuditInfo';
const PAGE_SIZE = 25;
const PASSWORDS_NEEDED_MESSAGE = t(
@@ -108,11 +112,7 @@ const Actions = styled.div`
`;
function DashboardList(props: DashboardListProps) {
- const {
- addDangerToast,
- addSuccessToast,
- user: { userId },
- } = props;
+ const { addDangerToast, addSuccessToast, user } = props;
const { roles } = useSelector<any, UserWithPermissionsAndRoles>(
state => state.user,
@@ -178,7 +178,7 @@ function DashboardList(props: DashboardListProps) {
};
// TODO: Fix usage of localStorage keying on the user id
- const userKey = dangerouslyGetItemDoNotUse(userId?.toString(), null);
+ const userKey = dangerouslyGetItemDoNotUse(user?.userId?.toString(), null);
const canCreate = hasPerm('can_write');
const canEdit = hasPerm('can_write');
@@ -274,7 +274,7 @@ function DashboardList(props: DashboardListProps) {
original: { id },
},
}: any) =>
- userId && (
+ user?.userId && (
<FaveStar
itemId={id}
saveFaveStar={saveFavoriteStatus}
@@ -285,7 +285,7 @@ function DashboardList(props: DashboardListProps) {
id: 'id',
disableSortBy: true,
size: 'xs',
- hidden: !userId,
+ hidden: !user?.userId,
},
{
Cell: ({
@@ -310,9 +310,20 @@ function DashboardList(props: DashboardListProps) {
{dashboardTitle}
</Link>
),
- Header: t('Title'),
+ Header: t('Name'),
accessor: 'dashboard_title',
},
+ {
+ Cell: ({
+ row: {
+ original: { status },
+ },
+ }: any) =>
+ status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'),
+ Header: t('Status'),
+ accessor: 'published',
+ size: 'xl',
+ },
{
Cell: ({
row: {
@@ -341,55 +352,25 @@ function DashboardList(props: DashboardListProps) {
{
Cell: ({
row: {
- original: { changed_by_name: changedByName },
- },
- }: any) => <>{changedByName}</>,
- Header: t('Modified by'),
- accessor: 'changed_by.first_name',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { status },
- },
- }: any) =>
- status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'),
- Header: t('Status'),
- accessor: 'published',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { changed_on_delta_humanized: changedOn },
- },
- }: any) => <span className="no-wrap">{changedOn}</span>,
- Header: t('Modified'),
- accessor: 'changed_on_delta_humanized',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { created_by: createdBy },
+ original: { owners = [] },
},
- }: any) =>
- createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
- Header: t('Created by'),
- accessor: 'created_by',
+ }: any) => <FacePile users={owners} />,
+ Header: t('Owners'),
+ accessor: 'owners',
disableSortBy: true,
size: 'xl',
},
{
Cell: ({
row: {
- original: { owners = [] },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => <FacePile users={owners} />,
- Header: t('Owners'),
- accessor: 'owners',
- disableSortBy: true,
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+ Header: t('Last modified'),
+ accessor: 'changed_on_delta_humanized',
size: 'xl',
},
{
@@ -475,9 +456,13 @@ function DashboardList(props: DashboardListProps) {
hidden: !canEdit && !canDelete && !canExport,
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[
- userId,
+ user?.userId,
canEdit,
canDelete,
canExport,
@@ -509,12 +494,37 @@ function DashboardList(props: DashboardListProps) {
const filters: Filters = useMemo(() => {
const filters_list = [
{
- Header: t('Search'),
+ Header: t('Name'),
key: 'search',
id: 'dashboard_title',
input: 'search',
operator: FilterOperator.titleOrSlug,
},
+ {
+ Header: t('Status'),
+ key: 'published',
+ id: 'published',
+ input: 'select',
+ operator: FilterOperator.equals,
+ unfilteredLabel: t('Any'),
+ selects: [
+ { label: t('Published'), value: true },
+ { label: t('Draft'), value: false },
+ ],
+ },
+ ...(isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+ ? [
+ {
+ Header: t('Tag'),
+ key: 'tags',
+ id: 'tags',
+ input: 'select',
+ operator: FilterOperator.dashboardTags,
+ unfilteredLabel: t('All'),
+ fetchSelects: loadTags,
+ },
+ ]
+ : []),
{
Header: t('Owner'),
key: 'owner',
@@ -537,41 +547,7 @@ function DashboardList(props: DashboardListProps) {
),
paginate: true,
},
- {
- Header: t('Created by'),
- key: 'created_by',
- id: 'created_by',
- input: 'select',
- operator: FilterOperator.relationOneMany,
- unfilteredLabel: t('All'),
- fetchSelects: createFetchRelated(
- 'dashboard',
- 'created_by',
- createErrorHandler(errMsg =>
- addDangerToast(
- t(
- 'An error occurred while fetching dashboard created by values: %s',
- errMsg,
- ),
- ),
- ),
- props.user,
- ),
- paginate: true,
- },
- {
- Header: t('Status'),
- key: 'published',
- id: 'published',
- input: 'select',
- operator: FilterOperator.equals,
- unfilteredLabel: t('Any'),
- selects: [
- { label: t('Published'), value: true },
- { label: t('Draft'), value: false },
- ],
- },
- ...(userId ? [favoritesFilter] : []),
+ ...(user?.userId ? [favoritesFilter] : []),
{
Header: t('Certified'),
key: 'certified',
@@ -585,18 +561,27 @@ function DashboardList(props: DashboardListProps) {
{ label: t('No'), value: false },
],
},
- ] as Filters;
- if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
- filters_list.push({
- Header: t('Tags'),
- key: 'tags',
- id: 'tags',
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
input: 'select',
- operator: FilterOperator.dashboardTags,
+ operator: FilterOperator.relationOneMany,
unfilteredLabel: t('All'),
- fetchSelects: loadTags,
- });
- }
+ fetchSelects: createFetchRelated(
+ 'dashboard',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
+ },
+ ] as Filters;
return filters_list;
}, [addDangerToast, favoritesFilter, props.user]);
@@ -632,7 +617,7 @@ function DashboardList(props: DashboardListProps) {
? userKey.thumbnails
: isFeatureEnabled(FeatureFlag.THUMBNAILS)
}
- userId={userId}
+ userId={user?.userId}
loading={loading}
openDashboardEditModal={openDashboardEditModal}
saveFavoriteStatus={saveFavoriteStatus}
@@ -646,7 +631,7 @@ function DashboardList(props: DashboardListProps) {
favoriteStatus,
hasPerm,
loading,
- userId,
+ user?.userId,
saveFavoriteStatus,
userKey,
],
@@ -743,7 +728,7 @@ function DashboardList(props: DashboardListProps) {
addSuccessToast,
addDangerToast,
undefined,
- userId,
+ user?.userId,
);
setDashboardToDelete(null);
}}
diff --git a/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx b/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
index fd989b50d2..b1bfb245d3 100644
--- a/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
+++ b/superset-frontend/src/pages/DatabaseList/DatabaseList.test.jsx
@@ -218,7 +218,7 @@ describe('Admin DatabaseList', () => {
await waitForComponentToPaint(wrapper);
expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/database/?q=(filters:!((col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f),(col:database_name,opr:ct,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
+ `"http://localhost/api/v1/database/?q=(filters:!((col:database_name,opr:ct,value:fooo),(col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
);
});
diff --git a/superset-frontend/src/pages/DatabaseList/index.tsx b/superset-frontend/src/pages/DatabaseList/index.tsx
index d2308bd117..8c98392aca 100644
--- a/superset-frontend/src/pages/DatabaseList/index.tsx
+++ b/superset-frontend/src/pages/DatabaseList/index.tsx
@@ -32,7 +32,11 @@ import { LocalStorageKeys, setItem } from 'src/utils/localStorageHelpers';
import Loading from 'src/components/Loading';
import { useListViewResource } from 'src/views/CRUD/hooks';
-import { createErrorHandler, uploadUserPerms } from 'src/views/CRUD/utils';
+import {
+ createErrorHandler,
+ createFetchRelated,
+ uploadUserPerms,
+} from 'src/views/CRUD/utils';
import withToasts from 'src/components/MessageToasts/withToasts';
import SubMenu, { SubMenuProps } from 'src/features/home/SubMenu';
import DeleteModal from 'src/components/DeleteModal';
@@ -48,6 +52,8 @@ import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import type { MenuObjectProps } from 'src/types/bootstrapTypes';
import DatabaseModal from 'src/features/databases/DatabaseModal';
import { DatabaseObject } from 'src/features/databases/types';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const extensionsRegistry = getExtensionsRegistry();
const DatabaseDeleteRelatedExtension = extensionsRegistry.get(
@@ -67,6 +73,11 @@ interface DatabaseDeleteObject extends DatabaseObject {
interface DatabaseListProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
+ user: {
+ userId: string | number;
+ firstName: string;
+ lastName: string;
+ };
}
const IconCheck = styled(Icons.Check)`
@@ -90,7 +101,11 @@ function BooleanDisplay({ value }: { value: Boolean }) {
return value ? <IconCheck /> : <IconCancelX />;
}
-function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
+function DatabaseList({
+ addDangerToast,
+ addSuccessToast,
+ user,
+}: DatabaseListProps) {
const {
state: {
loading,
@@ -105,7 +120,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
t('database'),
addDangerToast,
);
- const user = useSelector<any, UserWithPermissionsAndRoles>(
+ const fullUser = useSelector<any, UserWithPermissionsAndRoles>(
state => state.user,
);
const showDatabaseModal = getUrlParam(URL_PARAMS.showDatabaseModal);
@@ -123,11 +138,11 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
null,
);
const [allowUploads, setAllowUploads] = useState<boolean>(false);
- const isAdmin = isUserAdmin(user);
+ const isAdmin = isUserAdmin(fullUser);
const showUploads = allowUploads || isAdmin;
const [preparingExport, setPreparingExport] = useState<boolean>(false);
- const { roles } = user;
+ const { roles } = fullUser;
const {
CSV_EXTENSIONS,
COLUMNAR_EXTENSIONS,
@@ -313,7 +328,7 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
() => [
{
accessor: 'database_name',
- Header: t('Database'),
+ Header: t('Name'),
},
{
accessor: 'backend',
@@ -380,23 +395,14 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
size: 'md',
},
{
- accessor: 'created_by',
- disableSortBy: true,
- Header: t('Created by'),
Cell: ({
row: {
- original: { created_by: createdBy },
+ original: {
+ changed_by: changedBy,
+ changed_on_delta_humanized: changedOn,
+ },
},
- }: any) =>
- createdBy ? `${createdBy.first_name} ${createdBy.last_name}` : '',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { changed_on_delta_humanized: changedOn },
- },
- }: any) => changedOn,
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
Header: t('Last modified'),
accessor: 'changed_on_delta_humanized',
size: 'xl',
@@ -470,12 +476,23 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
hidden: !canEdit && !canDelete,
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canDelete, canEdit, canExport],
);
const filters: Filters = useMemo(
() => [
+ {
+ Header: t('Name'),
+ key: 'search',
+ id: 'database_name',
+ input: 'search',
+ operator: FilterOperator.contains,
+ },
{
Header: t('Expose in SQL Lab'),
key: 'expose_in_sql_lab',
@@ -509,11 +526,24 @@ function DatabaseList({ addDangerToast, addSuccessToast }: DatabaseListProps) {
],
},
{
- Header: t('Search'),
- key: 'search',
- id: 'database_name',
- input: 'search',
- operator: FilterOperator.contains,
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
+ input: 'select',
+ operator: FilterOperator.relationOneMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'database',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
},
],
[],
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
index 916dd0615b..c316001bb4 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
@@ -285,56 +285,41 @@ describe('RTL', () => {
});
describe('Prevent unsafe URLs', () => {
+ const columnCount = 8;
+ const exploreUrlIndex = 1;
+ const getTdIndex = (rowNumber: number): number =>
+ rowNumber * columnCount + exploreUrlIndex;
+
const mockedProps = {};
let wrapper: any;
it('Check prevent unsafe is on renders relative links', async () => {
- const tdColumnsNumber = 9;
useSelectorMock.mockReturnValue(true);
wrapper = await mountAndWait(mockedProps);
const tdElements = wrapper.find(ListView).find('td');
- expect(
- tdElements
- .at(0 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('/https://www.google.com?0');
- expect(
- tdElements
- .at(1 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('/https://www.google.com?1');
- expect(
- tdElements
- .at(2 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('/https://www.google.com?2');
+ expect(tdElements.at(getTdIndex(0)).find('a').prop('href')).toBe(
+ '/https://www.google.com?0',
+ );
+ expect(tdElements.at(getTdIndex(1)).find('a').prop('href')).toBe(
+ '/https://www.google.com?1',
+ );
+ expect(tdElements.at(getTdIndex(2)).find('a').prop('href')).toBe(
+ '/https://www.google.com?2',
+ );
});
it('Check prevent unsafe is off renders absolute links', async () => {
- const tdColumnsNumber = 9;
useSelectorMock.mockReturnValue(false);
wrapper = await mountAndWait(mockedProps);
const tdElements = wrapper.find(ListView).find('td');
- expect(
- tdElements
- .at(0 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('https://www.google.com?0');
- expect(
- tdElements
- .at(1 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('https://www.google.com?1');
- expect(
- tdElements
- .at(2 * tdColumnsNumber + 1)
- .find('a')
- .prop('href'),
- ).toBe('https://www.google.com?2');
+ expect(tdElements.at(getTdIndex(0)).find('a').prop('href')).toBe(
+ 'https://www.google.com?0',
+ );
+ expect(tdElements.at(getTdIndex(1)).find('a').prop('href')).toBe(
+ 'https://www.google.com?1',
+ );
+ expect(tdElements.at(getTdIndex(2)).find('a').prop('href')).toBe(
+ 'https://www.google.com?2',
+ );
});
});
diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx
index d86d7a7b0f..8a39cb0463 100644
--- a/superset-frontend/src/pages/DatasetList/index.tsx
+++ b/superset-frontend/src/pages/DatasetList/index.tsx
@@ -70,6 +70,8 @@ import {
} from 'src/features/datasets/constants';
import DuplicateDatasetModal from 'src/features/datasets/DuplicateDatasetModal';
import { useSelector } from 'react-redux';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const extensionsRegistry = getExtensionsRegistry();
const DatasetDeleteRelatedExtension = extensionsRegistry.get(
@@ -380,26 +382,6 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
accessor: 'schema',
size: 'lg',
},
- {
- Cell: ({
- row: {
- original: { changed_on_delta_humanized: changedOn },
- },
- }: any) => <span className="no-wrap">{changedOn}</span>,
- Header: t('Modified'),
- accessor: 'changed_on_delta_humanized',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { changed_by_name: changedByName },
- },
- }: any) => changedByName,
- Header: t('Modified by'),
- accessor: 'changed_by.first_name',
- size: 'xl',
- },
{
accessor: 'database',
disableSortBy: true,
@@ -416,6 +398,19 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
disableSortBy: true,
size: 'lg',
},
+ {
+ Cell: ({
+ row: {
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
+ },
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+ Header: t('Last modified'),
+ accessor: 'changed_on_delta_humanized',
+ size: 'xl',
+ },
{
accessor: 'sql',
hidden: true,
@@ -515,6 +510,10 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
hidden: !canEdit && !canDelete && !canDuplicate,
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canEdit, canDelete, canExport, openDatasetEditModal, canDuplicate, user],
);
@@ -522,31 +521,23 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
const filterTypes: Filters = useMemo(
() => [
{
- Header: t('Search'),
+ Header: t('Name'),
key: 'search',
id: 'table_name',
input: 'search',
operator: FilterOperator.contains,
},
{
- Header: t('Owner'),
- key: 'owner',
- id: 'owners',
+ Header: t('Type'),
+ key: 'sql',
+ id: 'sql',
input: 'select',
- operator: FilterOperator.relationManyMany,
+ operator: FilterOperator.datasetIsNullOrEmpty,
unfilteredLabel: 'All',
- fetchSelects: createFetchRelated(
- 'dataset',
- 'owners',
- createErrorHandler(errMsg =>
- t(
- 'An error occurred while fetching dataset owner values: %s',
- errMsg,
- ),
- ),
- user,
- ),
- paginate: true,
+ selects: [
+ { label: t('Virtual'), value: false },
+ { label: t('Physical'), value: true },
+ ],
},
{
Header: t('Database'),
@@ -581,16 +572,24 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
paginate: true,
},
{
- Header: t('Type'),
- key: 'sql',
- id: 'sql',
+ Header: t('Owner'),
+ key: 'owner',
+ id: 'owners',
input: 'select',
- operator: FilterOperator.datasetIsNullOrEmpty,
+ operator: FilterOperator.relationManyMany,
unfilteredLabel: 'All',
- selects: [
- { label: t('Virtual'), value: false },
- { label: t('Physical'), value: true },
- ],
+ fetchSelects: createFetchRelated(
+ 'dataset',
+ 'owners',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset owner values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
},
{
Header: t('Certified'),
@@ -605,6 +604,26 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
{ label: t('No'), value: false },
],
},
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
+ input: 'select',
+ operator: FilterOperator.relationOneMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'dataset',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
+ },
],
[user],
);
diff --git a/superset-frontend/src/pages/QueryHistoryList/index.tsx b/superset-frontend/src/pages/QueryHistoryList/index.tsx
index 77177188e0..94b646d9e4 100644
--- a/superset-frontend/src/pages/QueryHistoryList/index.tsx
+++ b/superset-frontend/src/pages/QueryHistoryList/index.tsx
@@ -53,6 +53,7 @@ import { QueryObject, QueryObjectColumns } from 'src/views/CRUD/types';
import Icons from 'src/components/Icons';
import QueryPreviewModal from 'src/features/queries/QueryPreviewModal';
import { addSuccessToast } from 'src/components/MessageToasts/actions';
+import getOwnerName from 'src/utils/getOwnerName';
const PAGE_SIZE = 25;
const SQL_PREVIEW_MAX_LINES = 4;
@@ -311,7 +312,7 @@ function QueryList({ addDangerToast }: QueryListProps) {
row: {
original: { user },
},
- }: any) => (user ? `${user.first_name} ${user.last_name}` : ''),
+ }: any) => getOwnerName(user),
},
{
accessor: QueryObjectColumns.user,
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
index a4621ed10e..6721f73add 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
@@ -187,8 +187,8 @@ describe('RuleList RTL', () => {
const searchFilters = screen.queryAllByTestId('filters-search');
expect(searchFilters).toHaveLength(2);
- const typeFilter = await screen.findByTestId('filters-select');
- expect(typeFilter).toBeInTheDocument();
+ const typeFilter = screen.queryAllByTestId('filters-select');
+ expect(typeFilter).toHaveLength(2);
});
it('renders correct list columns', async () => {
@@ -201,7 +201,7 @@ describe('RuleList RTL', () => {
const fitlerTypeColumn = await within(table).findByText('Filter Type');
const groupKeyColumn = await within(table).findByText('Group Key');
const clauseColumn = await within(table).findByText('Clause');
- const modifiedColumn = await within(table).findByText('Modified');
+ const modifiedColumn = await within(table).findByText('Last modified');
const actionsColumn = await within(table).findByText('Actions');
expect(nameColumn).toBeInTheDocument();
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
index 3c1e3b8aae..bef42284d0 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/index.tsx
@@ -33,7 +33,9 @@ import rison from 'rison';
import { useListViewResource } from 'src/views/CRUD/hooks';
import RowLevelSecurityModal from 'src/features/rls/RowLevelSecurityModal';
import { RLSObject } from 'src/features/rls/types';
-import { createErrorHandler } from 'src/views/CRUD/utils';
+import { createErrorHandler, createFetchRelated } from 'src/views/CRUD/utils';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { QueryObjectColumns } from 'src/views/CRUD/types';
const Actions = styled.div`
color: ${({ theme }) => theme.colors.grayscale.base};
@@ -43,7 +45,7 @@ interface RLSProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
user: {
- userId?: string | number;
+ userId: string | number;
firstName: string;
lastName: string;
};
@@ -146,10 +148,13 @@ function RowLevelSecurityList(props: RLSProps) {
{
Cell: ({
row: {
- original: { changed_on_delta_humanized: changedOn },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => <span className="no-wrap">{changedOn}</span>,
- Header: t('Modified'),
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+ Header: t('Last modified'),
accessor: 'changed_on_delta_humanized',
size: 'xl',
},
@@ -218,6 +223,10 @@ function RowLevelSecurityList(props: RLSProps) {
hidden: !canEdit && !canWrite && !canExport,
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[
user.userId,
@@ -270,6 +279,26 @@ function RowLevelSecurityList(props: RLSProps) {
input: 'search',
operator: FilterOperator.startsWith,
},
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
+ input: 'select',
+ operator: FilterOperator.relationOneMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'rowlevelsecurity',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
+ },
],
[user],
);
diff --git a/superset-frontend/src/pages/SavedQueryList/index.tsx b/superset-frontend/src/pages/SavedQueryList/index.tsx
index 3ee62c2ce6..d48ffef8c9 100644
--- a/superset-frontend/src/pages/SavedQueryList/index.tsx
+++ b/superset-frontend/src/pages/SavedQueryList/index.tsx
@@ -18,20 +18,19 @@
*/
import {
- isFeatureEnabled,
FeatureFlag,
+ isFeatureEnabled,
styled,
SupersetClient,
t,
} from '@superset-ui/core';
-import React, { useState, useMemo, useCallback } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import rison from 'rison';
-import moment from 'moment';
import {
- createFetchRelated,
- createFetchDistinct,
createErrorHandler,
+ createFetchDistinct,
+ createFetchRelated,
} from 'src/views/CRUD/utils';
import { useSelector } from 'react-redux';
import Popover from 'src/components/Popover';
@@ -39,11 +38,11 @@ import withToasts from 'src/components/MessageToasts/withToasts';
import { useListViewResource } from 'src/views/CRUD/hooks';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import handleResourceExport from 'src/utils/export';
-import SubMenu, { SubMenuProps, ButtonProps } from 'src/features/home/SubMenu';
+import SubMenu, { ButtonProps, SubMenuProps } from 'src/features/home/SubMenu';
import ListView, {
- ListViewProps,
- Filters,
FilterOperator,
+ Filters,
+ ListViewProps,
} from 'src/components/ListView';
import Loading from 'src/components/Loading';
import DeleteModal from 'src/components/DeleteModal';
@@ -51,15 +50,14 @@ import ActionsBar, { ActionProps } from 'src/components/ListView/ActionsBar';
import { TagsList } from 'src/components/Tags';
import { Tooltip } from 'src/components/Tooltip';
import { commonMenuData } from 'src/features/home/commonMenuData';
-import { SavedQueryObject } from 'src/views/CRUD/types';
+import { QueryObjectColumns, SavedQueryObject } from 'src/views/CRUD/types';
import copyTextToClipboard from 'src/utils/copy';
import Tag from 'src/types/TagType';
import ImportModelsModal from 'src/components/ImportModal/index';
+import { ModifiedInfo } from 'src/components/AuditInfo';
+import { loadTags } from 'src/components/Tags/utils';
import Icons from 'src/components/Icons';
-import {
- BootstrapUser,
- UserWithPermissionsAndRoles,
-} from 'src/types/bootstrapTypes';
+import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import SavedQueryPreviewModal from 'src/features/queries/SavedQueryPreviewModal';
import { findPermission } from 'src/utils/findPermission';
@@ -80,7 +78,11 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
interface SavedQueryListProps {
addDangerToast: (msg: string) => void;
addSuccessToast: (msg: string) => void;
- user: BootstrapUser;
+ user: {
+ userId: string | number;
+ firstName: string;
+ lastName: string;
+ };
}
const StyledTableLabel = styled.div`
@@ -99,6 +101,7 @@ const StyledPopoverItem = styled.div`
function SavedQueryList({
addDangerToast,
addSuccessToast,
+ user,
}: SavedQueryListProps) {
const {
state: {
@@ -348,41 +351,6 @@ function SavedQueryList({
size: 'xl',
disableSortBy: true,
},
- {
- Cell: ({
- row: {
- original: { created_on: createdOn },
- },
- }: any) => {
- const date = new Date(createdOn);
- const utc = new Date(
- Date.UTC(
- date.getFullYear(),
- date.getMonth(),
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds(),
- date.getMilliseconds(),
- ),
- );
-
- return moment(utc).fromNow();
- },
- Header: t('Created on'),
- accessor: 'created_on',
- size: 'xl',
- },
- {
- Cell: ({
- row: {
- original: { changed_on_delta_humanized: changedOn },
- },
- }: any) => changedOn,
- Header: t('Modified'),
- accessor: 'changed_on_delta_humanized',
- size: 'xl',
- },
{
Cell: ({
row: {
@@ -397,6 +365,19 @@ function SavedQueryList({
disableSortBy: true,
hidden: !isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM),
},
+ {
+ Cell: ({
+ row: {
+ original: {
+ changed_by: changedBy,
+ changed_on_delta_humanized: changedOn,
+ },
+ },
+ }: any) => <ModifiedInfo user={changedBy} date={changedOn} />,
+ Header: t('Last modified'),
+ accessor: 'changed_on_delta_humanized',
+ size: 'xl',
+ },
{
Cell: ({ row: { original } }: any) => {
const handlePreview = () => {
@@ -452,12 +433,23 @@ function SavedQueryList({
id: 'actions',
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[canDelete, canEdit, canExport, copyQueryLink, handleSavedQueryPreview],
);
const filters: Filters = useMemo(
() => [
+ {
+ Header: t('Name'),
+ id: 'label',
+ key: 'search',
+ input: 'search',
+ operator: FilterOperator.allText,
+ },
{
Header: t('Database'),
key: 'database',
@@ -497,28 +489,42 @@ function SavedQueryList({
),
paginate: true,
},
-
+ ...((isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag
+ ? [
+ {
+ Header: t('Tag'),
+ id: 'tags',
+ key: 'tags',
+ input: 'select',
+ operator: FilterOperator.savedQueryTags,
+ fetchSelects: loadTags,
+ },
+ ]
+ : []) as Filters),
{
- Header: t('Search'),
- id: 'label',
- key: 'search',
- input: 'search',
- operator: FilterOperator.allText,
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
+ input: 'select',
+ operator: FilterOperator.relationOneMany,
+ unfilteredLabel: t('All'),
+ fetchSelects: createFetchRelated(
+ 'saved_query',
+ 'changed_by',
+ createErrorHandler(errMsg =>
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
+ ),
+ ),
+ user,
+ ),
+ paginate: true,
},
],
[addDangerToast],
);
- if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM) && canReadTag) {
- filters.push({
- Header: t('Tags'),
- id: 'tags',
- key: 'tags',
- input: 'search',
- operator: FilterOperator.savedQueryTags,
- });
- }
-
return (
<>
<SubMenu {...menuData} />
diff --git a/superset-frontend/src/pages/Tags/index.tsx b/superset-frontend/src/pages/Tags/index.tsx
index a66d7c7b61..d395ce7cde 100644
--- a/superset-frontend/src/pages/Tags/index.tsx
+++ b/superset-frontend/src/pages/Tags/index.tsx
@@ -19,9 +19,9 @@
import React, { useMemo, useState } from 'react';
import { isFeatureEnabled, FeatureFlag, t } from '@superset-ui/core';
import {
- createFetchRelated,
- createErrorHandler,
Actions,
+ createErrorHandler,
+ createFetchRelated,
} from 'src/views/CRUD/utils';
import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
@@ -35,13 +35,13 @@ import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers';
import withToasts from 'src/components/MessageToasts/withToasts';
import Icons from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';
-import FacePile from 'src/components/FacePile';
import { Link } from 'react-router-dom';
import { deleteTags } from 'src/features/tags/tags';
import { Tag as AntdTag } from 'antd';
-import { Tag } from 'src/views/CRUD/types';
+import { QueryObjectColumns, Tag } from 'src/views/CRUD/types';
import TagModal from 'src/features/tags/TagModal';
import FaveStar from 'src/components/FaveStar';
+import { ModifiedInfo } from 'src/components/AuditInfo';
const PAGE_SIZE = 25;
@@ -56,11 +56,8 @@ interface TagListProps {
}
function TagList(props: TagListProps) {
- const {
- addDangerToast,
- addSuccessToast,
- user: { userId },
- } = props;
+ const { addDangerToast, addSuccessToast, user } = props;
+ const { userId } = user;
const {
state: {
@@ -162,24 +159,16 @@ function TagList(props: TagListProps) {
{
Cell: ({
row: {
- original: { changed_on_delta_humanized: changedOn },
+ original: {
+ changed_on_delta_humanized: changedOn,
+ changed_by: changedBy,
+ },
},
- }: any) => <span className="no-wrap">{changedOn}</span>,
- Header: t('Modified'),
+ }: any) => <ModifiedInfo date={changedOn} user={changedBy} />,
+ Header: t('Last modified'),
accessor: 'changed_on_delta_humanized',
size: 'xl',
},
- {
- Cell: ({
- row: {
- original: { created_by: createdBy },
- },
- }: any) => (createdBy ? <FacePile users={[createdBy]} /> : ''),
- Header: t('Created by'),
- accessor: 'created_by',
- disableSortBy: true,
- size: 'xl',
- },
{
Cell: ({ row: { original } }: any) => {
const handleEdit = () => handleTagEdit(original);
@@ -238,6 +227,10 @@ function TagList(props: TagListProps) {
hidden: !canDelete,
disableSortBy: true,
},
+ {
+ accessor: QueryObjectColumns.changed_by,
+ hidden: true,
+ },
],
[userId, canDelete, refreshData, addSuccessToast, addDangerToast],
);
@@ -245,32 +238,31 @@ function TagList(props: TagListProps) {
const filters: Filters = useMemo(() => {
const filters_list = [
{
- Header: t('Created by'),
- id: 'created_by',
+ Header: t('Name'),
+ id: 'name',
+ input: 'search',
+ operator: FilterOperator.contains,
+ },
+ {
+ Header: t('Modified by'),
+ key: 'changed_by',
+ id: 'changed_by',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'tag',
- 'created_by',
+ 'changed_by',
createErrorHandler(errMsg =>
- addDangerToast(
- t(
- 'An error occurred while fetching tag created by values: %s',
- errMsg,
- ),
+ t(
+ 'An error occurred while fetching dataset datasource values: %s',
+ errMsg,
),
),
- props.user,
+ user,
),
paginate: true,
},
- {
- Header: t('Search'),
- id: 'name',
- input: 'search',
- operator: FilterOperator.contains,
- },
] as Filters;
return filters_list;
}, [addDangerToast, props.user]);
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/utils/getOwnerName.test.ts
similarity index 73%
copy from superset-frontend/src/features/cssTemplates/types.ts
copy to superset-frontend/src/utils/getOwnerName.test.ts
index 1bb5b2e659..a4a25e57b2 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/utils/getOwnerName.test.ts
@@ -16,17 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-type CreatedByUser = {
- id: number;
- first_name: string;
- last_name: string;
-};
+import getOwnerName from './getOwnerName';
-export type TemplateObject = {
- id?: number;
- changed_on_delta_humanized?: string;
- created_on?: string;
- created_by?: CreatedByUser;
- css?: string;
- template_name: string;
-};
+test('render owner name correctly', () => {
+ expect(getOwnerName({ id: 1, first_name: 'Foo', last_name: 'Bar' })).toEqual(
+ 'Foo Bar',
+ );
+});
+
+test('return empty string for undefined owner', () => {
+ expect(getOwnerName(undefined)).toEqual('');
+});
diff --git a/superset-frontend/src/features/cssTemplates/types.ts b/superset-frontend/src/utils/getOwnerName.ts
similarity index 75%
copy from superset-frontend/src/features/cssTemplates/types.ts
copy to superset-frontend/src/utils/getOwnerName.ts
index 1bb5b2e659..2534c45f2c 100644
--- a/superset-frontend/src/features/cssTemplates/types.ts
+++ b/superset-frontend/src/utils/getOwnerName.ts
@@ -16,17 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-type CreatedByUser = {
- id: number;
- first_name: string;
- last_name: string;
-};
+import Owner from 'src/types/Owner';
-export type TemplateObject = {
- id?: number;
- changed_on_delta_humanized?: string;
- created_on?: string;
- created_by?: CreatedByUser;
- css?: string;
- template_name: string;
-};
+export default function getOwnerName(owner?: Owner): string {
+ if (!owner) {
+ return '';
+ }
+ return `${owner.first_name} ${owner.last_name}`;
+}
diff --git a/superset-frontend/src/views/CRUD/types.ts b/superset-frontend/src/views/CRUD/types.ts
index 5a53b57696..2fff111b47 100644
--- a/superset-frontend/src/views/CRUD/types.ts
+++ b/superset-frontend/src/views/CRUD/types.ts
@@ -112,6 +112,7 @@ export interface QueryObject {
export enum QueryObjectColumns {
id = 'id',
changed_on = 'changed_on',
+ changed_by = 'changed_by',
database = 'database',
database_name = 'database.database_name',
schema = 'schema',
@@ -138,17 +139,11 @@ export type ImportResourceName =
export interface Tag {
changed_on_delta_humanized: string;
- changed_by: {
- first_name: string;
- last_name: string;
- };
+ changed_by: Owner;
created_on_delta_humanized: string;
name: string;
id: number;
- created_by: {
- first_name: string;
- last_name: string;
- };
+ created_by: Owner;
description: string;
type: string;
}
diff --git a/superset/annotation_layers/api.py b/superset/annotation_layers/api.py
index 886c151a68..5606e944ef 100644
--- a/superset/annotation_layers/api.py
+++ b/superset/annotation_layers/api.py
@@ -99,7 +99,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
]
search_filters = {"name": [AnnotationLayerAllTextFilter]}
- allowed_rel_fields = {"created_by"}
+ allowed_rel_fields = {"created_by", "changed_by"}
apispec_parameter_schemas = {
"get_delete_ids_schema": get_delete_ids_schema,
diff --git a/superset/charts/api.py b/superset/charts/api.py
index ea705f0aa9..191f09c66e 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -273,7 +273,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
}
- allowed_rel_fields = {"owners", "created_by"}
+ allowed_rel_fields = {"owners", "created_by", "changed_by"}
@expose("/", methods=("POST",))
@protect()
diff --git a/superset/css_templates/api.py b/superset/css_templates/api.py
index 25f4d50f30..ac222da66f 100644
--- a/superset/css_templates/api.py
+++ b/superset/css_templates/api.py
@@ -54,6 +54,10 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
allow_browser_login = True
show_columns = [
+ "changed_on_delta_humanized",
+ "changed_by.first_name",
+ "changed_by.id",
+ "changed_by.last_name",
"created_by.first_name",
"created_by.id",
"created_by.last_name",
@@ -79,7 +83,7 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
order_columns = ["template_name"]
search_filters = {"template_name": [CssTemplateAllTextFilter]}
- allowed_rel_fields = {"created_by"}
+ allowed_rel_fields = {"created_by", "changed_by"}
apispec_parameter_schemas = {
"get_delete_ids_schema": get_delete_ids_schema,
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index be773b83c3..cf75a644fb 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -261,7 +261,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
"roles": RelatedFieldFilter("name", FilterRelatedRoles),
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
}
- allowed_rel_fields = {"owners", "roles", "created_by"}
+ allowed_rel_fields = {"owners", "roles", "created_by", "changed_by"}
openapi_spec_tag = "Dashboards"
""" Override the name set for this collection of endpoints """
diff --git a/superset/databases/api.py b/superset/databases/api.py
index df69d9ccd7..8de84a16af 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -111,6 +111,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | {
RouteMethod.EXPORT,
RouteMethod.IMPORT,
+ RouteMethod.RELATED,
"tables",
"table_metadata",
"table_extra_metadata",
@@ -162,6 +163,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
"backend",
"changed_on",
"changed_on_delta_humanized",
+ "changed_by.first_name",
+ "changed_by.last_name",
"created_by.first_name",
"created_by.last_name",
"database_name",
@@ -194,7 +197,17 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
edit_columns = add_columns
+ search_columns = [
+ "allow_file_upload",
+ "allow_dml",
+ "allow_run_async",
+ "created_by",
+ "changed_by",
+ "database_name",
+ "expose_in_sqllab",
+ ]
search_filters = {"allow_file_upload": [DatabaseUploadEnabledFilter]}
+ allowed_rel_fields = {"changed_by", "created_by"}
list_select_columns = list_columns + ["extra", "sqlalchemy_uri", "password"]
order_columns = [
diff --git a/superset/datasets/api.py b/superset/datasets/api.py
index e256ff99d6..bc4a42e58e 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -247,8 +247,17 @@ class DatasetRestApi(BaseSupersetModelRestApi):
"sql": [DatasetIsNullOrEmptyFilter],
"id": [DatasetCertifiedFilter],
}
- search_columns = ["id", "database", "owners", "schema", "sql", "table_name"]
- allowed_rel_fields = {"database", "owners"}
+ search_columns = [
+ "id",
+ "database",
+ "owners",
+ "schema",
+ "sql",
+ "table_name",
+ "created_by",
+ "changed_by",
+ ]
+ allowed_rel_fields = {"database", "owners", "created_by", "changed_by"}
allowed_distinct_fields = {"schema"}
apispec_parameter_schemas = {
diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py
index 25ac520e45..ce283dd6d6 100644
--- a/superset/queries/saved_queries/api.py
+++ b/superset/queries/saved_queries/api.py
@@ -82,7 +82,11 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
base_filters = [["id", SavedQueryFilter, lambda: []]]
show_columns = [
+ "changed_on",
"changed_on_delta_humanized",
+ "changed_by.first_name",
+ "changed_by.id",
+ "changed_by.last_name",
"created_by.first_name",
"created_by.id",
"created_by.last_name",
@@ -97,7 +101,11 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
"template_parameters",
]
list_columns = [
+ "changed_on",
"changed_on_delta_humanized",
+ "changed_by.first_name",
+ "changed_by.id",
+ "changed_by.last_name",
"created_on",
"created_by.first_name",
"created_by.id",
@@ -140,7 +148,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
"last_run_delta_humanized",
]
- search_columns = ["id", "database", "label", "schema", "created_by"]
+ search_columns = ["id", "database", "label", "schema", "created_by", "changed_by"]
if is_feature_enabled("TAGGING_SYSTEM"):
search_columns += ["tags"]
search_filters = {
@@ -161,7 +169,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
"database": "database_name",
}
base_related_field_filters = {"database": [["id", DatabaseFilter, lambda: []]]}
- allowed_rel_fields = {"database"}
+ allowed_rel_fields = {"database", "changed_by", "created_by"}
allowed_distinct_fields = {"schema"}
def pre_add(self, item: SavedQuery) -> None:
diff --git a/superset/reports/api.py b/superset/reports/api.py
index ab4f80ae15..8238213fef 100644
--- a/superset/reports/api.py
+++ b/superset/reports/api.py
@@ -198,6 +198,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
search_columns = [
"name",
"active",
+ "changed_by",
"created_by",
"owners",
"type",
@@ -207,7 +208,14 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
"chart_id",
]
search_filters = {"name": [ReportScheduleAllTextFilter]}
- allowed_rel_fields = {"owners", "chart", "dashboard", "database", "created_by"}
+ allowed_rel_fields = {
+ "owners",
+ "chart",
+ "dashboard",
+ "database",
+ "created_by",
+ "changed_by",
+ }
base_related_field_filters = {
"chart": [["id", ChartFilter, lambda: []]],
diff --git a/superset/row_level_security/api.py b/superset/row_level_security/api.py
index e7347f5280..fc505e724f 100644
--- a/superset/row_level_security/api.py
+++ b/superset/row_level_security/api.py
@@ -77,6 +77,9 @@ class RLSRestApi(BaseSupersetModelRestApi):
"roles.name",
"clause",
"changed_on_delta_humanized",
+ "changed_by.first_name",
+ "changed_by.last_name",
+ "changed_by.id",
"group_key",
]
order_columns = [
@@ -115,6 +118,8 @@ class RLSRestApi(BaseSupersetModelRestApi):
"roles",
"group_key",
"clause",
+ "created_by",
+ "changed_by",
)
edit_columns = add_columns
@@ -123,7 +128,7 @@ class RLSRestApi(BaseSupersetModelRestApi):
add_model_schema = RLSPostSchema()
edit_model_schema = RLSPutSchema()
- allowed_rel_fields = {"tables", "roles"}
+ allowed_rel_fields = {"tables", "roles", "created_by", "changed_by"}
base_related_field_filters = {
"tables": [["id", DatasourceFilter, lambda: []]],
"roles": [["id", BaseFilterRelatedRoles, lambda: []]],
diff --git a/superset/row_level_security/schemas.py b/superset/row_level_security/schemas.py
index 6c8249b875..f02767ec13 100644
--- a/superset/row_level_security/schemas.py
+++ b/superset/row_level_security/schemas.py
@@ -20,6 +20,7 @@ from marshmallow import fields, Schema
from marshmallow.validate import Length, OneOf
from superset.connectors.sqla.models import RowLevelSecurityFilter
+from superset.dashboards.schemas import UserSchema
from superset.utils.core import RowLevelSecurityFilterType
id_description = "Unique if of rls filter"
@@ -81,6 +82,7 @@ class RLSListSchema(Schema):
)
group_key = fields.String(metadata={"description": "group_key_description"})
description = fields.String(metadata={"description": "description_description"})
+ changed_by = fields.Nested(UserSchema(exclude=["username"]))
class RLSShowSchema(Schema):
diff --git a/superset/tags/api.py b/superset/tags/api.py
index a3c95a5814..c0df921e3e 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -117,7 +117,7 @@ class TagRestApi(BaseSupersetModelRestApi):
related_field_filters = {
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
}
- allowed_rel_fields = {"created_by"}
+ allowed_rel_fields = {"created_by", "changed_by"}
add_model_schema = TagPostSchema()
edit_model_schema = TagPutSchema()
diff --git a/tests/integration_tests/css_templates/api_tests.py b/tests/integration_tests/css_templates/api_tests.py
index b28cca955c..ceb46f553b 100644
--- a/tests/integration_tests/css_templates/api_tests.py
+++ b/tests/integration_tests/css_templates/api_tests.py
@@ -19,6 +19,8 @@
import json
import pytest
import prison
+from datetime import datetime
+from freezegun import freeze_time
from sqlalchemy.sql import func
import tests.integration_tests.test_app
@@ -189,20 +191,27 @@ class TestCssTemplateApi(SupersetTestCase):
"""
CSS Template API: Test get CSS Template
"""
- css_template = (
- db.session.query(CssTemplate)
- .filter(CssTemplate.template_name == "template_name1")
- .one_or_none()
- )
- self.login(username="admin")
- uri = f"api/v1/css_template/{css_template.id}"
- rv = self.get_assert_metric(uri, "get")
+ with freeze_time(datetime.now()):
+ css_template = (
+ db.session.query(CssTemplate)
+ .filter(CssTemplate.template_name == "template_name1")
+ .one_or_none()
+ )
+ self.login(username="admin")
+ uri = f"api/v1/css_template/{css_template.id}"
+ rv = self.get_assert_metric(uri, "get")
assert rv.status_code == 200
expected_result = {
"id": css_template.id,
"template_name": "template_name1",
"css": "css1",
+ "changed_by": {
+ "first_name": css_template.created_by.first_name,
+ "id": css_template.created_by.id,
+ "last_name": css_template.created_by.last_name,
+ },
+ "changed_on_delta_humanized": "now",
"created_by": {
"first_name": css_template.created_by.first_name,
"id": css_template.created_by.id,
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 496012390e..0bc1f245a1 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -197,6 +197,7 @@ class TestDatabaseApi(SupersetTestCase):
"allows_subquery",
"allows_virtual_table_explore",
"backend",
+ "changed_by",
"changed_on",
"changed_on_delta_humanized",
"created_by",
diff --git a/tests/integration_tests/queries/saved_queries/api_tests.py b/tests/integration_tests/queries/saved_queries/api_tests.py
index 09929e4d23..c51c0dcbf0 100644
--- a/tests/integration_tests/queries/saved_queries/api_tests.py
+++ b/tests/integration_tests/queries/saved_queries/api_tests.py
@@ -17,6 +17,7 @@
# isort:skip_file
"""Unit tests for Superset"""
import json
+from datetime import datetime
from io import BytesIO
from typing import Optional
from zipfile import is_zipfile, ZipFile
@@ -24,6 +25,7 @@ from zipfile import is_zipfile, ZipFile
import yaml
import pytest
import prison
+from freezegun import freeze_time
from sqlalchemy.sql import func, and_
import tests.integration_tests.test_app
@@ -507,14 +509,17 @@ class TestSavedQueryApi(SupersetTestCase):
db.session.query(SavedQuery).filter(SavedQuery.label == "label1").all()[0]
)
self.login(username="admin")
- uri = f"api/v1/saved_query/{saved_query.id}"
- rv = self.get_assert_metric(uri, "get")
- assert rv.status_code == 200
+ with freeze_time(datetime.now()):
+ uri = f"api/v1/saved_query/{saved_query.id}"
+ rv = self.get_assert_metric(uri, "get")
+ assert rv.status_code == 200
expected_result = {
"id": saved_query.id,
"database": {"id": saved_query.database.id, "database_name": "examples"},
"description": "cool description",
+ "changed_by": None,
+ "changed_on_delta_humanized": "now",
"created_by": {
"first_name": saved_query.created_by.first_name,
"id": saved_query.created_by.id,
@@ -527,9 +532,8 @@ class TestSavedQueryApi(SupersetTestCase):
"template_parameters": None,
}
data = json.loads(rv.data.decode("utf-8"))
- self.assertIn("changed_on_delta_humanized", data["result"])
for key, value in data["result"].items():
- if key not in ("changed_on_delta_humanized",):
+ if key != "changed_on":
assert value == expected_result[key]
def test_get_saved_query_not_found(self):
(superset) 02/15: chore(deps): bump pillow deps (#25931)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit e382d0dd2878cfdbee6dd24b2eb94aab0f12aeda
Author: Gnought <16...@users.noreply.github.com>
AuthorDate: Wed Nov 29 21:59:27 2023 +0800
chore(deps): bump pillow deps (#25931)
(cherry picked from commit a27a0df1a4933fbe8bced50e80bcb3856cd5db2a)
---
setup.py | 2 +-
superset/utils/screenshots.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index 29df567e04..735154720e 100644
--- a/setup.py
+++ b/setup.py
@@ -201,7 +201,7 @@ setup(
"thrift>=0.14.1, <1.0.0",
],
"teradata": ["teradatasql>=16.20.0.23"],
- "thumbnails": ["Pillow>=9.5.0, <10.0.0"],
+ "thumbnails": ["Pillow>=10.0.1, <11"],
"vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"],
"netezza": ["nzalchemy>=11.0.2"],
"starrocks": ["starrocks>=1.0.0"],
diff --git a/superset/utils/screenshots.py b/superset/utils/screenshots.py
index 8609d65038..bf6ed0f9e8 100644
--- a/superset/utils/screenshots.py
+++ b/superset/utils/screenshots.py
@@ -201,7 +201,7 @@ class BaseScreenshot:
logger.debug("Cropping to: %s*%s", str(img.size[0]), str(desired_width))
img = img.crop((0, 0, img.size[0], desired_width))
logger.debug("Resizing to %s", str(thumb_size))
- img = img.resize(thumb_size, Image.ANTIALIAS)
+ img = img.resize(thumb_size, Image.Resampling.LANCZOS)
new_img = BytesIO()
if output != "png":
img = img.convert("RGB")
(superset) 03/15: fix: remove default secret key from helm (#23916)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 93319696ded850302f3c2093cd6a9408a325c042
Author: Daniel Vaz Gaspar <da...@gmail.com>
AuthorDate: Wed Nov 29 15:48:39 2023 +0000
fix: remove default secret key from helm (#23916)
(cherry picked from commit 6a5a765689ef2d906784c055fe6007d1799eb33d)
---
helm/superset/Chart.yaml | 2 +-
helm/superset/README.md | 8 +++++++-
helm/superset/README.md.gotmpl | 6 ++++++
helm/superset/templates/_helpers.tpl | 1 -
helm/superset/values.yaml | 2 ++
5 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index 36d40645df..1f7d974c2b 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
-version: 0.10.15
+version: 0.11.0
dependencies:
- name: postgresql
version: 12.1.6
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 1c9bab285e..058ddd615f 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
-![Version: 0.10.15](https://img.shields.io/badge/Version-0.10.15-informational?style=flat-square)
+![Version: 0.11.0](https://img.shields.io/badge/Version-0.11.0-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -40,6 +40,12 @@ helm repo add superset http://apache.github.io/superset/
helm install my-superset superset/superset
```
+Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
+securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
+It should be a long random bytes or str.
+
+On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
+
## Requirements
| Repository | Name | Version |
diff --git a/helm/superset/README.md.gotmpl b/helm/superset/README.md.gotmpl
index c17a7e31a7..facb955e31 100644
--- a/helm/superset/README.md.gotmpl
+++ b/helm/superset/README.md.gotmpl
@@ -39,6 +39,12 @@ helm repo add superset http://apache.github.io/superset/
helm install my-superset superset/superset
```
+Make sure you set your own `SECRET_KEY` to something unique and secret. This secret key is used by Flask for
+securely signing the session cookie and will be used to encrypt sensitive data on Superset's metadata database.
+It should be a long random bytes or str.
+
+On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverrides.secrets`
+
{{ template "chart.requirementsSection" . }}
{{ template "chart.valuesSection" . }}
diff --git a/helm/superset/templates/_helpers.tpl b/helm/superset/templates/_helpers.tpl
index 40b769054e..26d68ce603 100644
--- a/helm/superset/templates/_helpers.tpl
+++ b/helm/superset/templates/_helpers.tpl
@@ -82,7 +82,6 @@ DATA_CACHE_CONFIG = CACHE_CONFIG
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{env('DB_USER')}:{env('DB_PASS')}@{env('DB_HOST')}:{env('DB_PORT')}/{env('DB_NAME')}"
SQLALCHEMY_TRACK_MODIFICATIONS = True
-SECRET_KEY = env('SECRET_KEY', 'thisISaSECRET_1234')
class CeleryConfig:
imports = ("superset.sql_lab", )
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index 67f685bf18..a5b70559d1 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -93,6 +93,8 @@ extraSecretEnv: {}
# # Google API Keys: https://console.cloud.google.com/apis/credentials
# GOOGLE_KEY: ...
# GOOGLE_SECRET: ...
+ # # Generate your own secret key for encryption. Use openssl rand -base64 42 to generate a good key
+ # SUPERSET_SECRET_KEY: 'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
# -- Extra files to mount on `/app/pythonpath`
extraConfigs: {}
(superset) 11/15: fix(sqllab): table preview has gone (#25977)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit d0aa34bf7969810b40762266ef75f2d95ce78cd1
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Fri Dec 1 13:41:31 2023 -0800
fix(sqllab): table preview has gone (#25977)
(cherry picked from commit cdbbd83705d32e12fbc0a0628e78abb1e98a9404)
---
.../src/components/DatabaseSelector/DatabaseSelector.test.tsx | 9 ++++++++-
superset-frontend/src/components/DatabaseSelector/index.tsx | 10 +++++++---
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
index 7635361d89..874d22ea6b 100644
--- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
@@ -290,7 +290,13 @@ test('Sends the correct db when changing the database', async () => {
test('Sends the correct schema when changing the schema', async () => {
const props = createProps();
- render(<DatabaseSelector {...props} />, { useRedux: true, store });
+ const { rerender } = render(<DatabaseSelector {...props} db={null} />, {
+ useRedux: true,
+ store,
+ });
+ await waitFor(() => expect(fetchMock.calls(databaseApiRoute).length).toBe(1));
+ rerender(<DatabaseSelector {...props} />);
+ expect(props.onSchemaChange).toBeCalledTimes(0);
const select = screen.getByRole('combobox', {
name: 'Select schema or type to search schemas',
});
@@ -301,4 +307,5 @@ test('Sends the correct schema when changing the schema', async () => {
await waitFor(() =>
expect(props.onSchemaChange).toHaveBeenCalledWith('information_schema'),
);
+ expect(props.onSchemaChange).toBeCalledTimes(1);
});
diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx
index d17489a9c2..7b4afd9af0 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { ReactNode, useState, useMemo, useEffect } from 'react';
+import React, { ReactNode, useState, useMemo, useEffect, useRef } from 'react';
import { styled, SupersetClient, t } from '@superset-ui/core';
import rison from 'rison';
import { AsyncSelect, Select } from 'src/components';
@@ -133,6 +133,8 @@ export default function DatabaseSelector({
const [currentSchema, setCurrentSchema] = useState<SchemaOption | undefined>(
schema ? { label: schema, value: schema, title: schema } : undefined,
);
+ const schemaRef = useRef(schema);
+ schemaRef.current = schema;
const { addSuccessToast } = useToasts();
const loadDatabases = useMemo(
@@ -215,7 +217,7 @@ export default function DatabaseSelector({
function changeSchema(schema: SchemaOption | undefined) {
setCurrentSchema(schema);
- if (onSchemaChange) {
+ if (onSchemaChange && schema?.value !== schemaRef.current) {
onSchemaChange(schema?.value);
}
}
@@ -229,7 +231,9 @@ export default function DatabaseSelector({
onSuccess: (schemas, isFetched) => {
if (schemas.length === 1) {
changeSchema(schemas[0]);
- } else if (!schemas.find(schemaOption => schema === schemaOption.value)) {
+ } else if (
+ !schemas.find(schemaOption => schemaRef.current === schemaOption.value)
+ ) {
changeSchema(undefined);
}
(superset) 05/15: chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit fad4616d2f8a4a66769f5dbb11ddf93f85f71166
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Thu Nov 30 12:27:40 2023 +0100
chore: Rename SET_ACTIVE_TABS action, add a new action (#26147)
(cherry picked from commit d00c17dde2b80c2deb64ae8f8585cf5c225f3275)
---
.../src/dashboard/actions/dashboardState.js | 9 +++++++--
.../DashboardBuilder/DashboardBuilder.test.tsx | 6 +++---
.../dashboard/components/gridComponents/Tabs.jsx | 8 ++++----
.../dashboard/containers/DashboardComponent.jsx | 4 ++--
.../src/dashboard/reducers/dashboardState.js | 9 ++++++++-
.../src/dashboard/reducers/dashboardState.test.ts | 22 +++++++++++++++++-----
6 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index dcf1020e6d..b461275d8c 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -611,9 +611,14 @@ export function setDirectPathToChild(path) {
return { type: SET_DIRECT_PATH, path };
}
+export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB';
+export function setActiveTab(tabId, prevTabId) {
+ return { type: SET_ACTIVE_TAB, tabId, prevTabId };
+}
+
export const SET_ACTIVE_TABS = 'SET_ACTIVE_TABS';
-export function setActiveTabs(tabId, prevTabId) {
- return { type: SET_ACTIVE_TABS, tabId, prevTabId };
+export function setActiveTabs(activeTabs) {
+ return { type: SET_ACTIVE_TABS, activeTabs };
}
export const SET_FOCUSED_FILTER_FIELD = 'SET_FOCUSED_FILTER_FIELD';
diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 7c3dd23392..02a3a49971 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -25,7 +25,7 @@ import DashboardBuilder from 'src/dashboard/components/DashboardBuilder/Dashboar
import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
import {
fetchFaveStar,
- setActiveTabs,
+ setActiveTab,
setDirectPathToChild,
} from 'src/dashboard/actions/dashboardState';
import {
@@ -41,7 +41,7 @@ fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
jest.mock('src/dashboard/actions/dashboardState', () => ({
...jest.requireActual('src/dashboard/actions/dashboardState'),
fetchFaveStar: jest.fn(),
- setActiveTabs: jest.fn(),
+ setActiveTab: jest.fn(),
setDirectPathToChild: jest.fn(),
}));
jest.mock('src/components/ResizableSidebar/useStoredSidebarWidth');
@@ -90,7 +90,7 @@ describe('DashboardBuilder', () => {
favStarStub = (fetchFaveStar as jest.Mock).mockReturnValue({
type: 'mock-action',
});
- activeTabsStub = (setActiveTabs as jest.Mock).mockReturnValue({
+ activeTabsStub = (setActiveTab as jest.Mock).mockReturnValue({
type: 'mock-action',
});
(useStoredSidebarWidth as jest.Mock).mockImplementation(() => [
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
index 7d9a46b75d..67f4b3c598 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.jsx
@@ -51,7 +51,7 @@ const propTypes = {
// actions (from DashboardComponent.jsx)
logEvent: PropTypes.func.isRequired,
- setActiveTabs: PropTypes.func,
+ setActiveTab: PropTypes.func,
// grid related
availableColumnCount: PropTypes.number,
@@ -75,7 +75,7 @@ const defaultProps = {
columnWidth: 0,
activeTabs: [],
directPathToChild: [],
- setActiveTabs() {},
+ setActiveTab() {},
onResizeStart() {},
onResize() {},
onResizeStop() {},
@@ -125,12 +125,12 @@ export class Tabs extends React.PureComponent {
}
componentDidMount() {
- this.props.setActiveTabs(this.state.activeKey);
+ this.props.setActiveTab(this.state.activeKey);
}
componentDidUpdate(prevProps, prevState) {
if (prevState.activeKey !== this.state.activeKey) {
- this.props.setActiveTabs(this.state.activeKey, prevState.activeKey);
+ this.props.setActiveTab(this.state.activeKey, prevState.activeKey);
}
}
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index 08b7ed9f82..68478adb07 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -35,7 +35,7 @@ import {
} from 'src/dashboard/actions/dashboardLayout';
import {
setDirectPathToChild,
- setActiveTabs,
+ setActiveTab,
setFullSizeChartId,
} from 'src/dashboard/actions/dashboardState';
@@ -109,7 +109,7 @@ function mapDispatchToProps(dispatch) {
handleComponentDrop,
setDirectPathToChild,
setFullSizeChartId,
- setActiveTabs,
+ setActiveTab,
logEvent,
},
dispatch,
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.js b/superset-frontend/src/dashboard/reducers/dashboardState.js
index 5d81cd8ac1..015cb9822c 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.js
@@ -37,6 +37,7 @@ import {
SET_DIRECT_PATH,
SET_FOCUSED_FILTER_FIELD,
UNSET_FOCUSED_FILTER_FIELD,
+ SET_ACTIVE_TAB,
SET_ACTIVE_TABS,
SET_FULL_SIZE_CHART_ID,
ON_FILTERS_REFRESH,
@@ -179,7 +180,7 @@ export default function dashboardStateReducer(state = {}, action) {
directPathLastUpdated: Date.now(),
};
},
- [SET_ACTIVE_TABS]() {
+ [SET_ACTIVE_TAB]() {
const newActiveTabs = new Set(state.activeTabs);
newActiveTabs.delete(action.prevTabId);
newActiveTabs.add(action.tabId);
@@ -188,6 +189,12 @@ export default function dashboardStateReducer(state = {}, action) {
activeTabs: Array.from(newActiveTabs),
};
},
+ [SET_ACTIVE_TABS]() {
+ return {
+ ...state,
+ activeTabs: action.activeTabs,
+ };
+ },
[SET_OVERRIDE_CONFIRM]() {
return {
...state,
diff --git a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
index 274b26733c..3a8adc6cbb 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
+++ b/superset-frontend/src/dashboard/reducers/dashboardState.test.ts
@@ -18,21 +18,33 @@
*/
import dashboardStateReducer from './dashboardState';
-import { setActiveTabs } from '../actions/dashboardState';
+import { setActiveTab, setActiveTabs } from '../actions/dashboardState';
describe('DashboardState reducer', () => {
- it('SET_ACTIVE_TABS', () => {
+ it('SET_ACTIVE_TAB', () => {
expect(
- dashboardStateReducer({ activeTabs: [] }, setActiveTabs('tab1')),
+ dashboardStateReducer({ activeTabs: [] }, setActiveTab('tab1')),
).toEqual({ activeTabs: ['tab1'] });
expect(
- dashboardStateReducer({ activeTabs: ['tab1'] }, setActiveTabs('tab1')),
+ dashboardStateReducer({ activeTabs: ['tab1'] }, setActiveTab('tab1')),
).toEqual({ activeTabs: ['tab1'] });
expect(
dashboardStateReducer(
{ activeTabs: ['tab1'] },
- setActiveTabs('tab2', 'tab1'),
+ setActiveTab('tab2', 'tab1'),
),
).toEqual({ activeTabs: ['tab2'] });
});
+
+ it('SET_ACTIVE_TABS', () => {
+ expect(
+ dashboardStateReducer({ activeTabs: [] }, setActiveTabs(['tab1'])),
+ ).toEqual({ activeTabs: ['tab1'] });
+ expect(
+ dashboardStateReducer(
+ { activeTabs: ['tab1', 'tab2'] },
+ setActiveTabs(['tab3', 'tab4']),
+ ),
+ ).toEqual({ activeTabs: ['tab3', 'tab4'] });
+ });
});
(superset) 12/15: fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 880086c75090c6c1c65617b16991d14767a646be
Author: Ross Mabbett <92...@users.noreply.github.com>
AuthorDate: Fri Dec 1 19:32:08 2023 -0500
fix(Alerts/Reports): allow use of ";" separator in slack recipient entry (#25894)
Co-authored-by: John Bodley <45...@users.noreply.github.com>
(cherry picked from commit b7a9c220e14c6e85840568da4bf87be84b246749)
---
superset/reports/notifications/slack.py | 11 +++-
.../reports/notifications/slack_tests.py | 58 ++++++++++++++++++++++
2 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/superset/reports/notifications/slack.py b/superset/reports/notifications/slack.py
index a769622b57..fbae398bc5 100644
--- a/superset/reports/notifications/slack.py
+++ b/superset/reports/notifications/slack.py
@@ -44,6 +44,7 @@ from superset.reports.notifications.exceptions import (
NotificationParamException,
NotificationUnprocessableException,
)
+from superset.utils.core import get_email_address_list
from superset.utils.decorators import statsd_gauge
logger = logging.getLogger(__name__)
@@ -60,7 +61,15 @@ class SlackNotification(BaseNotification): # pylint: disable=too-few-public-met
type = ReportRecipientType.SLACK
def _get_channel(self) -> str:
- return json.loads(self._recipient.recipient_config_json)["target"]
+ """
+ Get the recipient's channel(s).
+ Note Slack SDK uses "channel" to refer to one or more
+ channels. Multiple channels are demarcated by a comma.
+ :returns: The comma separated list of channel(s)
+ """
+ recipient_str = json.loads(self._recipient.recipient_config_json)["target"]
+
+ return ",".join(get_email_address_list(recipient_str))
def _message_template(self, table: str = "") -> str:
return __(
diff --git a/tests/unit_tests/reports/notifications/slack_tests.py b/tests/unit_tests/reports/notifications/slack_tests.py
new file mode 100644
index 0000000000..0a5e9baa46
--- /dev/null
+++ b/tests/unit_tests/reports/notifications/slack_tests.py
@@ -0,0 +1,58 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import pandas as pd
+
+
+def test_get_channel_with_multi_recipients() -> None:
+ """
+ Test the _get_channel function to ensure it will return a string
+ with recipients separated by commas without interstitial spacing
+ """
+ from superset.reports.models import ReportRecipients, ReportRecipientType
+ from superset.reports.notifications.base import NotificationContent
+ from superset.reports.notifications.slack import SlackNotification
+
+ content = NotificationContent(
+ name="test alert",
+ header_data={
+ "notification_format": "PNG",
+ "notification_type": "Alert",
+ "owners": [1],
+ "notification_source": None,
+ "chart_id": None,
+ "dashboard_id": None,
+ },
+ embedded_data=pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ "C": ["111", "222", '<a href="http://www.example.com">333</a>'],
+ }
+ ),
+ description='<p>This is <a href="#">a test</a> alert</p><br />',
+ )
+ slack_notification = SlackNotification(
+ recipient=ReportRecipients(
+ type=ReportRecipientType.SLACK,
+ recipient_config_json='{"target": "some_channel; second_channel, third_channel"}',
+ ),
+ content=content,
+ )
+
+ result = slack_notification._get_channel()
+
+ assert result == "some_channel,second_channel,third_channel"
(superset) 13/15: fix: Migration order due to cherry which went astray (#26160)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit aaa50c4b4aba30a95154dfb2cc46d6788b3a1ccc
Author: John Bodley <45...@users.noreply.github.com>
AuthorDate: Fri Dec 1 18:29:21 2023 -0800
fix: Migration order due to cherry which went astray (#26160)
(cherry picked from commit 8644b1a3192ffef3d20357f76cfa1feac20e4147)
---
superset/migrations/shared/utils.py | 8 ++--
...317970b4400c_added_time_secondary_column_to_.py | 34 +++++++++--------
...12-01_12-03_b7851ee5522f_replay_317970b4400c.py | 44 ++++++++++++++++++++++
3 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/superset/migrations/shared/utils.py b/superset/migrations/shared/utils.py
index 32e7dc1a39..2ae0dfeac1 100644
--- a/superset/migrations/shared/utils.py
+++ b/superset/migrations/shared/utils.py
@@ -43,11 +43,9 @@ def table_has_column(table: str, column: str) -> bool:
:param column: A column name
:returns: True iff the column exists in the table
"""
- config = op.get_context().config
- engine = engine_from_config(
- config.get_section(config.config_ini_section), prefix="sqlalchemy."
- )
- insp = reflection.Inspector.from_engine(engine)
+
+ insp = inspect(op.get_context().bind)
+
try:
return any(col["name"] == column for col in insp.get_columns(table))
except NoSuchTableError:
diff --git a/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py b/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
index 859a6fe590..4972a86911 100755
--- a/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
+++ b/superset/migrations/versions/2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_.py
@@ -32,7 +32,7 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from superset import db
-from superset.migrations.shared.utils import paginated_update
+from superset.migrations.shared.utils import paginated_update, table_has_column
Base = declarative_base()
@@ -45,23 +45,25 @@ class SqlaTable(Base):
def upgrade():
- op.add_column(
- "tables",
- sa.Column(
- "always_filter_main_dttm",
- sa.Boolean(),
- nullable=True,
- default=False,
- server_default=sa.false(),
- ),
- )
+ if not table_has_column("tables", "always_filter_main_dttm"):
+ op.add_column(
+ "tables",
+ sa.Column(
+ "always_filter_main_dttm",
+ sa.Boolean(),
+ nullable=True,
+ default=False,
+ server_default=sa.false(),
+ ),
+ )
- bind = op.get_bind()
- session = db.Session(bind=bind)
+ bind = op.get_bind()
+ session = db.Session(bind=bind)
- for table in paginated_update(session.query(SqlaTable)):
- table.always_filter_main_dttm = False
+ for table in paginated_update(session.query(SqlaTable)):
+ table.always_filter_main_dttm = False
def downgrade():
- op.drop_column("tables", "always_filter_main_dttm")
+ if table_has_column("tables", "always_filter_main_dttm"):
+ op.drop_column("tables", "always_filter_main_dttm")
diff --git a/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py b/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py
new file mode 100644
index 0000000000..b4286736f0
--- /dev/null
+++ b/superset/migrations/versions/2023-12-01_12-03_b7851ee5522f_replay_317970b4400c.py
@@ -0,0 +1,44 @@
+# 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.
+"""replay 317970b4400c
+
+Revision ID: b7851ee5522f
+Revises: 4b85906e5b91
+Create Date: 2023-12-01 12:03:27.538945
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "b7851ee5522f"
+down_revision = "4b85906e5b91"
+
+from importlib import import_module
+
+import sqlalchemy as sa
+from alembic import op
+
+module = import_module(
+ "superset.migrations.versions.2023-09-06_13-18_317970b4400c_added_time_secondary_column_to_"
+)
+
+
+def upgrade():
+ module.upgrade()
+
+
+def downgrade():
+ module.downgrade()
(superset) 14/15: chore: Clean up the examples dashboards (#26158)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5ec1edc87638d30ad1b13411fd99846984cc5972
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Mon Dec 4 16:05:08 2023 -0300
chore: Clean up the examples dashboards (#26158)
(cherry picked from commit 3ab27c6ec90d4b27e6a7c21783bd3d1f203280a4)
---
.../cypress/e2e/dashboard/nativeFilters.test.ts | 6 +-
.../cypress/e2e/dashboard/tabs.test.ts | 3 -
.../cypress-base/cypress/e2e/dashboard/utils.ts | 1 -
.../cypress-base/cypress/support/e2e.ts | 2 +-
.../examples/configs/charts/Filter_Segments.yaml | 68 ---
.../configs/charts/Filtering_Vaccines.yaml | 53 --
.../configs/charts/Vehicle_Sales_Filter.yaml | 47 --
.../configs/charts/Video_Game_Sales_Filter.yaml | 55 --
.../dashboards/COVID_Vaccine_Dashboard.yaml | 128 ++---
.../dashboards/FCC_New_Coder_Survey_2018.yaml | 608 ++++++++++-----------
.../configs/dashboards/Sales_Dashboard.yaml | 300 ++++------
.../configs/dashboards/Video_Game_Sales.yaml | 389 ++++++-------
superset/examples/misc_dashboard.py | 148 ++---
superset/examples/world_bank.py | 39 +-
tests/integration_tests/charts/api_tests.py | 6 +-
.../integration_tests/dashboards/commands_tests.py | 20 +-
tests/integration_tests/dashboards/dao_tests.py | 54 --
tests/integration_tests/databases/api_tests.py | 2 +-
tests/integration_tests/utils_tests.py | 44 --
19 files changed, 659 insertions(+), 1314 deletions(-)
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
index e8457ba94b..7683d7f878 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts
@@ -113,7 +113,7 @@ function prepareDashboardFilters(
},
type: 'NATIVE_FILTER',
description: '',
- chartsInScope: [6],
+ chartsInScope: [5],
tabsInScope: [],
});
});
@@ -150,7 +150,7 @@ function prepareDashboardFilters(
meta: {
width: 4,
height: 50,
- chartId: 6,
+ chartId: 5,
sliceName: 'Most Populated Countries',
},
},
@@ -414,7 +414,7 @@ describe('Native filters', () => {
cy.createSampleDashboards([0]);
});
- it('Verify that default value is respected after revisit', () => {
+ it.only('Verify that default value is respected after revisit', () => {
prepareDashboardFilters([
{ name: 'country_name', column: 'country_name', datasetId: 2 },
]);
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
index 6fc89c1446..ba442e600a 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/tabs.test.ts
@@ -25,7 +25,6 @@ import { TABBED_DASHBOARD } from 'cypress/utils/urls';
import { expandFilterOnLeftPanel } from './utils';
const TREEMAP = { name: 'Treemap', viz: 'treemap_v2' };
-const FILTER_BOX = { name: 'Region Filter', viz: 'filter_box' };
const LINE_CHART = { name: 'Growth Rate', viz: 'line' };
const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' };
const BIG_NUMBER = { name: 'Number of Girls', viz: 'big_number_total' };
@@ -41,7 +40,6 @@ function topLevelTabs() {
function resetTabs() {
topLevelTabs();
cy.get('@top-level-tabs').first().click();
- waitForChartLoad(FILTER_BOX);
waitForChartLoad(TREEMAP);
waitForChartLoad(BIG_NUMBER);
waitForChartLoad(TABLE);
@@ -96,7 +94,6 @@ describe('Dashboard tabs', () => {
it.skip('should send new queries when tab becomes visible', () => {
// landing in first tab
- waitForChartLoad(FILTER_BOX);
waitForChartLoad(TREEMAP);
getChartAliasBySpec(TREEMAP).then(treemapAlias => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
index ca539039cf..c63df51d10 100644
--- a/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/dashboard/utils.ts
@@ -23,7 +23,6 @@ import { ChartSpec, waitForChartLoad } from 'cypress/utils';
export const WORLD_HEALTH_CHARTS = [
{ name: '% Rural', viz: 'world_map' },
{ name: 'Most Populated Countries', viz: 'table' },
- { name: 'Region Filter', viz: 'filter_box' },
{ name: "World's Population", viz: 'big_number' },
{ name: 'Growth Rate', viz: 'line' },
{ name: 'Rural Breakdown', viz: 'sunburst' },
diff --git a/superset-frontend/cypress-base/cypress/support/e2e.ts b/superset-frontend/cypress-base/cypress/support/e2e.ts
index 6642e0120c..cccc7b2005 100644
--- a/superset-frontend/cypress-base/cypress/support/e2e.ts
+++ b/superset-frontend/cypress-base/cypress/support/e2e.ts
@@ -18,7 +18,7 @@
*/
import '@cypress/code-coverage/support';
import '@applitools/eyes-cypress/commands';
-import failOnConsoleError, { Config } from 'cypress-fail-on-console-error';
+import failOnConsoleError from 'cypress-fail-on-console-error';
require('cy-verify-downloads').addCustomCommand();
diff --git a/superset/examples/configs/charts/Filter_Segments.yaml b/superset/examples/configs/charts/Filter_Segments.yaml
deleted file mode 100644
index 605e33ca7e..0000000000
--- a/superset/examples/configs/charts/Filter_Segments.yaml
+++ /dev/null
@@ -1,68 +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.
-slice_name: Filter Segments
-viz_type: filter_box
-params:
- adhoc_filters: []
- datasource: 42__table
- date_filter: false
- filter_configs:
- - asc: true
- clearable: true
- column: ethnic_minority
- key: -xNBqpfQo
- label: Ethnic Minority
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: gender
- key: 19VeBGTKf
- label: Gender
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: developer_type
- key: OWTb4s69T
- label: Developer Type
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: lang_at_home
- key: Fn-YClyhb
- label: Language at Home
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: country_live
- key: 2fNskRCLJ
- label: Country live
- multiple: true
- searchAllOptions: false
- granularity_sqla: time_start
- queryFields: {}
- slice_id: 1387
- time_range: No filter
- url_params: {}
- viz_type: filter_box
-cache_timeout: null
-uuid: 6420629a-ce74-2c6b-ef7d-b2e78baa3cfe
-version: 1.0.0
-dataset_uuid: d95a2865-53ce-1f82-a53d-8e3c89331469
diff --git a/superset/examples/configs/charts/Filtering_Vaccines.yaml b/superset/examples/configs/charts/Filtering_Vaccines.yaml
deleted file mode 100644
index e458c5a009..0000000000
--- a/superset/examples/configs/charts/Filtering_Vaccines.yaml
+++ /dev/null
@@ -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.
-slice_name: Filtering Vaccines
-viz_type: filter_box
-params:
- adhoc_filters: []
- datasource: 69__table
- date_filter: false
- filter_configs:
- - asc: true
- clearable: true
- column: country_name
- key: D00hRxPLE
- label: Country
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: product_category
- key: jJ7x2cuIc
- label: Vaccine Approach
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: clinical_stage
- key: EgGwwAUU6
- label: Clinical Stage
- multiple: true
- searchAllOptions: false
- queryFields: {}
- slice_id: 3965
- time_range: No filter
- url_params: {}
- viz_type: filter_box
-cache_timeout: null
-uuid: c29381ce-0e99-4cf3-bf0f-5f55d6b94176
-version: 1.0.0
-dataset_uuid: 974b7a1c-22ea-49cb-9214-97b7dbd511e0
diff --git a/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml b/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
deleted file mode 100644
index 91c8f76bb8..0000000000
--- a/superset/examples/configs/charts/Vehicle_Sales_Filter.yaml
+++ /dev/null
@@ -1,47 +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.
-slice_name: Vehicle Sales Filter
-viz_type: filter_box
-params:
- adhoc_filters: []
- datasource: 23__table
- date_filter: true
- filter_configs:
- - asc: true
- clearable: true
- column: product_line
- key: 7oUjq15eQ
- label: Product Line
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: deal_size
- key: c3hO6Eub8
- label: Deal Size
- multiple: true
- searchAllOptions: false
- granularity_sqla: order_date
- queryFields: {}
- slice_id: 671
- time_range: '2003-01-01T00:00:00 : 2005-06-01T00:00:00'
- url_params: {}
- viz_type: filter_box
-cache_timeout: null
-uuid: a5689df7-98fc-7c51-602c-ebd92dc3ec70
-version: 1.0.0
-dataset_uuid: e8623bb9-5e00-f531-506a-19607f5f8005
diff --git a/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml b/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
deleted file mode 100644
index 6c76d53e8e..0000000000
--- a/superset/examples/configs/charts/Video_Game_Sales_Filter.yaml
+++ /dev/null
@@ -1,55 +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.
-slice_name: Video Game Sales filter
-viz_type: filter_box
-params:
- adhoc_filters: []
- datasource: 21__table
- date_filter: true
- filter_configs:
- - asc: true
- clearable: true
- column: platform
- key: s3ItH9vhG
- label: Platform
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: genre
- key: 202hDeMsG
- label: Genre
- multiple: true
- searchAllOptions: false
- - asc: true
- clearable: true
- column: publisher
- key: 5Os6jsJFK
- label: Publisher
- multiple: true
- searchAllOptions: false
- granularity_sqla: year
- queryFields: {}
- time_range: No filter
- url_params:
- preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre":
- null, "__time_range": "No filter"}}'
- viz_type: filter_box
-cache_timeout: null
-uuid: fd9ce7ec-ae08-4f71-93e0-7c26b132b2e6
-version: 1.0.0
-dataset_uuid: 53d47c0c-c03d-47f0-b9ac-81225f808283
diff --git a/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml b/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
index 363077aebe..1d870880b9 100644
--- a/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
+++ b/superset/examples/configs/dashboards/COVID_Vaccine_Dashboard.yaml
@@ -18,6 +18,9 @@ dashboard_title: COVID Vaccine Dashboard
description: null
css: ""
slug: null
+certified_by: ""
+certification_details: ""
+published: true
uuid: f4065089-110a-41fa-8dd7-9ce98a65e250
position:
CHART-63bEuxjDMJ:
@@ -25,32 +28,32 @@ position:
id: CHART-63bEuxjDMJ
meta:
chartId: 3961
- height: 72
+ height: 60
sliceName: Vaccine Candidates per Country
sliceNameOverride: Map of Vaccine Candidates
uuid: ddc91df6-fb40-4826-bdca-16b85af1c024
- width: 12
+ width: 8
parents:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-zvw7luvEL
+ - ROW-xSeNAspgw
type: CHART
CHART-F-fkth0Dnv:
children: []
id: CHART-F-fkth0Dnv
meta:
chartId: 3960
- height: 60
+ height: 82
sliceName: Vaccine Candidates per Country
sliceNameOverride: Treemap of Vaccine Candidates per Country
uuid: e2f5a8a7-feb0-4f79-bc6b-01fe55b98b3c
- width: 8
+ width: 4
parents:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-xSeNAspgw
+ - ROW-dieUdkeUw
type: CHART
CHART-RjD_ygqtwH:
children: []
@@ -66,7 +69,7 @@ position:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-zvw7luvEL
+ - ROW-zhOlQLQnB
type: CHART
CHART-aGfmWtliqA:
children: []
@@ -81,17 +84,17 @@ position:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-zvw7luvEL
+ - ROW-zhOlQLQnB
type: CHART
- CHART-j4hUvP5dDD:
+ CHART-dCUpAcPsji:
children: []
- id: CHART-j4hUvP5dDD
+ id: CHART-dCUpAcPsji
meta:
- chartId: 3962
+ chartId: 3963
height: 82
- sliceName: Vaccine Candidates per Approach & Stage
- sliceNameOverride: Heatmap of Approaches & Clinical Stages
- uuid: 0c953c84-0c9a-418d-be9f-2894d2a2cee0
+ sliceName: Vaccine Candidates per Country & Stage
+ sliceNameOverride: Heatmap of Countries & Clinical Stages
+ uuid: cd111331-d286-4258-9020-c7949a109ed2
width: 4
parents:
- ROOT_ID
@@ -99,37 +102,37 @@ position:
- TAB-BCIJF4NvgQ
- ROW-dieUdkeUw
type: CHART
- CHART-dCUpAcPsji:
+ CHART-fYo7IyvKZQ:
children: []
- id: CHART-dCUpAcPsji
+ id: CHART-fYo7IyvKZQ
meta:
- chartId: 3963
- height: 72
+ chartId: 3964
+ height: 60
sliceName: Vaccine Candidates per Country & Stage
- sliceNameOverride: Heatmap of Countries & Clinical Stages
- uuid: cd111331-d286-4258-9020-c7949a109ed2
+ sliceNameOverride: Sunburst of Country & Clinical Stages
+ uuid: f69c556f-15fe-4a82-a8bb-69d5b6954123
width: 4
parents:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-dieUdkeUw
+ - ROW-xSeNAspgw
type: CHART
- CHART-eirDduqb1A:
+ CHART-j4hUvP5dDD:
children: []
- id: CHART-eirDduqb1A
+ id: CHART-j4hUvP5dDD
meta:
- chartId: 3965
- height: 60
- sliceName: Filtering Vaccines
- sliceNameOverride: Filter Box of Vaccines
- uuid: c29381ce-0e99-4cf3-bf0f-5f55d6b94176
+ chartId: 3962
+ height: 82
+ sliceName: Vaccine Candidates per Approach & Stage
+ sliceNameOverride: Heatmap of Approaches & Clinical Stages
+ uuid: 0c953c84-0c9a-418d-be9f-2894d2a2cee0
width: 4
parents:
- ROOT_ID
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
- - ROW-xSeNAspgw
+ - ROW-dieUdkeUw
type: CHART
DASHBOARD_VERSION_KEY: v2
GRID_ID:
@@ -189,46 +192,17 @@ position:
- TAB-BCIJF4NvgQ
- ROW-zhOlQLQnB
type: MARKDOWN
- CHART-fYo7IyvKZQ:
- children: []
- id: CHART-fYo7IyvKZQ
- meta:
- chartId: 3964
- height: 72
- sliceName: Vaccine Candidates per Country & Stage
- sliceNameOverride: Sunburst of Country & Clinical Stages
- uuid: f69c556f-15fe-4a82-a8bb-69d5b6954123
- width: 4
- parents:
- - ROOT_ID
- - TABS-wUKya7eQ0Z
- - TAB-BCIJF4NvgQ
- - ROW-dieUdkeUw
- type: CHART
ROOT_ID:
children:
- TABS-wUKya7eQ0Z
id: ROOT_ID
type: ROOT
- ROW-zhOlQLQnB:
- children:
- - MARKDOWN-VjQQ5SFj5v
- - CHART-RjD_ygqtwH
- - CHART-aGfmWtliqA
- id: ROW-zhOlQLQnB
- meta:
- "0": ROOT_ID
- background: BACKGROUND_TRANSPARENT
- parents:
- - ROOT_ID
- - TABS-wUKya7eQ0Z
- - TAB-BCIJF4NvgQ
- type: ROW
- ROW-xSeNAspgw:
+ ROW-dieUdkeUw:
children:
- - CHART-eirDduqb1A
- CHART-F-fkth0Dnv
- id: ROW-xSeNAspgw
+ - CHART-dCUpAcPsji
+ - CHART-j4hUvP5dDD
+ id: ROW-dieUdkeUw
meta:
"0": ROOT_ID
background: BACKGROUND_TRANSPARENT
@@ -237,12 +211,11 @@ position:
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
type: ROW
- ROW-dieUdkeUw:
+ ROW-xSeNAspgw:
children:
- - CHART-dCUpAcPsji
+ - CHART-63bEuxjDMJ
- CHART-fYo7IyvKZQ
- - CHART-j4hUvP5dDD
- id: ROW-dieUdkeUw
+ id: ROW-xSeNAspgw
meta:
"0": ROOT_ID
background: BACKGROUND_TRANSPARENT
@@ -251,10 +224,12 @@ position:
- TABS-wUKya7eQ0Z
- TAB-BCIJF4NvgQ
type: ROW
- ROW-zvw7luvEL:
+ ROW-zhOlQLQnB:
children:
- - CHART-63bEuxjDMJ
- id: ROW-zvw7luvEL
+ - MARKDOWN-VjQQ5SFj5v
+ - CHART-RjD_ygqtwH
+ - CHART-aGfmWtliqA
+ id: ROW-zhOlQLQnB
meta:
"0": ROOT_ID
background: BACKGROUND_TRANSPARENT
@@ -267,7 +242,6 @@ position:
children:
- ROW-zhOlQLQnB
- ROW-xSeNAspgw
- - ROW-zvw7luvEL
- ROW-dieUdkeUw
id: TAB-BCIJF4NvgQ
meta:
@@ -316,18 +290,4 @@ metadata:
Unknown: "#EFA1AA"
Live attenuated virus: "#FDE380"
COUNT(*): "#D1C6BC"
- filter_scopes:
- "3965":
- country_name:
- scope:
- - ROOT_ID
- immune: []
- product_category:
- scope:
- - ROOT_ID
- immune: []
- clinical_stage:
- scope:
- - ROOT_ID
- immune: []
version: 1.0.0
diff --git a/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml b/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
index 2e97e6b576..b1508daff0 100644
--- a/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
+++ b/superset/examples/configs/dashboards/FCC_New_Coder_Survey_2018.yaml
@@ -16,8 +16,11 @@
# under the License.
dashboard_title: FCC New Coder Survey 2018
description: null
-css: ''
+css: ""
slug: null
+certified_by: ""
+certification_details: ""
+published: true
uuid: 5b12b583-8204-08e9-392c-422209c29787
position:
CHART--0GPGmD-pO:
@@ -25,17 +28,17 @@ position:
id: CHART--0GPGmD-pO
meta:
chartId: 1361
- height: 48
- sliceName: 'Current Developers: Is this your first development job?'
+ height: 56
+ sliceName: "Current Developers: Is this your first development job?"
sliceNameOverride: Is this your first development job?
uuid: bfe5a8e6-146f-ef59-5e6c-13d519b236a8
width: 2
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-b7USYEngT
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-b7USYEngT
type: CHART
CHART--w_Br1tPP3:
children: []
@@ -47,27 +50,27 @@ position:
uuid: a6dd2d5a-2cdc-c8ec-f30c-85920f4f8a65
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW-DR80aHJA2c
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW-DR80aHJA2c
type: CHART
CHART-0-zzTwBINh:
children: []
id: CHART-0-zzTwBINh
meta:
chartId: 3631
- height: 49
+ height: 55
sliceName: Last Year Income Distribution
uuid: a2ec5256-94b4-43c4-b8c7-b83f70c5d4df
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-b7USYEngT
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-b7USYEngT
type: CHART
CHART-37fu7fO6Z0:
children: []
@@ -79,11 +82,11 @@ position:
uuid: 02f546ae-1bf4-bd26-8bc2-14b9279c8a62
width: 7
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-kNjtGVFpp
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-kNjtGVFpp
type: CHART
CHART-5QwNlSbXYU:
children: []
@@ -95,11 +98,11 @@ position:
uuid: 097c05c9-2dd2-481d-813d-d6c0c12b4a3d
width: 5
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-kNjtGVFpp
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-kNjtGVFpp
type: CHART
CHART-FKuVqq4kaA:
children: []
@@ -112,11 +115,11 @@ position:
uuid: e6b09c28-98cf-785f-4caf-320fd4fca802
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW-DR80aHJA2c
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW-DR80aHJA2c
type: CHART
CHART-JnpdZOhVer:
children: []
@@ -124,16 +127,16 @@ position:
meta:
chartId: 1369
height: 50
- sliceName: "\U0001F393 Highest degree held"
+ sliceName: Highest degree held
uuid: 9f7d2b9c-6b3a-69f9-f03e-d3a141514639
width: 2
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW--BIzjz9F0
- - COLUMN-IEKAo_QJlz
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW--BIzjz9F0
+ - COLUMN-IEKAo_QJlz
type: CHART
CHART-LjfhrUkEef:
children: []
@@ -145,11 +148,11 @@ position:
uuid: 067c4a1e-ae03-4c0c-8e2a-d2c0f4bf43c3
width: 5
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-s3l4os7YY
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-s3l4os7YY
type: CHART
CHART-Q3pbwsH3id:
children: []
@@ -162,27 +165,27 @@ position:
uuid: def07750-b5c0-0b69-6228-cb2330916166
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-mOvr_xWm1
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-mOvr_xWm1
type: CHART
CHART-QVql08s5Bv:
children: []
id: CHART-QVql08s5Bv
meta:
chartId: 3632
- height: 50
+ height: 56
sliceName: First Time Developer?
uuid: edc75073-8f33-4123-a28d-cd6dfb33cade
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-b7USYEngT
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-b7USYEngT
type: CHART
CHART-UtSaz4pfV6:
children: []
@@ -194,12 +197,12 @@ position:
uuid: 5f1ea868-604e-f69d-a241-5daa83ff33be
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-UsW-_RPAb
- - COLUMN-OJ5spdMmNh
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-UsW-_RPAb
+ - COLUMN-OJ5spdMmNh
type: CHART
CHART-VvFbGxi3X_:
children: []
@@ -211,12 +214,12 @@ position:
uuid: 03a74c97-52fc-cf87-233c-d4275f8c550c
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-UsW-_RPAb
- - COLUMN-OJ5spdMmNh
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-UsW-_RPAb
+ - COLUMN-OJ5spdMmNh
type: CHART
CHART-XHncHuS5pZ:
children: []
@@ -229,11 +232,11 @@ position:
uuid: a0e5329f-224e-6fc8-efd2-d37d0f546ee8
width: 2
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW-DR80aHJA2c
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW-DR80aHJA2c
type: CHART
CHART-YSzS5GOOLf:
children: []
@@ -245,11 +248,11 @@ position:
uuid: 4880e4f4-b701-4be0-86f3-e7e89432e83b
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-mOvr_xWm1
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-mOvr_xWm1
type: CHART
CHART-ZECnzPz8Bi:
children: []
@@ -261,44 +264,27 @@ position:
uuid: 5596e0f6-78a9-465d-8325-7139c794a06a
width: 7
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-s3l4os7YY
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-s3l4os7YY
type: CHART
CHART-aytwlT4GAq:
children: []
id: CHART-aytwlT4GAq
meta:
chartId: 1384
- height: 50
+ height: 30
sliceName: Breakdown of Developer Type
uuid: b8386be8-f44e-6535-378c-2aa2ba461286
- width: 4
- parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-y-GwJPgxLr
- type: CHART
- CHART-d6vjW6rC6V:
- children: []
- id: CHART-d6vjW6rC6V
- meta:
- chartId: 1387
- height: 54
- sliceName: Filter Segments
- sliceNameOverride: Filter By
- uuid: 6420629a-ce74-2c6b-ef7d-b2e78baa3cfe
- width: 5
+ width: 6
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-y-GwJPgxLr
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-y-GwJPgxLr
type: CHART
CHART-fLpTSAHpAO:
children: []
@@ -310,11 +296,11 @@ position:
uuid: 2ba66056-a756-d6a3-aaec-0c243fb7062e
width: 9
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-UsW-_RPAb
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-UsW-_RPAb
type: CHART
CHART-lQVSAw0Or3:
children: []
@@ -327,11 +313,11 @@ position:
uuid: cb8998ab-9f93-4f0f-4e4b-3bfe4b0dea9d
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW--BIzjz9F0
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW--BIzjz9F0
type: CHART
CHART-o-JPAWMZK-:
children: []
@@ -343,11 +329,11 @@ position:
uuid: 0f6b447c-828c-e71c-87ac-211bc412b214
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-mOvr_xWm1
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-mOvr_xWm1
type: CHART
CHART-v22McUFMtx:
children: []
@@ -360,12 +346,12 @@ position:
uuid: 6d0ceb30-2008-d19c-d285-cf77dc764433
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW--BIzjz9F0
- - COLUMN-IEKAo_QJlz
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW--BIzjz9F0
+ - COLUMN-IEKAo_QJlz
type: CHART
CHART-wxWVtlajRF:
children: []
@@ -377,49 +363,49 @@ position:
uuid: bff88053-ccc4-92f2-d6f5-de83e950e8cd
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW--BIzjz9F0
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW--BIzjz9F0
type: CHART
COLUMN-IEKAo_QJlz:
children:
- - CHART-JnpdZOhVer
- - CHART-v22McUFMtx
+ - CHART-JnpdZOhVer
+ - CHART-v22McUFMtx
id: COLUMN-IEKAo_QJlz
meta:
background: BACKGROUND_TRANSPARENT
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW--BIzjz9F0
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW--BIzjz9F0
type: COLUMN
COLUMN-OJ5spdMmNh:
children:
- - CHART-VvFbGxi3X_
- - CHART-UtSaz4pfV6
+ - CHART-VvFbGxi3X_
+ - CHART-UtSaz4pfV6
id: COLUMN-OJ5spdMmNh
meta:
background: BACKGROUND_TRANSPARENT
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-UsW-_RPAb
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-UsW-_RPAb
type: COLUMN
DASHBOARD_VERSION_KEY: v2
GRID_ID:
children:
- - TABS-L-d9eyOE-b
+ - TABS-L-d9eyOE-b
id: GRID_ID
parents:
- - ROOT_ID
+ - ROOT_ID
type: GRID
HEADER_ID:
id: HEADER_ID
@@ -453,21 +439,21 @@ position:
height: 50
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
- - ROW-DR80aHJA2c
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
+ - ROW-DR80aHJA2c
type: MARKDOWN
MARKDOWN-NQmSPDOtpl:
children: []
id: MARKDOWN-NQmSPDOtpl
meta:
- code: '# Current Developers
+ code: "# Current Developers
- While majority of the students on FCC are Aspiring developers, there''s a
- nontrivial minority that''s there to continue leveling up their skills (17%
+ While majority of the students on FCC are Aspiring developers, there's a
+ nontrivial minority that's there to continue leveling up their skills (17%
of the survey respondents).
@@ -480,28 +466,28 @@ position:
- The proportion of developers whose current job is their first developer
job
- - Distribution of last year''s income
+ - Distribution of last year's income
- The geographic distribution of these developers
- The overlap between commute time and if their current job is their first
developer job
- - Potential link between highest degree earned and last year''s income'
- height: 50
+ - Potential link between highest degree earned and last year's income"
+ height: 56
width: 4
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
- - ROW-b7USYEngT
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
+ - ROW-b7USYEngT
type: MARKDOWN
MARKDOWN-__u6CsUyfh:
children: []
id: MARKDOWN-__u6CsUyfh
meta:
- code: '## FreeCodeCamp New Coder Survey 2018
+ code: "## FreeCodeCamp New Coder Survey 2018
Every year, FCC surveys its user base (mostly budding software developers)
@@ -513,21 +499,22 @@ position:
- [Dataset](https://github.com/freeCodeCamp/2018-new-coder-survey)
- - [FCC Blog Post](https://www.freecodecamp.org/news/we-asked-20-000-people-who-they-are-and-how-theyre-learning-to-code-fff5d668969/)'
- height: 45
- width: 3
- parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-y-GwJPgxLr
+ - [FCC Blog Post](https://www.freecodecamp.org/news/we-asked-20-000-people-who-they-are-and-how-theyre-learning-to-code-fff5d668969/)"
+ height: 30
+ width: 6
+ parents:
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-y-GwJPgxLr
type: MARKDOWN
MARKDOWN-zc2mWxZeox:
children: []
id: MARKDOWN-zc2mWxZeox
meta:
- code: "# Demographics\n\nFreeCodeCamp is a completely-online community of people\
+ code:
+ "# Demographics\n\nFreeCodeCamp is a completely-online community of people\
\ learning to code and consists of aspiring & current developers from all\
\ over the world. That doesn't necessarily mean that access to these types\
\ of opportunities are evenly distributed. \n\nThe following charts can begin\
@@ -537,243 +524,220 @@ position:
height: 52
width: 3
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
- - ROW-mOvr_xWm1
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
+ - ROW-mOvr_xWm1
type: MARKDOWN
ROOT_ID:
children:
- - GRID_ID
+ - GRID_ID
id: ROOT_ID
type: ROOT
ROW--BIzjz9F0:
children:
- - COLUMN-IEKAo_QJlz
- - CHART-lQVSAw0Or3
- - CHART-wxWVtlajRF
+ - COLUMN-IEKAo_QJlz
+ - CHART-lQVSAw0Or3
+ - CHART-wxWVtlajRF
id: ROW--BIzjz9F0
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
type: ROW
ROW-DR80aHJA2c:
children:
- - MARKDOWN-BUmyHM2s0x
- - CHART-XHncHuS5pZ
- - CHART--w_Br1tPP3
- - CHART-FKuVqq4kaA
+ - MARKDOWN-BUmyHM2s0x
+ - CHART-XHncHuS5pZ
+ - CHART--w_Br1tPP3
+ - CHART-FKuVqq4kaA
id: ROW-DR80aHJA2c
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-YT6eNksV-
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-YT6eNksV-
type: ROW
ROW-UsW-_RPAb:
children:
- - COLUMN-OJ5spdMmNh
- - CHART-fLpTSAHpAO
+ - COLUMN-OJ5spdMmNh
+ - CHART-fLpTSAHpAO
id: ROW-UsW-_RPAb
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
type: ROW
ROW-b7USYEngT:
children:
- - MARKDOWN-NQmSPDOtpl
- - CHART--0GPGmD-pO
- - CHART-QVql08s5Bv
- - CHART-0-zzTwBINh
+ - MARKDOWN-NQmSPDOtpl
+ - CHART--0GPGmD-pO
+ - CHART-QVql08s5Bv
+ - CHART-0-zzTwBINh
id: ROW-b7USYEngT
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
type: ROW
ROW-kNjtGVFpp:
children:
- - CHART-5QwNlSbXYU
- - CHART-37fu7fO6Z0
+ - CHART-5QwNlSbXYU
+ - CHART-37fu7fO6Z0
id: ROW-kNjtGVFpp
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
type: ROW
ROW-mOvr_xWm1:
children:
- - MARKDOWN-zc2mWxZeox
- - CHART-Q3pbwsH3id
- - CHART-o-JPAWMZK-
- - CHART-YSzS5GOOLf
+ - MARKDOWN-zc2mWxZeox
+ - CHART-Q3pbwsH3id
+ - CHART-o-JPAWMZK-
+ - CHART-YSzS5GOOLf
id: ROW-mOvr_xWm1
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
type: ROW
ROW-s3l4os7YY:
children:
- - CHART-LjfhrUkEef
- - CHART-ZECnzPz8Bi
+ - CHART-LjfhrUkEef
+ - CHART-ZECnzPz8Bi
id: ROW-s3l4os7YY
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-l_9I0aNYZ
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-l_9I0aNYZ
type: ROW
ROW-y-GwJPgxLr:
children:
- - MARKDOWN-__u6CsUyfh
- - CHART-aytwlT4GAq
- - CHART-d6vjW6rC6V
+ - MARKDOWN-__u6CsUyfh
+ - CHART-aytwlT4GAq
id: ROW-y-GwJPgxLr
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
- - TAB-AsMaxdYL_t
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
+ - TAB-AsMaxdYL_t
type: ROW
TAB-AsMaxdYL_t:
children:
- - ROW-y-GwJPgxLr
- - ROW-mOvr_xWm1
- - ROW-UsW-_RPAb
+ - ROW-y-GwJPgxLr
+ - ROW-mOvr_xWm1
+ - ROW-UsW-_RPAb
id: TAB-AsMaxdYL_t
meta:
text: Overview
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
type: TAB
TAB-YT6eNksV-:
children:
- - ROW-DR80aHJA2c
- - ROW--BIzjz9F0
+ - ROW-DR80aHJA2c
+ - ROW--BIzjz9F0
id: TAB-YT6eNksV-
meta:
text: "\U0001F680 Aspiring Developers"
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
type: TAB
TAB-l_9I0aNYZ:
children:
- - ROW-b7USYEngT
- - ROW-kNjtGVFpp
- - ROW-s3l4os7YY
+ - ROW-b7USYEngT
+ - ROW-kNjtGVFpp
+ - ROW-s3l4os7YY
id: TAB-l_9I0aNYZ
meta:
text: "\U0001F4BB Current Developers"
parents:
- - ROOT_ID
- - GRID_ID
- - TABS-L-d9eyOE-b
+ - ROOT_ID
+ - GRID_ID
+ - TABS-L-d9eyOE-b
type: TAB
TABS-L-d9eyOE-b:
children:
- - TAB-AsMaxdYL_t
- - TAB-YT6eNksV-
- - TAB-l_9I0aNYZ
+ - TAB-AsMaxdYL_t
+ - TAB-YT6eNksV-
+ - TAB-l_9I0aNYZ
id: TABS-L-d9eyOE-b
meta: {}
parents:
- - ROOT_ID
- - GRID_ID
+ - ROOT_ID
+ - GRID_ID
type: TABS
metadata:
timed_refresh_immune_slices: []
expanded_slices: {}
refresh_frequency: 0
- default_filters: '{}'
+ default_filters: "{}"
color_scheme: supersetColors
- filter_scopes:
- '1387':
- ethnic_minority:
- scope:
- - TAB-AsMaxdYL_t
- immune: []
- gender:
- scope:
- - ROOT_ID
- immune: []
- developer_type:
- scope:
- - ROOT_ID
- immune: []
- lang_at_home:
- scope:
- - ROOT_ID
- immune: []
- country_live:
- scope:
- - ROOT_ID
- immune: []
label_colors:
- '0': '#FCC700'
- '1': '#A868B7'
- '15': '#3CCCCB'
- '30': '#A38F79'
- '45': '#8FD3E4'
- age: '#1FA8C9'
- Yes,: '#1FA8C9'
- Female: '#454E7C'
- Prefer: '#5AC189'
- No,: '#FF7F44'
- Male: '#666666'
- Prefer not to say: '#E04355'
- Ph.D.: '#FCC700'
- associate's degree: '#A868B7'
- bachelor's degree: '#3CCCCB'
- high school diploma or equivalent (GED): '#A38F79'
- master's degree (non-professional): '#8FD3E4'
- no high school (secondary school): '#A1A6BD'
- professional degree (MBA, MD, JD, etc.): '#ACE1C4'
- some college credit, no degree: '#FEC0A1'
- some high school: '#B2B2B2'
- trade, technical, or vocational training: '#EFA1AA'
- No, not an ethnic minority: '#1FA8C9'
- Yes, an ethnic minority: '#454E7C'
- <NULL>: '#5AC189'
- 'Yes': '#FF7F44'
- 'No': '#666666'
- last_yr_income: '#E04355'
- More: '#A1A6BD'
- Less: '#ACE1C4'
- I: '#FEC0A1'
- expected_earn: '#B2B2B2'
- 'Yes: Willing To': '#EFA1AA'
- 'No: Not Willing to': '#FDE380'
- No Answer: '#D3B3DA'
- In an Office (with Other Developers): '#9EE5E5'
- No Preference: '#D1C6BC'
- From Home: '#1FA8C9'
+ "0": "#FCC700"
+ "1": "#A868B7"
+ "15": "#3CCCCB"
+ "30": "#A38F79"
+ "45": "#8FD3E4"
+ age: "#1FA8C9"
+ Yes,: "#1FA8C9"
+ Female: "#454E7C"
+ Prefer: "#5AC189"
+ No,: "#FF7F44"
+ Male: "#666666"
+ Prefer not to say: "#E04355"
+ Ph.D.: "#FCC700"
+ associate's degree: "#A868B7"
+ bachelor's degree: "#3CCCCB"
+ high school diploma or equivalent (GED): "#A38F79"
+ master's degree (non-professional): "#8FD3E4"
+ no high school (secondary school): "#A1A6BD"
+ professional degree (MBA, MD, JD, etc.): "#ACE1C4"
+ some college credit, no degree: "#FEC0A1"
+ some high school: "#B2B2B2"
+ trade, technical, or vocational training: "#EFA1AA"
+ No, not an ethnic minority: "#1FA8C9"
+ Yes, an ethnic minority: "#454E7C"
+ <NULL>: "#5AC189"
+ "Yes": "#FF7F44"
+ "No": "#666666"
+ last_yr_income: "#E04355"
+ More: "#A1A6BD"
+ Less: "#ACE1C4"
+ I: "#FEC0A1"
+ expected_earn: "#B2B2B2"
+ "Yes: Willing To": "#EFA1AA"
+ "No: Not Willing to": "#FDE380"
+ No Answer: "#D3B3DA"
+ In an Office (with Other Developers): "#9EE5E5"
+ No Preference: "#D1C6BC"
+ From Home: "#1FA8C9"
version: 1.0.0
diff --git a/superset/examples/configs/dashboards/Sales_Dashboard.yaml b/superset/examples/configs/dashboards/Sales_Dashboard.yaml
index 3efea3af25..439b763d0c 100644
--- a/superset/examples/configs/dashboards/Sales_Dashboard.yaml
+++ b/superset/examples/configs/dashboards/Sales_Dashboard.yaml
@@ -16,8 +16,11 @@
# under the License.
dashboard_title: Sales Dashboard
description: null
-css: ''
+css: ""
slug: null
+certified_by: ""
+certification_details: ""
+published: true
uuid: 04f79081-fb49-7bac-7f14-cc76cd2ad93b
position:
CHART-1NOOLm5YPs:
@@ -31,26 +34,26 @@ position:
uuid: c3d643cd-fd6f-4659-a5b7-59402487a8d0
width: 2
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-Tyv02UA_6W
- - COLUMN-8Rp54B6ikC
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-Tyv02UA_6W
+ - COLUMN-8Rp54B6ikC
type: CHART
CHART-AYpv8gFi_q:
children: []
id: CHART-AYpv8gFi_q
meta:
chartId: 2810
- height: 91
+ height: 70
sliceName: Number of Deals (for each Combination)
uuid: bd20fc69-dd51-46c1-99b5-09e37a434bf1
- width: 3
+ width: 6
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-0l1WcDzW3
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
+ - ROW-0l1WcDzW3
type: CHART
CHART-KKT9BsnUst:
children: []
@@ -63,90 +66,74 @@ position:
uuid: db9609e4-9b78-4a32-87a7-4d9e19d51cd8
width: 7
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-oAtmu5grZ
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-oAtmu5grZ
type: CHART
CHART-OJ9aWDmn1q:
children: []
id: CHART-OJ9aWDmn1q
meta:
chartId: 2808
- height: 91
+ height: 70
sliceName: Proportion of Revenue by Product Line
sliceNameOverride: Proportion of Monthly Revenue by Product Line
uuid: 08aff161-f60c-4cb3-a225-dc9b1140d2e3
width: 6
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-0l1WcDzW3
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
+ - ROW-0l1WcDzW3
type: CHART
CHART-YFg-9wHE7s:
children: []
id: CHART-YFg-9wHE7s
meta:
chartId: 2811
- height: 63
+ height: 49
sliceName: Seasonality of Revenue (per Product Line)
uuid: cf0da099-b3ab-4d94-ab62-cf353ac3c611
width: 6
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-E7MDSGfnm
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
+ - ROW-E7MDSGfnm
type: CHART
CHART-_LMKI0D3tj:
children: []
id: CHART-_LMKI0D3tj
meta:
chartId: 2809
- height: 62
- sliceName: Revenue by Deal SIze
+ height: 49
+ sliceName: Revenue by Deal Size
sliceNameOverride: Monthly Revenue by Deal SIze
uuid: f065a533-2e13-42b9-bd19-801a21700dff
width: 6
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-E7MDSGfnm
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
+ - ROW-E7MDSGfnm
type: CHART
CHART-id4RGv80N-:
children: []
id: CHART-id4RGv80N-
meta:
chartId: 2807
- height: 40
+ height: 59
sliceName: Total Items Sold (By Product Line)
sliceNameOverride: Total Products Sold (By Product Line)
uuid: b8b7ca30-6291-44b0-bc64-ba42e2892b86
width: 2
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-oAtmu5grZ
- - COLUMN-G6_2DvG8aK
- type: CHART
- CHART-iyvXMcqHt9:
- children: []
- id: CHART-iyvXMcqHt9
- meta:
- chartId: 671
- height: 39
- sliceName: Filter
- uuid: a5689df7-98fc-7c51-602c-ebd92dc3ec70
- width: 2
- parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-0l1WcDzW3
- - COLUMN-jlNWyWCfTC
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-oAtmu5grZ
+ - COLUMN-G6_2DvG8aK
type: CHART
CHART-j24u8ve41b:
children: []
@@ -159,10 +146,10 @@ position:
uuid: 09c497e0-f442-1121-c9e7-671e37750424
width: 3
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-oAtmu5grZ
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-oAtmu5grZ
type: CHART
CHART-lFanAaYKBK:
children: []
@@ -174,11 +161,11 @@ position:
uuid: 7b12a243-88e0-4dc5-ac33-9a840bb0ac5a
width: 3
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-Tyv02UA_6W
- - COLUMN-8Rp54B6ikC
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-Tyv02UA_6W
+ - COLUMN-8Rp54B6ikC
type: CHART
CHART-vomBOiI7U9:
children: []
@@ -191,58 +178,44 @@ position:
uuid: 692aca26-a526-85db-c94c-411c91cc1077
width: 7
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-Tyv02UA_6W
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-Tyv02UA_6W
type: CHART
COLUMN-8Rp54B6ikC:
children:
- - CHART-lFanAaYKBK
- - CHART-1NOOLm5YPs
+ - CHART-lFanAaYKBK
+ - CHART-1NOOLm5YPs
id: COLUMN-8Rp54B6ikC
meta:
background: BACKGROUND_TRANSPARENT
width: 2
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-Tyv02UA_6W
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-Tyv02UA_6W
type: COLUMN
COLUMN-G6_2DvG8aK:
children:
- - CHART-id4RGv80N-
+ - CHART-id4RGv80N-
id: COLUMN-G6_2DvG8aK
meta:
background: BACKGROUND_TRANSPARENT
width: 2
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-oAtmu5grZ
- type: COLUMN
- COLUMN-jlNWyWCfTC:
- children:
- - MARKDOWN-HrzsMmvGQo
- - CHART-iyvXMcqHt9
- id: COLUMN-jlNWyWCfTC
- meta:
- background: BACKGROUND_TRANSPARENT
- width: 3
- parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-0l1WcDzW3
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-oAtmu5grZ
type: COLUMN
DASHBOARD_VERSION_KEY: v2
GRID_ID:
children: []
id: GRID_ID
parents:
- - ROOT_ID
+ - ROOT_ID
type: GRID
HEADER_ID:
id: HEADER_ID
@@ -253,7 +226,8 @@ position:
children: []
id: MARKDOWN--AtDSWnapE
meta:
- code: "# \U0001F697 Vehicle Sales Dashboard \U0001F3CD\n\nThis example dashboard\
+ code:
+ "# \U0001F697 Vehicle Sales Dashboard \U0001F3CD\n\nThis example dashboard\
\ provides insight into the business operations of vehicle seller. The dataset\
\ powering this dashboard can be found [here on Kaggle](https://www.kaggle.com/kyanyoga/sample-sales-data).\n\
\n### Timeline\n\nThe dataset contains data on all orders from the 2003 and\
@@ -265,151 +239,113 @@ position:
height: 53
width: 3
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
- - ROW-Tyv02UA_6W
- type: MARKDOWN
- MARKDOWN-HrzsMmvGQo:
- children: []
- id: MARKDOWN-HrzsMmvGQo
- meta:
- code: "# \U0001F50D Filter Box\n\nDashboard filters are a powerful way to enable\
- \ teams to dive deeper into their business operations data. This filter box\
- \ helps focus the charts along the following variables:\n\n- Time Range: Focus\
- \ in on a specific time period (e.g. a holiday or quarter)\n- Product Line:\
- \ Choose 1 or more product lines to see relevant sales data\n- Deal Size:\
- \ Zoom in on small, medium, and / or large sales deals\n\nThe filter box below\
- \ \U0001F447 is configured to only apply to the charts in this tab (**Exploratory**).\
- \ You can customize the charts that this filter box applies to by:\n\n- entering\
- \ Edit mode in this dashboard\n- selecting the `...` in the top right corner\n\
- - selecting the **Set filter mapping** button"
- height: 50
- width: 3
- parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
- - ROW-0l1WcDzW3
- - COLUMN-jlNWyWCfTC
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
+ - ROW-Tyv02UA_6W
type: MARKDOWN
ROOT_ID:
children:
- - TABS-e5Ruro0cjP
+ - TABS-e5Ruro0cjP
id: ROOT_ID
type: ROOT
ROW-0l1WcDzW3:
children:
- - COLUMN-jlNWyWCfTC
- - CHART-OJ9aWDmn1q
- - CHART-AYpv8gFi_q
+ - CHART-OJ9aWDmn1q
+ - CHART-AYpv8gFi_q
id: ROW-0l1WcDzW3
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
type: ROW
ROW-E7MDSGfnm:
children:
- - CHART-YFg-9wHE7s
- - CHART-_LMKI0D3tj
+ - CHART-YFg-9wHE7s
+ - CHART-_LMKI0D3tj
id: ROW-E7MDSGfnm
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-4fthLQmdX
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-4fthLQmdX
type: ROW
ROW-Tyv02UA_6W:
children:
- - COLUMN-8Rp54B6ikC
- - CHART-vomBOiI7U9
- - MARKDOWN--AtDSWnapE
+ - COLUMN-8Rp54B6ikC
+ - CHART-vomBOiI7U9
+ - MARKDOWN--AtDSWnapE
id: ROW-Tyv02UA_6W
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
type: ROW
ROW-oAtmu5grZ:
children:
- - COLUMN-G6_2DvG8aK
- - CHART-KKT9BsnUst
- - CHART-j24u8ve41b
+ - COLUMN-G6_2DvG8aK
+ - CHART-KKT9BsnUst
+ - CHART-j24u8ve41b
id: ROW-oAtmu5grZ
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
- - TAB-d-E0Zc1cTH
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
+ - TAB-d-E0Zc1cTH
type: ROW
TAB-4fthLQmdX:
children:
- - ROW-0l1WcDzW3
- - ROW-E7MDSGfnm
+ - ROW-0l1WcDzW3
+ - ROW-E7MDSGfnm
id: TAB-4fthLQmdX
meta:
text: "\U0001F9ED Exploratory"
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
type: TAB
TAB-d-E0Zc1cTH:
children:
- - ROW-Tyv02UA_6W
- - ROW-oAtmu5grZ
+ - ROW-Tyv02UA_6W
+ - ROW-oAtmu5grZ
id: TAB-d-E0Zc1cTH
meta:
text: "\U0001F3AF Sales Overview"
parents:
- - ROOT_ID
- - TABS-e5Ruro0cjP
+ - ROOT_ID
+ - TABS-e5Ruro0cjP
type: TAB
TABS-e5Ruro0cjP:
children:
- - TAB-d-E0Zc1cTH
- - TAB-4fthLQmdX
+ - TAB-d-E0Zc1cTH
+ - TAB-4fthLQmdX
id: TABS-e5Ruro0cjP
meta: {}
parents:
- - ROOT_ID
+ - ROOT_ID
type: TABS
metadata:
timed_refresh_immune_slices: []
expanded_slices: {}
refresh_frequency: 0
- default_filters: '{"671": {"__time_range": "No filter"}}'
- filter_scopes:
- "671":
- product_line:
- scope:
- - TAB-4fthLQmdX
- immune: []
- deal_size:
- scope:
- - ROOT_ID
- immune: []
- __time_range:
- scope:
- - ROOT_ID
- immune: []
+ default_filters: "{}"
color_scheme: supersetColors
label_colors:
- Medium: '#1FA8C9'
- Small: '#454E7C'
- Large: '#5AC189'
- SUM(SALES): '#1FA8C9'
- Classic Cars: '#454E7C'
- Vintage Cars: '#5AC189'
- Motorcycles: '#FF7F44'
- Trucks and Buses: '#666666'
- Planes: '#E04355'
- Ships: '#FCC700'
- Trains: '#A868B7'
+ Medium: "#1FA8C9"
+ Small: "#454E7C"
+ Large: "#5AC189"
+ SUM(SALES): "#1FA8C9"
+ Classic Cars: "#454E7C"
+ Vintage Cars: "#5AC189"
+ Motorcycles: "#FF7F44"
+ Trucks and Buses: "#666666"
+ Planes: "#E04355"
+ Ships: "#FCC700"
+ Trains: "#A868B7"
version: 1.0.0
diff --git a/superset/examples/configs/dashboards/Video_Game_Sales.yaml b/superset/examples/configs/dashboards/Video_Game_Sales.yaml
index 958d32b069..2edaad2d1a 100644
--- a/superset/examples/configs/dashboards/Video_Game_Sales.yaml
+++ b/superset/examples/configs/dashboards/Video_Game_Sales.yaml
@@ -16,39 +16,27 @@
# under the License.
dashboard_title: Video Game Sales
description: null
-css: ''
+css: ""
slug: null
+certified_by: ""
+certification_details: ""
+published: true
uuid: c7bc10f4-6a2d-7569-caae-bbc91864ee11
position:
- CHART-1L7NIcXvVN:
- children: []
- id: CHART-1L7NIcXvVN
- meta:
- chartId: 3544
- height: 79
- sliceName: Games per Genre over time
- uuid: 0f8976aa-7bb4-40c7-860b-64445a51aaaf
- width: 6
- parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-fjg6YQBkH
- type: CHART
CHART-7mKdnU7OUJ:
children: []
id: CHART-7mKdnU7OUJ
meta:
chartId: 3545
- height: 80
+ height: 55
sliceName: Games per Genre
uuid: 0499bdec-0837-44f3-ae8a-8c670de81afd
- width: 3
+ width: 8
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-yP9SB89PZ
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
+ - ROW-yP9SB89PZ
type: CHART
CHART-8OG3UJX-Tn:
children: []
@@ -56,15 +44,15 @@ position:
meta:
chartId: 661
height: 54
- sliceName: '# of Games That Hit 100k in Sales By Release Year'
- sliceNameOverride: 'Top 10 Consoles, by # of Hit Games'
+ sliceName: "# of Games That Hit 100k in Sales By Release Year"
+ sliceNameOverride: "Top 10 Consoles, by # of Hit Games"
uuid: 2b69887b-23e3-b46d-d38c-8ea11856c555
width: 6
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-7kAf1blYU
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-7kAf1blYU
type: CHART
CHART-W02beJK7ms:
children: []
@@ -77,10 +65,10 @@ position:
uuid: d20b7324-3b80-24d4-37e2-3bd583b66713
width: 3
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-7kAf1blYU
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-7kAf1blYU
type: CHART
CHART-XFag0yZdLk:
children: []
@@ -93,10 +81,10 @@ position:
uuid: 1810975a-f6d4-07c3-495c-c3b535d01f21
width: 3
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-7kAf1blYU
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-7kAf1blYU
type: CHART
CHART-XRvRfsMsaQ:
children: []
@@ -104,14 +92,14 @@ position:
meta:
chartId: 3546
height: 62
- sliceName: 'Top 10 Games: Proportion of Sales in Markets'
+ sliceName: "Top 10 Games: Proportion of Sales in Markets"
uuid: a40879d5-653a-42fe-9314-bbe88ad26e92
width: 6
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-NuR8GFQTO
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-NuR8GFQTO
type: CHART
CHART-XVIYTeubZh:
children: []
@@ -121,12 +109,12 @@ position:
height: 80
sliceName: Games
uuid: 2a5e562b-ab37-1b9b-1de3-1be4335c8e83
- width: 5
+ width: 6
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-yP9SB89PZ
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
+ - ROW-yP9SB89PZ
type: CHART
CHART-_sx22yawJO:
children: []
@@ -138,78 +126,45 @@ position:
uuid: 326fc7e5-b7f1-448e-8a6f-80d0e7ce0b64
width: 6
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-NuR8GFQTO
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-NuR8GFQTO
type: CHART
CHART-nYns6xr4Ft:
children: []
id: CHART-nYns6xr4Ft
meta:
chartId: 3548
- height: 79
+ height: 80
sliceName: Total Sales per Market (Grouped by Genre)
uuid: d8bf948e-46fd-4380-9f9c-a950c34bcc92
width: 6
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-fjg6YQBkH
- type: CHART
- CHART-uP9GF0z0rT:
- children: []
- id: CHART-uP9GF0z0rT
- meta:
- chartId: 3547
- height: 45
- sliceName: Filter
- uuid: fd9ce7ec-ae08-4f71-93e0-7c26b132b2e6
- width: 4
- parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-yP9SB89PZ
- - COLUMN-F53B1OSMcz
- type: CHART
- CHART-wt6ZO8jRXZ:
- children: []
- id: CHART-wt6ZO8jRXZ
- meta:
- chartId: 659
- height: 72
- sliceName: Rise & Fall of Video Game Consoles
- sliceNameOverride: Global Sales per Console
- uuid: 83b0e2d0-d38b-d980-ed8e-e1c9846361b6
- width: 12
- parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-XT1DsNA_V
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
+ - ROW-fjg6YQBkH
type: CHART
COLUMN-F53B1OSMcz:
children:
- - MARKDOWN-7K5cBNy7qu
- - CHART-uP9GF0z0rT
+ - MARKDOWN-7K5cBNy7qu
id: COLUMN-F53B1OSMcz
meta:
background: BACKGROUND_TRANSPARENT
width: 4
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-yP9SB89PZ
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
+ - ROW-yP9SB89PZ
type: COLUMN
DASHBOARD_VERSION_KEY: v2
GRID_ID:
children: []
id: GRID_ID
parents:
- - ROOT_ID
+ - ROOT_ID
type: GRID
HEADER_ID:
id: HEADER_ID
@@ -220,224 +175,194 @@ position:
children: []
id: MARKDOWN-7K5cBNy7qu
meta:
- code: "# \U0001F93F Explore Trends\n\nDive into data on popular video games\
+ code:
+ "# \U0001F93F Explore Trends\n\nDive into data on popular video games\
\ using the following dimensions:\n\n- Year\n- Platform\n- Publisher\n- Genre\n\
\nTo use the **Filter Games** box below, select values for each dimension\
\ you want to zoom in on and then click **Apply**. \n\nThe filter criteria\
\ you set in this Filter-box will apply to *all* charts in this tab."
- height: 33
+ height: 55
width: 4
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
- - ROW-yP9SB89PZ
- - COLUMN-F53B1OSMcz
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
+ - ROW-yP9SB89PZ
+ - COLUMN-F53B1OSMcz
type: MARKDOWN
MARKDOWN-JOZKOjVc3a:
children: []
id: MARKDOWN-JOZKOjVc3a
meta:
- code: "## \U0001F3AEVideo Game Sales\n\nThis dashboard visualizes sales & platform\
+ code:
+ "## \U0001F3AEVideo Game Sales\n\nThis dashboard visualizes sales & platform\
\ data on video games that sold more than 100k copies. The data was last updated\
\ in early 2017.\n\n[Original dataset](https://www.kaggle.com/gregorut/videogamesales)"
height: 18
width: 12
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- - ROW-0F99WDC-sz
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
+ - ROW-0F99WDC-sz
type: MARKDOWN
ROOT_ID:
children:
- - TABS-97PVJa11D_
+ - TABS-97PVJa11D_
id: ROOT_ID
type: ROOT
ROW-0F99WDC-sz:
children:
- - MARKDOWN-JOZKOjVc3a
+ - MARKDOWN-JOZKOjVc3a
id: ROW-0F99WDC-sz
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
type: ROW
ROW-7kAf1blYU:
children:
- - CHART-W02beJK7ms
- - CHART-XFag0yZdLk
- - CHART-8OG3UJX-Tn
+ - CHART-W02beJK7ms
+ - CHART-XFag0yZdLk
+ - CHART-8OG3UJX-Tn
id: ROW-7kAf1blYU
meta:
- '0': ROOT_ID
+ "0": ROOT_ID
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
type: ROW
ROW-NuR8GFQTO:
children:
- - CHART-_sx22yawJO
- - CHART-XRvRfsMsaQ
+ - CHART-_sx22yawJO
+ - CHART-XRvRfsMsaQ
id: ROW-NuR8GFQTO
meta:
- '0': ROOT_ID
- '1': TABS-97PVJa11D_
- background: BACKGROUND_TRANSPARENT
- parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
- type: ROW
- ROW-XT1DsNA_V:
- children:
- - CHART-wt6ZO8jRXZ
- id: ROW-XT1DsNA_V
- meta:
+ "0": ROOT_ID
+ "1": TABS-97PVJa11D_
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-lg-5ymUDgm
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-lg-5ymUDgm
type: ROW
ROW-fjg6YQBkH:
children:
- - CHART-1L7NIcXvVN
- - CHART-nYns6xr4Ft
+ - CHART-nYns6xr4Ft
+ - CHART-XVIYTeubZh
id: ROW-fjg6YQBkH
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
type: ROW
ROW-yP9SB89PZ:
children:
- - COLUMN-F53B1OSMcz
- - CHART-XVIYTeubZh
- - CHART-7mKdnU7OUJ
+ - COLUMN-F53B1OSMcz
+ - CHART-7mKdnU7OUJ
id: ROW-yP9SB89PZ
meta:
background: BACKGROUND_TRANSPARENT
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
- - TAB-2_QXp8aNq
+ - ROOT_ID
+ - TABS-97PVJa11D_
+ - TAB-2_QXp8aNq
type: ROW
TAB-2_QXp8aNq:
children:
- - ROW-yP9SB89PZ
- - ROW-fjg6YQBkH
+ - ROW-yP9SB89PZ
+ - ROW-fjg6YQBkH
id: TAB-2_QXp8aNq
meta:
text: "\U0001F93F Explore Trends"
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
+ - ROOT_ID
+ - TABS-97PVJa11D_
type: TAB
TAB-lg-5ymUDgm:
children:
- - ROW-0F99WDC-sz
- - ROW-XT1DsNA_V
- - ROW-7kAf1blYU
- - ROW-NuR8GFQTO
+ - ROW-0F99WDC-sz
+ - ROW-7kAf1blYU
+ - ROW-NuR8GFQTO
id: TAB-lg-5ymUDgm
meta:
text: Overview
parents:
- - ROOT_ID
- - TABS-97PVJa11D_
+ - ROOT_ID
+ - TABS-97PVJa11D_
type: TAB
TABS-97PVJa11D_:
children:
- - TAB-lg-5ymUDgm
- - TAB-2_QXp8aNq
+ - TAB-lg-5ymUDgm
+ - TAB-2_QXp8aNq
id: TABS-97PVJa11D_
meta: {}
parents:
- - ROOT_ID
+ - ROOT_ID
type: TABS
metadata:
timed_refresh_immune_slices: []
expanded_slices: {}
refresh_frequency: 0
- default_filters: '{"3547": {"platform": ["PS", "PS2", "PS3", "XB", "X360"], "__time_range":
- "No filter"}}'
+ default_filters: "{}"
color_scheme: supersetColors
- filter_scopes:
- "3547":
- platform:
- scope:
- - TAB-2_QXp8aNq
- immune: []
- genre:
- scope:
- - ROOT_ID
- immune: []
- publisher:
- scope:
- - ROOT_ID
- immune: []
- __time_range:
- scope:
- - ROOT_ID
- immune: []
label_colors:
- '0': '#1FA8C9'
- '1': '#454E7C'
- '2600': '#666666'
- Europe: '#5AC189'
- Japan: '#FF7F44'
- North America: '#666666'
- Other: '#E04355'
- PS2: '#FCC700'
- X360: '#A868B7'
- PS3: '#3CCCCB'
- Wii: '#A38F79'
- DS: '#8FD3E4'
- PS: '#A1A6BD'
- GBA: '#ACE1C4'
- PSP: '#FEC0A1'
- PS4: '#B2B2B2'
- PC: '#EFA1AA'
- GB: '#FDE380'
- XB: '#D3B3DA'
- NES: '#9EE5E5'
- 3DS: '#D1C6BC'
- N64: '#1FA8C9'
- SNES: '#454E7C'
- GC: '#5AC189'
- XOne: '#FF7F44'
- WiiU: '#E04355'
- PSV: '#FCC700'
- SAT: '#A868B7'
- GEN: '#3CCCCB'
- DC: '#A38F79'
- SCD: '#8FD3E4'
- NG: '#A1A6BD'
- WS: '#ACE1C4'
- TG16: '#FEC0A1'
- 3DO: '#B2B2B2'
- GG: '#EFA1AA'
- PCFX: '#FDE380'
- Nintendo: '#D3B3DA'
- Take-Two Interactive: '#9EE5E5'
- Microsoft Game Studios: '#D1C6BC'
- Action: '#1FA8C9'
- Adventure: '#454E7C'
- Fighting: '#5AC189'
- Misc: '#FF7F44'
- Platform: '#666666'
- Puzzle: '#E04355'
- Racing: '#FCC700'
- Role-Playing: '#A868B7'
- Shooter: '#3CCCCB'
- Simulation: '#A38F79'
- Sports: '#8FD3E4'
- Strategy: '#A1A6BD'
+ "0": "#1FA8C9"
+ "1": "#454E7C"
+ "2600": "#666666"
+ Europe: "#5AC189"
+ Japan: "#FF7F44"
+ North America: "#666666"
+ Other: "#E04355"
+ PS2: "#FCC700"
+ X360: "#A868B7"
+ PS3: "#3CCCCB"
+ Wii: "#A38F79"
+ DS: "#8FD3E4"
+ PS: "#A1A6BD"
+ GBA: "#ACE1C4"
+ PSP: "#FEC0A1"
+ PS4: "#B2B2B2"
+ PC: "#EFA1AA"
+ GB: "#FDE380"
+ XB: "#D3B3DA"
+ NES: "#9EE5E5"
+ 3DS: "#D1C6BC"
+ N64: "#1FA8C9"
+ SNES: "#454E7C"
+ GC: "#5AC189"
+ XOne: "#FF7F44"
+ WiiU: "#E04355"
+ PSV: "#FCC700"
+ SAT: "#A868B7"
+ GEN: "#3CCCCB"
+ DC: "#A38F79"
+ SCD: "#8FD3E4"
+ NG: "#A1A6BD"
+ WS: "#ACE1C4"
+ TG16: "#FEC0A1"
+ 3DO: "#B2B2B2"
+ GG: "#EFA1AA"
+ PCFX: "#FDE380"
+ Nintendo: "#D3B3DA"
+ Take-Two Interactive: "#9EE5E5"
+ Microsoft Game Studios: "#D1C6BC"
+ Action: "#1FA8C9"
+ Adventure: "#454E7C"
+ Fighting: "#5AC189"
+ Misc: "#FF7F44"
+ Platform: "#666666"
+ Puzzle: "#E04355"
+ Racing: "#FCC700"
+ Role-Playing: "#A868B7"
+ Shooter: "#3CCCCB"
+ Simulation: "#A38F79"
+ Sports: "#8FD3E4"
+ Strategy: "#A1A6BD"
version: 1.0.0
diff --git a/superset/examples/misc_dashboard.py b/superset/examples/misc_dashboard.py
index 6336a78820..aa8d037495 100644
--- a/superset/examples/misc_dashboard.py
+++ b/superset/examples/misc_dashboard.py
@@ -38,37 +38,22 @@ def load_misc_dashboard() -> None:
js = textwrap.dedent(
"""\
{
- "CHART-BkeVbh8ANQ": {
- "children": [],
- "id": "CHART-BkeVbh8ANQ",
- "meta": {
- "chartId": 4004,
- "height": 34,
- "sliceName": "Multi Line",
- "width": 8
- },
- "type": "CHART"
- },
- "CHART-H1HYNzEANX": {
- "children": [],
- "id": "CHART-H1HYNzEANX",
- "meta": {
- "chartId": 3940,
- "height": 50,
- "sliceName": "Energy Sankey",
- "width": 6
- },
- "type": "CHART"
- },
"CHART-HJOYVMV0E7": {
"children": [],
"id": "CHART-HJOYVMV0E7",
"meta": {
"chartId": 3969,
- "height": 63,
+ "height": 69,
"sliceName": "Mapbox Long/Lat",
- "width": 6
+ "uuid": "164efe31-295b-4408-aaa6-2f4bfb58a212",
+ "width": 4
},
+ "parents": [
+ "ROOT_ID",
+ "GRID_ID",
+ "ROW-S1MK4M4A4X",
+ "COLUMN-ByUFVf40EQ"
+ ],
"type": "CHART"
},
"CHART-S1WYNz4AVX": {
@@ -76,32 +61,16 @@ def load_misc_dashboard() -> None:
"id": "CHART-S1WYNz4AVX",
"meta": {
"chartId": 3989,
- "height": 25,
+ "height": 69,
"sliceName": "Parallel Coordinates",
+ "uuid": "e84f7e74-031a-47bb-9f80-ae0694dcca48",
"width": 4
},
- "type": "CHART"
- },
- "CHART-r19KVMNCE7": {
- "children": [],
- "id": "CHART-r19KVMNCE7",
- "meta": {
- "chartId": 3971,
- "height": 34,
- "sliceName": "Calendar Heatmap multiformat 0",
- "width": 4
- },
- "type": "CHART"
- },
- "CHART-rJ4K4GV04Q": {
- "children": [],
- "id": "CHART-rJ4K4GV04Q",
- "meta": {
- "chartId": 3941,
- "height": 63,
- "sliceName": "Energy Force Layout",
- "width": 6
- },
+ "parents": [
+ "ROOT_ID",
+ "GRID_ID",
+ "ROW-SytNzNA4X"
+ ],
"type": "CHART"
},
"CHART-rkgF4G4A4X": {
@@ -109,54 +78,27 @@ def load_misc_dashboard() -> None:
"id": "CHART-rkgF4G4A4X",
"meta": {
"chartId": 3970,
- "height": 25,
+ "height": 69,
"sliceName": "Birth in France by department in 2016",
- "width": 8
- },
- "type": "CHART"
- },
- "CHART-rywK4GVR4X": {
- "children": [],
- "id": "CHART-rywK4GVR4X",
- "meta": {
- "chartId": 3942,
- "height": 50,
- "sliceName": "Heatmap",
- "width": 6
- },
- "type": "CHART"
- },
- "COLUMN-ByUFVf40EQ": {
- "children": [
- "CHART-rywK4GVR4X",
- "CHART-HJOYVMV0E7"
- ],
- "id": "COLUMN-ByUFVf40EQ",
- "meta": {
- "background": "BACKGROUND_TRANSPARENT",
- "width": 6
+ "uuid": "54583ae9-c99a-42b5-a906-7ee2adfe1fb1",
+ "width": 4
},
- "type": "COLUMN"
- },
- "COLUMN-rkmYVGN04Q": {
- "children": [
- "CHART-rJ4K4GV04Q",
- "CHART-H1HYNzEANX"
+ "parents": [
+ "ROOT_ID",
+ "GRID_ID",
+ "ROW-SytNzNA4X"
],
- "id": "COLUMN-rkmYVGN04Q",
- "meta": {
- "background": "BACKGROUND_TRANSPARENT",
- "width": 6
- },
- "type": "COLUMN"
+ "type": "CHART"
},
+ "DASHBOARD_VERSION_KEY": "v2",
"GRID_ID": {
"children": [
- "ROW-SytNzNA4X",
- "ROW-S1MK4M4A4X",
- "ROW-HkFFEzVRVm"
+ "ROW-SytNzNA4X"
],
"id": "GRID_ID",
+ "parents": [
+ "ROOT_ID"
+ ],
"type": "GRID"
},
"HEADER_ID": {
@@ -173,40 +115,22 @@ def load_misc_dashboard() -> None:
"id": "ROOT_ID",
"type": "ROOT"
},
- "ROW-HkFFEzVRVm": {
- "children": [
- "CHART-r19KVMNCE7",
- "CHART-BkeVbh8ANQ"
- ],
- "id": "ROW-HkFFEzVRVm",
- "meta": {
- "background": "BACKGROUND_TRANSPARENT"
- },
- "type": "ROW"
- },
- "ROW-S1MK4M4A4X": {
- "children": [
- "COLUMN-rkmYVGN04Q",
- "COLUMN-ByUFVf40EQ"
- ],
- "id": "ROW-S1MK4M4A4X",
- "meta": {
- "background": "BACKGROUND_TRANSPARENT"
- },
- "type": "ROW"
- },
"ROW-SytNzNA4X": {
"children": [
"CHART-rkgF4G4A4X",
- "CHART-S1WYNz4AVX"
+ "CHART-S1WYNz4AVX",
+ "CHART-HJOYVMV0E7"
],
"id": "ROW-SytNzNA4X",
"meta": {
"background": "BACKGROUND_TRANSPARENT"
},
+ "parents": [
+ "ROOT_ID",
+ "GRID_ID"
+ ],
"type": "ROW"
- },
- "DASHBOARD_VERSION_KEY": "v2"
+ }
}
"""
)
diff --git a/superset/examples/world_bank.py b/superset/examples/world_bank.py
index 5513a52d64..1541e3e472 100644
--- a/superset/examples/world_bank.py
+++ b/superset/examples/world_bank.py
@@ -167,35 +167,6 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
}
return [
- Slice(
- slice_name="Region Filter",
- viz_type="filter_box",
- datasource_type=DatasourceType.TABLE,
- datasource_id=tbl.id,
- params=get_slice_json(
- defaults,
- viz_type="filter_box",
- date_filter=False,
- filter_configs=[
- {
- "asc": False,
- "clearable": True,
- "column": "region",
- "key": "2s98dfu",
- "metric": "sum__SP_POP_TOTL",
- "multiple": False,
- },
- {
- "asc": False,
- "clearable": True,
- "key": "li3j2lk",
- "column": "country_name",
- "metric": "sum__SP_POP_TOTL",
- "multiple": True,
- },
- ],
- ),
- ),
Slice(
slice_name="World's Population",
viz_type="big_number",
@@ -372,18 +343,12 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
dashboard_positions = {
- "CHART-36bfc934": {
- "children": [],
- "id": "CHART-36bfc934",
- "meta": {"chartId": 40, "height": 25, "sliceName": "Region Filter", "width": 2},
- "type": "CHART",
- },
"CHART-37982887": {
"children": [],
"id": "CHART-37982887",
"meta": {
"chartId": 41,
- "height": 25,
+ "height": 52,
"sliceName": "World's Population",
"width": 2,
},
@@ -464,7 +429,7 @@ dashboard_positions = {
"type": "COLUMN",
},
"COLUMN-fe3914b8": {
- "children": ["CHART-36bfc934", "CHART-37982887"],
+ "children": ["CHART-37982887"],
"id": "COLUMN-fe3914b8",
"meta": {"background": "BACKGROUND_TRANSPARENT", "width": 2},
"type": "COLUMN",
diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index fe489a0f36..69888104fa 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -981,7 +981,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
- self.assertEqual(data["count"], 34)
+ self.assertEqual(data["count"], 33)
@pytest.mark.usefixtures("load_energy_table_with_slice", "add_dashboard_to_chart")
def test_get_charts_dashboards(self):
@@ -1447,7 +1447,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
"""
Chart API: Test get charts filter
"""
- # Assuming we have 34 sample charts
+ # Assuming we have 33 sample charts
self.login(username="admin")
arguments = {"page_size": 10, "page": 0}
uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
@@ -1461,7 +1461,7 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
rv = self.get_assert_metric(uri, "get_list")
self.assertEqual(rv.status_code, 200)
data = json.loads(rv.data.decode("utf-8"))
- self.assertEqual(len(data["result"]), 4)
+ self.assertEqual(len(data["result"]), 3)
def test_get_charts_no_data_access(self):
"""
diff --git a/tests/integration_tests/dashboards/commands_tests.py b/tests/integration_tests/dashboards/commands_tests.py
index cfc992b95f..175a8a3198 100644
--- a/tests/integration_tests/dashboards/commands_tests.py
+++ b/tests/integration_tests/dashboards/commands_tests.py
@@ -97,17 +97,11 @@ class TestExportDashboardsCommand(SupersetTestCase):
"published": False,
"uuid": str(example_dashboard.uuid),
"position": {
- "CHART-36bfc934": {
- "children": [],
- "id": "CHART-36bfc934",
- "meta": {"height": 25, "sliceName": "Region Filter", "width": 2},
- "type": "CHART",
- },
"CHART-37982887": {
"children": [],
"id": "CHART-37982887",
"meta": {
- "height": 25,
+ "height": 52,
"sliceName": "World's Population",
"width": 2,
},
@@ -180,7 +174,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
"type": "COLUMN",
},
"COLUMN-fe3914b8": {
- "children": ["CHART-36bfc934", "CHART-37982887"],
+ "children": ["CHART-37982887"],
"id": "COLUMN-fe3914b8",
"meta": {"background": "BACKGROUND_TRANSPARENT", "width": 2},
"type": "COLUMN",
@@ -299,7 +293,9 @@ class TestExportDashboardsCommand(SupersetTestCase):
mock_suffix.side_effect = (str(i) for i in itertools.count(1))
position = get_default_position("example")
- chart_1 = db.session.query(Slice).filter_by(slice_name="Region Filter").one()
+ chart_1 = (
+ db.session.query(Slice).filter_by(slice_name="World's Population").one()
+ )
new_position = append_charts(position, {chart_1})
assert new_position == {
"DASHBOARD_VERSION_KEY": "v2",
@@ -328,7 +324,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
"meta": {
"chartId": chart_1.id,
"height": 50,
- "sliceName": "Region Filter",
+ "sliceName": "World's Population",
"uuid": str(chart_1.uuid),
"width": 4,
},
@@ -375,7 +371,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
"meta": {
"chartId": chart_1.id,
"height": 50,
- "sliceName": "Region Filter",
+ "sliceName": "World's Population",
"uuid": str(chart_1.uuid),
"width": 4,
},
@@ -406,7 +402,7 @@ class TestExportDashboardsCommand(SupersetTestCase):
"meta": {
"chartId": chart_1.id,
"height": 50,
- "sliceName": "Region Filter",
+ "sliceName": "World's Population",
"uuid": str(chart_1.uuid),
"width": 4,
},
diff --git a/tests/integration_tests/dashboards/dao_tests.py b/tests/integration_tests/dashboards/dao_tests.py
index 8f73e5c2f8..65fc9e32dd 100644
--- a/tests/integration_tests/dashboards/dao_tests.py
+++ b/tests/integration_tests/dashboards/dao_tests.py
@@ -33,60 +33,6 @@ from tests.integration_tests.fixtures.world_bank_dashboard import (
class TestDashboardDAO(SupersetTestCase):
- @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
- def test_set_dash_metadata(self):
- dash: Dashboard = (
- db.session.query(Dashboard).filter_by(slug="world_health").first()
- )
- data = dash.data
- positions = data["position_json"]
- data.update({"positions": positions})
- original_data = copy.deepcopy(data)
-
- # add filter scopes
- filter_slice = next(slc for slc in dash.slices if slc.viz_type == "filter_box")
- immune_slices = [slc for slc in dash.slices if slc != filter_slice]
- filter_scopes = {
- str(filter_slice.id): {
- "region": {
- "scope": ["ROOT_ID"],
- "immune": [slc.id for slc in immune_slices],
- }
- }
- }
- data.update({"filter_scopes": json.dumps(filter_scopes)})
- DashboardDAO.set_dash_metadata(dash, data)
- updated_metadata = json.loads(dash.json_metadata)
- self.assertEqual(updated_metadata["filter_scopes"], filter_scopes)
-
- # remove a slice and change slice ids (as copy slices)
- removed_slice = immune_slices.pop()
- removed_components = [
- key
- for (key, value) in positions.items()
- if isinstance(value, dict)
- and value.get("type") == "CHART"
- and value["meta"]["chartId"] == removed_slice.id
- ]
- for component_id in removed_components:
- del positions[component_id]
-
- data.update({"positions": positions})
- DashboardDAO.set_dash_metadata(dash, data)
- updated_metadata = json.loads(dash.json_metadata)
- expected_filter_scopes = {
- str(filter_slice.id): {
- "region": {
- "scope": ["ROOT_ID"],
- "immune": [slc.id for slc in immune_slices],
- }
- }
- }
- self.assertEqual(updated_metadata["filter_scopes"], expected_filter_scopes)
-
- # reset dash to original data
- DashboardDAO.set_dash_metadata(dash, original_data)
-
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
@patch("superset.utils.core.g")
@patch("superset.security.manager.g")
diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py
index 6e6f6e15b8..496012390e 100644
--- a/tests/integration_tests/databases/api_tests.py
+++ b/tests/integration_tests/databases/api_tests.py
@@ -2074,7 +2074,7 @@ class TestDatabaseApi(SupersetTestCase):
rv = self.get_assert_metric(uri, "related_objects")
self.assertEqual(rv.status_code, 200)
response = json.loads(rv.data.decode("utf-8"))
- self.assertEqual(response["charts"]["count"], 34)
+ self.assertEqual(response["charts"]["count"], 33)
self.assertEqual(response["dashboards"]["count"], 3)
def test_get_database_related_objects_not_found(self):
diff --git a/tests/integration_tests/utils_tests.py b/tests/integration_tests/utils_tests.py
index 405040e3d0..ddd0b0caf4 100644
--- a/tests/integration_tests/utils_tests.py
+++ b/tests/integration_tests/utils_tests.py
@@ -744,50 +744,6 @@ class TestUtils(SupersetTestCase):
self.assertListEqual(as_list([123]), [123])
self.assertListEqual(as_list("foo"), ["foo"])
- @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
- def test_build_extra_filters(self):
- world_health = db.session.query(Dashboard).filter_by(slug="world_health").one()
- layout = json.loads(world_health.position_json)
- filter_ = db.session.query(Slice).filter_by(slice_name="Region Filter").one()
- world = db.session.query(Slice).filter_by(slice_name="World's Population").one()
- box_plot = db.session.query(Slice).filter_by(slice_name="Box plot").one()
- treemap = db.session.query(Slice).filter_by(slice_name="Treemap").one()
-
- filter_scopes = {
- str(filter_.id): {
- "region": {"scope": ["ROOT_ID"], "immune": [treemap.id]},
- "country_name": {
- "scope": ["ROOT_ID"],
- "immune": [treemap.id, box_plot.id],
- },
- }
- }
-
- default_filters = {
- str(filter_.id): {
- "region": ["North America"],
- "country_name": ["United States"],
- }
- }
-
- # immune to all filters
- assert (
- build_extra_filters(layout, filter_scopes, default_filters, treemap.id)
- == []
- )
-
- # in scope
- assert build_extra_filters(
- layout, filter_scopes, default_filters, world.id
- ) == [
- {"col": "region", "op": "==", "val": "North America"},
- {"col": "country_name", "op": "in", "val": ["United States"]},
- ]
-
- assert build_extra_filters(
- layout, filter_scopes, default_filters, box_plot.id
- ) == [{"col": "region", "op": "==", "val": "North America"}]
-
def test_merge_extra_filters_with_no_extras(self):
form_data = {
"time_range": "Last 10 days",
(superset) 04/15: chore(tags): Allow for lookup via ids vs. name in the API (#25996)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 2c3bf2895fdc5fc0001bd8466cbb81bc76f349ca
Author: Hugh A. Miles II <hu...@gmail.com>
AuthorDate: Wed Nov 29 13:59:59 2023 -0500
chore(tags): Allow for lookup via ids vs. name in the API (#25996)
(cherry picked from commit ee308fbc6459273bd6234de03c418e9d1dcbc3a7)
---
superset-frontend/src/features/tags/TagModal.tsx | 6 ++---
superset-frontend/src/features/tags/tags.ts | 17 ++++++++++++
superset-frontend/src/pages/AllEntities/index.tsx | 10 ++++---
superset/daos/tag.py | 8 ++++++
superset/tags/api.py | 15 ++++++++---
tests/integration_tests/tags/dao_tests.py | 33 +++++++++++++++++++++++
6 files changed, 80 insertions(+), 9 deletions(-)
diff --git a/superset-frontend/src/features/tags/TagModal.tsx b/superset-frontend/src/features/tags/TagModal.tsx
index a0ac8636a5..5057c8441d 100644
--- a/superset-frontend/src/features/tags/TagModal.tsx
+++ b/superset-frontend/src/features/tags/TagModal.tsx
@@ -26,7 +26,7 @@ import { Input } from 'antd';
import { Divider } from 'src/components';
import Button from 'src/components/Button';
import { Tag } from 'src/views/CRUD/types';
-import { fetchObjects } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds } from 'src/features/tags/tags';
const StyledModalBody = styled.div`
.ant-select-dropdown {
@@ -115,8 +115,8 @@ const TagModal: React.FC<TagModalProps> = ({
};
clearResources();
if (isEditMode) {
- fetchObjects(
- { tags: editTag.name, types: null },
+ fetchObjectsByTagIds(
+ { tagIds: [editTag.id], types: null },
(data: Tag[]) => {
data.forEach(updateResourceOptions);
setDashboardsToTag(resourceMap[TaggableResources.Dashboard]);
diff --git a/superset-frontend/src/features/tags/tags.ts b/superset-frontend/src/features/tags/tags.ts
index 45c4e88fc5..db172681cb 100644
--- a/superset-frontend/src/features/tags/tags.ts
+++ b/superset-frontend/src/features/tags/tags.ts
@@ -194,3 +194,20 @@ export function fetchObjects(
.then(({ json }) => callback(json.result))
.catch(response => error(response));
}
+
+export function fetchObjectsByTagIds(
+ {
+ tagIds = [],
+ types,
+ }: { tagIds: number[] | undefined; types: string | null },
+ callback: (json: JsonObject) => void,
+ error: (response: Response) => void,
+) {
+ let url = `/api/v1/tag/get_objects/?tagIds=${tagIds}`;
+ if (types) {
+ url += `&types=${types}`;
+ }
+ SupersetClient.get({ endpoint: url })
+ .then(({ json }) => callback(json.result))
+ .catch(response => error(response));
+}
diff --git a/superset-frontend/src/pages/AllEntities/index.tsx b/superset-frontend/src/pages/AllEntities/index.tsx
index ca815795d6..a1e2c52fe4 100644
--- a/superset-frontend/src/pages/AllEntities/index.tsx
+++ b/superset-frontend/src/pages/AllEntities/index.tsx
@@ -33,7 +33,7 @@ import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
import { Tag } from 'src/views/CRUD/types';
import TagModal from 'src/features/tags/TagModal';
import withToasts, { useToasts } from 'src/components/MessageToasts/withToasts';
-import { fetchObjects, fetchSingleTag } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
import Loading from 'src/components/Loading';
interface TaggedObject {
@@ -146,8 +146,12 @@ function AllEntities() {
const fetchTaggedObjects = () => {
setLoading(true);
- fetchObjects(
- { tags: tag?.name || '', types: null },
+ if (!tag) {
+ addDangerToast('Error tag object is not referenced!');
+ return;
+ }
+ fetchObjectsByTagIds(
+ { tagIds: [tag?.id] || '', types: null },
(data: TaggedObject[]) => {
const objects = { dashboard: [], chart: [], query: [] };
data.forEach(function (object) {
diff --git a/superset/daos/tag.py b/superset/daos/tag.py
index fbc9aa229e..60362bfbbd 100644
--- a/superset/daos/tag.py
+++ b/superset/daos/tag.py
@@ -167,6 +167,14 @@ class TagDAO(BaseDAO[Tag]):
.first()
)
+ @staticmethod
+ def get_tagged_objects_by_tag_id(
+ tag_ids: Optional[list[int]], obj_types: Optional[list[str]] = None
+ ) -> list[dict[str, Any]]:
+ tags = db.session.query(Tag).filter(Tag.id.in_(tag_ids)).all()
+ tag_names = [tag.name for tag in tags]
+ return TagDAO.get_tagged_objects_for_tags(tag_names, obj_types)
+
@staticmethod
def get_tagged_objects_for_tags(
tags: Optional[list[str]] = None, obj_types: Optional[list[str]] = None
diff --git a/superset/tags/api.py b/superset/tags/api.py
index a4fc185f29..a3c95a5814 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -584,12 +584,21 @@ class TagRestApi(BaseSupersetModelRestApi):
500:
$ref: '#/components/responses/500'
"""
+ tag_ids = [
+ tag_id for tag_id in request.args.get("tagIds", "").split(",") if tag_id
+ ]
tags = [tag for tag in request.args.get("tags", "").split(",") if tag]
# filter types
types = [type_ for type_ in request.args.get("types", "").split(",") if type_]
try:
- tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+ if tag_ids:
+ # priotize using ids for lookups vs. names mainly using this
+ # for backward compatibility
+ tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids, types)
+ else:
+ tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+
result = [
self.object_entity_response_schema.dump(tagged_object)
for tagged_object in tagged_objects
@@ -609,11 +618,11 @@ class TagRestApi(BaseSupersetModelRestApi):
log_to_statsd=False,
)
def favorite_status(self, **kwargs: Any) -> Response:
- """Favorite Stars for Dashboards
+ """Favorite Stars for Tags
---
get:
description: >-
- Check favorited dashboards for current user
+ Get favorited tags for current user
parameters:
- in: query
name: q
diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py
index ea4b3ba783..272ba43ed3 100644
--- a/tests/integration_tests/tags/dao_tests.py
+++ b/tests/integration_tests/tags/dao_tests.py
@@ -207,6 +207,39 @@ class TestTagsDAO(SupersetTestCase):
tagged_objects = TagDAO.get_tagged_objects_for_tags(obj_types=["chart"])
assert len(tagged_objects) == num_charts
+ @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+ @pytest.mark.usefixtures("with_tagging_system_feature")
+ @pytest.mark.usefixtures("create_tags")
+ # test get objects from tag
+ def test_get_objects_from_tag_with_id(self):
+ # create tagged objects
+ dashboard = (
+ db.session.query(Dashboard)
+ .filter(Dashboard.dashboard_title == "World Bank's Data")
+ .first()
+ )
+ dashboard_id = dashboard.id
+ tag_1 = db.session.query(Tag).filter_by(name="example_tag_1").one()
+ tag_2 = db.session.query(Tag).filter_by(name="example_tag_2").one()
+ tag_ids = [tag_1.id, tag_2.id]
+ self.insert_tagged_object(
+ object_id=dashboard_id, object_type=ObjectType.dashboard, tag_id=tag_1.id
+ )
+ # get objects
+ tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids)
+ assert len(tagged_objects) == 1
+
+ # test get objects from tag with type
+ tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+ tag_ids, obj_types=["dashboard", "chart"]
+ )
+ assert len(tagged_objects) == 1
+
+ tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+ tag_ids, obj_types=["chart"]
+ )
+ assert len(tagged_objects) == 0
+
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
@pytest.mark.usefixtures("with_tagging_system_feature")
@pytest.mark.usefixtures("create_tagged_objects")
(superset) 09/15: fix: set label on adhoc column should persist (#26154)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit ceac19fa2fb623756a938c069fabc184f78d9e91
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Fri Dec 1 08:40:52 2023 -0500
fix: set label on adhoc column should persist (#26154)
(cherry picked from commit b2ea97a98484e18eee760b7a2914926143918231)
---
.../ColumnSelectPopover.test.tsx | 77 ++++++++++++++++++++++
.../DndColumnSelectControl/ColumnSelectPopover.tsx | 39 +++++++++--
.../ColumnSelectPopoverTrigger.tsx | 13 ++--
3 files changed, 121 insertions(+), 8 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
new file mode 100644
index 0000000000..e7ff7cd9a7
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
@@ -0,0 +1,77 @@
+/**
+ * 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 { render, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import ColumnSelectPopover from 'src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover';
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+describe('ColumnSelectPopover - onTabChange function', () => {
+ it('updates adhocColumn when switching to sqlExpression tab with custom label', () => {
+ const mockColumns = [{ column_name: 'year' }];
+ const mockOnClose = jest.fn();
+ const mockOnChange = jest.fn();
+ const mockGetCurrentTab = jest.fn();
+ const mockSetDatasetModal = jest.fn();
+ const mockSetLabel = jest.fn();
+
+ const store = mockStore({ explore: { datasource: { type: 'table' } } });
+
+ const { container, getByText } = render(
+ <Provider store={store}>
+ <ThemeProvider theme={supersetTheme}>
+ <ColumnSelectPopover
+ columns={mockColumns}
+ editedColumn={mockColumns[0]}
+ getCurrentTab={mockGetCurrentTab}
+ hasCustomLabel
+ isTemporal
+ label="Custom Label"
+ onChange={mockOnChange}
+ onClose={mockOnClose}
+ setDatasetModal={mockSetDatasetModal}
+ setLabel={mockSetLabel}
+ />
+ </ThemeProvider>
+ </Provider>,
+ );
+
+ const sqlExpressionTab = container.querySelector(
+ '#adhoc-metric-edit-tabs-tab-sqlExpression',
+ );
+ expect(sqlExpressionTab).not.toBeNull();
+ fireEvent.click(sqlExpressionTab!);
+ expect(mockGetCurrentTab).toHaveBeenCalledWith('sqlExpression');
+
+ const saveButton = getByText('Save');
+ fireEvent.click(saveButton);
+ expect(mockOnChange).toHaveBeenCalledWith({
+ label: 'Custom Label',
+ sqlExpression: 'year',
+ expressionType: 'SQL',
+ });
+ });
+});
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
index 4806e5394a..96abf36484 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
@@ -68,6 +68,7 @@ interface ColumnSelectPopoverProps {
editedColumn?: ColumnMeta | AdhocColumn;
onChange: (column: ColumnMeta | AdhocColumn) => void;
onClose: () => void;
+ hasCustomLabel: boolean;
setLabel: (title: string) => void;
getCurrentTab: (tab: string) => void;
label: string;
@@ -93,13 +94,14 @@ const getInitialColumnValues = (
const ColumnSelectPopover = ({
columns,
editedColumn,
+ getCurrentTab,
+ hasCustomLabel,
+ isTemporal,
+ label,
onChange,
onClose,
setDatasetModal,
setLabel,
- getCurrentTab,
- label,
- isTemporal,
}: ColumnSelectPopoverProps) => {
const datasourceType = useSelector<ExplorePageState, string | undefined>(
state => state.explore.datasource.type,
@@ -117,6 +119,7 @@ const ColumnSelectPopover = ({
const [selectedSimpleColumn, setSelectedSimpleColumn] = useState<
ColumnMeta | undefined
>(initialSimpleColumn);
+ const [selectedTab, setSelectedTab] = useState<string | null>(null);
const [resizeButton, width, height] = useResizeButton(
POPOVER_INITIAL_WIDTH,
@@ -188,7 +191,34 @@ const ColumnSelectPopover = ({
useEffect(() => {
getCurrentTab(defaultActiveTabKey);
- }, [defaultActiveTabKey, getCurrentTab]);
+ setSelectedTab(defaultActiveTabKey);
+ }, [defaultActiveTabKey, getCurrentTab, setSelectedTab]);
+
+ useEffect(() => {
+ /* if the adhoc column is not set (because it was never edited) but the
+ * tab is selected and the label has changed, then we need to set the
+ * adhoc column manually */
+ if (
+ adhocColumn === undefined &&
+ selectedTab === 'sqlExpression' &&
+ hasCustomLabel
+ ) {
+ const sqlExpression =
+ selectedSimpleColumn?.column_name ||
+ selectedCalculatedColumn?.expression ||
+ '';
+ setAdhocColumn({ label, sqlExpression, expressionType: 'SQL' });
+ }
+ }, [
+ adhocColumn,
+ defaultActiveTabKey,
+ hasCustomLabel,
+ getCurrentTab,
+ label,
+ selectedCalculatedColumn,
+ selectedSimpleColumn,
+ selectedTab,
+ ]);
const onSave = useCallback(() => {
if (adhocColumn && adhocColumn.label !== label) {
@@ -225,6 +255,7 @@ const ColumnSelectPopover = ({
const onTabChange = useCallback(
tab => {
getCurrentTab(tab);
+ setSelectedTab(tab);
// @ts-ignore
sqlEditorRef.current?.editor.focus();
},
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
index 4340317f04..341d91e616 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopoverTrigger.tsx
@@ -103,6 +103,7 @@ const ColumnSelectPopoverTrigger = ({
setDatasetModal={setDatasetModal}
onClose={handleClosePopover}
onChange={onColumnEdit}
+ hasCustomLabel={hasCustomLabel}
label={popoverLabel}
setLabel={setPopoverLabel}
getCurrentTab={getCurrentTab}
@@ -114,6 +115,7 @@ const ColumnSelectPopoverTrigger = ({
columns,
editedColumn,
getCurrentTab,
+ hasCustomLabel,
handleClosePopover,
isTemporal,
onColumnEdit,
@@ -121,10 +123,13 @@ const ColumnSelectPopoverTrigger = ({
],
);
- const onLabelChange = useCallback((e: any) => {
- setPopoverLabel(e.target.value);
- setHasCustomLabel(true);
- }, []);
+ const onLabelChange = useCallback(
+ (e: any) => {
+ setPopoverLabel(e.target.value);
+ setHasCustomLabel(true);
+ },
+ [setPopoverLabel, setHasCustomLabel],
+ );
const popoverTitle = useMemo(
() => (
(superset) 07/15: feat(helm): Add option to deploy extra containers to remaining deployments (#26123)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 4a4f9983dfe9d0301f749f8b6e144a11dae7bab7
Author: Tom Murphy <to...@limepay.com.au>
AuthorDate: Fri Dec 1 09:39:15 2023 +1100
feat(helm): Add option to deploy extra containers to remaining deployments (#26123)
(cherry picked from commit 4f004048054776c3074dfa5b4d5fd684cced47f1)
---
helm/superset/Chart.yaml | 2 +-
helm/superset/README.md | 5 ++++-
helm/superset/templates/deployment-beat.yaml | 3 +++
helm/superset/templates/deployment-flower.yaml | 3 +++
helm/superset/templates/deployment-ws.yaml | 3 +++
helm/superset/values.yaml | 6 ++++++
6 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index 1f7d974c2b..459392f8a1 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: craig@craigrueda.com
url: https://github.com/craig-rueda
-version: 0.11.0
+version: 0.11.1
dependencies:
- name: postgresql
version: 12.1.6
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 058ddd615f..886623e3f8 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
# superset
-![Version: 0.11.0](https://img.shields.io/badge/Version-0.11.0-informational?style=flat-square)
+![Version: 0.11.1](https://img.shields.io/badge/Version-0.11.1-informational?style=flat-square)
Apache Superset is a modern, enterprise-ready business intelligence web application
@@ -130,6 +130,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetCeleryBeat.containerSecurityContext | object | `{}` | |
| supersetCeleryBeat.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat deployment |
| supersetCeleryBeat.enabled | bool | `false` | This is only required if you intend to use alerts and reports |
+| supersetCeleryBeat.extraContainers | list | `[]` | Launch additional containers into supersetCeleryBeat pods |
| supersetCeleryBeat.forceReload | bool | `false` | If true, forces deployment to reload on each upgrade |
| supersetCeleryBeat.initContainers | list | a container waiting for postgres | List of init containers |
| supersetCeleryBeat.podAnnotations | object | `{}` | Annotations to be added to supersetCeleryBeat pods |
@@ -142,6 +143,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetCeleryFlower.containerSecurityContext | object | `{}` | |
| supersetCeleryFlower.deploymentAnnotations | object | `{}` | Annotations to be added to supersetCeleryFlower deployment |
| supersetCeleryFlower.enabled | bool | `false` | Enables a Celery flower deployment (management UI to monitor celery jobs) WARNING: on superset 1.x, this requires a Superset image that has `flower<1.0.0` installed (which is NOT the case of the default images) flower>=1.0.0 requires Celery 5+ which Superset 1.5 does not support |
+| supersetCeleryFlower.extraContainers | list | `[]` | Launch additional containers into supersetCeleryFlower pods |
| supersetCeleryFlower.initContainers | list | a container waiting for postgres and redis | List of init containers |
| supersetCeleryFlower.livenessProbe.failureThreshold | int | `3` | |
| supersetCeleryFlower.livenessProbe.httpGet.path | string | `"/api/workers"` | |
@@ -229,6 +231,7 @@ On helm this can be set on `extraSecretEnv.SUPERSET_SECRET_KEY` or `configOverri
| supersetWebsockets.containerSecurityContext | object | `{}` | |
| supersetWebsockets.deploymentAnnotations | object | `{}` | |
| supersetWebsockets.enabled | bool | `false` | This is only required if you intend to use `GLOBAL_ASYNC_QUERIES` in `ws` mode see https://github.com/apache/superset/blob/master/CONTRIBUTING.md#async-chart-queries |
+| supersetWebsockets.extraContainers | list | `[]` | Launch additional containers into supersetWebsockets pods |
| supersetWebsockets.image.pullPolicy | string | `"IfNotPresent"` | |
| supersetWebsockets.image.repository | string | `"oneacrefund/superset-websocket"` | There is no official image (yet), this one is community-supported |
| supersetWebsockets.image.tag | string | `"latest"` | |
diff --git a/helm/superset/templates/deployment-beat.yaml b/helm/superset/templates/deployment-beat.yaml
index eab9a6f3eb..30d1eff61a 100644
--- a/helm/superset/templates/deployment-beat.yaml
+++ b/helm/superset/templates/deployment-beat.yaml
@@ -120,6 +120,9 @@ spec:
{{- else }}
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
+ {{- if .Values.supersetCeleryBeat.extraContainers }}
+ {{- toYaml .Values.supersetCeleryBeat.extraContainers | nindent 8 }}
+ {{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
diff --git a/helm/superset/templates/deployment-flower.yaml b/helm/superset/templates/deployment-flower.yaml
index 2213ffa353..e4b05a17e9 100644
--- a/helm/superset/templates/deployment-flower.yaml
+++ b/helm/superset/templates/deployment-flower.yaml
@@ -115,6 +115,9 @@ spec:
{{- else }}
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
+ {{- if .Values.supersetCeleryFlower.extraContainers }}
+ {{- toYaml .Values.supersetCeleryFlower.extraContainers | nindent 8 }}
+ {{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
diff --git a/helm/superset/templates/deployment-ws.yaml b/helm/superset/templates/deployment-ws.yaml
index 6bc9faac67..7612900b07 100644
--- a/helm/superset/templates/deployment-ws.yaml
+++ b/helm/superset/templates/deployment-ws.yaml
@@ -114,6 +114,9 @@ spec:
{{- if .Values.supersetWebsockets.livenessProbe }}
livenessProbe: {{- .Values.supersetWebsockets.livenessProbe | toYaml | nindent 12 }}
{{- end }}
+ {{- if .Values.supersetWebsockets.extraContainers }}
+ {{- toYaml .Values.supersetWebsockets.extraContainers | nindent 8 }}
+ {{- end }}
{{- with .Values.nodeSelector }}
nodeSelector: {{- toYaml . | nindent 8 }}
{{- end }}
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index a5b70559d1..26d4547420 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -443,6 +443,8 @@ supersetCeleryBeat:
- /bin/sh
- -c
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
+ # -- Launch additional containers into supersetCeleryBeat pods
+ extraContainers: []
# -- Annotations to be added to supersetCeleryBeat deployment
deploymentAnnotations: {}
# -- Affinity to be added to supersetCeleryBeat deployment
@@ -524,6 +526,8 @@ supersetCeleryFlower:
- /bin/sh
- -c
- dockerize -wait "tcp://$DB_HOST:$DB_PORT" -wait "tcp://$REDIS_HOST:$REDIS_PORT" -timeout 120s
+ # -- Launch additional containers into supersetCeleryFlower pods
+ extraContainers: []
# -- Annotations to be added to supersetCeleryFlower deployment
deploymentAnnotations: {}
# -- Affinity to be added to supersetCeleryFlower deployment
@@ -590,6 +594,8 @@ supersetWebsockets:
http: nil
command: []
resources: {}
+ # -- Launch additional containers into supersetWebsockets pods
+ extraContainers: []
deploymentAnnotations: {}
# -- Affinity to be added to supersetWebsockets deployment
affinity: {}
(superset) 08/15: feat: Adds legacy time support for Waterfall chart (#26136)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 79d59750286eb9cac9fbfa2b066ae203cf11d2eb
Author: Michael S. Molina <70...@users.noreply.github.com>
AuthorDate: Fri Dec 1 10:04:53 2023 -0300
feat: Adds legacy time support for Waterfall chart (#26136)
(cherry picked from commit f405ba033e04e2694f869738163d33e1d3991297)
---
.../plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts | 7 +++----
.../plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx | 8 +++++---
.../plugins/plugin-chart-echarts/src/Waterfall/index.ts | 2 +-
.../plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts | 6 +++++-
4 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
index e47effb3c2..deb3571938 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
@@ -19,15 +19,14 @@
import {
buildQueryContext,
ensureIsArray,
- getXAxisColumn,
- isXAxisSet,
QueryFormData,
} from '@superset-ui/core';
export default function buildQuery(formData: QueryFormData) {
+ const { x_axis, granularity_sqla, groupby } = formData;
const columns = [
- ...(isXAxisSet(formData) ? ensureIsArray(getXAxisColumn(formData)) : []),
- ...ensureIsArray(formData.groupby),
+ ...ensureIsArray(x_axis || granularity_sqla),
+ ...ensureIsArray(groupby),
];
return buildQueryContext(formData, baseQueryObject => [
{
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
index 7a71dd4fcb..d07e5175e6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
@@ -17,25 +17,27 @@
* under the License.
*/
import React from 'react';
-import { t } from '@superset-ui/core';
+import { hasGenericChartAxes, t } from '@superset-ui/core';
import {
ControlPanelConfig,
ControlSubSectionHeader,
D3_TIME_FORMAT_DOCS,
DEFAULT_TIME_FORMAT,
formatSelectOptions,
+ sections,
sharedControls,
} from '@superset-ui/chart-controls';
import { showValueControl } from '../controls';
const config: ControlPanelConfig = {
controlPanelSections: [
+ sections.genericTime,
{
label: t('Query'),
expanded: true,
controlSetRows: [
- ['x_axis'],
- ['time_grain_sqla'],
+ [hasGenericChartAxes ? 'x_axis' : null],
+ [hasGenericChartAxes ? 'time_grain_sqla' : null],
['groupby'],
['metric'],
['adhoc_filters'],
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
index c0d7a11067..b8c66fabb1 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
@@ -61,7 +61,7 @@ export default class EchartsWaterfallChartPlugin extends ChartPlugin<
{ url: example3 },
],
name: t('Waterfall Chart'),
- tags: [t('Categorical'), t('Comparison'), t('ECharts')],
+ tags: [t('Categorical'), t('Comparison'), t('ECharts'), t('Popular')],
thumbnail,
}),
transformProps,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
index 7b5faed1b2..84fbbf6cb9 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
@@ -185,6 +185,7 @@ export default function transformProps(
const { setDataMask = () => {}, onContextMenu, onLegendStateChanged } = hooks;
const {
currencyFormat,
+ granularitySqla = '',
groupby,
increaseColor,
decreaseColor,
@@ -213,7 +214,10 @@ export default function transformProps(
const breakdownName = isAdhocColumn(breakdownColumn)
? breakdownColumn.label!
: breakdownColumn;
- const xAxisName = isAdhocColumn(xAxis) ? xAxis.label! : xAxis;
+ const xAxisColumn = xAxis || granularitySqla;
+ const xAxisName = isAdhocColumn(xAxisColumn)
+ ? xAxisColumn.label!
+ : xAxisColumn;
const metricLabel = getMetricLabel(metric);
const transformedData = transformer({
(superset) 01/15: fix: alias column when fetching values (#26120)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit f4fd0e19e26a7545597da49cc9e1616fdc747433
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Nov 28 15:11:20 2023 -0500
fix: alias column when fetching values (#26120)
(cherry picked from commit 7223633da600fb3973834cb24d977f194a76f328)
---
superset/models/helpers.py | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index fc947ff577..df3dd93488 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -1352,7 +1352,13 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
tbl, cte = self.get_from_clause(tp)
qry = (
- sa.select([target_col.get_sqla_col(template_processor=tp)])
+ sa.select(
+ # The alias (label) here is important because some dialects will
+ # automatically add a random alias to the projection because of the
+ # call to DISTINCT; others will uppercase the column names. This
+ # gives us a deterministic column name in the dataframe.
+ [target_col.get_sqla_col(template_processor=tp).label("column_values")]
+ )
.select_from(tbl)
.distinct()
)
@@ -1368,7 +1374,7 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
sql = self.mutate_query_from_config(sql)
df = pd.read_sql_query(sql=sql, con=engine)
- return df[denormalized_col_name].to_list()
+ return df["column_values"].to_list()
def get_timestamp_expression(
self,
(superset) 06/15: fix(annotations): time grain column (#26140)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 26e59662fb56b7a604a2c54f289b53a568410c4b
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Nov 30 11:13:12 2023 -0500
fix(annotations): time grain column (#26140)
(cherry picked from commit cff473f825825a419eb544d56960ce3a8a541592)
---
.../src/components/Chart/chartAction.js | 9 ++-
.../src/components/Chart/chartActions.test.js | 67 ++++++++++++++++++++++
2 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/src/components/Chart/chartAction.js b/superset-frontend/src/components/Chart/chartAction.js
index 42aa3fc5b1..8cd3785ae5 100644
--- a/superset-frontend/src/components/Chart/chartAction.js
+++ b/superset-frontend/src/components/Chart/chartAction.js
@@ -269,9 +269,12 @@ export function runAnnotationQuery({
return Promise.resolve();
}
- const granularity = fd.time_grain_sqla || fd.granularity;
- fd.time_grain_sqla = granularity;
- fd.granularity = granularity;
+ // In the original formData the `granularity` attribute represents the time grain (eg
+ // `P1D`), but in the request payload it corresponds to the name of the column where
+ // the time grain should be applied (eg, `Date`), so we need to move things around.
+ fd.time_grain_sqla = fd.time_grain_sqla || fd.granularity;
+ fd.granularity = fd.granularity_sqla;
+
const overridesKeys = Object.keys(annotation.overrides);
if (overridesKeys.includes('since') || overridesKeys.includes('until')) {
annotation.overrides = {
diff --git a/superset-frontend/src/components/Chart/chartActions.test.js b/superset-frontend/src/components/Chart/chartActions.test.js
index b44ca7c8d7..b3a6fed9f5 100644
--- a/superset-frontend/src/components/Chart/chartActions.test.js
+++ b/superset-frontend/src/components/Chart/chartActions.test.js
@@ -21,6 +21,7 @@ import fetchMock from 'fetch-mock';
import sinon from 'sinon';
import * as chartlib from '@superset-ui/core';
+import { SupersetClient } from '@superset-ui/core';
import { LOG_EVENT } from 'src/logger/actions';
import * as exploreUtils from 'src/explore/exploreUtils';
import * as actions from 'src/components/Chart/chartAction';
@@ -233,4 +234,70 @@ describe('chart actions', () => {
expect(json.result[0].value.toString()).toEqual(expectedBigNumber);
});
});
+
+ describe('runAnnotationQuery', () => {
+ const mockDispatch = jest.fn();
+ const mockGetState = () => ({
+ charts: {
+ chartKey: {
+ latestQueryFormData: {
+ time_grain_sqla: 'P1D',
+ granularity_sqla: 'Date',
+ },
+ },
+ },
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should dispatch annotationQueryStarted and annotationQuerySuccess on successful query', async () => {
+ const annotation = {
+ name: 'Holidays',
+ annotationType: 'EVENT',
+ sourceType: 'NATIVE',
+ color: null,
+ opacity: '',
+ style: 'solid',
+ width: 1,
+ showMarkers: false,
+ hideLine: false,
+ value: 1,
+ overrides: {
+ time_range: null,
+ },
+ show: true,
+ showLabel: false,
+ titleColumn: '',
+ descriptionColumns: [],
+ timeColumn: '',
+ intervalEndColumn: '',
+ };
+ const key = undefined;
+
+ const postSpy = jest.spyOn(SupersetClient, 'post');
+ postSpy.mockImplementation(() =>
+ Promise.resolve({ json: { result: [] } }),
+ );
+ const buildV1ChartDataPayloadSpy = jest.spyOn(
+ exploreUtils,
+ 'buildV1ChartDataPayload',
+ );
+
+ const queryFunc = actions.runAnnotationQuery({ annotation, key });
+ await queryFunc(mockDispatch, mockGetState);
+
+ expect(buildV1ChartDataPayloadSpy).toHaveBeenCalledWith({
+ formData: {
+ granularity: 'Date',
+ granularity_sqla: 'Date',
+ time_grain_sqla: 'P1D',
+ },
+ force: false,
+ resultFormat: 'json',
+ resultType: 'full',
+ });
+ });
+ });
});
(superset) 10/15: fix(database-import): Support importing a DB connection with a version set (#26116)
Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 3.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 77332bfb3891ce401d56522e7bb563b90081ba11
Author: Vitor Avila <96...@users.noreply.github.com>
AuthorDate: Fri Dec 1 18:20:27 2023 -0300
fix(database-import): Support importing a DB connection with a version set (#26116)
(cherry picked from commit c033ca959d391cca8f0e5a34e563ca4297ce2848)
---
superset/databases/schemas.py | 1 +
.../databases/commands/importers/v1/import_test.py | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py
index ac4d0e127d..b56c98c5d6 100644
--- a/superset/databases/schemas.py
+++ b/superset/databases/schemas.py
@@ -750,6 +750,7 @@ class ImportV1DatabaseExtraSchema(Schema):
allows_virtual_table_explore = fields.Boolean(required=False)
cancel_query_on_windows_unload = fields.Boolean(required=False)
disable_data_preview = fields.Boolean(required=False)
+ version = fields.String(required=False, allow_none=True)
class ImportV1DatabaseSchema(Schema):
diff --git a/tests/unit_tests/databases/commands/importers/v1/import_test.py b/tests/unit_tests/databases/commands/importers/v1/import_test.py
index dcd093a9cf..5fb4d12ce5 100644
--- a/tests/unit_tests/databases/commands/importers/v1/import_test.py
+++ b/tests/unit_tests/databases/commands/importers/v1/import_test.py
@@ -17,6 +17,7 @@
# pylint: disable=unused-argument, import-outside-toplevel, invalid-name
import copy
+import json
import pytest
from pytest_mock import MockFixture
@@ -142,3 +143,23 @@ def test_import_database_without_permission(
str(excinfo.value)
== "Database doesn't exist and user doesn't have permission to create databases"
)
+
+
+def test_import_database_with_version(mocker: MockFixture, session: Session) -> None:
+ """
+ Test importing a database with a version set.
+ """
+ from superset import security_manager
+ from superset.commands.database.importers.v1.utils import import_database
+ from superset.models.core import Database
+ from tests.integration_tests.fixtures.importexport import database_config
+
+ mocker.patch.object(security_manager, "can_access", return_value=True)
+
+ engine = session.get_bind()
+ Database.metadata.create_all(engine) # pylint: disable=no-member
+
+ config = copy.deepcopy(database_config)
+ config["extra"]["version"] = "1.1.1"
+ database = import_database(session, config)
+ assert json.loads(database.extra)["version"] == "1.1.1"