You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by GitBox <gi...@apache.org> on 2022/07/08 03:04:45 UTC

[GitHub] [superset] zhaoyongjie commented on a diff in pull request #20498: chore(explore): Update chart save to use API endpoints

zhaoyongjie commented on code in PR #20498:
URL: https://github.com/apache/superset/pull/20498#discussion_r916402265


##########
superset-frontend/src/explore/actions/saveModalActions.js:
##########
@@ -60,36 +61,143 @@ export function removeSaveModalAlert() {
   return { type: REMOVE_SAVE_MODAL_ALERT };
 }
 
-export function saveSlice(formData, requestParams) {
-  return dispatch => {
-    let url = getExploreUrl({
-      formData,
-      endpointType: 'base',
-      force: false,
-      curUrl: null,
-      requestParams,
+export const getSlicePayload = (
+  sliceName,
+  formDataWithNativeFilters,
+  owners,
+) => {
+  const formData = {
+    ...formDataWithNativeFilters,
+    adhoc_filters: formDataWithNativeFilters.adhoc_filters?.filter(
+      f => !f.isExtra,
+    ),
+  };
+
+  const [datasourceId, datasourceType] = formData.datasource.split('__');
+  const payload = {
+    params: JSON.stringify(formData),
+    slice_name: sliceName,
+    viz_type: formData.viz_type,
+    datasource_id: parseInt(datasourceId, 10),
+    datasource_type: datasourceType,
+    dashboards: formData.dashboards,
+    owners,
+    query_context: JSON.stringify(
+      buildV1ChartDataPayload({
+        formData,
+        force: false,
+        resultFormat: 'json',
+        resultType: 'full',
+        setDataMask: null,
+        ownState: null,
+      }),
+    ),
+  };
+  return payload;
+};
+
+const addToasts = (isNewSlice, sliceName, addedToDashboard) => {
+  const toasts = [];
+  if (isNewSlice) {
+    toasts.push(addSuccessToast(t('Chart [%s] has been saved', sliceName)));
+  } else {
+    toasts.push(
+      addSuccessToast(t('Chart [%s] has been overwritten', sliceName)),
+    );
+  }
+
+  if (addedToDashboard) {
+    if (addedToDashboard.new) {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Dashboard [%s] just got created and chart [%s] was added to it',
+            addedToDashboard.title,
+            sliceName,
+          ),
+        ),
+      );
+    } else {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Chart [%s] was added to dashboard [%s]',
+            sliceName,
+            addedToDashboard.title,
+          ),
+        ),
+      );
+    }
+  }
+
+  return toasts;
+};
+
+//  Update existing slice
+export const updateSlice =
+  ({ slice_id: sliceId, owners }, sliceName, formData, addedToDashboard) =>
+  async dispatch => {
+    try {
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/chart/${sliceId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(getSlicePayload(sliceName, formData, owners)),
+      });
+
+      dispatch(saveSliceSuccess());
+      addToasts(false, sliceName, addedToDashboard).map(dispatch);
+      return response.json;
+    } catch (error) {
+      dispatch(saveSliceFailed());
+      throw error;
+    }
+  };
+
+//  Create new slice
+export const createSlice =
+  (sliceName, formData, addedToDashboard) => async dispatch => {
+    try {
+      const response = await SupersetClient.post({
+        endpoint: `/api/v1/chart/`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(getSlicePayload(sliceName, formData)),

Review Comment:
   ```suggestion
           jsonPayload: getSlicePayload(sliceName, formData),
   ```



##########
superset-frontend/src/explore/ExplorePage.tsx:
##########
@@ -16,50 +16,58 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import { useDispatch } from 'react-redux';
+import { useLocation } from 'react-router-dom';
 import { makeApi, t } from '@superset-ui/core';
 import Loading from 'src/components/Loading';
+import { addDangerToast } from 'src/components/MessageToasts/actions';
+import { isNullish } from 'src/utils/common';
+import { getUrlParam } from 'src/utils/urlUtils';
+import { URL_PARAMS } from 'src/constants';
 import { getParsedExploreURLParams } from './exploreUtils/getParsedExploreURLParams';
 import { hydrateExplore } from './actions/hydrateExplore';
 import ExploreViewContainer from './components/ExploreViewContainer';
 import { ExploreResponsePayload } from './types';
 import { fallbackExploreInitialData } from './fixtures';
-import { addDangerToast } from '../components/MessageToasts/actions';
-import { isNullish } from '../utils/common';
 
 const loadErrorMessage = t('Failed to load chart data.');
 
-const fetchExploreData = () => {
-  const exploreUrlParams = getParsedExploreURLParams();
-  return makeApi<{}, ExploreResponsePayload>({
+const fetchExploreData = (exploreUrlParams: URLSearchParams) =>
+  makeApi<{}, ExploreResponsePayload>({
     method: 'GET',
     endpoint: 'api/v1/explore/',
   })(exploreUrlParams);
-};
 
 const ExplorePage = () => {
   const [isLoaded, setIsLoaded] = useState(false);
+  const isExploreInitialized = useRef(false);
   const dispatch = useDispatch();
+  const location = useLocation();
 
   useEffect(() => {
-    fetchExploreData()
-      .then(({ result }) => {
-        if (isNullish(result.dataset?.id) && isNullish(result.dataset?.uid)) {
+    const exploreUrlParams = getParsedExploreURLParams(location);
+    const isSaveAction = !!getUrlParam(URL_PARAMS.saveAction);
+    if (!isExploreInitialized.current || isSaveAction) {
+      fetchExploreData(exploreUrlParams)
+        .then(({ result }) => {
+          if (isNullish(result.dataset?.id) && isNullish(result.dataset?.uid)) {
+            dispatch(hydrateExplore(fallbackExploreInitialData));
+            dispatch(addDangerToast(loadErrorMessage));
+          } else {
+            dispatch(hydrateExplore(result));
+          }
+        })

Review Comment:
   nits, if `isDefined` is used
   
   ```suggestion
           if (isDefined(result.dataset?.id) && isDefined(result.dataset?.uid)) {
             dispatch(hydrateExplore(result));
           } else {
             dispatch(hydrateExplore(fallbackExploreInitialData));
             dispatch(addDangerToast(loadErrorMessage));            
           }
   ```



##########
superset-frontend/src/explore/actions/saveModalActions.js:
##########
@@ -60,36 +61,143 @@ export function removeSaveModalAlert() {
   return { type: REMOVE_SAVE_MODAL_ALERT };
 }
 
-export function saveSlice(formData, requestParams) {
-  return dispatch => {
-    let url = getExploreUrl({
-      formData,
-      endpointType: 'base',
-      force: false,
-      curUrl: null,
-      requestParams,
+export const getSlicePayload = (
+  sliceName,
+  formDataWithNativeFilters,
+  owners,
+) => {
+  const formData = {
+    ...formDataWithNativeFilters,
+    adhoc_filters: formDataWithNativeFilters.adhoc_filters?.filter(
+      f => !f.isExtra,
+    ),
+  };
+
+  const [datasourceId, datasourceType] = formData.datasource.split('__');
+  const payload = {
+    params: JSON.stringify(formData),
+    slice_name: sliceName,
+    viz_type: formData.viz_type,
+    datasource_id: parseInt(datasourceId, 10),
+    datasource_type: datasourceType,
+    dashboards: formData.dashboards,
+    owners,
+    query_context: JSON.stringify(
+      buildV1ChartDataPayload({
+        formData,
+        force: false,
+        resultFormat: 'json',
+        resultType: 'full',
+        setDataMask: null,
+        ownState: null,
+      }),
+    ),
+  };
+  return payload;
+};
+
+const addToasts = (isNewSlice, sliceName, addedToDashboard) => {
+  const toasts = [];
+  if (isNewSlice) {
+    toasts.push(addSuccessToast(t('Chart [%s] has been saved', sliceName)));
+  } else {
+    toasts.push(
+      addSuccessToast(t('Chart [%s] has been overwritten', sliceName)),
+    );
+  }
+
+  if (addedToDashboard) {
+    if (addedToDashboard.new) {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Dashboard [%s] just got created and chart [%s] was added to it',
+            addedToDashboard.title,
+            sliceName,
+          ),
+        ),
+      );
+    } else {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Chart [%s] was added to dashboard [%s]',
+            sliceName,
+            addedToDashboard.title,
+          ),
+        ),
+      );
+    }
+  }
+
+  return toasts;
+};
+
+//  Update existing slice
+export const updateSlice =
+  ({ slice_id: sliceId, owners }, sliceName, formData, addedToDashboard) =>
+  async dispatch => {
+    try {
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/chart/${sliceId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(getSlicePayload(sliceName, formData, owners)),
+      });
+
+      dispatch(saveSliceSuccess());
+      addToasts(false, sliceName, addedToDashboard).map(dispatch);
+      return response.json;
+    } catch (error) {
+      dispatch(saveSliceFailed());
+      throw error;
+    }
+  };
+
+//  Create new slice
+export const createSlice =
+  (sliceName, formData, addedToDashboard) => async dispatch => {
+    try {
+      const response = await SupersetClient.post({
+        endpoint: `/api/v1/chart/`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(getSlicePayload(sliceName, formData)),
+      });
+
+      dispatch(saveSliceSuccess());
+      addToasts(true, sliceName, addedToDashboard).map(dispatch);
+      return response.json;
+    } catch (error) {
+      dispatch(saveSliceFailed());
+      throw error;
+    }
+  };
+
+//  Create new dashboard
+export const createDashboard = dashboardName => async dispatch => {
+  try {
+    const response = await SupersetClient.post({
+      endpoint: `/api/v1/dashboard/`,
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ dashboard_title: dashboardName }),

Review Comment:
   ```suggestion
         jsonPayload: { dashboard_title: dashboardName },
   ```



##########
superset-frontend/src/explore/actions/saveModalActions.js:
##########
@@ -60,36 +61,143 @@ export function removeSaveModalAlert() {
   return { type: REMOVE_SAVE_MODAL_ALERT };
 }
 
-export function saveSlice(formData, requestParams) {
-  return dispatch => {
-    let url = getExploreUrl({
-      formData,
-      endpointType: 'base',
-      force: false,
-      curUrl: null,
-      requestParams,
+export const getSlicePayload = (
+  sliceName,
+  formDataWithNativeFilters,
+  owners,
+) => {
+  const formData = {
+    ...formDataWithNativeFilters,
+    adhoc_filters: formDataWithNativeFilters.adhoc_filters?.filter(
+      f => !f.isExtra,
+    ),
+  };
+
+  const [datasourceId, datasourceType] = formData.datasource.split('__');
+  const payload = {
+    params: JSON.stringify(formData),
+    slice_name: sliceName,
+    viz_type: formData.viz_type,
+    datasource_id: parseInt(datasourceId, 10),
+    datasource_type: datasourceType,
+    dashboards: formData.dashboards,
+    owners,
+    query_context: JSON.stringify(
+      buildV1ChartDataPayload({
+        formData,
+        force: false,
+        resultFormat: 'json',
+        resultType: 'full',
+        setDataMask: null,
+        ownState: null,
+      }),
+    ),
+  };
+  return payload;
+};
+
+const addToasts = (isNewSlice, sliceName, addedToDashboard) => {
+  const toasts = [];
+  if (isNewSlice) {
+    toasts.push(addSuccessToast(t('Chart [%s] has been saved', sliceName)));
+  } else {
+    toasts.push(
+      addSuccessToast(t('Chart [%s] has been overwritten', sliceName)),
+    );
+  }
+
+  if (addedToDashboard) {
+    if (addedToDashboard.new) {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Dashboard [%s] just got created and chart [%s] was added to it',
+            addedToDashboard.title,
+            sliceName,
+          ),
+        ),
+      );
+    } else {
+      toasts.push(
+        addSuccessToast(
+          t(
+            'Chart [%s] was added to dashboard [%s]',
+            sliceName,
+            addedToDashboard.title,
+          ),
+        ),
+      );
+    }
+  }
+
+  return toasts;
+};
+
+//  Update existing slice
+export const updateSlice =
+  ({ slice_id: sliceId, owners }, sliceName, formData, addedToDashboard) =>
+  async dispatch => {
+    try {
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/chart/${sliceId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify(getSlicePayload(sliceName, formData, owners)),

Review Comment:
   ```suggestion
           jsonPayload: getSlicePayload(sliceName, formData),
   ```



##########
superset-frontend/src/explore/ExplorePage.tsx:
##########
@@ -16,50 +16,58 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import { useDispatch } from 'react-redux';
+import { useLocation } from 'react-router-dom';
 import { makeApi, t } from '@superset-ui/core';
 import Loading from 'src/components/Loading';
+import { addDangerToast } from 'src/components/MessageToasts/actions';
+import { isNullish } from 'src/utils/common';

Review Comment:
   there is a util is `isDefined` in the `@superset-ui/core`
   ```
   import { isDefined } from '@superset-ui/core';
   ```



##########
superset-frontend/src/explore/actions/saveModalActions.js:
##########
@@ -60,36 +61,143 @@ export function removeSaveModalAlert() {
   return { type: REMOVE_SAVE_MODAL_ALERT };
 }
 
-export function saveSlice(formData, requestParams) {
-  return dispatch => {
-    let url = getExploreUrl({
-      formData,
-      endpointType: 'base',
-      force: false,
-      curUrl: null,
-      requestParams,
+export const getSlicePayload = (
+  sliceName,
+  formDataWithNativeFilters,
+  owners,
+) => {
+  const formData = {
+    ...formDataWithNativeFilters,
+    adhoc_filters: formDataWithNativeFilters.adhoc_filters?.filter(
+      f => !f.isExtra,
+    ),
+  };
+
+  const [datasourceId, datasourceType] = formData.datasource.split('__');
+  const payload = {
+    params: JSON.stringify(formData),
+    slice_name: sliceName,
+    viz_type: formData.viz_type,
+    datasource_id: parseInt(datasourceId, 10),
+    datasource_type: datasourceType,
+    dashboards: formData.dashboards,
+    owners,
+    query_context: JSON.stringify(
+      buildV1ChartDataPayload({
+        formData,
+        force: false,
+        resultFormat: 'json',
+        resultType: 'full',
+        setDataMask: null,
+        ownState: null,
+      }),
+    ),

Review Comment:
   nits:
   ```suggestion
       query_context: JSON.stringify(buildV1ChartDataPayload({ formData })),
   ```
   
   One thing is strange, why does every JSON non-primitive value have to be serialized. e.g. `params` and `query_context`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org