การใช้งาน Apache Superset ร่วมกับ superset-ui/embedded-sdk

Teeppiphat Phokaewkul
6 min readOct 30, 2023

--

Apache Superset เป็นเครื่องมือการวิเคราะห์ข้อมูลที่มีความสามารถหลากหลายและสามารถปรับแต่งได้, ซึ่งถูกออกแบบมาเพื่อช่วยให้ผู้ใช้สามารถสำรวจ, วิเคราะห์, และแสดงข้อมูลได้อย่างมีประสิทธิภาพ. Superset ให้ความสามารถในการเชื่อมต่อกับฐานข้อมูลหลายๆ แห่ง, สร้างแดชบอร์ด, และแชร์ insights ได้

ในส่วนของ superset-ui/embedded-sdk, มันคือชุดเครื่องมือสำหรับนักพัฒนาที่ต้องการที่จะฝัง Apache Superset ลงในแอพพลิเคชันของตนเอง. SDK นี้ช่วยให้นักพัฒนาสามารถสร้างและปรับแต่งการประสบการณ์ของผู้ใช้ในการทำงานร่วมกับ Superset ได้

สามารถดูข้อมูลเพิ่มของ embedded-sdk เติมได้ที่ @superset-ui/embedded-sdk — npm (npmjs.com)

การใช้งาน Apache Superset ร่วมกับ superset-ui/embedded-sdk

จะมีลักษณะการใช้งานตามภาพนี้

แนวคิดของ diagram นี้แสดงถึงการทำงานของระบบที่มีการเชื่อมต่อกับหลายแหล่งข้อมูล และมีการจัดการกับการยืนยันตัวตนของผู้ใช้ผ่านทั้งระบบยืนยันตัวตนแบบ guest:

  1. แหล่งข้อมูล (Source A, B, C): แสดงถึงฐานข้อมูลหรือแหล่งข้อมูลต่างๆ ที่ระบบ Superset สามารถเข้าถึงได้
  2. Superset: คือระบบหลักที่เชื่อมต่อกับแหล่งข้อมูลต่างๆ และมีการจัดการกับผู้ใช้และสิทธิ์การเข้าถึงข้อมูล
  3. API Endpoints: /api/v1/security/login: จัดการการยืนยันตัวตนของผู้ใช้ และเมื่อยืนยันตัวตนเรียบร้อยจะคืนค่าเป็น access_token และ refresh_token
  4. API Endpoints: /api/v1/security/guest_token: สำหรับการยืนยันตัวตนแบบ guest ซึ่งจะคืนค่าเป็น token แบบง่ายๆ
  5. Auth Backend for embedded iframe: เป็นระบบ backend สำหรับการยืนยันตัวตนของ iframe ที่ฝังอยู่ใน frontend
  6. Dashboard: คือ enpoint ของแดชบอร์ดของระบบ Superset ที่ผู้ใช้สามารถดูข้อมูลและสถิติต่างๆ ใน code จะเป็นการอ้างอิงจาก ID ของ Dashboard
  7. Web frontend with iframe: เว็บของระบบหลักที่มี iframe ฝังอยู่ ซึ่ง iframe นั้นอาจจะเชื่อมต่อกับระบบ Superset หรือระบบอื่นๆ ที่ต้องการการยืนยันตัวตน

Diagram นี้จะช่วยในการเข้าใจวิธีการทำงานของระบบยืนยันตัวตนและการเชื่อมต่อกับแหล่งข้อมูลต่างๆ ในระบบ Superset

ลิ้งที่เกี่ยวข้อง

การใช้งาน Superset และ embedded-sdk

ส่วนแรก เริ่มต้นจากให้เราทำการติดตั้งและกำหนดค่าคอนฟิกของ Apache Superset ให้เรียบร้อย สามารถดูการ setup และ config ได้ที่ การติดตั้ง Apache Superset. Apache Superset… | Medium

ที่ตัว apache superset จำเป็นที่จะต้องไปสร้าง user ใหม่ 1 account และแก้ไข Role ที่มี Permission รองรับการเข้าใช้งานสำหรับ embedded ตามภาพด้านล่างนี้

สร้างยูเซอร์ใหม่ที่มี Role Public และ Gamma
ให้กำหนด permission สำหรับ Role Public
ให้กำหนด permission สำหรับ Role Gamma

Permission สำหรับ embedded สำหรับ Role Public

