一、环境
- 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 下载依赖包
// 核心包,用于将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
2.3 修改相关配置信息
2.3.1 修改package.json
我们用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"
}
2.3.2 在根目录下新建craco.config.js,并添加以下代码
const webpack = require('webpack');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
module.exports = {
configureWebpack: config => {
const plugins = []
// 手动引入node
plugins.push(new NodePolyfillPlugin())
},
webpack: {
plugins: [
// 手动引入node环境下的Buffer
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"]
})
]
},
devServer: {
host: 'xxxx', // 当前主机地址
} }
2.3.3 在src目录下新建setupProxy.js,并配置http请求代理地址
const {createProxyMiddleware: proxy} = require('http-proxy-middleware');
module.exports = function(app){
app.use( proxy('/superset',{
target:'xxxx', // http请求代理地址
changeOrigin:true,
pathRewrite:{'^/superset':''}
})
)
}
2.4 启动项目
cd my-app
npm start
2.5 新增请求相关文件
2.5.1 在src下新建request文件夹,里面创建三个文件request.js、api.js、type.js
2.5.2 request.js 用于处理基本请求的公共方法
//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;
2.5.3 type.js 用于存放请求的地址
该项目中有两个请求地址,一个是获取用户token的地址,一个是获取guest_token的地址,地址如下:
export const LOGIN_URL = '/api/v1/security/login';
export const GUEST_TOKEN_URL = '/api/v1/security/guest_token/';
2.5.4 api.js 用于存放各类请求
该项目中有两个请求方法,一个是获取用户的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
});
}
2.6 编写fetchGuestTokenFromBackend方法
在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;
}
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方法
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看板的,需要后台赋予相应的权限
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功能已完成,效果如下
参考: www.npmjs.com/package/@su…
三、如何适应手机端
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的时候做相应的计算
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,
);
}
3.3 修改相关css
找到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; }
}
}
}
最终效果
四、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里添加以下代码
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"]
})
]
},
}