From ff5b4bc0e47f057e0660d453a9e53f939613356b Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Tue, 12 Jul 2022 19:17:25 +0200 Subject: [PATCH] feat: Reuse Dashboard redux data in Explore (#20668) --- .../superset-ui-core/src/utils/isDefined.ts | 2 +- .../src/dashboard/actions/hydrate.js | 6 +- .../components/gridComponents/Chart.jsx | 2 +- superset-frontend/src/explore/ExplorePage.tsx | 61 +++++++++++++++++-- .../src/explore/actions/hydrateExplore.ts | 2 +- superset/charts/schemas.py | 10 +++ 6 files changed, 75 insertions(+), 8 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/utils/isDefined.ts b/superset-frontend/packages/superset-ui-core/src/utils/isDefined.ts index 097115e11c..0cdba14eb6 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/isDefined.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/isDefined.ts @@ -17,6 +17,6 @@ * under the License. */ -export default function isDefined(x: unknown) { +export default function isDefined(x: T): x is NonNullable { return x !== null && x !== undefined; } diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 7f393f3bc2..b533e3d421 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -150,9 +150,13 @@ export const hydrateDashboard = datasource: slice.form_data.datasource, description: slice.description, description_markeddown: slice.description_markeddown, - owners: slice.owners, + owners: slice.owners.map(owner => owner.id), modified: slice.modified, changed_on: new Date(slice.changed_on).getTime(), + is_managed_externally: slice.is_managed_externally, + query_context: slice.query_context, + certified_by: slice.certified_by, + certification_details: slice.certification_details, }; sliceIds.add(key); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index 0c2abbfb0c..8298f957d5 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -302,7 +302,7 @@ class Chart extends React.Component { ) { window.open(url, '_blank', 'noreferrer'); } else { - this.props.history.push(url); + this.props.history.push(url, { dashboardId: this.props.dashboardId }); } } catch (error) { logging.error(error); diff --git a/superset-frontend/src/explore/ExplorePage.tsx b/superset-frontend/src/explore/ExplorePage.tsx index 8ae31cb883..02b709672c 100644 --- a/superset-frontend/src/explore/ExplorePage.tsx +++ b/superset-frontend/src/explore/ExplorePage.tsx @@ -16,10 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { makeApi, t } from '@superset-ui/core'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { isDefined, makeApi, SupersetClient, t } from '@superset-ui/core'; +import { useLocation } from 'react-router-dom'; import Loading from 'src/components/Loading'; +import pick from 'lodash/pick'; +import { Dataset } from '@superset-ui/chart-controls'; import { getParsedExploreURLParams } from './exploreUtils/getParsedExploreURLParams'; import { hydrateExplore } from './actions/hydrateExplore'; import ExploreViewContainer from './components/ExploreViewContainer'; @@ -27,6 +30,10 @@ import { ExploreResponsePayload } from './types'; import { fallbackExploreInitialData } from './fixtures'; import { addDangerToast } from '../components/MessageToasts/actions'; import { isNullish } from '../utils/common'; +import { getUrlParam } from '../utils/urlUtils'; +import { URL_PARAMS } from '../constants'; +import { RootState } from '../dashboard/types'; +import { Slice } from '../types/Chart'; const loadErrorMessage = t('Failed to load chart data.'); @@ -38,12 +45,58 @@ const fetchExploreData = () => { })(exploreUrlParams); }; +const useExploreInitialData = ( + shouldUseDashboardData: boolean, + sliceId: string | null, +) => { + const slice = useSelector(({ sliceEntities }) => + isDefined(sliceId) ? sliceEntities?.slices?.[sliceId] : null, + ); + const formData = slice?.form_data; + const { id: datasourceId, type: datasourceType } = useSelector< + RootState, + { id: number | undefined; type: string | undefined } + >(({ datasources }) => + formData?.datasource + ? pick(datasources[formData.datasource], ['id', 'type']) + : { id: undefined, type: undefined }, + ); + return useCallback(() => { + if ( + !shouldUseDashboardData || + !isDefined(slice) || + !isDefined(formData) || + !isDefined(datasourceId) || + !isDefined(datasourceType) + ) { + return fetchExploreData(); + } + return SupersetClient.get({ + endpoint: `/datasource/get/${datasourceType}/${datasourceId}/`, + }).then(({ json }) => ({ + result: { + slice, + form_data: formData, + dataset: json as Dataset, + message: '', + }, + })); + /* eslint-disable react-hooks/exhaustive-deps */ + }, []); +}; + const ExplorePage = () => { const [isLoaded, setIsLoaded] = useState(false); const dispatch = useDispatch(); + const location = useLocation<{ dashboardId?: number }>(); + + const getExploreInitialData = useExploreInitialData( + isDefined(location.state?.dashboardId), + getUrlParam(URL_PARAMS.sliceId), + ); useEffect(() => { - fetchExploreData() + getExploreInitialData() .then(({ result }) => { if (isNullish(result.dataset?.id) && isNullish(result.dataset?.uid)) { dispatch(hydrateExplore(fallbackExploreInitialData)); diff --git a/superset-frontend/src/explore/actions/hydrateExplore.ts b/superset-frontend/src/explore/actions/hydrateExplore.ts index ec49c0cdd5..5a8905bf65 100644 --- a/superset-frontend/src/explore/actions/hydrateExplore.ts +++ b/superset-frontend/src/explore/actions/hydrateExplore.ts @@ -56,7 +56,7 @@ export const hydrateExplore = initialFormData.dashboardId = dashboardId; } const initialDatasource = - datasources?.[initialFormData.datasource] ?? dataset; + dataset ?? datasources?.[initialFormData.datasource]; const initialExploreState = { form_data: initialFormData, diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py index 2e091cd1b8..de13a7a60c 100644 --- a/superset/charts/schemas.py +++ b/superset/charts/schemas.py @@ -121,6 +121,7 @@ description_markeddown_description = "Sanitized HTML version of the chart descri owners_name_description = "Name of an owner of the chart." certified_by_description = "Person or group that has certified this chart" certification_details_description = "Details of the certification" +is_managed_externally_description = "If the chart is managed outside externally" # # OpenAPI method specification overrides @@ -148,6 +149,10 @@ openapi_spec_methods_override = { } +class UserSchema(Schema): + id = fields.Int() + + class ChartEntityResponseSchema(Schema): """ Schema for a chart object @@ -165,6 +170,11 @@ class ChartEntityResponseSchema(Schema): slice_url = fields.String(description=slice_url_description) certified_by = fields.String(description=certified_by_description) certification_details = fields.String(description=certification_details_description) + is_managed_externally = fields.Boolean( + description=is_managed_externally_description + ) + owners = fields.List(fields.Nested(UserSchema)) + query_context = fields.String(description=query_context_description) class ChartPostSchema(Schema):