• Superset embed Dashboard到React App


    一、环境

    • Python 3.8.0

    • Linux version 3.10.0-1160.53.1.el7.x86_64

    • node v14.19.0

    • superset1.5

    二、操作步骤

    2.1 创建react app 项目

    npx

    npx create-react-app my-app

    npm

    npm init react-app my-app

    Yarn

    yarn create react-app my-app

    参考文档:Create React App 中文文档 Getting Started

    2.2 下载依赖包

    1. // 核心包,用于将superset嵌入到第三方app
    2. npm install @superset-ui/embedded-sdk
    3. // http库,用于发送http请求
    4. npm install axios
    5. // webpack5中移除了nodejs核心模块的polyfill自动引入,所以需要手动引入。
    6. //用于解决 @superset-ui/embedded-sdk中Buffer的相关报错
    7. npm install node-polyfill-webpack-plugin
    8. // 对项目中 wepback 进行自定义配置
    9. npm install @craco/craco
    10. // 地址代理
    11. npm install http-proxy-middleware

    2.3 修改相关配置信息

    2.3.1 修改package.json

    我们用craco修改webpack配置,并用craco启动项目

    1. "scripts": {
    2. - "start": "react-scripts start",
    3. - "build": "react-scripts build",
    4. - "test": "react-scripts test",
    5. + "start": "craco start",
    6. + "build": "craco build",
    7. + "test": "craco test",
    8. + "eject": "craco eject"
    9. }

    2.3.2 在根目录下新建craco.config.js,并添加以下代码

    1. const webpack = require('webpack');
    2. const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
    3. module.exports = {
    4. configureWebpack: config => {
    5. const plugins = []
    6. // 手动引入node环境下的Buffer
    7. plugins.push(new NodePolyfillPlugin())
    8. },
    9. webpack: {
    10. plugins: [
    11. // 手动引入node环境下的Buffer
    12. new webpack.ProvidePlugin({
    13. Buffer: ["buffer", "Buffer"]
    14. })
    15. ]
    16. },
    17. devServer: {
    18. host: 'xxxx', // 当前主机地址
    19. }
    20. }

    2.3.3 在src目录下新建setupProxy.js,并配置http请求代理地址

    1. const {createProxyMiddleware: proxy} = require('http-proxy-middleware');
    2. module.exports = function(app){
    3. app.use(
    4. proxy('/superset',{
    5. target:'xxxx', // http请求代理地址
    6. changeOrigin:true,
    7. pathRewrite:{'^/superset':''}
    8. })
    9. )
    10. }

    2.4 启动项目

    1. cd my-app
    2. npm start

    2.5 新增请求相关文件

    2.5.1 在src下新建request文件夹,里面创建三个文件request.js、api.js、type.js

    2.5.2 request.js 用于处理基本请求的公共方法

    1. //request.js
    2. import axios from "axios";
    3. /****** 创建axios实例 ******/
    4. const service = axios.create();
    5. /****** request拦截器==>对请求参数做处理 ******/
    6. service.interceptors.request.use(config => {
    7. const url = process.env.NODE_ENV === 'production' ? window.ENV_CONFIG.HTTP_SERVER : '/superset';
    8. config.url = `${url}${config.url}`;
    9. return config;
    10. }, error => { //请求错误处理
    11. const err = error || '服务器内部错误';
    12. Promise.reject(err)
    13. });
    14. /****** respone拦截器==>对响应做处理 ******/
    15. service.interceptors.response.use(
    16. response => { //成功请求到数据
    17. // 可自行扩展
    18. if(response.data.message){
    19. return Promise.reject(response.data.message);
    20. }
    21. //这里根据后端提供的数据进行对应的处理
    22. return Promise.resolve(response.data);
    23. },
    24. error => { //响应错误处理
    25. console.log('error');
    26. const err = error || '服务器内部错误';
    27. return Promise.reject(err)
    28. }
    29. );
    30. export default service;

    2.5.3 type.js 用于存放请求的地址

    该项目中有两个请求地址,一个是获取用户token的地址,一个是获取guest_token的地址,地址如下:

    1. export const LOGIN_URL = '/api/v1/security/login';
    2. export const GUEST_TOKEN_URL = '/api/v1/security/guest_token/';

    2.5.4 api.js 用于存放各类请求

    该项目中有两个请求方法,一个是获取用户的token,一个是获取guest_token,方法如下

    1. //api.js
    2. import {LOGIN_URL, GUEST_TOKEN_URL} from './type';
    3. import service from './base';
    4. // 获取用户token
    5. export const getUserToken = (data) => {
    6. return service({
    7. url: LOGIN_URL,
    8. method: 'post',
    9. data
    10. });
    11. }
    12. // 获取guest_token
    13. export const getGuestToken = (data, headers) => {
    14. return service({
    15. url: GUEST_TOKEN_URL,
    16. method: 'post',
    17. data,
    18. headers
    19. });
    20. }

    2.6 编写fetchGuestTokenFromBackend方法

    在src文件夹下新建embeded.js, 代码如下:

    1. import {getUserToken, getGuestToken} from './request/api';
    2. export default async function fetchGuestTokenFromBackend(config) {
    3. const {username, password, provider = 'ldap', refresh = true, dashboardId, userId} = config
    4. // 获取token
    5. const { access_token } = await getUserToken({
    6. username,
    7. password,
    8. provider,
    9. refresh
    10. });
    11. const { token } = await getGuestToken({
    12. "resources": [
    13. {
    14. "id": dashboardId,
    15. "type": "dashboard"
    16. }
    17. ],
    18. "rls": [],
    19. "user": {
    20. "first_name": username,
    21. "last_name": username,
    22. "username": username
    23. }
    24. },
    25. {
    26. Authorization: `Bearer ${access_token}`
    27. },
    28. )
    29. return token;
    30. }

    2.7 开启embed dashboard权限

    2.7.1 superset项目中 /superset/config.py,查找EMBED相关权限,将EMBEDDED_SUPERSET改为True

    "EMBEDDED_SUPERSET": True,

    此时看板功能可以出现Embed dashboard

    2.7.2 获取embedId,将地址加入白名单

    选择要嵌入的看板,点击Embed dashboard 弹出以下窗口

    填入开发地址(地址+端口号),获取ID 

    2.8 在App.js中编写embeded方法

    1. import React from 'react';
    2. import { embedDashboard } from "@superset-ui/embedded-sdk";
    3. import fetchGuestTokenFromBackend from './embeded';
    4. import './App.css'
    5. const App = () => {
    6. let json = {
    7. // superset中已加入的用户
    8. username: 'test',
    9. password: '123456',
    10. // 要插入iframe的dom id
    11. domId: "superset-container",
    12. provider: 'db',
    13. // 需要在supertset中进行配置,同2.1.6中生成的id
    14. dashboardId: 'xxx',
    15. // superset运行地址
    16. supersetDomain: 'xxx'
    17. };
    18. fetchGuestTokenFromBackend(json).then((token) => {
    19. embedDashboard({
    20. id: json.dashboardId, // given by the Superset embedding UI
    21. supersetDomain: json.supersetDomain,
    22. mountPoint: document.getElementById((json)?.domId), // html element in which iframe render
    23. fetchGuestToken: () => token,
    24. dashboardUiConfig: { hideTitle: true }
    25. });
    26. });
    27. // 与dom id相同
    28. return <div id="superset-container"></div>
    29. };
    30. export default App;

    此时是无法访问superset看板的,需要后台赋予相应的权限

    2.9 给embed用户赋权

    2.9.1 新建角色test_role

    此时可以做以下操作

     选择角色,点击操作,复制角色,可以出现下面角色,此时可以修改成test_role

     2.9.2 修改xx/superset/config.py

    查找GUEST_ROLE_NAME,给它赋予test_role权限(与2.9.1创建的角色相同)

    GUEST_ROLE_NAME = "test_role"

    此时在react app中还是无法浏览看板,需要有获取guest_token的权限

    2.9.3 新建test_role_grant角色,给角色赋予以下权限

    2.9.4 将test_role_grant赋权给test用户

     此时embed功能已完成,效果如下

     参考: @superset-ui/embedded-sdk

    三、如何适应手机端

    3.1 查找图表实现相关组件

    3.1.1 DashboardGrid.jsx 看板网格布局组件

    查看元素标签,我们可以发现整个看板都是在grid-container里的,其对应入口superset-frontend/src/dashboard/components/DashboardGrid.jsx

    3.1.2 DashboardComponent.jsx 根绝类型渲染不同组件的中间方法

    继续向下查找,发现一行就是一个grid-row,其对应入口superset-frontend/src/dashboard/containers/DashboardComponent.jsx

    3.1.3 superset-frontend/src/dashboard/components/gridComponents/index.js 所有组成看板组件的集合

    根据DashboardComponent组件的方法查找componentLookup

    发现componentLookup:可以根据组件类型生成对应的组件

    此时可以在DashboardComponent中打印component相关信息

     发现component.type = CHART时会渲染chart相关组件

    3.1.4 superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx

    在ChartHolder中,可以发现chart的宽度是由chartWidth控制的

    查找chartWidth相关定义,发现可以自动计算

    因此可以通过修改width的相关计算方式来实现手机端上一行一个图表的效果。

    3.2 修改chartWidth相关计算方法

    在superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx中添加以下代码,当窗口宽度<700并且chart的容器宽度>100的时候做相应的计算

    1. if (isFullSize) {
    2. chartWidth = window.innerWidth - CHART_MARGIN;
    3. chartHeight = window.innerHeight - CHART_MARGIN;
    4. // TODO: 平铺
    5. } else if(window.innerWidth < 700 && document.querySelector('.slice_container')?.clientWidth > 100) {
    6. chartWidth = document.querySelector('.slice_container')?.clientWidth;
    7. chartHeight = Math.floor(
    8. component.meta.height * GRID_BASE_UNIT - CHART_MARGIN,
    9. );
    10. } else {
    11. chartWidth = Math.floor(
    12. widthMultiple * columnWidth +
    13. (widthMultiple - 1) * GRID_GUTTER_SIZE -
    14. CHART_MARGIN,
    15. );
    16. chartHeight = Math.floor(
    17. component.meta.height * GRID_BASE_UNIT - CHART_MARGIN,
    18. );
    19. }

    3.3 修改相关css

    找到superset-frontend/src/dashboard/stylesheets/components/row.less,添加一下代码,强制更改容器的宽度,以达到平铺的效果

    1. @media screen and (max-width:700px) {
    2. .grid-container {
    3. margin-left: 10px !important;
    4. margin-right: 10px !important;
    5. }
    6. .grid-row {
    7. flex-wrap: wrap;
    8. .dragdroppable-column {
    9. width: 100%;
    10. margin-right: 0 !important;
    11. margin-bottom: 20px;
    12. &:last-child {
    13. margin-bottom: 0;
    14. }
    15. .resizable-container {
    16. width: 100% !important;
    17. max-width: 100% !important;
    18. }
    19. }
    20. }
    21. }

    最终效果

    四、bug及解决方式

    4.1 [Violation] Added non-passive event listener to a scroll-blocking <some> event. Consider marking eve

    embed后手机模式下会出现该警告,导致页面不停闪烁

    解决方式:

    4.1.1 安装 default-passive-events

    npm i default-passive-events

    4.1.2 全局引入

    import 'default-passive-events';

    4.2 embed后页面白屏

    检查代码中supersetDomain地址是否正确,尾部不能有斜杠(/)

    4.3 superset-embedded-sdk: Buffer is not defined

    当 webpack >= 5时需要在配置文件里手动引入相关核心模块,即Polyfill Node.js核心模块, Buffer

    解决:在craco.config.js里添加以下代码

    1. const path = require('path');
    2. const webpack = require('webpack');
    3. const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
    4. module.exports = {
    5. configureWebpack: config => {
    6. const plugins = []
    7. plugins.push(new NodePolyfillPlugin())
    8. },
    9. webpack: {
    10. plugins: [
    11. new webpack.ProvidePlugin({
    12. Buffer: ["buffer", "Buffer"]
    13. })
    14. ]
    15. },
    16. }

  • 相关阅读:
    分布式模式之Broker模式
    MySQL数据库管理
    企业如何利用 Serverless 快速扩展业务系统?
    做一个高中信息技术教资复习
    JavaWeb基础之Servlet
    C# 给Json添加Note
    C++基础语法——智能指针
    【小程序项目开发-- 京东商城】uni-app之自定义搜索组件(中)-- 搜索建议
    Java高并发(线程创建以及线程池,异步调用,CompletableFuture)
    面试突击65:为什么要用HTTPS?它有什么优点?
  • 原文地址:https://blog.csdn.net/qq_16933879/article/details/127125366