can read on Chart
can read on Dataset
can read on Dashboard
can read on Database
can read on DashboardFilterStateRestApi
can write on DashboardFilterStateRestApi
can get embedded on Dashboard
can read on EmbeddedDashboard
can explore json on Superset
can dashboard on Superset
can grant guest token on SecurityRestApi
menu access on Row Level Security
menu access on Datasets
all database access on all_database_access

Permission สำหรับ embedded สำหรับ Role Gamma

can read on Chart 
can write on Chart
can read on Dataset
can read on Dashboard
can write on Dashboard
can read on Database
can this form post on ResetMyPasswordView
can this form get on ResetMyPasswordView
can userinfo on UserDBModelView
resetmypassword on UserDBModelView
can get on OpenApi
can show on SwaggerView
can get on MenuApi
can list on AsyncEventsRestApi
can read on AdvancedDataType
can read on AvailableDomains
can invalidate on CacheRestApi
can export on Chart
can read on DashboardFilterStateRestApi
can write on DashboardFilterStateRestApi
can write on DashboardPermalinkRestApi
can read on DashboardPermalinkRestApi
can get embedded on Dashboard
can delete embedded on Dashboard
can export on Dashboard
can read on EmbeddedDashboard
can read on Explore
can read on ExploreFormDataRestApi
can write on ExploreFormDataRestApi
can write on ExplorePermalinkRestApi
can read on ExplorePermalinkRestApi
can add on FilterSets
can list on FilterSets
can edit on FilterSets
can delete on FilterSets
can write on Tag
can bulk create on Tag
can read on Tag
can get on SQLLab
can estimate query cost on SQLLab
can list on DynamicPlugin|
can show on DynamicPlugin
can time range on Api
can query form data on Api
can query on Api|
can external metadata by name on Datasource
can external metadata on Datasource
can get on Datasource
can store on KV
can get value on KV
can read on Profile
can list on SavedQuery
can import dashboards on Superset
can profile on Superset
can dashboard on Superset
can explore on Superset
can slice on Superset
can dashboard permalink on Superset
can fetch datasource metadata on Superset
can explore json on Superset
can log on Superset
can list on Tags
can add on Tags
can edit on Tags
can delete on Tags
can show on Tags
can download on Tags
can tags on TagView
can recent activity on Log
can read on SecurityRestApi
can read on RowLevelSecurity
menu access on Home
menu access on Data
menu access on Databases
menu access on Dashboards
menu access on Charts
menu access on Datasets
menu access on Plugins
menu access on Import Dashboards
menu access on Tags
can csv on Superset
can share dashboard on Superset
can share chart on Superset

สำหรับ Role Gamma รายการที่ต้องเพิ่มเข้าไปในส่วนที่แตกต่างจากที่มีอยู่แล้วคือ

can fetch datasource metadata on superset
can read on annotation
can read on csstemplate
can read on dashboardfilterstaterestapi
can userinfo on userdbmodelview
can write on dashboardfilterstaterestapi

can fetch datasource metadata on superset
can get on sqllab
can read on dashboardfilterstaterestapi
can userinfo on userdbmodelview
can write on dashboardfilterstaterestapi

Auth Backend

ส่วนที่สอง เป็นการสร้าง Auth Backend จาก python เพื่อใช้ในการรับค่า Token จาก code นี้ โดยสร้างไฟล์ app.py แล้วนำ code นี้ไปใช้

import requests
import os

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)
# This will allow all origins. You can customize it to allow only specific origins.
CORS(app, origins=["http://localhost:5001", "http://127.0.0.1:5001", "http://localhost:5173"])

SUPRESET_BASE = os.environ['SUPRESET_BASE']


def login():
LOGIN_URL = f"{SUPRESET_BASE}/api/v1/security/login"
session = requests.Session()
payload = {
"password": os.environ['SUPERSET_PASS'],
"provider": "db",
"refresh": True,
"username": os.environ['SUPERSET_ADMIN']
}

# print(os.environ['SUPERSET_PASS'], os.environ['SUPERSET_ADMIN'])

response = session.post(LOGIN_URL, json=payload)
if response.status_code == 200:
access_token = response.json().get('access_token')
refresh_token = response.json().get('refresh_token')
else:
raise Exception("Login failed")
return (access_token, refresh_token)


def get_csrf_token():
access_token, _ = login()
headers = {'Authorization': f"Bearer {access_token}"}
response = requests.get(
f"{SUPRESET_BASE}/api/v1/security/csrf_token/", headers=headers)
csrf_token = response.json().get('result')
return csrf_token


