This is an automated email from the ASF dual-hosted git repository. hugh pushed a commit to branch hm/ar-filters in repository https://gitbox.apache.org/repos/asf/superset.git
commit ef0a7ee4ef694cb123f182f732121f33846c8fe1 Merge: 25d2850a0c 9e5876dc17 Author: Hugh Miles <[email protected]> AuthorDate: Mon Feb 3 16:49:33 2025 +0100 Merge branch 'master' of https://github.com/apache/superset into hm/ar-filters docs/docs/configuration/databases.mdx | 18 + pyproject.toml | 1 + superset-frontend/package-lock.json | 28 ++ superset-frontend/package.json | 2 + .../superset-ui-core/src/ui-overrides/types.ts | 11 + superset-frontend/src/GlobalStyles.tsx | 3 +- superset-frontend/src/SqlLab/actions/sqlLab.js | 40 +- .../src/SqlLab/actions/sqlLab.test.js | 70 +++- .../AceEditorWrapper/AceEditorWrapper.test.tsx | 19 +- .../AceEditorWrapper/useKeywords.test.ts | 2 + .../src/SqlLab/components/App/index.tsx | 2 +- .../SqlLab/components/ResultSet/ResultSet.test.tsx | 10 +- .../SqlLab/components/SaveDatasetModal/index.tsx | 4 +- .../src/SqlLab/components/ShowSQL/index.tsx | 12 +- .../SqlLab/components/SouthPane/SouthPane.test.tsx | 6 +- .../src/SqlLab/components/SouthPane/index.tsx | 95 +++-- .../SqlEditorLeftBar/SqlEditorLeftBar.test.tsx | 63 ++- .../SqlLab/components/SqlEditorLeftBar/index.tsx | 11 +- .../components/TableElement/TableElement.test.tsx | 10 +- .../components/TablePreview/TablePreview.test.tsx | 173 +++++++++ .../src/SqlLab/components/TablePreview/index.tsx | 430 +++++++++++++++++++++ superset-frontend/src/SqlLab/fixtures.ts | 5 +- .../src/SqlLab/reducers/getInitialState.test.ts | 10 +- .../src/SqlLab/reducers/getInitialState.ts | 7 +- superset-frontend/src/SqlLab/reducers/sqlLab.js | 50 ++- superset-frontend/src/SqlLab/types.ts | 2 +- .../Chart/DrillBy/useDisplayModeToggle.tsx | 28 +- .../FilterableTable/FilterableTable.test.tsx | 33 +- .../src/components/FilterableTable/index.tsx | 251 ++++-------- .../src/components/GridTable/GridTable.test.tsx | 66 ++++ .../src/components/GridTable/Header.test.tsx | 109 ++++++ .../src/components/GridTable/Header.tsx | 200 ++++++++++ .../src/components/GridTable/HeaderMenu.test.tsx | 266 +++++++++++++ .../src/components/GridTable/HeaderMenu.tsx | 247 ++++++++++++ .../Radio.stories.tsx => GridTable/constants.ts} | 41 +- .../src/components/GridTable/index.tsx | 241 ++++++++++++ .../src/components/Icons/AntdEnhanced.tsx | 16 + .../src/components/Radio/Radio.stories.tsx | 149 +++++-- superset-frontend/src/components/Radio/index.tsx | 80 ++-- .../header-renderers/HeaderWithRadioGroup.tsx | 20 +- .../FiltersConfigForm/FiltersConfigForm.tsx | 36 +- .../explore/components/DataTableControl/index.tsx | 22 +- .../components/ExploreChartHeader/index.jsx | 2 +- .../controls/DateFilterControl/DateFilterLabel.tsx | 6 - .../DateFilterControl/components/CalendarFrame.tsx | 18 +- .../DateFilterControl/components/CommonFrame.tsx | 18 +- .../components/CurrentCalendarFrame.tsx | 25 +- .../DateFilterControl/components/CustomFrame.tsx | 15 +- .../DateFilterControl/tests/CalendarFrame.test.tsx | 8 +- .../DateFilterControl/tests/CustomFrame.test.tsx | 8 +- .../controls/DateFilterControl/utils/constants.ts | 13 +- .../src/features/alerts/AlertReportModal.tsx | 86 ++++- .../src/features/reports/ReportModal/index.tsx | 40 +- .../src/features/reports/ReportModal/styles.tsx | 4 - superset-frontend/src/hooks/apiResources/sqlLab.ts | 4 +- superset-frontend/src/hooks/apiResources/tables.ts | 10 +- superset/charts/api.py | 111 +++--- superset/charts/schemas.py | 15 + superset/commands/database/tables.py | 6 + superset/commands/report/execute.py | 28 +- superset/config.py | 11 + superset/dashboards/api.py | 261 +++++++------ superset/dashboards/schemas.py | 6 + superset/db_engine_specs/parseable.py | 84 ++++ superset/migrations/shared/catalogs.py | 11 +- superset/models/slice.py | 4 +- superset/reports/models.py | 34 ++ superset/tasks/thumbnails.py | 15 +- superset/utils/screenshots.py | 190 +++++---- superset/utils/webdriver.py | 10 +- tests/integration_tests/async_events/api_tests.py | 9 +- tests/integration_tests/cli_tests.py | 6 +- tests/integration_tests/dashboards/api_tests.py | 40 +- tests/integration_tests/thumbnails_tests.py | 27 +- tests/unit_tests/commands/databases/tables_test.py | 28 +- tests/unit_tests/commands/report/execute_test.py | 115 ++++++ tests/unit_tests/db_engine_specs/test_parseable.py | 77 ++++ .../unit_tests/migrations/shared/catalogs_test.py | 250 ++++++++++++ tests/unit_tests/models/core_test.py | 56 +++ tests/unit_tests/utils/screenshot_test.py | 194 ++++++++++ 80 files changed, 3824 insertions(+), 910 deletions(-) diff --cc superset-frontend/src/features/alerts/AlertReportModal.tsx index 8cd3ca112a,f3f8bab182..5152160b4f --- a/superset-frontend/src/features/alerts/AlertReportModal.tsx +++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx @@@ -456,12 -454,6 +456,12 @@@ const AlertReportModal: FunctionCompone const [dashboardOptions, setDashboardOptions] = useState<MetaObject[]>([]); const [chartOptions, setChartOptions] = useState<MetaObject[]>([]); const [tabOptions, setTabOptions] = useState<TabNode[]>([]); + const [nativeFilterOptions, setNativeFilterOptions] = useState<object>([]); + const [nativeFilterValues, setNativeFilterValues] = useState<object>([]); + - const [nativeFilterOptionsTwo, setNativeFilterOptionsTwo] = useState<object>([]); - - console.log('nativeFilters', nativeFilterOptions); ++ // todo(hughhh): refactor to handle multiple native filters ++ const [nativeFilter, setSelectedNativeFilter] = useState<object>({}); ++ const [nativeFilters, setGlobalNativeFilters] = useState<object>({}); // Validation const [validationStatus, setValidationStatus] = useState<ValidationObject>({ @@@ -673,6 -665,6 +673,11 @@@ const shouldEnableForceScreenshot = contentType === ContentType.Chart && !isReport; ++ ++ ++ // todo(hughhh): refactor to handle multiple native filters ++ currentAlert.extra.nativeFilter = nativeFilter ++ const data: any = { ...currentAlert, type: isReport ? 'Report' : 'Alert', @@@ -841,64 -832,9 +846,68 @@@ value: JSON.stringify(Object.keys(allTabs)), }); setTabOptions(tabTree); - + // console.log('setting native filters', nativeFilters); - // setNativeFilterOptions(nativeFilters); ++ setGlobalNativeFilters(nativeFilters); + + // const dashboardId = dashboard.value; + // const newFormData = getFormData({ + // ...filter, + // datasetId, + // dependencies, + // groupby, + // adhoc_filters, + // time_range, + // dashboardId, + // }); + + const chartDataExample = { + formData: { + // enableEmptyFilter: false, + // defaultToFirstItem: false, + // multiSelect: true, + // searchAllOptions: false, + // inverseSelection: false, + datasource: "15__table", + groupby: ["clinical_stage"], + // adhoc_filters: [], + // extra_filters: [], + // extra_form_data: {}, + metrics: ["count"], + row_limit: 1000, + showSearch: true, + // url_params: { + // native_filters_key: "0ktJOz1FTTo" + // }, + // inView: true, + viz_type: "filter_select", + // type: "NATIVE_FILTER", + dashboardId: 6, + // native_filter_id: "NATIVE_FILTER-8jS1fx4hl" + }, + force: false, + ownState: {} + } + + // get filter values + // getChartDataRequest(chartDataExample).then(response => { + // console.log('response.json.result', response.json.result[0].data) + // }); + const anchor = currentAlert?.extra?.dashboard?.anchor; + console.log('anchor', anchor); if (anchor) { + // const nativeFiltersOptions = nativeFilters[anchor].map((filter: any) => {value: filter.id, label: filter.name}) + console.log('nativeFiltersOptions', nativeFilters[anchor]); - setNativeFilterOptions(nativeFilters[anchor].map((filter: any) => ({value: filter.id, label: filter.name}))); - getChartDataRequest(chartDataExample).then(response => { - console.log('hello', response) - setNativeFilterValues(response.json.result[0].data.map((item: any) => ({value: item.clinical_stage, label: item.clinical_stage}))) - }); ++ setNativeFilterOptions( ++ nativeFilters[anchor].map((filter: any) => ({ ++ value: filter.id, ++ label: filter.name, ++ })) ++ // getChartDataRequest(chartDataExample).then(response => { ++ // console.log('hello', response) ++ // setNativeFilterValues(response.json.result[0].data.map((item: any) => ({value: item.clinical_stage, label: item.clinical_stage}))) ++ // }); + try { const parsedAnchor = JSON.parse(anchor); if (Array.isArray(parsedAnchor)) { @@@ -1177,10 -1111,6 +1186,62 @@@ setForceScreenshot(event.target.checked); }; + const onChangeDashboardFilter = (value: any) => { + console.log('dashboardFilter', value); ++ // set dashboardFilter ++ const anchor = currentAlert?.extra?.dashboard?.anchor ++ const inScopeFilters = nativeFilters[anchor]; ++ const filter = inScopeFilters.filter((f: any) => f.id === value)[0] ++ console.log(filter) // use filter to grab values from API ++ console.log('about to get filter values', value); ++ ++ const datasetId = filter.targets[0].datasetId; ++ const columnName = filter.targets[0].column.name; ++ const dashboardId = currentAlert?.dashboard?.value; ++ ++ // Get values tied to the selected filter ++ const filterValues = { ++ formData: { ++ // enableEmptyFilter: false, ++ // defaultToFirstItem: false, ++ // multiSelect: true, ++ // searchAllOptions: false, ++ // inverseSelection: false, ++ datasource: `${datasetId}__table`, ++ groupby: [columnName], ++ // adhoc_filters: [], ++ // extra_filters: [], ++ // extra_form_data: {}, ++ metrics: ["count"], ++ row_limit: 1000, ++ showSearch: true, ++ // url_params: { ++ // native_filters_key: "0ktJOz1FTTo" ++ // }, ++ // inView: true, ++ viz_type: "filter_select", ++ // type: "NATIVE_FILTER", ++ dashboardId: dashboardId, ++ // native_filter_id: "NATIVE_FILTER-8jS1fx4hl" ++ }, ++ force: false, ++ ownState: {} ++ } ++ ++ getChartDataRequest(filterValues).then(response => { ++ setNativeFilterValues(response.json.result[0].data.map((item: any) => ({value: item[columnName], label: item[columnName]}))) ++ }); ++ ++ } ++ ++ const onChangeDashboardFilterValue = (value: any) => { ++ console.log('dashboardValue', value); ++ // set dashboardValue ++ setSelectedNativeFilter({...nativeFilter, nativeFilterValue: value}); ++ ++ + } + // Make sure notification settings has the required info const checkNotificationSettings = () => { if (!notificationSettings.length) { @@@ -1839,22 -1768,6 +1900,23 @@@ placeholder={t('Select a tab')} /> </> + <div style={{marginTop: '10px', display: 'flex', flexDirection: 'column'}}> + <div className="control-label">{t('Select Dashboard Filter')}</div> + <Select + disabled={nativeFilterOptions?.length < 1} + ariaLabel={t('Select Filter')} - value={"value"} ++ value={nativeFilter.nativeFilter} + options={nativeFilterOptions} ++ onChange={onChangeDashboardFilter} + /> + <div className="control-label">{t('Select Dashboard Value')}</div> + <Select + ariaLabel={t('Value')} - value={"value"} ++ value={nativeFilter.nativeFilterValue} + options={nativeFilterValues} - onChange={onChangeDashboardFilter} ++ onChange={onChangeDashboardFilterValue} + /> + </div> </StyledInputContainer> )} {isScreenshot && ( diff --cc superset/commands/report/execute.py index 9e69258650,0c57d55bb5..4345a7160b --- a/superset/commands/report/execute.py +++ b/superset/commands/report/execute.py @@@ -282,7 -282,7 +282,7 @@@ class BaseReportState "anchor": tab_anchor, "dataMask": None, "activeTabs": None, -- "urlParams": None, ++ "urlParams": None, # could potentially back the native_filters here? }, user_friendly=user_friendly, ) diff --cc superset/reports/models.py index e4cdd7c9b4,e4cdd7c9b4..932c4d6077 --- a/superset/reports/models.py +++ b/superset/reports/models.py @@@ -15,6 -15,6 +15,7 @@@ # specific language governing permissions and limitations # under the License. """A collection of ORM sqlalchemy models for Superset""" ++import prison from cron_descriptor import get_description from flask_appbuilder import Model @@@ -34,6 -34,6 +35,7 @@@ from sqlalchemy import from sqlalchemy.orm import backref, relationship from sqlalchemy.schema import UniqueConstraint from sqlalchemy_utils import UUIDType ++from typing import Optional from superset.extensions import security_manager from superset.models.core import Database @@@ -44,6 -44,6 +46,7 @@@ from superset.reports.types import Repo from superset.utils.backports import StrEnum from superset.utils.core import MediumText ++ metadata = Model.metadata # pylint: disable=no-member @@@ -182,6 -182,6 +185,37 @@@ class ReportSchedule(AuditMixinNullable @renders("crontab") def crontab_humanized(self) -> str: return get_description(self.crontab) ++ ++ def get_native_filters_params(self) -> Optional[str]: ++ params = None ++ if self.extra and self.extra.get("native_filters"): ++ filter = self.extra.get("native_filters", {}) ++ params = self._generate_native_filter(filter.get("id"), filter.get("column"), filter.get("value")) ++ return prison.dumps(params) ++ ++ def _generate_native_filter(native_filter_id: str, column: str, value: str) -> dict: ++ return { ++ "native_filters": { ++ native_filter_id: { ++ "id": native_filter_id, ++ "extraFormData": { ++ "filters": [ ++ { ++ "col": column, ++ "op": "IN", ++ "val": [value] ++ } ++ ] ++ }, ++ "filterState": { ++ "label": column, # pretty name but still works without this value ++ "validateStatus": False, ++ "value": [value] ++ }, ++ "ownState": {} ++ } ++ } ++ } class ReportRecipients(Model, AuditMixinNullable):
