Python 3.8.0
Linux version 3.10.0-1160.53.1.el7.x86_64
node v14.19.0
superset1.5
npx create-react-app my-app
npm init react-app my-app
yarn create react-app my-app
参考文档:Create React App 中文文档 Getting Started
- // 核心包,用于将superset嵌入到第三方app
- npm install @superset-ui/embedded-sdk
- // http库,用于发送http请求
- npm install axios
- // webpack5中移除了nodejs核心模块的polyfill自动引入,所以需要手动引入。
- //用于解决 @superset-ui/embedded-sdk中Buffer的相关报错
- npm install node-polyfill-webpack-plugin
- // 对项目中 wepback 进行自定义配置
- npm install @craco/craco
- // 地址代理
- npm install http-proxy-middleware
我们用craco修改webpack配置,并用craco启动项目
- "scripts": {
- - "start": "react-scripts start",
- - "build": "react-scripts build",
- - "test": "react-scripts test",
- + "start": "craco start",
- + "build": "craco build",
- + "test": "craco test",
- + "eject": "craco eject"
- }
- const webpack = require('webpack');
-
- const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
-
- module.exports = {
- configureWebpack: config => {
- const plugins = []
- // 手动引入node环境下的Buffer
- plugins.push(new NodePolyfillPlugin())
- },
- webpack: {
- plugins: [
- // 手动引入node环境下的Buffer
- new webpack.ProvidePlugin({
- Buffer: ["buffer", "Buffer"]
- })
- ]
- },
- devServer: {
- host: 'xxxx', // 当前主机地址
- }
- }
-
- const {createProxyMiddleware: proxy} = require('http-proxy-middleware');
-
- module.exports = function(app){
- app.use(
- proxy('/superset',{
- target:'xxxx', // http请求代理地址
- changeOrigin:true,
- pathRewrite:{'^/superset':''}
- })
- )
- }
- cd my-app
- npm start
- //request.js
- import axios from "axios";
- /****** 创建axios实例 ******/
- const service = axios.create();
-
- /****** request拦截器==>对请求参数做处理 ******/
- service.interceptors.request.use(config => {
- const url = process.env.NODE_ENV === 'production' ? window.ENV_CONFIG.HTTP_SERVER : '/superset';
- config.url = `${url}${config.url}`;
- return config;
- }, error => { //请求错误处理
- const err = error || '服务器内部错误';
- Promise.reject(err)
- });
-
- /****** respone拦截器==>对响应做处理 ******/
- service.interceptors.response.use(
- response => { //成功请求到数据
- // 可自行扩展
- if(response.data.message){
- return Promise.reject(response.data.message);
- }
- //这里根据后端提供的数据进行对应的处理
- return Promise.resolve(response.data);
-
- },
- error => { //响应错误处理
- console.log('error');
- const err = error || '服务器内部错误';
- return Promise.reject(err)
- }
- );
- export default service;
该项目中有两个请求地址,一个是获取用户token的地址,一个是获取guest_token的地址,地址如下:
- export const LOGIN_URL = '/api/v1/security/login';
- export const GUEST_TOKEN_URL = '/api/v1/security/guest_token/';
该项目中有两个请求方法,一个是获取用户的token,一个是获取guest_token,方法如下
- //api.js
- import {LOGIN_URL, GUEST_TOKEN_URL} from './type';
- import service from './base';
- // 获取用户token
- export const getUserToken = (data) => {
- return service({
- url: LOGIN_URL,
- method: 'post',
- data
- });
- }
-
- // 获取guest_token
- export const getGuestToken = (data, headers) => {
- return service({
- url: GUEST_TOKEN_URL,
- method: 'post',
- data,
- headers
- });
- }
在src文件夹下新建embeded.js, 代码如下:
-
- import {getUserToken, getGuestToken} from './request/api';
-
- export default async function fetchGuestTokenFromBackend(config) {
- const {username, password, provider = 'ldap', refresh = true, dashboardId, userId} = config
- // 获取token
- const { access_token } = await getUserToken({
- username,
- password,
- provider,
- refresh
- });
- const { token } = await getGuestToken({
- "resources": [
- {
- "id": dashboardId,
- "type": "dashboard"
- }
- ],
- "rls": [],
- "user": {
- "first_name": username,
- "last_name": username,
- "username": username
- }
- },
- {
- Authorization: `Bearer ${access_token}`
- },
- )
- return token;
- }
"EMBEDDED_SUPERSET": True,
此时看板功能可以出现Embed dashboard

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