def create_guest_token():
access_token, _ = login()
session = requests.Session()
session.headers['Authorization'] = f"Bearer {access_token}"
session.headers['Content-Type'] = 'application/json'
csrf_url = f"{SUPRESET_BASE}/api/v1/security/csrf_token/"
csrf_res = session.get(csrf_url)
csrf_token = csrf_res.json()['result']
session.headers['Referer'] = csrf_url
session.headers['X-CSRFToken'] = csrf_token
guest_token_endpoint = f"{SUPRESET_BASE}/api/v1/security/guest_token/"
payload = {
"resources": [
{
"id": "839526e6-e6b2-4004-afb4-836e25dc947a",
"type": "dashboard"
}
],
"rls": [
{
"clause": "platform IS NOT NULL",
"dataset": 1
}
],
"user": {
"username": "embedded",
"first_name": "embedded",
"last_name": "embedded"
}
}
response = session.post(guest_token_endpoint, json=payload)
return response.json().get('token')


@app.route('/api/guest-token', methods=['GET'])
def get_guest_token():
guest_token = create_guest_token()
return jsonify(guestToken=guest_token)


if __name__ == '__main__':
app.run(debug=True, port=5001)

จากนั้นให้สร้างไฟล์ .env ตามนี้

SUPERSET_PASS=admin
SUPERSET_ADMIN=admin
SUPRESET_BASE={ip-or-domain-of-superset:port}

กำหนดค่า Username, Password และ SUPRESET_BASE ให้ถูกต้อง

Web front-end สำหรับ embedded dashboard

ส่วนที่สาม จะเป็นการสร้าง code สำหรับ web front-end สำหรับ embedded dashboard ใน code ส่วนนี้จะเป็น react + vite สามารถเริ่มต้น project ได้จาก

# create new project
npm create vite@latest superset-demo -- --template react

# inatall
npm install

# run test
npm run dev

# install embedded-sdk
npm install --save @superset-ui/embedded-sdk

โครงสร้างของ code แต่ละส่วนใน project จะมีโครงสร้างตามภาพนี้

ที่ไฟล์ App.jsx

//App.jsx

// import { useState } from 'react'
// import reactLogo from './assets/react.svg'
// import viteLogo from '/vite.svg'
import './App.css'
import SupersetDashboard from './SupersetDashboard';

function App() {

return (
<>
<div>
app
<SupersetDashboard />
</div>
</>
)
}

export default App

ที่ไฟล์ App.css

/* App.css */

#root {
/* max-width: 1280px; */
margin: 0 auto;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}


.my-superset-container iframe {
width: 100vw;
height: 100vh;
}

ที่ไฟล์ SupersetDashboard.jsx

# SupersetDashboard.jsx

import React from 'react';
import { embedDashboard } from "@superset-ui/embedded-sdk";
class SupersetDashboard extends React.Component {
componentDidMount() {
this.embed();
}

async fetchGuestToken() {
// Fetch the guest token from your backend
const response = await fetch('http://localhost:5001/api/guest-token');
const data = await response.json();
return data.guestToken;
}

embed() {
const guestToken = this.fetchGuestToken();

embedDashboard({
id: "839526e6-e6b2-4004-afb4-836e25dc947a", // Replace with your dashboard ID
supersetDomain: "{ip-or-domain-of-superset:port}",
mountPoint: document.getElementById("my-superset-container"),
fetchGuestToken: () => guestToken,
dashboardUiConfig: {
hideTitle: true,
hideTab: true,

filters: {
expanded: false,
}
},
});
}

render() {
return (
<div id="my-superset-container" className='my-superset-container'></div>
);
}
}

export default SupersetDashboard;

จากนั้นให้กำหนด supersetDomain ให้ถูกต้อง แล้วทดลองรัน code ด้วย

npm run dev

จากนั้นเข้าไปที่ http://localhost:5173/ ถ้าไม่มีอะไรผิดพลาดจะได้ผลตามภาพนี้

นอกจากนี้ superset-ui ยังมี chart plugins อีกหลายตัว ลองดูต่อได้ที่

Chart Plugins | preset — chart — xy / BoxPlot — Legacy ⋅ Storybook (apache-superset.github.io)

--

--

Teeppiphat Phokaewkul

จดไปเรื่อย ไว้อ่านกันลืม