填入开发地址(地址+端口号),获取ID
-
- import React from 'react';
- import { embedDashboard } from "@superset-ui/embedded-sdk";
- import fetchGuestTokenFromBackend from './embeded';
- import './App.css'
-
- const App = () => {
- let json = {
- // superset中已加入的用户
- username: 'test',
- password: '123456',
- // 要插入iframe的dom id
- domId: "superset-container",
- provider: 'db',
- // 需要在supertset中进行配置,同2.1.6中生成的id
- dashboardId: 'xxx',
- // superset运行地址
- supersetDomain: 'xxx'
- };
- fetchGuestTokenFromBackend(json).then((token) => {
- embedDashboard({
- id: json.dashboardId, // given by the Superset embedding UI
- supersetDomain: json.supersetDomain,
- mountPoint: document.getElementById((json)?.domId), // html element in which iframe render
- fetchGuestToken: () => token,
- dashboardUiConfig: { hideTitle: true }
- });
-
- });
- // 与dom id相同
- return <div id="superset-container"></div>
- };
- export default App;
此时是无法访问superset看板的,需要后台赋予相应的权限
此时可以做以下操作
选择角色,点击操作,复制角色,可以出现下面角色,此时可以修改成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的权限


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

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

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

根据DashboardComponent组件的方法查找componentLookup

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

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

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

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

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

因此可以通过修改width的相关计算方式来实现手机端上一行一个图表的效果。
在superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx中添加以下代码,当窗口宽度<700并且chart的容器宽度>100的时候做相应的计算
- if (isFullSize) {
- chartWidth = window.innerWidth - CHART_MARGIN;
- chartHeight = window.innerHeight - CHART_MARGIN;
- // TODO: 平铺
- } else if(window.innerWidth < 700 && document.querySelector('.slice_container')?.clientWidth > 100) {
- chartWidth = document.querySelector('.slice_container')?.clientWidth;
- chartHeight = Math.floor(
- component.meta.height * GRID_BASE_UNIT - CHART_MARGIN,
- );
- } else {
- chartWidth = Math.floor(
- widthMultiple * columnWidth +
- (widthMultiple - 1) * GRID_GUTTER_SIZE -
- CHART_MARGIN,
- );
- chartHeight = Math.floor(
- component.meta.height * GRID_BASE_UNIT - CHART_MARGIN,
- );
- }
找到superset-frontend/src/dashboard/stylesheets/components/row.less,添加一下代码,强制更改容器的宽度,以达到平铺的效果
- @media screen and (max-width:700px) {
- .grid-container {
- margin-left: 10px !important;
- margin-right: 10px !important;
- }
- .grid-row {
- flex-wrap: wrap;
- .dragdroppable-column {
- width: 100%;
- margin-right: 0 !important;
- margin-bottom: 20px;
- &:last-child {
- margin-bottom: 0;
- }
- .resizable-container {
- width: 100% !important;
- max-width: 100% !important;
- }
- }
- }
- }
最终效果

embed后手机模式下会出现该警告,导致页面不停闪烁
解决方式:
npm i default-passive-events
import 'default-passive-events';
检查代码中supersetDomain地址是否正确,尾部不能有斜杠(/)
当 webpack >= 5时需要在配置文件里手动引入相关核心模块,即Polyfill Node.js核心模块, Buffer
解决:在craco.config.js里添加以下代码
- const path = require('path');
- const webpack = require('webpack');
-
- const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
-
- module.exports = {
- configureWebpack: config => {
- const plugins = []
- plugins.push(new NodePolyfillPlugin())
- },
- webpack: {
- plugins: [
- new webpack.ProvidePlugin({
- Buffer: ["buffer", "Buffer"]
- })
- ]
- },
-
- }